From 238fc80a693069e08ea4f738a0e0b6cc699cc8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Fri, 3 May 2024 09:51:31 +0200 Subject: [PATCH] Fix #1502: FIDO2: Return excludeCredentials in RegistrationChallenge (#1504) --- ...{AllowCredentials.java => Credential.java} | 4 ++-- .../response/AssertionChallengeResponse.java | 4 ++-- .../RegistrationChallengeResponse.java | 4 ++++ .../AssertionChallengeConverter.java | 8 +++---- .../RegistrationChallengeConverter.java | 17 +++++++++++++++ .../rest/model/entity/AssertionChallenge.java | 4 ++-- .../model/entity/RegistrationChallenge.java | 4 ++++ .../AssertionChallengeConverterTest.java | 10 ++++----- .../fido2/PowerAuthRegistrationProvider.java | 21 ++++++++++++------- .../fido2/Fido2AuthenticatorTest.java | 2 ++ 10 files changed, 56 insertions(+), 22 deletions(-) rename powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/{AllowCredentials.java => Credential.java} (94%) diff --git a/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/AllowCredentials.java b/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/Credential.java similarity index 94% rename from powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/AllowCredentials.java rename to powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/Credential.java index bece272c9..46f35197d 100644 --- a/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/AllowCredentials.java +++ b/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/entity/Credential.java @@ -25,7 +25,7 @@ import java.util.List; /** - * Representation of an allowed authenticator instance. + * Representation of a FIDO2 Credential. * * @author Jan Pesek, jan.pesek@wultra.com */ @@ -34,7 +34,7 @@ @ToString @Builder @Jacksonized -public class AllowCredentials { +public class Credential { private final byte[] credentialId; diff --git a/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/AssertionChallengeResponse.java b/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/AssertionChallengeResponse.java index ce32c6991..68a6cdde5 100644 --- a/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/AssertionChallengeResponse.java +++ b/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/AssertionChallengeResponse.java @@ -18,7 +18,7 @@ package com.wultra.security.powerauth.fido2.model.response; -import com.wultra.security.powerauth.fido2.model.entity.AllowCredentials; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import lombok.Data; import lombok.ToString; @@ -38,6 +38,6 @@ public class AssertionChallengeResponse { private String userId; private Long failedAttempts; private Long maxFailedAttempts; - private List allowCredentials; + private List allowCredentials; } diff --git a/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/RegistrationChallengeResponse.java b/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/RegistrationChallengeResponse.java index 0f6e78d17..130412c67 100644 --- a/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/RegistrationChallengeResponse.java +++ b/powerauth-fido2-model/src/main/java/com/wultra/security/powerauth/fido2/model/response/RegistrationChallengeResponse.java @@ -18,9 +18,12 @@ package com.wultra.security.powerauth.fido2.model.response; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import lombok.Data; import lombok.ToString; +import java.util.List; + /** * @author Roman Strobl, roman.strobl@wultra.com */ @@ -32,5 +35,6 @@ public class RegistrationChallengeResponse { @ToString.Exclude private String challenge; private String userId; + private List excludeCredentials; } diff --git a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverter.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverter.java index 2ca37dfcd..a790ccdb2 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverter.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverter.java @@ -21,7 +21,7 @@ import com.wultra.powerauth.fido2.service.model.Fido2DefaultAuthenticators; import com.wultra.security.powerauth.client.model.request.OperationCreateRequest; import com.wultra.security.powerauth.client.model.response.OperationDetailResponse; -import com.wultra.security.powerauth.fido2.model.entity.AllowCredentials; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import com.wultra.powerauth.fido2.rest.model.entity.AssertionChallenge; import com.wultra.security.powerauth.fido2.model.entity.AuthenticatorDetail; import com.wultra.security.powerauth.fido2.model.request.AssertionChallengeRequest; @@ -108,7 +108,7 @@ public static AssertionChallenge convertAssertionChallengeFromOperationDetail(Op destination.setMaxFailedAttempts(source.getMaxFailureCount()); if (authenticatorDetails != null && !authenticatorDetails.isEmpty()) { - final List allowCredentials = new ArrayList<>(); + final List allowCredentials = new ArrayList<>(); for (AuthenticatorDetail ad: authenticatorDetails) { @SuppressWarnings("unchecked") @@ -121,11 +121,11 @@ public static AssertionChallenge convertAssertionChallengeFromOperationDetail(Op credentialId = ByteUtils.concat(credentialId, operationDataBytes); } - final AllowCredentials ac = AllowCredentials.builder() + final Credential credential = Credential.builder() .credentialId(credentialId) .transports(transports) .build(); - allowCredentials.add(ac); + allowCredentials.add(credential); } destination.setAllowCredentials(allowCredentials); } diff --git a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/RegistrationChallengeConverter.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/RegistrationChallengeConverter.java index 484c2e0f2..6a088e48e 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/RegistrationChallengeConverter.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/converter/RegistrationChallengeConverter.java @@ -19,10 +19,15 @@ package com.wultra.powerauth.fido2.rest.model.converter; import com.wultra.powerauth.fido2.rest.model.entity.RegistrationChallenge; +import com.wultra.security.powerauth.fido2.model.entity.AuthenticatorDetail; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import com.wultra.security.powerauth.fido2.model.response.RegistrationChallengeResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.Base64; +import java.util.List; + /** * @author Petr Dvorak, petr@wultra.com */ @@ -45,7 +50,19 @@ public RegistrationChallengeResponse fromChallenge(RegistrationChallenge source) destination.setActivationId(source.getActivationId()); destination.setApplicationId(source.getApplicationId()); destination.setChallenge(source.getChallenge()); + destination.setExcludeCredentials(source.getExcludeCredentials()); return destination; } + public static Credential toCredentialDescriptor(final AuthenticatorDetail authenticatorDetail) { + @SuppressWarnings("unchecked") + final List transports = (List) authenticatorDetail.getExtras().get("transports"); + final byte[] credentialId = Base64.getDecoder().decode(authenticatorDetail.getCredentialId()); + + return Credential.builder() + .credentialId(credentialId) + .transports(transports) + .build(); + } + } diff --git a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/AssertionChallenge.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/AssertionChallenge.java index 518978cd0..353ef1215 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/AssertionChallenge.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/AssertionChallenge.java @@ -18,7 +18,7 @@ package com.wultra.powerauth.fido2.rest.model.entity; -import com.wultra.security.powerauth.fido2.model.entity.AllowCredentials; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import lombok.Data; import java.util.List; @@ -36,6 +36,6 @@ public class AssertionChallenge { private String userId; private Long failedAttempts; private Long maxFailedAttempts; - private List allowCredentials; + private List allowCredentials; } diff --git a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/RegistrationChallenge.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/RegistrationChallenge.java index dc97ee192..c9c30ac9c 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/RegistrationChallenge.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/RegistrationChallenge.java @@ -18,8 +18,11 @@ package com.wultra.powerauth.fido2.rest.model.entity; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import lombok.Data; +import java.util.List; + /** * Model class representing registration challenge. * @@ -31,4 +34,5 @@ public class RegistrationChallenge { private String applicationId; private String challenge; private String userId; + private List excludeCredentials; } diff --git a/powerauth-fido2/src/test/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverterTest.java b/powerauth-fido2/src/test/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverterTest.java index 7d997db0d..d4f97b13b 100644 --- a/powerauth-fido2/src/test/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverterTest.java +++ b/powerauth-fido2/src/test/java/com/wultra/powerauth/fido2/rest/model/converter/AssertionChallengeConverterTest.java @@ -20,7 +20,7 @@ import com.wultra.security.powerauth.client.model.request.OperationCreateRequest; import com.wultra.security.powerauth.client.model.response.OperationDetailResponse; -import com.wultra.security.powerauth.fido2.model.entity.AllowCredentials; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import com.wultra.powerauth.fido2.rest.model.entity.AssertionChallenge; import com.wultra.security.powerauth.fido2.model.entity.AuthenticatorDetail; import com.wultra.security.powerauth.fido2.model.request.AssertionChallengeRequest; @@ -121,7 +121,7 @@ void testConvertAssertionChallengeFromOperationDetail_nonWultraAuthenticatorDeta assertEquals(5L, assertionChallenge.getMaxFailedAttempts()); assertNotNull(assertionChallenge.getAllowCredentials()); - final AllowCredentials allowCredential = assertionChallenge.getAllowCredentials().get(0); + final Credential allowCredential = assertionChallenge.getAllowCredentials().get(0); assertArrayEquals("credential-1".getBytes(), allowCredential.getCredentialId()); assertEquals("hybrid", allowCredential.getTransports().get(0)); assertEquals("public-key", allowCredential.getType()); @@ -154,7 +154,7 @@ void testConvertAssertionChallengeFromOperationDetail_withWultraAuthenticatorDet assertNotNull(assertionChallenge.getAllowCredentials()); assertEquals(1, assertionChallenge.getAllowCredentials().size()); - final AllowCredentials allowCredential = assertionChallenge.getAllowCredentials().get(0); + final Credential allowCredential = assertionChallenge.getAllowCredentials().get(0); assertArrayEquals("credential-1A1*A100CZK".getBytes(), allowCredential.getCredentialId()); assertEquals("usb", allowCredential.getTransports().get(0)); assertEquals("public-key", allowCredential.getType()); @@ -194,12 +194,12 @@ void testConvertAssertionChallengeFromOperationDetail_multipleWultraAuthenticato assertNotNull(assertionChallenge.getAllowCredentials()); assertEquals(2, assertionChallenge.getAllowCredentials().size()); - final AllowCredentials allowCredential1 = assertionChallenge.getAllowCredentials().get(0); + final Credential allowCredential1 = assertionChallenge.getAllowCredentials().get(0); assertArrayEquals("credential-1A1*A100CZK".getBytes(), allowCredential1.getCredentialId()); assertEquals("usb", allowCredential1.getTransports().get(0)); assertEquals("public-key", allowCredential1.getType()); - final AllowCredentials allowCredential2 = assertionChallenge.getAllowCredentials().get(1); + final Credential allowCredential2 = assertionChallenge.getAllowCredentials().get(1); assertArrayEquals("credential-2A1*A100CZK".getBytes(), allowCredential2.getCredentialId()); assertEquals("usb", allowCredential2.getTransports().get(0)); assertEquals("public-key", allowCredential2.getType()); diff --git a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/fido2/PowerAuthRegistrationProvider.java b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/fido2/PowerAuthRegistrationProvider.java index b7f3b5569..5c479608b 100644 --- a/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/fido2/PowerAuthRegistrationProvider.java +++ b/powerauth-java-server/src/main/java/io/getlime/security/powerauth/app/server/service/fido2/PowerAuthRegistrationProvider.java @@ -19,6 +19,7 @@ package io.getlime.security.powerauth.app.server.service.fido2; import com.wultra.powerauth.fido2.errorhandling.Fido2AuthenticationFailedException; +import com.wultra.powerauth.fido2.rest.model.converter.RegistrationChallengeConverter; import com.wultra.powerauth.fido2.rest.model.entity.RegistrationChallenge; import com.wultra.powerauth.fido2.service.provider.RegistrationProvider; import com.wultra.security.powerauth.client.model.entity.ApplicationConfigurationItem; @@ -27,6 +28,7 @@ import com.wultra.security.powerauth.client.model.request.GetApplicationConfigRequest; import com.wultra.security.powerauth.client.model.response.GetApplicationConfigResponse; import com.wultra.security.powerauth.client.model.response.InitActivationResponse; +import com.wultra.security.powerauth.fido2.model.entity.Credential; import io.getlime.security.powerauth.app.server.database.RepositoryCatalogue; import io.getlime.security.powerauth.app.server.database.model.entity.ActivationRecordEntity; import io.getlime.security.powerauth.app.server.database.model.entity.ApplicationEntity; @@ -42,10 +44,7 @@ import org.springframework.transaction.annotation.Transactional; import java.nio.ByteBuffer; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import static com.wultra.powerauth.fido2.rest.model.enumeration.Fido2ConfigKeys.CONFIG_KEY_ALLOWED_AAGUIDS; import static com.wultra.powerauth.fido2.rest.model.enumeration.Fido2ConfigKeys.CONFIG_KEY_ALLOWED_ATTESTATION_FMT; @@ -61,25 +60,33 @@ public class PowerAuthRegistrationProvider implements RegistrationProvider { private final RepositoryCatalogue repositoryCatalogue; private final ServiceBehaviorCatalogue serviceBehaviorCatalogue; - + private final PowerAuthAuthenticatorProvider authenticatorProvider; private final KeyConvertor keyConvertor = new KeyConvertor(); @Autowired - public PowerAuthRegistrationProvider(RepositoryCatalogue repositoryCatalogue, ServiceBehaviorCatalogue serviceBehaviorCatalogue) { + public PowerAuthRegistrationProvider(final RepositoryCatalogue repositoryCatalogue, final ServiceBehaviorCatalogue serviceBehaviorCatalogue, final PowerAuthAuthenticatorProvider authenticatorProvider) { this.repositoryCatalogue = repositoryCatalogue; this.serviceBehaviorCatalogue = serviceBehaviorCatalogue; + this.authenticatorProvider = authenticatorProvider; } @Override @Transactional - public RegistrationChallenge provideChallengeForRegistration(String userId, String applicationId) throws GenericServiceException { + public RegistrationChallenge provideChallengeForRegistration(String userId, String applicationId) throws GenericServiceException, Fido2AuthenticationFailedException { final InitActivationResponse initActivationResponse = serviceBehaviorCatalogue.getActivationServiceBehavior() .initActivation(ActivationProtocol.FIDO2, applicationId, userId, null, null, ActivationOtpValidation.NONE, null, null, keyConvertor); + + final List excludeCredentials = authenticatorProvider.findByUserId(userId, applicationId) + .stream() + .map(RegistrationChallengeConverter::toCredentialDescriptor) + .toList(); + final RegistrationChallenge registrationChallenge = new RegistrationChallenge(); registrationChallenge.setUserId(initActivationResponse.getUserId()); registrationChallenge.setApplicationId(initActivationResponse.getApplicationId()); registrationChallenge.setActivationId(initActivationResponse.getActivationId()); registrationChallenge.setChallenge(initActivationResponse.getActivationCode()); + registrationChallenge.setExcludeCredentials(excludeCredentials); return registrationChallenge; } diff --git a/powerauth-java-server/src/test/java/com/wultra/powerauth/fido2/Fido2AuthenticatorTest.java b/powerauth-java-server/src/test/java/com/wultra/powerauth/fido2/Fido2AuthenticatorTest.java index ada1d96ad..0bef164fe 100644 --- a/powerauth-java-server/src/test/java/com/wultra/powerauth/fido2/Fido2AuthenticatorTest.java +++ b/powerauth-java-server/src/test/java/com/wultra/powerauth/fido2/Fido2AuthenticatorTest.java @@ -47,6 +47,7 @@ import com.wultra.security.powerauth.fido2.model.response.RegistrationResponse; import io.getlime.security.powerauth.app.server.Application; import io.getlime.security.powerauth.app.server.service.PowerAuthService; +import jakarta.transaction.Transactional; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -66,6 +67,7 @@ */ @SpringBootTest(classes = Application.class) @ActiveProfiles("test") +@Transactional class Fido2AuthenticatorTest { private static final String RP_ID = "powerauth.com";