TrustPathValidator::validate()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 30
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 11
c 0
b 0
f 0
nc 5
nop 2
dl 0
loc 30
ccs 0
cts 12
cp 0
crap 56
rs 8.8333
1
<?php
2
3
namespace MadWizard\WebAuthn\Attestation\TrustAnchor;
4
5
use MadWizard\WebAuthn\Attestation\TrustPath\CertificateTrustPath;
6
use MadWizard\WebAuthn\Attestation\TrustPath\TrustPathInterface;
7
use MadWizard\WebAuthn\Pki\ChainValidatorInterface;
8
use MadWizard\WebAuthn\Pki\X509Certificate;
9
use function array_reverse;
10
11
final class TrustPathValidator implements TrustPathValidatorInterface
12
{
13
    /**
14
     * @var ChainValidatorInterface
15
     */
16
    private $chainValidator;
17
18 18
    public function __construct(ChainValidatorInterface $chainValidator)
19
    {
20 18
        $this->chainValidator = $chainValidator;
21 18
    }
22
23
    public function validate(TrustPathInterface $trustPath, TrustAnchorInterface $trustAnchor): bool
24
    {
25
        if ($trustAnchor instanceof CertificateTrustAnchor && $trustPath instanceof CertificateTrustPath) {
26
            // WebAauthn SPEC (v2):
27
            // Use  the X.509 certificates returned as the attestation trust path from the verification procedure
28
            // to verify that the attestation public key either correctly chains up to an acceptable root certificate,
29
            // or is itself an acceptable certificate
30
            // (i.e., it and the root certificate obtained in Step 20 may be the same).
31
32
            $trustAnchorCert = $trustAnchor->getCertificate();
33
            $trustPathCerts = $trustPath->getCertificates();
34
35
            // Check if trust path is trust anchor itself
36
            if (count($trustPathCerts) === 1 && $trustPathCerts[0]->equals($trustAnchorCert)) {
37
                return true;
38
            }
39
40
            $chain = array_merge([$trustAnchorCert], array_reverse($trustPath->getCertificates()));
41
42
            // RFC5280 6.1: "A certificate MUST NOT appear more than once in a prospective certification path."
43
            // https://github.com/fido-alliance/conformance-test-tools-resources/issues/605
44
            if ($this->containsDuplicates(...$chain)) {
45
                return false;
46
            }
47
48
            if ($this->chainValidator->validateChain(...$chain)) {
49
                return true;
50
            }
51
        }
52
        return false;
53
    }
54
55
    private function containsDuplicates(X509Certificate ...$chain): bool
56
    {
57
        $map = [];
58
        foreach ($chain as $cert) {
59
            $pem = $cert->asPem();
60
            if (isset($map[$pem])) {
61
                return true;
62
            }
63
            $map[$pem] = true;
64
        }
65
        return false;
66
    }
67
}
68