| 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 WP_DEFENDER_VENDOR\Webauthn\AttestationStatement; |
| 15 |
|
| 16 |
use WP_DEFENDER_VENDOR\Assert\Assertion; |
| 17 |
use WP_DEFENDER_VENDOR\Base64Url\Base64Url; |
| 18 |
use WP_DEFENDER_VENDOR\CBOR\Decoder; |
| 19 |
use WP_DEFENDER_VENDOR\CBOR\MapObject; |
| 20 |
use WP_DEFENDER_VENDOR\CBOR\OtherObject\OtherObjectManager; |
| 21 |
use WP_DEFENDER_VENDOR\CBOR\Tag\TagObjectManager; |
| 22 |
use function ord; |
| 23 |
use WP_DEFENDER_VENDOR\Psr\Log\LoggerInterface; |
| 24 |
use WP_DEFENDER_VENDOR\Psr\Log\NullLogger; |
| 25 |
use WP_DEFENDER_VENDOR\Ramsey\Uuid\Uuid; |
| 26 |
use function WP_DEFENDER_VENDOR\Safe\sprintf; |
| 27 |
use function WP_DEFENDER_VENDOR\Safe\unpack; |
| 28 |
use Throwable; |
| 29 |
use WP_DEFENDER_VENDOR\Webauthn\AttestedCredentialData; |
| 30 |
use WP_DEFENDER_VENDOR\Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; |
| 31 |
use WP_DEFENDER_VENDOR\Webauthn\AuthenticatorData; |
| 32 |
use WP_DEFENDER_VENDOR\Webauthn\MetadataService\MetadataStatementRepository; |
| 33 |
use WP_DEFENDER_VENDOR\Webauthn\StringStream; |
| 34 |
|
| 35 |
class AttestationObjectLoader |
| 36 |
{ |
| 37 |
private const FLAG_AT = 0b01000000; |
| 38 |
private const FLAG_ED = 0b10000000; |
| 39 |
|
| 40 |
/** |
| 41 |
* @var Decoder |
| 42 |
*/ |
| 43 |
private $decoder; |
| 44 |
|
| 45 |
/** |
| 46 |
* @var AttestationStatementSupportManager |
| 47 |
*/ |
| 48 |
private $attestationStatementSupportManager; |
| 49 |
|
| 50 |
/** |
| 51 |
* @var LoggerInterface|null |
| 52 |
*/ |
| 53 |
private $logger; |
| 54 |
|
| 55 |
public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, ?MetadataStatementRepository $metadataStatementRepository = null, ?LoggerInterface $logger = null) |
| 56 |
{ |
| 57 |
if (null !== $metadataStatementRepository) { |
| 58 |
@trigger_error('The argument "metadataStatementRepository" is deprecated since version 3.2 and will be removed in 4.0. Please set `null` instead.', E_USER_DEPRECATED); |
| 59 |
} |
| 60 |
if (null !== $logger) { |
| 61 |
@trigger_error('The argument "logger" is deprecated since version 3.3 and will be removed in 4.0. Please use the method "setLogger" instead.', E_USER_DEPRECATED); |
| 62 |
} |
| 63 |
$this->decoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); |
| 64 |
$this->attestationStatementSupportManager = $attestationStatementSupportManager; |
| 65 |
$this->logger = $logger ?? new NullLogger(); |
| 66 |
} |
| 67 |
|
| 68 |
public static function create(AttestationStatementSupportManager $attestationStatementSupportManager): self |
| 69 |
{ |
| 70 |
return new self($attestationStatementSupportManager); |
| 71 |
} |
| 72 |
|
| 73 |
public function load(string $data): AttestationObject |
| 74 |
{ |
| 75 |
try { |
| 76 |
$this->logger->info('Trying to load the data', ['data' => $data]); |
| 77 |
$decodedData = Base64Url::decode($data); |
| 78 |
$stream = new StringStream($decodedData); |
| 79 |
$parsed = $this->decoder->decode($stream); |
| 80 |
|
| 81 |
$this->logger->info('Loading the Attestation Statement'); |
| 82 |
$attestationObject = $parsed->getNormalizedData(); |
| 83 |
Assertion::true($stream->isEOF(), 'Invalid attestation object. Presence of extra bytes.'); |
| 84 |
$stream->close(); |
| 85 |
Assertion::isArray($attestationObject, 'Invalid attestation object'); |
| 86 |
Assertion::keyExists($attestationObject, 'authData', 'Invalid attestation object'); |
| 87 |
Assertion::keyExists($attestationObject, 'fmt', 'Invalid attestation object'); |
| 88 |
Assertion::keyExists($attestationObject, 'attStmt', 'Invalid attestation object'); |
| 89 |
$authData = $attestationObject['authData']; |
| 90 |
|
| 91 |
$attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']); |
| 92 |
$attestationStatement = $attestationStatementSupport->load($attestationObject); |
| 93 |
$this->logger->info('Attestation Statement loaded'); |
| 94 |
$this->logger->debug('Attestation Statement loaded', ['attestationStatement' => $attestationStatement]); |
| 95 |
|
| 96 |
$authDataStream = new StringStream($authData); |
| 97 |
$rp_id_hash = $authDataStream->read(32); |
| 98 |
$flags = $authDataStream->read(1); |
| 99 |
$signCount = $authDataStream->read(4); |
| 100 |
$signCount = unpack('N', $signCount)[1]; |
| 101 |
$this->logger->debug(sprintf('Signature counter: %d', $signCount)); |
| 102 |
|
| 103 |
$attestedCredentialData = null; |
| 104 |
if (0 !== (ord($flags) & self::FLAG_AT)) { |
| 105 |
$this->logger->info('Attested Credential Data is present'); |
| 106 |
$aaguid = Uuid::fromBytes($authDataStream->read(16)); |
| 107 |
$credentialLength = $authDataStream->read(2); |
| 108 |
$credentialLength = unpack('n', $credentialLength)[1]; |
| 109 |
$credentialId = $authDataStream->read($credentialLength); |
| 110 |
$credentialPublicKey = $this->decoder->decode($authDataStream); |
| 111 |
Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); |
| 112 |
$attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); |
| 113 |
$this->logger->info('Attested Credential Data loaded'); |
| 114 |
$this->logger->debug('Attested Credential Data loaded', ['at' => $attestedCredentialData]); |
| 115 |
} |
| 116 |
|
| 117 |
$extension = null; |
| 118 |
if (0 !== (ord($flags) & self::FLAG_ED)) { |
| 119 |
$this->logger->info('Extension Data loaded'); |
| 120 |
$extension = $this->decoder->decode($authDataStream); |
| 121 |
$extension = AuthenticationExtensionsClientOutputsLoader::load($extension); |
| 122 |
$this->logger->info('Extension Data loaded'); |
| 123 |
$this->logger->debug('Extension Data loaded', ['ed' => $extension]); |
| 124 |
} |
| 125 |
Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); |
| 126 |
$authDataStream->close(); |
| 127 |
|
| 128 |
$authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); |
| 129 |
$attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData); |
| 130 |
$this->logger->info('Attestation Object loaded'); |
| 131 |
$this->logger->debug('Attestation Object', ['ed' => $attestationObject]); |
| 132 |
|
| 133 |
return $attestationObject; |
| 134 |
} catch (Throwable $throwable) { |
| 135 |
$this->logger->error('An error occurred', [ |
| 136 |
'exception' => $throwable, |
| 137 |
]); |
| 138 |
throw $throwable; |
| 139 |
} |
| 140 |
} |
| 141 |
|
| 142 |
public function setLogger(LoggerInterface $logger): self |
| 143 |
{ |
| 144 |
$this->logger = $logger; |
| 145 |
|
| 146 |
return $this; |
| 147 |
} |
| 148 |
} |
| 149 |
|