From 3e88b041ab2e03562d8a5c0819dc5a1d952031a4 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Tue, 9 Jul 2024 15:47:53 -0700 Subject: [PATCH] Conditional registration support (#85) 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. --- src/CreateResponse.php | 3 +- src/Enums/CredentialMediationRequirement.php | 18 ++++++ tests/CreateResponseTest.php | 58 +++++++++++++++++++- 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/Enums/CredentialMediationRequirement.php diff --git a/src/CreateResponse.php b/src/CreateResponse.php index 88dfe95..6d522df 100644 --- a/src/CreateResponse.php +++ b/src/CreateResponse.php @@ -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 @@ -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'); } diff --git a/src/Enums/CredentialMediationRequirement.php b/src/Enums/CredentialMediationRequirement.php new file mode 100644 index 0000000..dc8bc8d --- /dev/null +++ b/src/Enums/CredentialMediationRequirement.php @@ -0,0 +1,18 @@ +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