|
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
|
|
|
|