1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace X509\CertificationPath\Policy; |
4
|
|
|
|
5
|
|
|
use X509\Certificate\Certificate; |
6
|
|
|
use X509\Certificate\Extension\CertificatePolicy\PolicyInformation; |
7
|
|
|
use X509\Certificate\Extension\PolicyMappings\PolicyMapping; |
8
|
|
|
use X509\CertificationPath\PathValidation\ValidatorState; |
9
|
|
|
|
10
|
|
|
|
11
|
|
|
class PolicyTree |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* Root node at depth zero. |
15
|
|
|
* |
16
|
|
|
* @var PolicyNode|null |
17
|
|
|
*/ |
18
|
|
|
protected $_root; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Constructor. |
22
|
|
|
* |
23
|
|
|
* @param PolicyNode $root Initial root node |
24
|
|
|
*/ |
25
|
48 |
|
public function __construct(PolicyNode $root) { |
26
|
48 |
|
$this->_root = $root; |
27
|
48 |
|
} |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Process policy information from the certificate. |
31
|
|
|
* |
32
|
|
|
* Certificate policies extension must be present. |
33
|
|
|
* |
34
|
|
|
* @param ValidatorState $state |
35
|
|
|
* @param Certificate $cert |
36
|
|
|
* @return ValidatorState |
37
|
|
|
*/ |
38
|
14 |
|
public function processPolicies(ValidatorState $state, Certificate $cert) { |
39
|
14 |
|
$policies = $cert->tbsCertificate() |
40
|
14 |
|
->extensions() |
41
|
14 |
|
->certificatePolicies(); |
42
|
14 |
|
$tree = clone $this; |
43
|
|
|
// (d.1) for each policy P not equal to anyPolicy |
44
|
14 |
|
foreach ($policies as $policy) { |
45
|
14 |
|
if ($policy->isAnyPolicy()) { |
46
|
6 |
|
$tree->_processAnyPolicy($policy, $cert, $state); |
47
|
6 |
|
} else { |
48
|
12 |
|
$tree->_processPolicy($policy, $state); |
49
|
|
|
} |
50
|
14 |
|
} |
51
|
|
|
// if whole tree is pruned |
52
|
14 |
|
if (!$tree->_pruneTree($state->index() - 1)) { |
53
|
1 |
|
return $state->withoutValidPolicyTree(); |
54
|
|
|
} |
55
|
14 |
|
return $state->withValidPolicyTree($tree); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Process policy mappings from the certificate. |
60
|
|
|
* |
61
|
|
|
* @param ValidatorState $state |
62
|
|
|
* @param Certificate $cert |
63
|
|
|
* @return ValidatorState |
64
|
|
|
*/ |
65
|
3 |
|
public function processMappings(ValidatorState $state, Certificate $cert) { |
66
|
3 |
|
$tree = clone $this; |
67
|
3 |
|
if ($state->policyMapping() > 0) { |
68
|
2 |
|
$tree->_applyMappings($cert, $state); |
69
|
3 |
|
} else if ($state->policyMapping() == 0) { |
70
|
1 |
|
$tree->_deleteMappings($cert, $state); |
71
|
1 |
|
} |
72
|
|
|
// if whole tree is pruned |
73
|
3 |
|
if (!$tree->_root) { |
74
|
1 |
|
return $state->withoutValidPolicyTree(); |
75
|
|
|
} |
76
|
2 |
|
return $state->withValidPolicyTree($tree); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Calculate policy intersection as specified in Wrap-Up Procedure 6.1.5.g. |
81
|
|
|
* |
82
|
|
|
* @param ValidatorState $state |
83
|
|
|
* @param array $policies |
84
|
|
|
* @return ValidatorState |
85
|
|
|
*/ |
86
|
6 |
|
public function calculateIntersection(ValidatorState $state, array $policies) { |
87
|
6 |
|
$tree = clone $this; |
88
|
6 |
|
$valid_policy_node_set = $tree->_validPolicyNodeSet(); |
89
|
|
|
// 2. If the valid_policy of any node in the valid_policy_node_set |
90
|
|
|
// is not in the user-initial-policy-set and is not anyPolicy, |
91
|
|
|
// delete this node and all its children. |
92
|
6 |
|
$valid_policy_node_set = array_filter($valid_policy_node_set, |
93
|
|
|
function (PolicyNode $node) use ($policies) { |
94
|
6 |
|
if ($node->isAnyPolicy()) { |
95
|
4 |
|
return true; |
96
|
|
|
} |
97
|
5 |
|
if (in_array($node->validPolicy(), $policies)) { |
98
|
3 |
|
return true; |
99
|
|
|
} |
100
|
3 |
|
$node->remove(); |
101
|
3 |
|
return false; |
102
|
6 |
|
}); |
103
|
|
|
// array of valid policy OIDs |
104
|
6 |
|
$valid_policy_set = array_map( |
105
|
|
|
function (PolicyNode $node) { |
106
|
5 |
|
return $node->validPolicy(); |
107
|
6 |
|
}, $valid_policy_node_set); |
108
|
|
|
// 3. If the valid_policy_tree includes a node of depth n with |
109
|
|
|
// the valid_policy anyPolicy and the user-initial-policy-set |
110
|
|
|
// is not any-policy |
111
|
6 |
|
foreach ($tree->_nodesAtDepth($state->index()) as $node) { |
112
|
4 |
|
if ($node->hasParent() && $node->isAnyPolicy()) { |
113
|
|
|
// a. Set P-Q to the qualifier_set in the node of depth n |
114
|
|
|
// with valid_policy anyPolicy. |
115
|
1 |
|
$pq = $node->qualifiers(); |
116
|
|
|
// b. For each P-OID in the user-initial-policy-set that is not |
117
|
|
|
// the valid_policy of a node in the valid_policy_node_set, |
118
|
|
|
// create a child node whose parent is the node of depth n-1 |
119
|
|
|
// with the valid_policy anyPolicy. |
120
|
1 |
|
$poids = array_diff($policies, $valid_policy_set); |
121
|
1 |
|
foreach ($tree->_nodesAtDepth($state->index() - 1) as $parent) { |
122
|
1 |
|
if ($parent->isAnyPolicy()) { |
123
|
|
|
// Set the values in the child node as follows: |
124
|
|
|
// set the valid_policy to P-OID, set the qualifier_set |
125
|
|
|
// to P-Q, and set the expected_policy_set to {P-OID}. |
126
|
1 |
|
foreach ($poids as $poid) { |
127
|
1 |
|
$parent->addChild( |
128
|
1 |
|
new PolicyNode($poid, $pq, array($poid))); |
129
|
1 |
|
} |
130
|
1 |
|
break; |
131
|
|
|
} |
132
|
1 |
|
} |
133
|
|
|
// c. Delete the node of depth n with the |
134
|
|
|
// valid_policy anyPolicy. |
135
|
1 |
|
$node->remove(); |
136
|
1 |
|
} |
137
|
6 |
|
} |
138
|
|
|
// 4. If there is a node in the valid_policy_tree of depth n-1 or less |
139
|
|
|
// without any child nodes, delete that node. Repeat this step until |
140
|
|
|
// there are no nodes of depth n-1 or less without children. |
141
|
6 |
|
if (!$tree->_pruneTree($state->index() - 1)) { |
142
|
2 |
|
return $state->withoutValidPolicyTree(); |
143
|
|
|
} |
144
|
4 |
|
return $state->withValidPolicyTree($tree); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Get policies at given policy tree depth. |
149
|
|
|
* |
150
|
|
|
* @param int $i Depth in range 1..n |
151
|
|
|
* @return PolicyInformation[] |
152
|
|
|
*/ |
153
|
6 |
|
public function policiesAtDepth($i) { |
154
|
6 |
|
$policies = array(); |
155
|
6 |
|
foreach ($this->_nodesAtDepth($i) as $node) { |
156
|
5 |
|
$policies[] = new PolicyInformation($node->validPolicy(), |
157
|
5 |
|
...$node->qualifiers()); |
158
|
6 |
|
} |
159
|
6 |
|
return $policies; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Process single policy information. |
164
|
|
|
* |
165
|
|
|
* @param PolicyInformation $policy |
166
|
|
|
* @param ValidatorState $state |
167
|
|
|
*/ |
168
|
12 |
|
protected function _processPolicy(PolicyInformation $policy, |
169
|
|
|
ValidatorState $state) { |
170
|
12 |
|
$p_oid = $policy->oid(); |
171
|
12 |
|
$i = $state->index(); |
172
|
12 |
|
$match_count = 0; |
173
|
|
|
// (d.1.i) for each node of depth i-1 in the valid_policy_tree... |
174
|
12 |
View Code Duplication |
foreach ($this->_nodesAtDepth($i - 1) as $node) { |
|
|
|
|
175
|
|
|
// ...where P-OID is in the expected_policy_set |
176
|
12 |
|
if ($node->hasExpectedPolicy($p_oid)) { |
177
|
6 |
|
$node->addChild( |
178
|
6 |
|
new PolicyNode($p_oid, $policy->qualifiers(), array($p_oid))); |
179
|
6 |
|
++$match_count; |
180
|
6 |
|
} |
181
|
12 |
|
} |
182
|
|
|
// (d.1.ii) if there was no match in step (i)... |
183
|
12 |
|
if (!$match_count) { |
184
|
|
|
// ...and the valid_policy_tree includes a node of depth i-1 with |
185
|
|
|
// the valid_policy anyPolicy |
186
|
11 |
View Code Duplication |
foreach ($this->_nodesAtDepth($i - 1) as $node) { |
|
|
|
|
187
|
11 |
|
if ($node->isAnyPolicy()) { |
188
|
11 |
|
$node->addChild( |
189
|
11 |
|
new PolicyNode($p_oid, $policy->qualifiers(), |
190
|
11 |
|
array($p_oid))); |
191
|
11 |
|
} |
192
|
11 |
|
} |
193
|
11 |
|
} |
194
|
12 |
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Process anyPolicy policy information. |
198
|
|
|
* |
199
|
|
|
* @param PolicyInformation $policy |
200
|
|
|
* @param Certificate $cert |
201
|
|
|
* @param ValidatorState $state |
202
|
|
|
*/ |
203
|
6 |
|
protected function _processAnyPolicy(PolicyInformation $policy, |
204
|
|
|
Certificate $cert, ValidatorState $state) { |
205
|
6 |
|
$i = $state->index(); |
206
|
|
|
// if (a) inhibit_anyPolicy is greater than 0 or |
207
|
|
|
// (b) i<n and the certificate is self-issued |
208
|
6 |
|
if (!($state->inhibitAnyPolicy() > 0 || |
209
|
6 |
|
($i < $state->pathLength() && $cert->isSelfIssued()))) { |
210
|
1 |
|
return; |
211
|
|
|
} |
212
|
|
|
// for each node in the valid_policy_tree of depth i-1 |
213
|
6 |
|
foreach ($this->_nodesAtDepth($i - 1) as $node) { |
214
|
|
|
// for each value in the expected_policy_set |
215
|
6 |
|
foreach ($node->expectedPolicies() as $p_oid) { |
216
|
|
|
// that does not appear in a child node |
217
|
6 |
|
if (!$node->hasChildWithValidPolicy($p_oid)) { |
218
|
6 |
|
$node->addChild( |
219
|
6 |
|
new PolicyNode($p_oid, $policy->qualifiers(), |
220
|
6 |
|
array($p_oid))); |
221
|
6 |
|
} |
222
|
6 |
|
} |
223
|
6 |
|
} |
224
|
6 |
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Apply policy mappings to the policy tree. |
228
|
|
|
* |
229
|
|
|
* @param Certificate $cert |
230
|
|
|
* @param ValidatorState $state |
231
|
|
|
*/ |
232
|
2 |
|
protected function _applyMappings(Certificate $cert, ValidatorState $state) { |
233
|
2 |
|
$policy_mappings = $cert->tbsCertificate() |
234
|
2 |
|
->extensions() |
235
|
2 |
|
->policyMappings(); |
236
|
|
|
// (6.1.4. b.1.) for each node in the valid_policy_tree of depth i... |
237
|
2 |
|
foreach ($policy_mappings->flattenedMappings() as $idp => $sdps) { |
238
|
2 |
|
$match_count = 0; |
239
|
2 |
|
foreach ($this->_nodesAtDepth($state->index()) as $node) { |
240
|
|
|
// ...where ID-P is the valid_policy |
241
|
2 |
|
if ($node->validPolicy() == $idp) { |
242
|
|
|
// set expected_policy_set to the set of subjectDomainPolicy |
243
|
|
|
// values that are specified as equivalent to ID-P by |
244
|
|
|
// the policy mappings extension |
245
|
1 |
|
$node->setExpectedPolicies(...$sdps); |
246
|
1 |
|
++$match_count; |
247
|
1 |
|
} |
248
|
2 |
|
} |
249
|
|
|
// if no node of depth i in the valid_policy_tree has |
250
|
|
|
// a valid_policy of ID-P... |
251
|
2 |
|
if (!$match_count) { |
252
|
1 |
|
$this->_applyAnyPolicyMapping($cert, $state, $idp, $sdps); |
253
|
1 |
|
} |
254
|
2 |
|
} |
255
|
2 |
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Apply anyPolicy mapping to the policy tree as specified in 6.1.4 (b)(1). |
259
|
|
|
* |
260
|
|
|
* @param Certificate $cert |
261
|
|
|
* @param ValidatorState $state |
262
|
|
|
* @param string $idp OID of the issuer domain policy |
263
|
|
|
* @param array $sdps Array of subject domain policy OIDs |
264
|
|
|
*/ |
265
|
1 |
|
protected function _applyAnyPolicyMapping(Certificate $cert, |
266
|
|
|
ValidatorState $state, $idp, array $sdps) { |
267
|
|
|
// (6.1.4. b.1.) ...but there is a node of depth i with |
268
|
|
|
// a valid_policy of anyPolicy |
269
|
1 |
|
foreach ($this->_nodesAtDepth($state->index()) as $node) { |
270
|
1 |
|
if ($node->isAnyPolicy()) { |
271
|
|
|
// then generate a child node of the node of depth i-1 |
272
|
|
|
// that has a valid_policy of anyPolicy as follows... |
273
|
1 |
|
foreach ($this->_nodesAtDepth($state->index() - 1) as $node) { |
274
|
1 |
|
if ($node->isAnyPolicy()) { |
275
|
|
|
// try to fetch qualifiers of anyPolicy certificate policy |
276
|
1 |
|
$qualifiers = array(); |
277
|
|
|
try { |
278
|
1 |
|
$qualifiers = $cert->tbsCertificate() |
279
|
1 |
|
->extensions() |
280
|
1 |
|
->certificatePolicies() |
281
|
1 |
|
->anyPolicy() |
282
|
1 |
|
->qualifiers(); |
283
|
1 |
|
} catch (\LogicException $e) {} |
|
|
|
|
284
|
1 |
|
$node->addChild( |
285
|
1 |
|
new PolicyNode($idp, $qualifiers, $sdps)); |
286
|
|
|
// bail after first anyPolicy has been processed |
287
|
1 |
|
break; |
288
|
|
|
} |
289
|
1 |
|
} |
290
|
|
|
// bail after first anyPolicy has been processed |
291
|
1 |
|
break; |
292
|
|
|
} |
293
|
1 |
|
} |
294
|
1 |
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Delete nodes as specified in 6.1.4 (b)(2). |
298
|
|
|
* |
299
|
|
|
* @param Certificate $cert |
300
|
|
|
* @param ValidatorState $state |
301
|
|
|
*/ |
302
|
1 |
|
protected function _deleteMappings(Certificate $cert, ValidatorState $state) { |
303
|
1 |
|
$idps = $cert->tbsCertificate() |
304
|
1 |
|
->extensions() |
305
|
1 |
|
->policyMappings() |
306
|
1 |
|
->issuerDomainPolicies(); |
307
|
|
|
// delete each node of depth i in the valid_policy_tree |
308
|
|
|
// where ID-P is the valid_policy |
309
|
1 |
|
foreach ($this->_nodesAtDepth($state->index()) as $node) { |
310
|
1 |
|
if (in_array($node->validPolicy(), $idps)) { |
311
|
1 |
|
$node->remove(); |
312
|
1 |
|
} |
313
|
1 |
|
} |
314
|
1 |
|
$this->_pruneTree($state->index() - 1); |
315
|
1 |
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* Prune tree starting from given depth. |
319
|
|
|
* |
320
|
|
|
* @param int $depth |
321
|
|
|
* @return The number of nodes left in a tree |
322
|
|
|
*/ |
323
|
14 |
|
protected function _pruneTree($depth) { |
324
|
14 |
|
for ($i = $depth; $i > 0; --$i) { |
325
|
11 |
|
foreach ($this->_nodesAtDepth($i) as $node) { |
326
|
11 |
|
if (!count($node)) { |
327
|
4 |
|
$node->remove(); |
328
|
4 |
|
} |
329
|
11 |
|
} |
330
|
11 |
|
} |
331
|
|
|
// if root has no children left |
332
|
14 |
|
if (!count($this->_root)) { |
333
|
4 |
|
$this->_root = null; |
334
|
4 |
|
return 0; |
|
|
|
|
335
|
|
|
} |
336
|
14 |
|
return $this->_root->nodeCount(); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Get all nodes at given depth. |
341
|
|
|
* |
342
|
|
|
* @param int $i |
343
|
|
|
* @return PolicyNode[] |
344
|
|
|
*/ |
345
|
15 |
|
protected function _nodesAtDepth($i) { |
346
|
15 |
|
if (!$this->_root) { |
347
|
1 |
|
return array(); |
348
|
|
|
} |
349
|
14 |
|
$depth = 0; |
350
|
14 |
|
$nodes = array($this->_root); |
351
|
14 |
|
while ($depth < $i) { |
352
|
11 |
|
$nodes = self::_gatherChildren(...$nodes); |
353
|
11 |
|
if (!count($nodes)) { |
354
|
2 |
|
break; |
355
|
|
|
} |
356
|
11 |
|
++$depth; |
357
|
11 |
|
} |
358
|
14 |
|
return $nodes; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Get the valid policy node set as specified in spec 6.1.5.(g)(iii)1. |
363
|
|
|
* |
364
|
|
|
* @return PolicyNode[] |
365
|
|
|
*/ |
366
|
7 |
|
protected function _validPolicyNodeSet() { |
367
|
|
|
// 1. Determine the set of policy nodes whose parent nodes have |
368
|
|
|
// a valid_policy of anyPolicy. This is the valid_policy_node_set. |
369
|
7 |
|
$set = array(); |
370
|
7 |
|
if (!$this->_root) { |
371
|
1 |
|
return $set; |
372
|
|
|
} |
373
|
|
|
// for each node in a tree |
374
|
6 |
|
$this->_root->walkNodes( |
375
|
6 |
|
function (PolicyNode $node) use (&$set) { |
376
|
6 |
|
$parents = $node->parents(); |
377
|
|
|
// node has parents |
378
|
6 |
|
if (count($parents)) { |
379
|
|
|
// check that each ancestor is an anyPolicy node |
380
|
6 |
|
foreach ($parents as $ancestor) { |
381
|
6 |
|
if (!$ancestor->isAnyPolicy()) { |
382
|
2 |
|
return; |
383
|
|
|
} |
384
|
6 |
|
} |
385
|
6 |
|
$set[] = $node; |
386
|
6 |
|
} |
387
|
6 |
|
}); |
388
|
6 |
|
return $set; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Gather all children of given nodes to a flattened array. |
393
|
|
|
* |
394
|
|
|
* @param PolicyNode ...$nodes |
395
|
|
|
* @return PolicyNode[] |
396
|
|
|
*/ |
397
|
11 |
|
private static function _gatherChildren(PolicyNode ...$nodes) { |
398
|
11 |
|
$children = array(); |
399
|
11 |
|
foreach ($nodes as $node) { |
400
|
11 |
|
$children = array_merge($children, $node->children()); |
401
|
11 |
|
} |
402
|
11 |
|
return $children; |
403
|
|
|
} |
404
|
|
|
} |
405
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.