Passed
Push — master ( 83aa3d...907404 )
by Thomas
07:17
created

AppleAttestationVerifier   A

Complexity

Total Complexity 7

Size/Duplication

Total Lines 57
Duplicated Lines 0 %

Test Coverage

Coverage 80%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 26
c 1
b 0
f 0
dl 0
loc 57
ccs 20
cts 25
cp 0.8
rs 10
wmc 7

2 Methods

Rating   Name   Duplication   Size   Complexity  
B verify() 0 45 6
A getSupportedFormat() 0 6 1
1
<?php
2
3
namespace MadWizard\WebAuthn\Attestation\Verifier;
4
5
use MadWizard\WebAuthn\Attestation\AttestationType;
6
use MadWizard\WebAuthn\Attestation\AuthenticatorData;
7
use MadWizard\WebAuthn\Attestation\Registry\AttestationFormatInterface;
8
use MadWizard\WebAuthn\Attestation\Registry\BuiltInAttestationFormat;
9
use MadWizard\WebAuthn\Attestation\Statement\AppleAttestationStatement;
10
use MadWizard\WebAuthn\Attestation\Statement\AttestationStatementInterface;
11
use MadWizard\WebAuthn\Attestation\TrustPath\CertificateTrustPath;
12
use MadWizard\WebAuthn\Crypto\Der;
13
use MadWizard\WebAuthn\Exception\VerificationException;
14
use MadWizard\WebAuthn\Pki\CertificateDetails;
15
16
final class AppleAttestationVerifier implements AttestationVerifierInterface
17
{
18
    private const OID_APPLE_CERTIFICATE_POLICY = '1.2.840.113635.100.8.2';
19
20 1
    public function verify(AttestationStatementInterface $attStmt, AuthenticatorData $authenticatorData, string $clientDataHash): VerificationResult
21
    {
22 1
        if (!($attStmt instanceof AppleAttestationStatement)) {
23
            throw new VerificationException('Expecting AppleAttestationStatement');
24
        }
25
26
        // 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
27
        // -> this is done in AppleAttestationStatement
28
29
        // 2. Concatenate authenticatorData and clientDataHash to form nonceToHash.
30 1
        $nonceToHash = $authenticatorData->getRaw()->getBinaryString() . $clientDataHash;
31
32
        // 3. Perform SHA-256 hash of nonceToHash to produce nonce.
33 1
        $nonce = hash('sha256', $nonceToHash, true);
34
35 1
        $x5c = $attStmt->getCertificates();
36
37 1
        $credCert = $x5c[0] ?? null;
38 1
        if ($credCert === null) {
39
            throw new VerificationException('No certificates in attestation.');
40
        }
41
42
        // 4. Verify that nonce equals the value of the extension with OID ( 1.2.840.113635.100.8.2 ) in credCert.
43 1
        $cert = CertificateDetails::fromPem($credCert->asPem());
44 1
        $extension = $cert->getExtensionData(self::OID_APPLE_CERTIFICATE_POLICY);
45 1
        if ($extension === null) {
46
            throw new VerificationException('Missing apple extension in attestation certificate.');
47
        }
48
49 1
        $correctExtensionValue = Der::sequence(Der::contextTag(1, true, Der::octetString($nonce)));
50
51 1
        if (!hash_equals($correctExtensionValue, $extension->getValue()->getBinaryString())) {
52
            throw new VerificationException("The nonce in the certificate's extension does not match the calculated nonce.");
53
        }
54
55
        // 5. Verify credential public key matches the Subject Public Key of credCert.
56 1
        $certPublicKeyDer = $cert->getPublicKeyDer();
57 1
        $authenticatorPublicKeyDer = $authenticatorData->getKey()->asDer();
58
59 1
        if ($certPublicKeyDer !== $authenticatorPublicKeyDer) {
60
            throw new VerificationException('The public key of the attestation certificate is different from the public key in the authenticator data.');
61
        }
62
63
        // 6. If successful, return implementation-specific values representing attestation type Anonymization CA and attestation trust path x5c.
64 1
        return new VerificationResult(AttestationType::ANON_CA, new CertificateTrustPath(...$x5c));
65
    }
66
67 19
    public function getSupportedFormat(): AttestationFormatInterface
68
    {
69 19
        return new BuiltInAttestationFormat(
70 19
            AppleAttestationStatement::FORMAT_ID,
71 19
            AppleAttestationStatement::class,
72
            $this
73
        );
74
    }
75
}
76