PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 6.2.2
Defender Security – Malware Scanner, Login Security & Firewall v6.2.2
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / lib / packages / Webauthn / AttestationStatement / AppleAttestationStatementSupport.php

AppleAttestationStatementSupport.php in Defender Security – Malware Scanner, Login Security & Firewall 6.2.2, at lib/packages/Webauthn/AttestationStatement/AppleAttestationStatementSupport.php

120 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 /*
6 * The MIT License (MIT)
7 *
8 * Copyright (c) 2014-2021 Spomky-Labs
9 *
10 * This software may be modified and distributed under the terms
11 * of the MIT license. See the LICENSE file for details.
12 */
13
14 namespace WP_DEFENDER_VENDOR\Webauthn\AttestationStatement;
15
16 use WP_DEFENDER_VENDOR\Assert\Assertion;
17 use WP_DEFENDER_VENDOR\CBOR\Decoder;
18 use WP_DEFENDER_VENDOR\CBOR\OtherObject\OtherObjectManager;
19 use WP_DEFENDER_VENDOR\CBOR\Tag\TagObjectManager;
20 use WP_DEFENDER_VENDOR\Cose\Key\Ec2Key;
21 use WP_DEFENDER_VENDOR\Cose\Key\Key;
22 use WP_DEFENDER_VENDOR\Cose\Key\RsaKey;
23 use function count;
24 use function WP_DEFENDER_VENDOR\Safe\openssl_pkey_get_public;
25 use function WP_DEFENDER_VENDOR\Safe\sprintf;
26 use WP_DEFENDER_VENDOR\Webauthn\AuthenticatorData;
27 use WP_DEFENDER_VENDOR\Webauthn\CertificateToolbox;
28 use WP_DEFENDER_VENDOR\Webauthn\StringStream;
29 use WP_DEFENDER_VENDOR\Webauthn\TrustPath\CertificateTrustPath;
30
31 final class AppleAttestationStatementSupport implements AttestationStatementSupport
32 {
33 /**
34 * @var Decoder
35 */
36 private $decoder;
37
38 public function __construct()
39 {
40 $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
41 }
42
43 public function name(): string
44 {
45 return 'apple';
46 }
47
48 /**
49 * @param mixed[] $attestation
50 */
51 public function load(array $attestation): AttestationStatement
52 {
53 Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
54 foreach (['x5c'] as $key) {
55 Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
56 }
57 $certificates = $attestation['attStmt']['x5c'];
58 Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
59 Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.');
60 Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
61 $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
62
63 return AttestationStatement::createAnonymizationCA($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
64 }
65
66 public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
67 {
68 $trustPath = $attestationStatement->getTrustPath();
69 Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
70
71 $certificates = $trustPath->getCertificates();
72
73 //Decode leaf attestation certificate
74 $leaf = $certificates[0];
75
76 $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData);
77
78 return true;
79 }
80
81 private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void
82 {
83 $resource = openssl_pkey_get_public($certificate);
84 $details = openssl_pkey_get_details($resource);
85 Assertion::isArray($details, 'Unable to read the certificate');
86
87 //Check that authData publicKey matches the public key in the attestation certificate
88 $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
89 Assertion::notNull($attestedCredentialData, 'No attested credential data found');
90 $publicKeyData = $attestedCredentialData->getCredentialPublicKey();
91 Assertion::notNull($publicKeyData, 'No attested public key found');
92 $publicDataStream = new StringStream($publicKeyData);
93 $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false);
94 Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.');
95 $publicDataStream->close();
96 $publicKey = Key::createFromData($coseKey);
97
98 Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type');
99
100 //We check the attested key corresponds to the key in the certificate
101 Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key');
102
103 /*---------------------------*/
104 $certDetails = openssl_x509_parse($certificate);
105
106 //Find Apple Extension with OID “1.2.840.113635.100.8.2” in certificate extensions
107 Assertion::isArray($certDetails, 'The certificate is not valid');
108 Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension');
109 Assertion::isArray($certDetails['extensions'], 'The certificate has no extension');
110 Assertion::keyExists($certDetails['extensions'], '1.2.840.113635.100.8.2', 'The certificate extension "1.2.840.113635.100.8.2" is missing');
111 $extension = $certDetails['extensions']['1.2.840.113635.100.8.2'];
112
113 $nonceToHash = $authenticatorData->getAuthData().$clientDataHash;
114 $nonce = hash('sha256', $nonceToHash);
115
116 //'3024a1220420' corresponds to the Sequence+Explicitly Tagged Object + Octet Object
117 Assertion::eq('3024a1220420'.$nonce, bin2hex($extension), 'The client data hash is not valid');
118 }
119 }
120