GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Branch php70 (9ac8e7)
by Joni
10:16 queued 03:54
created

PathValidator::_preparePolicyConstraints()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 19
ccs 12
cts 12
cp 1
rs 9.2222
c 0
b 0
f 0
cc 6
nc 5
nop 2
crap 6
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace X509\CertificationPath\PathValidation;
6
7
use Sop\CryptoBridge\Crypto;
8
use X509\Certificate\Certificate;
9
use X509\Certificate\TBSCertificate;
10
use X509\Certificate\Extension\CertificatePolicy\PolicyInformation;
11
use X509\CertificationPath\Exception\PathValidationException;
12
13
/**
14
 * Implements certification path validation.
15
 *
16
 * @link https://tools.ietf.org/html/rfc5280#section-6
17
 */
18
class PathValidator
19
{
20
    /**
21
     * Crypto engine.
22
     *
23
     * @var Crypto $_crypto
24
     */
25
    protected $_crypto;
26
    
27
    /**
28
     * Path validation configuration.
29
     *
30
     * @var PathValidationConfig $_config
31
     */
32
    protected $_config;
33
    
34
    /**
35
     * Certification path.
36
     *
37
     * @var Certificate[] $_certificates
38
     */
39
    protected $_certificates;
40
    
41
    /**
42
     * Certification path trust anchor.
43
     *
44
     * @var Certificate $_trustAnchor
45
     */
46
    protected $_trustAnchor;
47
    
48
    /**
49
     * Constructor.
50
     *
51
     * @param Crypto $crypto Crypto engine
52
     * @param PathValidationConfig $config Validation config
53
     * @param Certificate ...$certificates Certificates from the trust anchor to
54
     *        the end-entity certificate
55
     */
56 46
    public function __construct(Crypto $crypto, PathValidationConfig $config,
57
        Certificate ...$certificates)
58
    {
59 46
        if (!count($certificates)) {
60 1
            throw new \LogicException("No certificates.");
61
        }
62 45
        $this->_crypto = $crypto;
63 45
        $this->_config = $config;
64 45
        $this->_certificates = $certificates;
65
        // if trust anchor is explicitly given in configuration
66 45
        if ($config->hasTrustAnchor()) {
67 1
            $this->_trustAnchor = $config->trustAnchor();
68
        } else {
69 44
            $this->_trustAnchor = $certificates[0];
70
        }
71 45
    }
72
    
73
    /**
74
     * Validate certification path.
75
     *
76
     * @throws PathValidationException
77
     * @return PathValidationResult
78
     */
79 45
    public function validate(): PathValidationResult
80
    {
81 45
        $n = count($this->_certificates);
82 45
        $state = ValidatorState::initialize($this->_config, $this->_trustAnchor,
83
            $n);
84 45
        for ($i = 0; $i < $n; ++$i) {
85 44
            $state = $state->withIndex($i + 1);
86 44
            $cert = $this->_certificates[$i];
87
            // process certificate (section 6.1.3.)
88 44
            $state = $this->_processCertificate($state, $cert);
89 43
            if (!$state->isFinal()) {
90
                // prepare next certificate (section 6.1.4.)
91 43
                $state = $this->_prepareNext($state, $cert);
92
            }
93
        }
94 31
        if (!isset($cert)) {
95 1
            throw new \LogicException("No certificates.");
96
        }
97
        // wrap-up (section 6.1.5.)
98 30
        $state = $this->_wrapUp($state, $cert);
99
        // return outputs
100 28
        return $state->getResult($this->_certificates);
101
    }
102
    
103
    /**
104
     * Apply basic certificate processing according to RFC 5280 section 6.1.3.
105
     *
106
     * @link https://tools.ietf.org/html/rfc5280#section-6.1.3
107
     * @param ValidatorState $state
108
     * @param Certificate $cert
109
     * @throws PathValidationException
110
     * @return ValidatorState
111
     */
112 44
    private function _processCertificate(ValidatorState $state, Certificate $cert): ValidatorState
113
    {
114
        // (a.1) verify signature
115 44
        $this->_verifySignature($state, $cert);
116
        // (a.2) check validity period
117 44
        $this->_checkValidity($cert);
118
        // (a.3) check that certificate is not revoked
119 43
        $this->_checkRevocation($cert);
120
        // (a.4) check issuer
121 43
        $this->_checkIssuer($state, $cert);
122
        // (b)(c) if certificate is self-issued and it is not
123
        // the final certificate in the path, skip this step
124 43
        if (!($cert->isSelfIssued() && !$state->isFinal())) {
125
            // (b) check permitted subtrees
126 35
            $this->_checkPermittedSubtrees($state, $cert);
127
            // (c) check excluded subtrees
128 35
            $this->_checkExcludedSubtrees($state, $cert);
129
        }
130 43
        $extensions = $cert->tbsCertificate()->extensions();
131 43
        if ($extensions->hasCertificatePolicies()) {
132
            // (d) process policy information
133 26
            if ($state->hasValidPolicyTree()) {
134 26
                $state = $state->validPolicyTree()->processPolicies($state,
135
                    $cert);
136
            }
137
        } else {
138
            // (e) certificate policies extension not present,
139
            // set the valid_policy_tree to NULL
140 31
            $state = $state->withoutValidPolicyTree();
141
        }
142
        // (f) check that explicit_policy > 0 or valid_policy_tree is set
143 43
        if (!($state->explicitPolicy() > 0 || $state->hasValidPolicyTree())) {
144 3
            throw new PathValidationException("No valid policies.");
145
        }
146 43
        return $state;
147
    }
148
    
149
    /**
150
     * Apply preparation for the certificate i+1 according to rfc5280 section
151
     * 6.1.4.
152
     *
153
     * @link https://tools.ietf.org/html/rfc5280#section-6.1.4
154
     * @param ValidatorState $state
155
     * @param Certificate $cert
156
     * @return ValidatorState
157
     */
158 43
    private function _prepareNext(ValidatorState $state, Certificate $cert): ValidatorState
159
    {
160
        // (a)(b) if policy mappings extension is present
161 43
        $state = $this->_preparePolicyMappings($state, $cert);
162
        // (c) assign working_issuer_name
163 42
        $state = $state->withWorkingIssuerName(
164 42
            $cert->tbsCertificate()
165 42
                ->subject());
166
        // (d)(e)(f)
167 42
        $state = $this->_setPublicKeyState($state, $cert);
168
        // (g) if name constraints extension is present
169 42
        $state = $this->_prepareNameConstraints($state, $cert);
170
        // (h) if certificate is not self-issued
171 42
        if (!$cert->isSelfIssued()) {
172 21
            $state = $this->_prepareNonSelfIssued($state);
173
        }
174
        // (i) if policy constraints extension is present
175 42
        $state = $this->_preparePolicyConstraints($state, $cert);
176
        // (j) if inhibit any policy extension is present
177 42
        $state = $this->_prepareInhibitAnyPolicy($state, $cert);
178
        // (k) check basic constraints
179 42
        $this->_processBasicContraints($cert);
180
        // (l) verify max_path_length
181 40
        $state = $this->_verifyMaxPathLength($state, $cert);
182
        // (m) check pathLenContraint
183 40
        $state = $this->_processPathLengthContraint($state, $cert);
184
        // (n) check key usage
185 40
        $this->_checkKeyUsage($cert);
186
        // (o) process relevant extensions
187 39
        $state = $this->_processExtensions($state, $cert);
188 39
        return $state;
189
    }
190
    
191
    /**
192
     * Apply wrap-up procedure according to RFC 5280 section 6.1.5.
193
     *
194
     * @link https://tools.ietf.org/html/rfc5280#section-6.1.5
195
     * @param ValidatorState $state
196
     * @param Certificate $cert
197
     * @throws PathValidationException
198
     * @return ValidatorState
199
     */
200 30
    private function _wrapUp(ValidatorState $state, Certificate $cert): ValidatorState
201
    {
202 30
        $tbs_cert = $cert->tbsCertificate();
203 30
        $extensions = $tbs_cert->extensions();
204
        // (a)
205 30
        if ($state->explicitPolicy() > 0) {
206 26
            $state = $state->withExplicitPolicy($state->explicitPolicy() - 1);
207
        }
208
        // (b)
209 30
        if ($extensions->hasPolicyConstraints()) {
210 13
            $ext = $extensions->policyConstraints();
211 13
            if ($ext->hasRequireExplicitPolicy() &&
212 13
                 $ext->requireExplicitPolicy() == 0) {
213 1
                $state = $state->withExplicitPolicy(0);
214
            }
215
        }
216
        // (c)(d)(e)
217 30
        $state = $this->_setPublicKeyState($state, $cert);
218
        // (f) process relevant extensions
219 30
        $state = $this->_processExtensions($state, $cert);
220
        // (g) intersection of valid_policy_tree and the initial-policy-set
221 30
        $state = $this->_calculatePolicyIntersection($state);
222
        // check that explicit_policy > 0 or valid_policy_tree is set
223 30
        if (!($state->explicitPolicy() > 0 || $state->hasValidPolicyTree())) {
224 2
            throw new PathValidationException("No valid policies.");
225
        }
226
        // path validation succeeded
227 28
        return $state;
228
    }
229
    
230
    /**
231
     * Update working_public_key, working_public_key_parameters and
232
     * working_public_key_algorithm state variables from certificate.
233
     *
234
     * @param ValidatorState $state
235
     * @param Certificate $cert
236
     * @return ValidatorState
237
     */
238 42
    private function _setPublicKeyState(ValidatorState $state, Certificate $cert): ValidatorState
239
    {
240 42
        $pk_info = $cert->tbsCertificate()->subjectPublicKeyInfo();
241
        // assign working_public_key
242 42
        $state = $state->withWorkingPublicKey($pk_info);
243
        // assign working_public_key_parameters
244 42
        $params = ValidatorState::getAlgorithmParameters(
245 42
            $pk_info->algorithmIdentifier());
246 42
        if (null !== $params) {
247 42
            $state = $state->withWorkingPublicKeyParameters($params);
248
        } else {
249
            // if algorithms differ, set parameters to null
250 1
            if ($pk_info->algorithmIdentifier()->oid() !==
251 1
                 $state->workingPublicKeyAlgorithm()->oid()) {
252 1
                $state = $state->withWorkingPublicKeyParameters(null);
253
            }
254
        }
255
        // assign working_public_key_algorithm
256 42
        $state = $state->withWorkingPublicKeyAlgorithm(
257 42
            $pk_info->algorithmIdentifier());
258 42
        return $state;
259
    }
260
    
261
    /**
262
     * Verify certificate signature.
263
     *
264
     * @param ValidatorState $state
265
     * @param Certificate $cert
266
     * @throws PathValidationException
267
     */
268 44
    private function _verifySignature(ValidatorState $state, Certificate $cert)
269
    {
270
        try {
271 44
            $valid = $cert->verify($state->workingPublicKey(), $this->_crypto);
272 1
        } catch (\RuntimeException $e) {
273 1
            throw new PathValidationException(
274 1
                "Failed to verify signature: " . $e->getMessage(), 0, $e);
275
        }
276 44
        if (!$valid) {
277 2
            throw new PathValidationException(
278 2
                "Certificate signature doesn't match.");
279
        }
280 44
    }
281
    
282
    /**
283
     * Check certificate validity.
284
     *
285
     * @param Certificate $cert
286
     * @throws PathValidationException
287
     */
288 44
    private function _checkValidity(Certificate $cert)
289
    {
290 44
        $refdt = $this->_config->dateTime();
291 44
        $validity = $cert->tbsCertificate()->validity();
292 44
        if ($validity->notBefore()
293 44
            ->dateTime()
294 44
            ->diff($refdt)->invert) {
295 1
            throw new PathValidationException(
296 1
                "Certificate validity period has not started.");
297
        }
298 43
        if ($refdt->diff($validity->notAfter()
299 43
            ->dateTime())->invert) {
300 1
            throw new PathValidationException("Certificate has expired.");
301
        }
302 43
    }
303
    
304
    /**
305
     * Check certificate revocation.
306
     *
307
     * @param Certificate $cert
308
     */
309 43
    private function _checkRevocation(Certificate $cert)
310
    {
311
        // @todo Implement CRL handling
312 43
    }
313
    
314
    /**
315
     * Check certificate issuer.
316
     *
317
     * @param ValidatorState $state
318
     * @param Certificate $cert
319
     * @throws PathValidationException
320
     */
321 43
    private function _checkIssuer(ValidatorState $state, Certificate $cert)
322
    {
323 43
        if (!$cert->tbsCertificate()
324 43
            ->issuer()
325 43
            ->equals($state->workingIssuerName())) {
326 1
            throw new PathValidationException("Certification issuer mismatch.");
327
        }
328 43
    }
329
    
330
    /**
331
     *
332
     * @param ValidatorState $state
333
     * @param Certificate $cert
334
     */
335 35
    private function _checkPermittedSubtrees(ValidatorState $state,
336
        Certificate $cert)
0 ignored issues
show
Unused Code introduced by
The parameter $cert is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

336
        /** @scrutinizer ignore-unused */ Certificate $cert)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
337
    {
338
        // @todo Implement
339 35
        $state->permittedSubtrees();
340 35
    }
341
    
342
    /**
343
     *
344
     * @param ValidatorState $state
345
     * @param Certificate $cert
346
     */
347 35
    private function _checkExcludedSubtrees(ValidatorState $state,
348
        Certificate $cert)
0 ignored issues
show
Unused Code introduced by
The parameter $cert is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

348
        /** @scrutinizer ignore-unused */ Certificate $cert)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
349
    {
350
        // @todo Implement
351 35
        $state->excludedSubtrees();
352 35
    }
353
    
354
    /**
355
     * Apply policy mappings handling for the preparation step.
356
     *
357
     * @param ValidatorState $state
358
     * @param Certificate $cert
359
     * @throws PathValidationException
360
     * @return ValidatorState
361
     */
362 43
    private function _preparePolicyMappings(ValidatorState $state,
363
        Certificate $cert): ValidatorState
364
    {
365 43
        $extensions = $cert->tbsCertificate()->extensions();
366 43
        if ($extensions->hasPolicyMappings()) {
367
            // (a) verify that anyPolicy mapping is not used
368 4
            if ($extensions->policyMappings()->hasAnyPolicyMapping()) {
369 1
                throw new PathValidationException("anyPolicy mapping found.");
370
            }
371
            // (b) process policy mappings
372 3
            if ($state->hasValidPolicyTree()) {
373 3
                $state = $state->validPolicyTree()->processMappings($state,
374
                    $cert);
375
            }
376
        }
377 42
        return $state;
378
    }
379
    
380
    /**
381
     * Apply name constraints handling for the preparation step.
382
     *
383
     * @param ValidatorState $state
384
     * @param Certificate $cert
385
     * @return ValidatorState
386
     */
387 42
    private function _prepareNameConstraints(ValidatorState $state,
388
        Certificate $cert): ValidatorState
389
    {
390 42
        $extensions = $cert->tbsCertificate()->extensions();
391 42
        if ($extensions->hasNameConstraints()) {
392 1
            $state = $this->_processNameConstraints($state, $cert);
393
        }
394 42
        return $state;
395
    }
396
    
397
    /**
398
     * Apply preparation for a non-self-signed certificate.
399
     *
400
     * @param ValidatorState $state
401
     * @return ValidatorState
402
     */
403 21
    private function _prepareNonSelfIssued(ValidatorState $state): ValidatorState
404
    {
405
        // (h.1)
406 21
        if ($state->explicitPolicy() > 0) {
407 20
            $state = $state->withExplicitPolicy($state->explicitPolicy() - 1);
408
        }
409
        // (h.2)
410 21
        if ($state->policyMapping() > 0) {
411 20
            $state = $state->withPolicyMapping($state->policyMapping() - 1);
412
        }
413
        // (h.3)
414 21
        if ($state->inhibitAnyPolicy() > 0) {
415 21
            $state = $state->withInhibitAnyPolicy(
416 21
                $state->inhibitAnyPolicy() - 1);
417
        }
418 21
        return $state;
419
    }
420
    
421
    /**
422
     * Apply policy constraints handling for the preparation step.
423
     *
424
     * @param ValidatorState $state
425
     * @param Certificate $cert
426
     * @return ValidatorState
427
     */
428 42
    private function _preparePolicyConstraints(ValidatorState $state,
429
        Certificate $cert): ValidatorState
430
    {
431 42
        $extensions = $cert->tbsCertificate()->extensions();
432 42
        if (!$extensions->hasPolicyConstraints()) {
433 41
            return $state;
434
        }
435 2
        $ext = $extensions->policyConstraints();
436
        // (i.1)
437 2
        if ($ext->hasRequireExplicitPolicy() &&
438 2
             $ext->requireExplicitPolicy() < $state->explicitPolicy()) {
439 2
            $state = $state->withExplicitPolicy($ext->requireExplicitPolicy());
440
        }
441
        // (i.2)
442 2
        if ($ext->hasInhibitPolicyMapping() &&
443 2
             $ext->inhibitPolicyMapping() < $state->policyMapping()) {
444 1
            $state = $state->withPolicyMapping($ext->inhibitPolicyMapping());
445
        }
446 2
        return $state;
447
    }
448
    
449
    /**
450
     * Apply inhibit any-policy handling for the preparation step.
451
     *
452
     * @param ValidatorState $state
453
     * @param Certificate $cert
454
     * @return ValidatorState
455
     */
456 42
    private function _prepareInhibitAnyPolicy(ValidatorState $state,
457
        Certificate $cert): ValidatorState
458
    {
459 42
        $extensions = $cert->tbsCertificate()->extensions();
460 42
        if ($extensions->hasInhibitAnyPolicy()) {
461 2
            $ext = $extensions->inhibitAnyPolicy();
462 2
            if ($ext->skipCerts() < $state->inhibitAnyPolicy()) {
463 2
                $state = $state->withInhibitAnyPolicy($ext->skipCerts());
464
            }
465
        }
466 42
        return $state;
467
    }
468
    
469
    /**
470
     * Verify maximum certification path length for the preparation step.
471
     *
472
     * @param ValidatorState $state
473
     * @param Certificate $cert
474
     * @throws PathValidationException
475
     * @return ValidatorState
476
     */
477 40
    private function _verifyMaxPathLength(ValidatorState $state,
478
        Certificate $cert): ValidatorState
479
    {
480 40
        if (!$cert->isSelfIssued()) {
481 21
            if ($state->maxPathLength() <= 0) {
482 2
                throw new PathValidationException(
483 2
                    "Certification path length exceeded.");
484
            }
485 19
            $state = $state->withMaxPathLength($state->maxPathLength() - 1);
486
        }
487 40
        return $state;
488
    }
489
    
490
    /**
491
     * Check key usage extension for the preparation step.
492
     *
493
     * @param Certificate $cert
494
     * @throws PathValidationException
495
     */
496 40
    private function _checkKeyUsage(Certificate $cert)
497
    {
498 40
        $extensions = $cert->tbsCertificate()->extensions();
499 40
        if ($extensions->hasKeyUsage()) {
500 20
            $ext = $extensions->keyUsage();
501 20
            if (!$ext->isKeyCertSign()) {
502 1
                throw new PathValidationException("keyCertSign usage not set.");
503
            }
504
        }
505 39
    }
506
    
507
    /**
508
     *
509
     * @param ValidatorState $state
510
     * @param Certificate $cert
511
     * @return ValidatorState
512
     */
513 1
    private function _processNameConstraints(ValidatorState $state,
514
        Certificate $cert): ValidatorState
515
    {
516
        // @todo Implement
517 1
        return $state;
518
    }
519
    
520
    /**
521
     * Process basic constraints extension.
522
     *
523
     * @param Certificate $cert
524
     * @throws PathValidationException
525
     */
526 42
    private function _processBasicContraints(Certificate $cert)
527
    {
528 42
        if ($cert->tbsCertificate()->version() == TBSCertificate::VERSION_3) {
529 39
            $extensions = $cert->tbsCertificate()->extensions();
530 39
            if (!$extensions->hasBasicConstraints()) {
531 1
                throw new PathValidationException(
532 1
                    "v3 certificate must have basicConstraints extension.");
533
            }
534
            // verify that cA is set to TRUE
535 38
            if (!$extensions->basicConstraints()->isCA()) {
536 1
                throw new PathValidationException(
537 1
                    "Certificate is not a CA certificate.");
538
            }
539
        }
540 40
    }
541
    
542
    /**
543
     * Process pathLenConstraint.
544
     *
545
     * @param ValidatorState $state
546
     * @param Certificate $cert
547
     * @return ValidatorState
548
     */
549 40
    private function _processPathLengthContraint(ValidatorState $state,
550
        Certificate $cert): ValidatorState
551
    {
552 40
        $extensions = $cert->tbsCertificate()->extensions();
553 40
        if ($extensions->hasBasicConstraints()) {
554 37
            $ext = $extensions->basicConstraints();
555 37
            if ($ext->hasPathLen()) {
556 26
                if ($ext->pathLen() < $state->maxPathLength()) {
557 9
                    $state = $state->withMaxPathLength($ext->pathLen());
558
                }
559
            }
560
        }
561 40
        return $state;
562
    }
563
    
564
    /**
565
     *
566
     * @param ValidatorState $state
567
     * @param Certificate $cert
568
     * @return ValidatorState
569
     */
570 39
    private function _processExtensions(ValidatorState $state, Certificate $cert): ValidatorState
0 ignored issues
show
Unused Code introduced by
The parameter $cert is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

570
    private function _processExtensions(ValidatorState $state, /** @scrutinizer ignore-unused */ Certificate $cert): ValidatorState

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
571
    {
572
        // @todo Implement
573 39
        return $state;
574
    }
575
    
576
    /**
577
     *
578
     * @param ValidatorState $state
579
     * @return ValidatorState
580
     */
581 30
    private function _calculatePolicyIntersection(ValidatorState $state): ValidatorState
582
    {
583
        // (i) If the valid_policy_tree is NULL, the intersection is NULL
584 30
        if (!$state->hasValidPolicyTree()) {
585 21
            return $state;
586
        }
587
        // (ii) If the valid_policy_tree is not NULL and
588
        // the user-initial-policy-set is any-policy, the intersection
589
        // is the entire valid_policy_tree
590 9
        $initial_policies = $this->_config->policySet();
591 9
        if (in_array(PolicyInformation::OID_ANY_POLICY, $initial_policies)) {
592 3
            return $state;
593
        }
594
        // (iii) If the valid_policy_tree is not NULL and the
595
        // user-initial-policy-set is not any-policy, calculate
596
        // the intersection of the valid_policy_tree and the
597
        // user-initial-policy-set as follows
598 6
        return $state->validPolicyTree()->calculateIntersection($state,
599
            $initial_policies);
600
    }
601
}
602