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