From 9c3b11914dca4f22bb34b860c7792d0ffeeea11b Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:21:49 -0600 Subject: [PATCH] webauthn registerCredential returns transports The webauthn support previously did not pass the transports to webauthn4j. This meant that the result of Webauthn4jRelyingPartyOperations.registerCredential did not have any transports either. This commit ensures that the transports are passed to the webauth4j lib and then returned in the result of registerCredential. Closes gh-16084 --- .../Webauthn4JRelyingPartyOperations.java | 16 ++++++- ...Webauthn4jRelyingPartyOperationsTests.java | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java b/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java index e1983079fc..d8341b8ac4 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java @@ -20,6 +20,7 @@ import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -254,7 +255,9 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe boolean userPresenceRequired = true; List pubKeyCredParams = convertCredentialParamsToWebauthn4j( creationOptions.getPubKeyCredParams()); - RegistrationRequest webauthn4jRegistrationRequest = new RegistrationRequest(attestationObject, clientDataJSON); + Set transports = convertTransportsToString(response); + RegistrationRequest webauthn4jRegistrationRequest = new RegistrationRequest(attestationObject, clientDataJSON, + transports); RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, pubKeyCredParams, userVerificationRequired, userPresenceRequired); RegistrationData registrationData = this.webAuthnManager.validate(webauthn4jRegistrationRequest, @@ -283,6 +286,17 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe return userCredential; } + private static Set convertTransportsToString(AuthenticatorAttestationResponse response) { + if (response.getTransports() == null) { + return null; + } + Set transports = new HashSet<>(response.getTransports().size()); + for (AuthenticatorTransport transport : response.getTransports()) { + transports.add(transport.getValue()); + } + return transports; + } + private List convertCredentialParamsToWebauthn4j( List parameters) { return parameters.stream().map(this::convertParamToWebauthn4j).collect(Collectors.toUnmodifiableList()); diff --git a/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java b/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java index 2538bed4f0..4061746e88 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/management/Webauthn4jRelyingPartyOperationsTests.java @@ -45,6 +45,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse; import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse.AuthenticatorAttestationResponseBuilder; import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria; +import org.springframework.security.web.webauthn.api.AuthenticatorTransport; import org.springframework.security.web.webauthn.api.Bytes; import org.springframework.security.web.webauthn.api.CredentialRecord; import org.springframework.security.web.webauthn.api.PublicKeyCredential; @@ -224,6 +225,47 @@ class Webauthn4jRelyingPartyOperationsTests { assertThatIllegalArgumentException().isThrownBy(() -> this.rpOperations.registerCredential(null)); } + @Test + void registerCredentialWhenDefaultTransportsThenSuccess() { + PublicKeyCredentialCreationOptions creationOptions = TestPublicKeyCredentialCreationOptions + .createPublicKeyCredentialCreationOptions() + .build(); + PublicKeyCredential publicKeyCredential = TestPublicKeyCredential + .createPublicKeyCredential() + .build(); + RelyingPartyPublicKey rpPublicKey = new RelyingPartyPublicKey(publicKeyCredential, this.label); + + ImmutableRelyingPartyRegistrationRequest rpRegistrationRequest = new ImmutableRelyingPartyRegistrationRequest( + creationOptions, rpPublicKey); + CredentialRecord credentialRecord = this.rpOperations.registerCredential(rpRegistrationRequest); + assertThat(credentialRecord).isNotNull(); + assertThat(credentialRecord.getCredentialId()).isNotNull(); + assertThat(credentialRecord.getTransports()).containsExactlyInAnyOrder(AuthenticatorTransport.INTERNAL, + AuthenticatorTransport.HYBRID); + } + + @Test + void registerCredentialWhenInternalTransportThenCredentialRecordHasTransport() { + PublicKeyCredentialCreationOptions creationOptions = TestPublicKeyCredentialCreationOptions + .createPublicKeyCredentialCreationOptions() + .build(); + AuthenticatorAttestationResponse response = TestAuthenticatorAttestationResponse + .createAuthenticatorAttestationResponse() + .transports(AuthenticatorTransport.INTERNAL) + .build(); + PublicKeyCredential publicKeyCredential = TestPublicKeyCredential + .createPublicKeyCredential() + .response(response) + .build(); + RelyingPartyPublicKey rpPublicKey = new RelyingPartyPublicKey(publicKeyCredential, this.label); + + ImmutableRelyingPartyRegistrationRequest rpRegistrationRequest = new ImmutableRelyingPartyRegistrationRequest( + creationOptions, rpPublicKey); + CredentialRecord credentialRecord = this.rpOperations.registerCredential(rpRegistrationRequest); + assertThat(credentialRecord).isNotNull(); + assertThat(credentialRecord.getTransports()).containsExactlyInAnyOrder(AuthenticatorTransport.INTERNAL); + } + @Test void registerCredentialWhenExistsThenException() { PublicKeyCredentialCreationOptions creationOptions = TestPublicKeyCredentialCreationOptions