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