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 / PackedAttestationStatementSupport.php

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

195 lines 8.7 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 function array_key_exists;
17 use WP_DEFENDER_VENDOR\Assert\Assertion;
18 use WP_DEFENDER_VENDOR\CBOR\Decoder;
19 use WP_DEFENDER_VENDOR\CBOR\MapObject;
20 use WP_DEFENDER_VENDOR\CBOR\OtherObject\OtherObjectManager;
21 use WP_DEFENDER_VENDOR\CBOR\Tag\TagObjectManager;
22 use WP_DEFENDER_VENDOR\Cose\Algorithm\Manager;
23 use WP_DEFENDER_VENDOR\Cose\Algorithm\Signature\Signature;
24 use WP_DEFENDER_VENDOR\Cose\Algorithms;
25 use WP_DEFENDER_VENDOR\Cose\Key\Key;
26 use function in_array;
27 use InvalidArgumentException;
28 use function is_array;
29 use RuntimeException;
30 use WP_DEFENDER_VENDOR\Webauthn\AuthenticatorData;
31 use WP_DEFENDER_VENDOR\Webauthn\CertificateToolbox;
32 use WP_DEFENDER_VENDOR\Webauthn\StringStream;
33 use WP_DEFENDER_VENDOR\Webauthn\TrustPath\CertificateTrustPath;
34 use WP_DEFENDER_VENDOR\Webauthn\TrustPath\EcdaaKeyIdTrustPath;
35 use WP_DEFENDER_VENDOR\Webauthn\TrustPath\EmptyTrustPath;
36 use WP_DEFENDER_VENDOR\Webauthn\Util\CoseSignatureFixer;
37
38 final class PackedAttestationStatementSupport implements AttestationStatementSupport
39 {
40 /**
41 * @var Decoder
42 */
43 private $decoder;
44
45 /**
46 * @var Manager
47 */
48 private $algorithmManager;
49
50 public function __construct(Manager $algorithmManager)
51 {
52 $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
53 $this->algorithmManager = $algorithmManager;
54 }
55
56 public function name(): string
57 {
58 return 'packed';
59 }
60
61 /**
62 * @param mixed[] $attestation
63 */
64 public function load(array $attestation): AttestationStatement
65 {
66 Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.');
67 Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.');
68 Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.');
69 switch (true) {
70 case array_key_exists('x5c', $attestation['attStmt']):
71 return $this->loadBasicType($attestation);
72 case array_key_exists('ecdaaKeyId', $attestation['attStmt']):
73 return $this->loadEcdaaType($attestation['attStmt']);
74 default:
75 return $this->loadEmptyType($attestation);
76 }
77 }
78
79 public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
80 {
81 $trustPath = $attestationStatement->getTrustPath();
82 switch (true) {
83 case $trustPath instanceof CertificateTrustPath:
84 return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath);
85 case $trustPath instanceof EcdaaKeyIdTrustPath:
86 return $this->processWithECDAA();
87 case $trustPath instanceof EmptyTrustPath:
88 return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData);
89 default:
90 throw new InvalidArgumentException('Unsupported attestation statement');
91 }
92 }
93
94 /**
95 * @param mixed[] $attestation
96 */
97 private function loadBasicType(array $attestation): AttestationStatement
98 {
99 $certificates = $attestation['attStmt']['x5c'];
100 Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
101 Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.');
102 $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
103
104 return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
105 }
106
107 private function loadEcdaaType(array $attestation): AttestationStatement
108 {
109 $ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId'];
110 Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.');
111
112 return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId']));
113 }
114
115 /**
116 * @param mixed[] $attestation
117 */
118 private function loadEmptyType(array $attestation): AttestationStatement
119 {
120 return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath());
121 }
122
123 private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void
124 {
125 $parsed = openssl_x509_parse($attestnCert);
126 Assertion::isArray($parsed, 'Invalid certificate');
127
128 //Check version
129 Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version');
130
131 //Check subject field
132 Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"');
133
134 //Check extensions
135 Assertion::false(!isset($parsed['extensions']) || !is_array($parsed['extensions']), 'Certificate extensions are missing');
136
137 //Check certificate is not a CA cert
138 Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false');
139
140 $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
141 Assertion::notNull($attestedCredentialData, 'No attested credential available');
142
143 // id-fido-gen-ce-aaguid OID check
144 Assertion::false(in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate');
145 }
146
147 private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool
148 {
149 $certificates = $trustPath->getCertificates();
150
151 // Check leaf certificate
152 $this->checkCertificate($certificates[0], $authenticatorData);
153
154 // Get the COSE algorithm identifier and the corresponding OpenSSL one
155 $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg');
156 $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier);
157
158 // Verification of the signature
159 $signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
160 $result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier);
161
162 return 1 === $result;
163 }
164
165 private function processWithECDAA(): bool
166 {
167 throw new RuntimeException('ECDAA not supported');
168 }
169
170 private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
171 {
172 $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
173 Assertion::notNull($attestedCredentialData, 'No attested credential available');
174 $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey();
175 Assertion::notNull($credentialPublicKey, 'No credential public key available');
176 $publicKeyStream = new StringStream($credentialPublicKey);
177 $publicKey = $this->decoder->decode($publicKeyStream);
178 Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.');
179 $publicKeyStream->close();
180 Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.');
181 $publicKey = $publicKey->getNormalizedData(false);
182 $publicKey = new Key($publicKey);
183 Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.');
184
185 $dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash;
186 $algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg'));
187 if (!$algorithm instanceof Signature) {
188 throw new RuntimeException('Invalid algorithm');
189 }
190 $signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm);
191
192 return $algorithm->verify($dataToVerify, $publicKey, $signature);
193 }
194 }
195