Skip to content

Commit

Permalink
Conditional registration support (#85)
Browse files Browse the repository at this point in the history
This adds a new optional parameter to the credential registration
verification procedure (WebAuthn 7.1) to account for conditional
mediation.

> Verify that the
[UP](https://w3c.github.io/webauthn/#authdata-flags-up) bit of the
[flags](https://w3c.github.io/webauthn/#authdata-flags) in authData is
set, unless
options.[mediation](https://w3c.github.io/webappsec-credential-management/#dom-credentialcreationoptions-mediation)
is set to
[conditional](https://w3c.github.io/webappsec-credential-management/#dom-credentialmediationrequirement-conditional).

Note that this is only in the draft spec, but it's likely to remain
materially unchanged since this exact tooling was described at WWDC24.
Still, I'm going to leave this undocumented for now (besides the actual
method signature) in case anything changes before the next published
version.

Fixes #83.
  • Loading branch information
Firehed committed Jul 9, 2024
1 parent dc5e32a commit 3e88b04
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/CreateResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function verify(
RelyingPartyInterface $rp,
Enums\UserVerificationRequirement $uv = Enums\UserVerificationRequirement::Preferred,
bool $rejectUncertainTrustPaths = true,
Enums\CredentialMediationRequirement $mediation = Enums\CredentialMediationRequirement::Optional,
): CredentialInterface {
// 7.1.1 - 7.1.3 are client code
// 7.1.4 is temporarily skpped
Expand Down Expand Up @@ -93,7 +94,7 @@ public function verify(
}

// 7.1.14
if (!$authData->isUserPresent()) {
if (!$authData->isUserPresent() && $mediation !== Enums\CredentialMediationRequirement::Conditional) {
$this->fail('7.1.14', 'authData.isUserPresent');
}

Expand Down
18 changes: 18 additions & 0 deletions src/Enums/CredentialMediationRequirement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Firehed\WebAuthn\Enums;

/**
* Credential Management (Level 1)
* §2.3.2
* @link https://w3c.github.io/webappsec-credential-management/#dom-credentialmediationrequirement-conditional
*/
enum CredentialMediationRequirement: string
{
case Silent = 'silent';
case Optional = 'optional';
case Conditional = 'conditional';
case Required = 'required';
}
58 changes: 56 additions & 2 deletions tests/CreateResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,62 @@ public function testRelyingPartyIdMismatchIsError(): void
// 7.1.14
public function testUserNotPresentIsError(): void
{
// override authData
self::markTestIncomplete();
$ad = self::createMock(AuthenticatorData::class);
$ad->method('isUserPresent')->willReturn(false);
$ad->method('getRpIdHash')->willReturn(new BinaryString(hash('sha256', 'localhost', true)));

$ao = self::createMock(Attestations\AttestationObjectInterface::class);
$ao->method('getAuthenticatorData')->willReturn($ad);

$response = new CreateResponse(
type: Enums\PublicKeyCredentialType::PublicKey,
id: $this->id,
ao: $ao,
clientDataJson: $this->clientDataJson,
transports: [],
);

$this->expectRegistrationError('7.1.14');
$response->verify(
challengeLoader: $this->cm,
rp: $this->rp,
);
}

public function testUserNotPresentIsAllowedDuringConditionalRegistration(): void
{
$ad = self::createMock(AuthenticatorData::class);
$ad->method('isUserPresent')->willReturn(false);
$ad->method('getRpIdHash')->willReturn(new BinaryString(hash('sha256', 'localhost', true)));
$acd = new AttestedCredentialData(
aaguid: new BinaryString(''),
credentialId: $this->id,
coseKey: self::createMock(COSEKey::class),
);
$ad->method('getAttestedCredentialData')->willReturn($acd);

$ao = self::createMock(Attestations\AttestationObjectInterface::class);
$ao->method('getAuthenticatorData')->willReturn($ad);
$ao->method('verify')->willReturn(
new Attestations\VerificationResult(Attestations\AttestationType::None)
);

$response = new CreateResponse(
type: Enums\PublicKeyCredentialType::PublicKey,
id: $this->id,
ao: $ao,
clientDataJson: $this->clientDataJson,
transports: [],
);

$credential = $response->verify(
challengeLoader: $this->cm,
rp: $this->rp,
mediation: Enums\CredentialMediationRequirement::Conditional,
);

// This is mostly a smoke-test to inverse testUserNotPresentIsError
self::assertSame($this->id, $credential->getId());
}

// 7.1.15
Expand Down

0 comments on commit 3e88b04

Please sign in to comment.