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
This commit is contained in:
Rob Winch 2024-12-04 15:21:49 -06:00
parent cc2506b0c1
commit 9c3b11914d
2 changed files with 57 additions and 1 deletions

View File

@ -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<com.webauthn4j.data.PublicKeyCredentialParameters> pubKeyCredParams = convertCredentialParamsToWebauthn4j(
creationOptions.getPubKeyCredParams());
RegistrationRequest webauthn4jRegistrationRequest = new RegistrationRequest(attestationObject, clientDataJSON);
Set<String> 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<String> convertTransportsToString(AuthenticatorAttestationResponse response) {
if (response.getTransports() == null) {
return null;
}
Set<String> transports = new HashSet<>(response.getTransports().size());
for (AuthenticatorTransport transport : response.getTransports()) {
transports.add(transport.getValue());
}
return transports;
}
private List<com.webauthn4j.data.PublicKeyCredentialParameters> convertCredentialParamsToWebauthn4j(
List<PublicKeyCredentialParameters> parameters) {
return parameters.stream().map(this::convertParamToWebauthn4j).collect(Collectors.toUnmodifiableList());

View File

@ -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<AuthenticatorAttestationResponse> 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<AuthenticatorAttestationResponse> 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