[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 declare(strict_types=1); 4 5 /* 6 * The MIT License (MIT) 7 * 8 * Copyright (c) 2014-2019 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 FG\ASN1\ASNObject; 25 use FG\ASN1\ExplicitlyTaggedObject; 26 use FG\ASN1\Universal\OctetString; 27 use FG\ASN1\Universal\Sequence; 28 use Webauthn\AuthenticatorData; 29 use Webauthn\CertificateToolbox; 30 use Webauthn\MetadataService\MetadataStatementRepository; 31 use Webauthn\StringStream; 32 use Webauthn\TrustPath\CertificateTrustPath; 33 34 final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport 35 { 36 /** 37 * @var Decoder 38 */ 39 private $decoder; 40 41 /** 42 * @var MetadataStatementRepository|null 43 */ 44 private $metadataStatementRepository; 45 46 public function __construct(?Decoder $decoder = null, ?MetadataStatementRepository $metadataStatementRepository = null) 47 { 48 if (null !== $decoder) { 49 @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); 50 } 51 if (null === $metadataStatementRepository) { 52 @trigger_error('Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED); 53 } 54 $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); 55 $this->metadataStatementRepository = $metadataStatementRepository; 56 } 57 58 public function name(): string 59 { 60 return 'android-key'; 61 } 62 63 public function load(array $attestation): AttestationStatement 64 { 65 Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); 66 foreach (['sig', 'x5c', 'alg'] as $key) { 67 Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); 68 } 69 $certificates = $attestation['attStmt']['x5c']; 70 Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); 71 Assertion::greaterThan(\count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.'); 72 Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); 73 $certificates = CertificateToolbox::convertAllDERToPEM($certificates); 74 75 return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); 76 } 77 78 public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool 79 { 80 $trustPath = $attestationStatement->getTrustPath(); 81 Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); 82 83 $certificates = $trustPath->getCertificates(); 84 if (null !== $this->metadataStatementRepository) { 85 $certificates = CertificateToolbox::checkAttestationMedata( 86 $attestationStatement, 87 $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), 88 $certificates, 89 $this->metadataStatementRepository 90 ); 91 } 92 93 //Decode leaf attestation certificate 94 $leaf = $certificates[0]; 95 $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData); 96 97 $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; 98 $alg = $attestationStatement->get('alg'); 99 100 return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg)); 101 } 102 103 private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void 104 { 105 $resource = openssl_pkey_get_public($certificate); 106 Assertion::isResource($resource, 'Unable to read the certificate'); 107 $details = openssl_pkey_get_details($resource); 108 Assertion::isArray($details, 'Unable to read the certificate'); 109 110 //Check that authData publicKey matches the public key in the attestation certificate 111 $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); 112 Assertion::notNull($attestedCredentialData, 'No attested credential data found'); 113 $publicKeyData = $attestedCredentialData->getCredentialPublicKey(); 114 Assertion::notNull($publicKeyData, 'No attested public key found'); 115 $publicDataStream = new StringStream($publicKeyData); 116 $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false); 117 Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.'); 118 $publicDataStream->close(); 119 $publicKey = Key::createFromData($coseKey); 120 121 Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type'); 122 Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key'); 123 124 /*---------------------------*/ 125 $certDetails = openssl_x509_parse($certificate); 126 127 //Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions 128 Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension'); 129 Assertion::isArray($certDetails['extensions'], 'The certificate has no extension'); 130 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'); 131 $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17']; 132 $extensionAsAsn1 = ASNObject::fromBinary($extension); 133 Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 134 $objects = $extensionAsAsn1->getChildren(); 135 136 //Check that attestationChallenge is set to the clientDataHash. 137 Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 138 Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 139 Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid'); 140 141 //Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag. 142 Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 143 $softwareEnforcedFlags = $objects[6]; 144 Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 145 $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags); 146 147 Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 148 $teeEnforcedFlags = $objects[6]; 149 Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); 150 $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags); 151 } 152 153 private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void 154 { 155 foreach ($sequence->getChildren() as $tag) { 156 Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag'); 157 /* @var ExplicitlyTaggedObject $tag */ 158 Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found'); 159 } 160 } 161 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |