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