Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release 4.7.0 into 5.0.x #463

Merged
merged 25 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
86647fd
BE and BS flags support (#424)
Spomky Jun 21, 2023
49b5bbc
BS and BE flags added to the Symfony Token (#425)
Spomky Jun 21, 2023
8714844
Fix bundle extension classname (#429)
Spomky Jul 2, 2023
c53f585
Update scorecards.yml (#430)
Spomky Jul 2, 2023
69c7a16
Merge pull request #431 from web-auth/4.6.x
Spomky Jul 2, 2023
c3eae17
return transports in attestation response pk credential source
Spomky Jul 15, 2023
ca754d8
Merge pull request #435 from joostdebruijn/fix/add-transports
Spomky Jul 15, 2023
fb38183
Fix typos (#433)
szepeviktor Jul 15, 2023
3db3259
Fix wrong EdDSA key encoding (#437)
Spomky Jul 15, 2023
7e89cde
Merge up 4.6.x to 4.7.x
Spomky Jul 15, 2023
c295998
Merge pull request #439 from web-auth/temporary-branchhFjDEmnd
Spomky Jul 15, 2023
7ef9b7b
Fix Rector+ECS
Spomky Jul 15, 2023
8e9167b
Merge pull request #440 from web-auth/cs/fix-all
Spomky Jul 15, 2023
697c84f
Ability to register users without username or displayName (#443)
Spomky Jul 23, 2023
9c83a3f
Fixed composer keywords (#444)
Spomky Jul 23, 2023
3eeb057
JS deps updated (#446)
Spomky Jul 24, 2023
c9e54b7
Doc files updated (#445)
Spomky Jul 24, 2023
52b2774
Minor corrections (#447)
Spomky Jul 24, 2023
0e945db
Previous fix for eddsa key fail at authentication (#449)
Gashmob Jul 26, 2023
e5adcb1
Bugs fixed (#451)
Spomky Jul 27, 2023
080b81d
Sonarcloud Github Action (#452)
Spomky Jul 27, 2023
5f1eefd
Code Coverage for Sonarcloud (#453)
Spomky Jul 27, 2023
b2c2321
Code Coverage for Sonarcloud (#454)
Spomky Jul 27, 2023
8d78f43
New exclusions (#455)
Spomky Jul 27, 2023
87895ca
Deprecate DTOs/ValueObjects getters/setters in favor of direct acces …
Spomky Jul 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix wrong EdDSA key encoding (#437)
  • Loading branch information
Spomky authored Jul 15, 2023
commit 3db3259c1bbfb75d4a3ba32f9445968abc37ecaf
35 changes: 25 additions & 10 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1501,11 +1501,6 @@ parameters:
count: 1
path: src/webauthn/src/AttestationStatement/AppleAttestationStatementSupport.php

-
message: "#^Cannot access offset 1 on array\\|false\\.$#"
count: 3
path: src/webauthn/src/AttestationStatement/AttestationObjectLoader.php

-
message: "#^Parameter \\#1 \\$data of static method Webauthn\\\\TrustPath\\\\TrustPathLoader\\:\\:loadTrustPath\\(\\) expects array, mixed given\\.$#"
count: 1
Expand Down Expand Up @@ -1836,6 +1831,31 @@ parameters:
count: 1
path: src/webauthn/src/AuthenticatorAttestationResponseValidator.php

-
message: "#^Cannot access offset 1 on array\\|false\\.$#"
count: 2
path: src/webauthn/src/AuthenticatorDataLoader.php

-
message: "#^Parameter \\#1 \\$search of function str_replace expects array\\|string, string\\|false given\\.$#"
count: 1
path: src/webauthn/src/AuthenticatorDataLoader.php

-
message: "#^Parameter \\#2 \\$callback of function array_reduce expects callable\\(string, mixed\\)\\: string, Closure\\(string, string\\)\\: non\\-empty\\-string given\\.$#"
count: 1
path: src/webauthn/src/AuthenticatorDataLoader.php

-
message: "#^Parameter \\#2 \\$needle of function mb_strpos expects string, string\\|false given\\.$#"
count: 1
path: src/webauthn/src/AuthenticatorDataLoader.php

-
message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, string\\|false given\\.$#"
count: 1
path: src/webauthn/src/AuthenticatorDataLoader.php

-
message: """
#^Access to deprecated property \\$requireResidentKey of class Webauthn\\\\AuthenticatorSelectionCriteria\\:
Expand Down Expand Up @@ -1890,11 +1910,6 @@ parameters:
count: 1
path: src/webauthn/src/PublicKeyCredentialDescriptorCollection.php

-
message: "#^Cannot access offset 1 on array\\|false\\.$#"
count: 2
path: src/webauthn/src/PublicKeyCredentialLoader.php

-
message: "#^Parameter \\#1 \\$json of method Webauthn\\\\PublicKeyCredentialLoader\\:\\:loadArray\\(\\) expects array, mixed given\\.$#"
count: 1
Expand Down
75 changes: 5 additions & 70 deletions src/webauthn/src/AttestationStatement/AttestationObjectLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,13 @@

use function array_key_exists;
use CBOR\Decoder;
use CBOR\MapObject;
use CBOR\Normalizable;
use function is_array;
use function ord;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Uid\Uuid;
use Throwable;
use function unpack;
use Webauthn\AttestedCredentialData;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
use Webauthn\AuthenticatorData;
use Webauthn\AuthenticatorDataLoader;
use Webauthn\Event\AttestationObjectLoaded;
use Webauthn\Exception\InvalidDataException;
use Webauthn\MetadataService\CanLogData;
Expand All @@ -29,20 +23,13 @@

class AttestationObjectLoader implements CanDispatchEvents, CanLogData
{
private const FLAG_AT = 0b01000000;

private const FLAG_ED = 0b10000000;

private readonly Decoder $decoder;

private LoggerInterface $logger;

private EventDispatcherInterface $dispatcher;

public function __construct(
private readonly AttestationStatementSupportManager $attestationStatementSupportManager
) {
$this->decoder = Decoder::create();
$this->logger = new NullLogger();
$this->dispatcher = new NullEventDispatcher();
}
Expand All @@ -65,7 +52,7 @@ public function load(string $data): AttestationObject
]);
$decodedData = Base64::decode($data);
$stream = new StringStream($decodedData);
$parsed = $this->decoder->decode($stream);
$parsed = Decoder::create()->decode($stream);

$this->logger->info('Loading the Attestation Statement');
$parsed instanceof Normalizable || throw InvalidDataException::create(
Expand Down Expand Up @@ -94,69 +81,17 @@ public function load(string $data): AttestationObject
$attestationObject,
'Invalid attestation object'
);
$authData = $attestationObject['authData'];

$attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']);
$attestationStatement = $attestationStatementSupport->load($attestationObject);
$this->logger->info('Attestation Statement loaded');
$this->logger->debug('Attestation Statement loaded', [
'attestationStatement' => $attestationStatement,
]);
$authData = $attestationObject['authData'];
$authDataLoader = AuthenticatorDataLoader::create();
$authenticatorData = $authDataLoader->load($authData);

$authDataStream = new StringStream($authData);
$rp_id_hash = $authDataStream->read(32);
$flags = $authDataStream->read(1);
$signCount = $authDataStream->read(4);
$signCount = unpack('N', $signCount);
$this->logger->debug(sprintf('Signature counter: %d', $signCount[1]));

$attestedCredentialData = null;
if (0 !== (ord($flags) & self::FLAG_AT)) {
$this->logger->info('Attested Credential Data is present');
$aaguid = Uuid::fromBinary($authDataStream->read(16));
$credentialLength = $authDataStream->read(2);
$credentialLength = unpack('n', $credentialLength);
$credentialId = $authDataStream->read($credentialLength[1]);
$credentialPublicKey = $this->decoder->decode($authDataStream);
$credentialPublicKey instanceof MapObject || throw InvalidDataException::create(
$credentialPublicKey,
'The data does not contain a valid credential public key.'
);
$attestedCredentialData = new AttestedCredentialData(
$aaguid,
$credentialId,
(string) $credentialPublicKey
);
$this->logger->info('Attested Credential Data loaded');
$this->logger->debug('Attested Credential Data loaded', [
'at' => $attestedCredentialData,
]);
}

$extension = null;
if (0 !== (ord($flags) & self::FLAG_ED)) {
$this->logger->info('Extension Data loaded');
$extension = $this->decoder->decode($authDataStream);
$extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
$this->logger->info('Extension Data loaded');
$this->logger->debug('Extension Data loaded', [
'ed' => $extension,
]);
}
$authDataStream->isEOF() || throw InvalidDataException::create(
null,
'Invalid authentication data. Presence of extra bytes.'
);
$authDataStream->close();

$authenticatorData = new AuthenticatorData(
$authData,
$rp_id_hash,
$flags,
$signCount[1],
$attestedCredentialData,
$extension
);
$attestationObject = new AttestationObject($data, $attestationStatement, $authenticatorData);
$this->logger->info('Attestation Object loaded');
$this->logger->debug('Attestation Object', [
Expand Down
12 changes: 6 additions & 6 deletions src/webauthn/src/AuthenticatorData.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
*/
class AuthenticatorData
{
private const FLAG_UP = 0b00000001;
final public const FLAG_UP = 0b00000001;

private const FLAG_RFU1 = 0b00000010;
final public const FLAG_RFU1 = 0b00000010;

private const FLAG_UV = 0b00000100;
final public const FLAG_UV = 0b00000100;

private const FLAG_RFU2 = 0b00111000;
final public const FLAG_RFU2 = 0b00111000;

private const FLAG_AT = 0b01000000;
final public const FLAG_AT = 0b01000000;

private const FLAG_ED = 0b10000000;
final public const FLAG_ED = 0b10000000;

public function __construct(
protected string $authData,
Expand Down
117 changes: 117 additions & 0 deletions src/webauthn/src/AuthenticatorDataLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

declare(strict_types=1);

namespace Webauthn;

use CBOR\ByteStringObject;
use CBOR\Decoder;
use CBOR\ListObject;
use CBOR\MapObject;
use CBOR\NegativeIntegerObject;
use CBOR\TextStringObject;
use CBOR\UnsignedIntegerObject;
use function chr;
use function ord;
use Symfony\Component\Uid\Uuid;
use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader;
use Webauthn\Exception\InvalidDataException;

final class AuthenticatorDataLoader
{
private readonly Decoder $decoder;

private function __construct()
{
$this->decoder = Decoder::create();
}

public static function create(): self
{
return new self();
}

public function load(string $authData): AuthenticatorData
{
$authData = $this->fixIncorrectEdDSAKey($authData);
$authDataStream = new StringStream($authData);
$rp_id_hash = $authDataStream->read(32);
$flags = $authDataStream->read(1);
$signCount = $authDataStream->read(4);
$signCount = unpack('N', $signCount);

$attestedCredentialData = null;
if (0 !== (ord($flags) & AuthenticatorData::FLAG_AT)) {
$aaguid = Uuid::fromBinary($authDataStream->read(16));
$credentialLength = $authDataStream->read(2);
$credentialLength = unpack('n', $credentialLength);
$credentialId = $authDataStream->read($credentialLength[1]);
$credentialPublicKey = $this->decoder->decode($authDataStream);
$credentialPublicKey instanceof MapObject || throw InvalidDataException::create(
$authData,
'The data does not contain a valid credential public key.'
);
$attestedCredentialData = new AttestedCredentialData(
$aaguid,
$credentialId,
(string) $credentialPublicKey
);
}

$extension = null;
if (0 !== (ord($flags) & AuthenticatorData::FLAG_ED)) {
$extension = $this->decoder->decode($authDataStream);
$extension = AuthenticationExtensionsClientOutputsLoader::load($extension);
}
$authDataStream->isEOF() || throw InvalidDataException::create(
$authData,
'Invalid authentication data. Presence of extra bytes.'
);
$authDataStream->close();

return new AuthenticatorData(
$authData,
$rp_id_hash,
$flags,
$signCount[1],
$attestedCredentialData,
$extension
);
}

private function fixIncorrectEdDSAKey(string $data): string
{
$needle = hex2bin('a301634f4b500327206745643235353139');
$correct = hex2bin('a401634f4b500327206745643235353139');
$position = mb_strpos($data, $needle, 0, '8bit');
if ($position === false) {
return $data;
}

$begin = mb_substr($data, 0, $position, '8bit');
$end = mb_substr($data, $position, null, '8bit');
$end = str_replace($needle, $correct, $end);
$cbor = new StringStream($end);
$badKey = $this->decoder->decode($cbor);

($badKey instanceof MapObject && $cbor->isEOF()) || throw InvalidDataException::create(
$end,
'Invalid authentication data. Presence of extra bytes.'
);
$badX = $badKey->get(-2);
$badX instanceof ListObject || throw InvalidDataException::create($end, 'Invalid authentication data.');
$keyBytes = array_reduce(
$badX->normalize(),
static fn (string $carry, string $item): string => $carry . chr((int) $item),
''
);
$correctX = ByteStringObject::create($keyBytes);
$correctKey = MapObject::create()
->add(UnsignedIntegerObject::create(1), ByteStringObject::create('OKP'))
->add(UnsignedIntegerObject::create(3), NegativeIntegerObject::create(-8))
->add(NegativeIntegerObject::create(-1), TextStringObject::create('Ed25519'))
->add(NegativeIntegerObject::create(-2), $correctX);

return $begin . $correctKey;
}
}
Loading