[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package Joomla.Plugin 5 * @subpackage System.Webauthn 6 * 7 * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org> 8 * @license MIT; see libraries/vendor/web-auth/webauthn-lib/LICENSE 9 */ 10 11 namespace Joomla\Plugin\System\Webauthn\Hotfix; 12 13 use Assert\Assertion; 14 use CBOR\Decoder; 15 use CBOR\MapObject; 16 use CBOR\OtherObject\OtherObjectManager; 17 use CBOR\Tag\TagObjectManager; 18 use Cose\Key\Ec2Key; 19 use Webauthn\AttestationStatement\AttestationStatement; 20 use Webauthn\AttestationStatement\AttestationStatementSupport; 21 use Webauthn\AuthenticatorData; 22 use Webauthn\CertificateToolbox; 23 use Webauthn\MetadataService\MetadataStatementRepository; 24 use Webauthn\StringStream; 25 use Webauthn\TrustPath\CertificateTrustPath; 26 27 // phpcs:disable PSR1.Files.SideEffects 28 \defined('_JEXEC') or die; 29 // phpcs:enable PSR1.Files.SideEffects 30 31 /** 32 * We had to fork the key attestation support object from the WebAuthn server package to address an 33 * issue with PHP 8. 34 * 35 * We are currently using an older version of the WebAuthn library (2.x) which was written before 36 * PHP 8 was developed. We cannot upgrade the WebAuthn library to a newer major version because of 37 * Joomla's Semantic Versioning promise. 38 * 39 * The FidoU2FAttestationStatementSupport class forces an assertion on the result of the 40 * openssl_pkey_get_public() function, assuming it will return a resource. However, starting with 41 * PHP 8.0 this function returns an OpenSSLAsymmetricKey object and the assertion fails. As a 42 * result, you cannot use Android or FIDO U2F keys with WebAuthn. 43 * 44 * The assertion check is in a private method, therefore we have to fork both attestation support 45 * class to change the assertion. The assertion takes place through a third party library we cannot 46 * (and should not!) modify. 47 * 48 * @since 4.2.0 49 * 50 * @deprecated 5.0 We will upgrade the WebAuthn library to version 3 or later and this will go away. 51 */ 52 final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport 53 { 54 /** 55 * @var Decoder 56 * @since 4.2.0 57 */ 58 private $decoder; 59 60 /** 61 * @var MetadataStatementRepository|null 62 * @since 4.2.0 63 */ 64 private $metadataStatementRepository; 65 66 /** 67 * @param Decoder|null $decoder Obvious 68 * @param MetadataStatementRepository|null $metadataStatementRepository Obvious 69 * 70 * @since 4.2.0 71 */ 72 public function __construct( 73 ?Decoder $decoder = null, 74 ?MetadataStatementRepository $metadataStatementRepository = null 75 ) { 76 if ($decoder !== null) { 77 @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); 78 } 79 80 if ($metadataStatementRepository === null) { 81 @trigger_error( 82 'Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', 83 E_USER_DEPRECATED 84 ); 85 } 86 87 $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); 88 $this->metadataStatementRepository = $metadataStatementRepository; 89 } 90 91 /** 92 * @return string 93 * @since 4.2.0 94 */ 95 public function name(): string 96 { 97 return 'fido-u2f'; 98 } 99 100 /** 101 * @param array $attestation Obvious 102 * 103 * @return AttestationStatement 104 * @throws \Assert\AssertionFailedException 105 * 106 * @since 4.2.0 107 */ 108 public function load(array $attestation): AttestationStatement 109 { 110 Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); 111 112 foreach (['sig', 'x5c'] as $key) { 113 Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); 114 } 115 116 $certificates = $attestation['attStmt']['x5c']; 117 Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); 118 Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.'); 119 Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); 120 121 reset($certificates); 122 $certificates = CertificateToolbox::convertAllDERToPEM($certificates); 123 $this->checkCertificate($certificates[0]); 124 125 return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); 126 } 127 128 /** 129 * @param string $clientDataJSONHash Obvious 130 * @param AttestationStatement $attestationStatement Obvious 131 * @param AuthenticatorData $authenticatorData Obvious 132 * 133 * @return boolean 134 * @throws \Assert\AssertionFailedException 135 * @since 4.2.0 136 */ 137 public function isValid( 138 string $clientDataJSONHash, 139 AttestationStatement $attestationStatement, 140 AuthenticatorData $authenticatorData 141 ): bool { 142 Assertion::eq( 143 $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), 144 '00000000-0000-0000-0000-000000000000', 145 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"' 146 ); 147 148 if ($this->metadataStatementRepository !== null) { 149 CertificateToolbox::checkAttestationMedata( 150 $attestationStatement, 151 $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), 152 [], 153 $this->metadataStatementRepository 154 ); 155 } 156 157 $trustPath = $attestationStatement->getTrustPath(); 158 Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); 159 $dataToVerify = "\0"; 160 $dataToVerify .= $authenticatorData->getRpIdHash(); 161 $dataToVerify .= $clientDataJSONHash; 162 $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId(); 163 $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()); 164 165 return openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256) === 1; 166 } 167 168 /** 169 * @param string|null $publicKey Obvious 170 * 171 * @return string 172 * @throws \Assert\AssertionFailedException 173 * @since 4.2.0 174 */ 175 private function extractPublicKey(?string $publicKey): string 176 { 177 Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.'); 178 179 $publicKeyStream = new StringStream($publicKey); 180 $coseKey = $this->decoder->decode($publicKeyStream); 181 Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); 182 $publicKeyStream->close(); 183 Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); 184 185 $coseKey = $coseKey->getNormalizedData(); 186 $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]); 187 188 return "\x04" . $ec2Key->x() . $ec2Key->y(); 189 } 190 191 /** 192 * @param string $publicKey Obvious 193 * 194 * @return void 195 * @throws \Assert\AssertionFailedException 196 * @since 4.2.0 197 */ 198 private function checkCertificate(string $publicKey): void 199 { 200 try { 201 $resource = openssl_pkey_get_public($publicKey); 202 203 if (version_compare(PHP_VERSION, '8.0', 'lt')) { 204 Assertion::isResource($resource, 'Unable to read the certificate'); 205 } else { 206 /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ 207 Assertion::isInstanceOf($resource, \OpenSSLAsymmetricKey::class, 'Unable to read the certificate'); 208 } 209 } catch (\Throwable $throwable) { 210 throw new \InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable); 211 } 212 213 $details = openssl_pkey_get_details($resource); 214 Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain'); 215 Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain'); 216 Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain'); 217 Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain'); 218 Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain'); 219 } 220 }
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 |