diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java index f4bb48e28a1..836606509bc 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java @@ -402,8 +402,15 @@ public PublicKeyCredentialUserEntity authenticate(RelyingPartyAuthenticationRequ // CollectedClientData and ExtensionsClientOutputs is registration data, and can // be null at authentication time. + AttestedCredentialData attestedCredentialData = wa4jAttestationObject.getAuthenticatorData() + .getAttestedCredentialData(); + Assert.notNull(attestedCredentialData, "attestedCredentialData cannot be null"); com.webauthn4j.credential.CredentialRecord wa4jCredentialRecord = new CredentialRecordImpl( - wa4jAttestationObject, null, null, convertTransportsToWebauthn4j(credentialRecord.getTransports())); + wa4jAttestationObject.getAttestationStatement(), credentialRecord.isUvInitialized(), + credentialRecord.isBackupEligible(), credentialRecord.isBackupState(), + credentialRecord.getSignatureCount(), attestedCredentialData, + wa4jAttestationObject.getAuthenticatorData().getExtensions(), null, null, + convertTransportsToWebauthn4j(credentialRecord.getTransports())); List allowCredentials = convertAllowedCredentialsToWebauthn4j( request.getRequestOptions().getAllowCredentials()); AuthenticationParameters authenticationParameters = new AuthenticationParameters(serverProperty, diff --git a/webauthn/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java b/webauthn/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java index 069e6ca9353..4014499d7c3 100644 --- a/webauthn/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java +++ b/webauthn/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java @@ -36,6 +36,7 @@ import com.webauthn4j.converter.AttestationObjectConverter; import com.webauthn4j.converter.util.ObjectConverter; import com.webauthn4j.data.AuthenticationData; +import com.webauthn4j.data.AuthenticationParameters; import com.webauthn4j.data.AuthenticationRequest; import com.webauthn4j.data.attestation.AttestationObject; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; @@ -680,6 +681,51 @@ void authenticateThenWa4jRequestCredentialIdIsRawIdBytes() throws Exception { assertThat(wa4jRequest.getValue().getCredentialId()).isEqualTo(publicKey.getRawId().getBytes()); } + // gh-19994 + @Test + void authenticateThenWa4jCredentialRecordUsesStoredSignatureCount() throws Exception { + PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions.create().build(); + AuthenticatorAssertionResponse response = TestAuthenticationAssertionResponses + .createAuthenticatorAssertionResponse() + .build(); + PublicKeyCredential credentials = TestPublicKeyCredentials + .createPublicKeyCredential(response) + .build(); + RelyingPartyAuthenticationRequest request = new RelyingPartyAuthenticationRequest(options, credentials); + + ImmutableCredentialRecord credentialRecord = TestCredentialRecords.fullUserCredential() + .signatureCount(42) + .build(); + given(this.userCredentials.findByCredentialId(request.getPublicKey().getRawId())) + .willReturn(credentialRecord); + JsonMapper json = new JsonMapper(); + CBORMapper cbor = mock(CBORMapper.class); + CBORMapper.Builder builder = mock(CBORMapper.Builder.class, RETURNS_SELF); + given(builder.build()).willReturn(cbor); + given(cbor.rebuild()).willReturn(builder); + AttestationObject attestationObject = mock(AttestationObject.class); + AuthenticatorData wa4jAuthData = mock(AuthenticatorData.class); + given(attestationObject.getAuthenticatorData()).willReturn(wa4jAuthData); + given(wa4jAuthData.getAttestedCredentialData()).willReturn(mock(AttestedCredentialData.class)); + given(cbor.readValue(credentialRecord.getAttestationObject().getBytes(), AttestationObject.class)) + .willReturn(attestationObject); + this.rpOperations.setObjectConverter(new ObjectConverter(json, cbor)); + + WebAuthnManager manager = mock(WebAuthnManager.class); + ArgumentCaptor parameters = ArgumentCaptor + .forClass(AuthenticationParameters.class); + AuthenticationData wa4jData = mock(AuthenticationData.class); + given(wa4jData.getAuthenticatorData()).willReturn(mock(AuthenticatorData.class)); + given(manager.verify(any(AuthenticationRequest.class), parameters.capture())).willReturn(wa4jData); + given(this.userEntities.findById(any())) + .willReturn(TestPublicKeyCredentialUserEntities.userEntity().build()); + this.rpOperations.setWebAuthnManager(manager); + + this.rpOperations.authenticate(request); + + assertThat(parameters.getValue().getCredentialRecord().getCounter()).isEqualTo(42); + } + private static AuthenticatorAttestationResponse setFlag(byte... flags) throws Exception { AuthenticatorAttestationResponseBuilder authAttResponseBldr = TestAuthenticatorAttestationResponses .createAuthenticatorAttestationResponse();