PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.0
Defender Security – Malware Scanner, Login Security & Firewall v5.5.0
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 / vendor / web-auth / webauthn-lib / src / AttestationStatement / AndroidKeyAttestationStatementSupport.php

AndroidKeyAttestationStatementSupport.php in Defender Security – Malware Scanner, Login Security & Firewall 5.5.0, at vendor/web-auth/webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php

148 lines 6.8 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 Webauthn\AttestationStatement;
15
16 use Assert\Assertion;
17 use CBOR\Decoder;
18 use CBOR\OtherObject\OtherObjectManager;
19 use CBOR\Tag\TagObjectManager;
20 use Cose\Algorithms;
21 use Cose\Key\Ec2Key;
22 use Cose\Key\Key;
23 use Cose\Key\RsaKey;
24 use function count;
25 use FG\ASN1\ASNObject;
26 use FG\ASN1\ExplicitlyTaggedObject;
27 use FG\ASN1\Universal\OctetString;
28 use FG\ASN1\Universal\Sequence;
29 use function Safe\hex2bin;
30 use function Safe\openssl_pkey_get_public;
31 use function Safe\sprintf;
32 use Webauthn\AuthenticatorData;
33 use Webauthn\CertificateToolbox;
34 use Webauthn\StringStream;
35 use Webauthn\TrustPath\CertificateTrustPath;
36
37 final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport
38 {
39 /**
40 * @var Decoder
41 */
42 private $decoder;
43
44 public function __construct()
45 {
46 $this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager());
47 }
48
49 public function name(): string
50 {
51 return 'android-key';
52 }
53
54 /**
55 * @param mixed[] $attestation
56 */
57 public function load(array $attestation): AttestationStatement
58 {
59 Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object');
60 foreach (['sig', 'x5c', 'alg'] as $key) {
61 Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key));
62 }
63 $certificates = $attestation['attStmt']['x5c'];
64 Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
65 Assertion::greaterThan(count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.');
66 Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.');
67 $certificates = CertificateToolbox::convertAllDERToPEM($certificates);
68
69 return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates));
70 }
71
72 public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool
73 {
74 $trustPath = $attestationStatement->getTrustPath();
75 Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path');
76
77 $certificates = $trustPath->getCertificates();
78
79 //Decode leaf attestation certificate
80 $leaf = $certificates[0];
81 $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData);
82
83 $signedData = $authenticatorData->getAuthData().$clientDataJSONHash;
84 $alg = $attestationStatement->get('alg');
85
86 return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg));
87 }
88
89 private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void
90 {
91 $resource = openssl_pkey_get_public($certificate);
92 $details = openssl_pkey_get_details($resource);
93 Assertion::isArray($details, 'Unable to read the certificate');
94
95 //Check that authData publicKey matches the public key in the attestation certificate
96 $attestedCredentialData = $authenticatorData->getAttestedCredentialData();
97 Assertion::notNull($attestedCredentialData, 'No attested credential data found');
98 $publicKeyData = $attestedCredentialData->getCredentialPublicKey();
99 Assertion::notNull($publicKeyData, 'No attested public key found');
100 $publicDataStream = new StringStream($publicKeyData);
101 $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false);
102 Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.');
103 $publicDataStream->close();
104 $publicKey = Key::createFromData($coseKey);
105
106 Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type');
107 Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key');
108
109 /*---------------------------*/
110 $certDetails = openssl_x509_parse($certificate);
111
112 //Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions
113 Assertion::isArray($certDetails, 'The certificate is not valid');
114 Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension');
115 Assertion::isArray($certDetails['extensions'], 'The certificate has no extension');
116 Assertion::keyExists($certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing');
117 $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17'];
118 $extensionAsAsn1 = ASNObject::fromBinary($extension);
119 Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
120 $objects = $extensionAsAsn1->getChildren();
121
122 //Check that attestationChallenge is set to the clientDataHash.
123 Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
124 Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
125 Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid');
126
127 //Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag.
128 Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
129 $softwareEnforcedFlags = $objects[6];
130 Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
131 $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags);
132
133 Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
134 $teeEnforcedFlags = $objects[6];
135 Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid');
136 $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags);
137 }
138
139 private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void
140 {
141 foreach ($sequence->getChildren() as $tag) {
142 Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag');
143 /* @var ExplicitlyTaggedObject $tag */
144 Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found');
145 }
146 }
147 }
148