From fa0d785b4981c8ed7b8ed16792bfb543656f8346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Pe=C5=A1ek?= Date: Fri, 3 May 2024 16:20:37 +0200 Subject: [PATCH] Fix #1502: FIDO2: Return excludeCredentials in RegistrationChallenge (backport) (#1507) --- ...{AllowCredentials.java => Credential.java} | 4 ++-- .../fido2/AssertionChallengeResponse.java | 4 ++-- .../fido2/RegistrationChallengeResponse.java | 4 ++++ .../AssertionChallengeConverter.java | 8 +++---- .../RegistrationChallengeConverter.java | 17 ++++++++++++++ .../rest/model/entity/AssertionChallenge.java | 2 +- ...{AllowCredentials.java => Credential.java} | 4 ++-- .../model/entity/RegistrationChallenge.java | 3 +++ .../response/AssertionChallengeResponse.java | 4 ++-- .../RegistrationChallengeResponse.java | 5 ++++- .../AssertionChallengeConverterTest.java | 10 ++++----- .../fido2/PowerAuthRegistrationProvider.java | 22 ++++++++++++------- .../fido2/Fido2AuthenticatorTest.java | 2 ++ 13 files changed, 62 insertions(+), 27 deletions(-) rename powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/{AllowCredentials.java => Credential.java} (93%) rename powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/{AllowCredentials.java => Credential.java} (94%) diff --git a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/AllowCredentials.java b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/Credential.java similarity index 93% rename from powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/AllowCredentials.java rename to powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/Credential.java index 3885236ea..b8f4cbf48 100644 --- a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/AllowCredentials.java +++ b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/entity/fido2/Credential.java @@ -23,12 +23,12 @@ import java.util.List; /** - * Representation of an allowed authenticator instance. + * Representation of a FIDO2 Credential. * * @author Jan Pesek, jan.pesek@wultra.com */ @Data -public class AllowCredentials { +public class Credential { private byte[] credentialId; diff --git a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/AssertionChallengeResponse.java b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/AssertionChallengeResponse.java index 2356f987a..9606199e2 100644 --- a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/AssertionChallengeResponse.java +++ b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/AssertionChallengeResponse.java @@ -18,7 +18,7 @@ package com.wultra.security.powerauth.client.model.response.fido2; -import com.wultra.security.powerauth.client.model.entity.fido2.AllowCredentials; +import com.wultra.security.powerauth.client.model.entity.fido2.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-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/RegistrationChallengeResponse.java b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/RegistrationChallengeResponse.java index d8dcfa55f..08203157f 100644 --- a/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/RegistrationChallengeResponse.java +++ b/powerauth-client-model/src/main/java/com/wultra/security/powerauth/client/model/response/fido2/RegistrationChallengeResponse.java @@ -18,9 +18,12 @@ package com.wultra.security.powerauth.client.model.response.fido2; +import com.wultra.security.powerauth.client.model.entity.fido2.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 45e6c7888..049798b01 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 @@ -18,7 +18,7 @@ package com.wultra.powerauth.fido2.rest.model.converter; -import com.wultra.powerauth.fido2.rest.model.entity.AllowCredentials; +import com.wultra.powerauth.fido2.rest.model.entity.Credential; import com.wultra.powerauth.fido2.rest.model.entity.AssertionChallenge; import com.wultra.powerauth.fido2.rest.model.entity.AuthenticatorDetail; import com.wultra.powerauth.fido2.rest.model.entity.Fido2DefaultAuthenticators; @@ -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 c15314c3d..434a57d99 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 @@ -18,11 +18,16 @@ package com.wultra.powerauth.fido2.rest.model.converter; +import com.wultra.powerauth.fido2.rest.model.entity.AuthenticatorDetail; +import com.wultra.powerauth.fido2.rest.model.entity.Credential; import com.wultra.powerauth.fido2.rest.model.entity.RegistrationChallenge; import com.wultra.powerauth.fido2.rest.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 5d795fd07..cc8605969 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 @@ -35,6 +35,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/AllowCredentials.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/Credential.java similarity index 94% rename from powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/AllowCredentials.java rename to powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/Credential.java index c08dc6f84..cdf537d9f 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/AllowCredentials.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/entity/Credential.java @@ -27,7 +27,7 @@ import java.util.List; /** - * Representation of an allowed authenticator instance. + * Representation of a FIDO2 Credential. * * @author Petr Dvorak, petr@wultra.com */ @@ -35,7 +35,7 @@ @EqualsAndHashCode @ToString @Builder -public class AllowCredentials { +public class Credential { private final byte[] credentialId; 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..78c46f8c3 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 @@ -20,6 +20,8 @@ import lombok.Data; +import java.util.List; + /** * Model class representing registration challenge. * @@ -31,4 +33,5 @@ public class RegistrationChallenge { private String applicationId; private String challenge; private String userId; + private List excludeCredentials; } diff --git a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/AssertionChallengeResponse.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/AssertionChallengeResponse.java index 5a85ae631..08608bafe 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/AssertionChallengeResponse.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/AssertionChallengeResponse.java @@ -18,7 +18,7 @@ package com.wultra.powerauth.fido2.rest.model.response; -import com.wultra.powerauth.fido2.rest.model.entity.AllowCredentials; +import com.wultra.powerauth.fido2.rest.model.entity.Credential; import lombok.Data; import java.util.List; @@ -36,6 +36,6 @@ public class AssertionChallengeResponse { 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/response/RegistrationChallengeResponse.java b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/RegistrationChallengeResponse.java index 37904dd53..7016fecfe 100644 --- a/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/RegistrationChallengeResponse.java +++ b/powerauth-fido2/src/main/java/com/wultra/powerauth/fido2/rest/model/response/RegistrationChallengeResponse.java @@ -18,8 +18,11 @@ package com.wultra.powerauth.fido2.rest.model.response; +import com.wultra.powerauth.fido2.rest.model.entity.Credential; import lombok.Data; +import java.util.List; + /** * Registration challenge response. * @@ -32,6 +35,6 @@ public class RegistrationChallengeResponse { 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 d85fb5bcb..f5c8d5ea9 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 @@ -18,7 +18,7 @@ package com.wultra.powerauth.fido2.rest.model.converter; -import com.wultra.powerauth.fido2.rest.model.entity.AllowCredentials; +import com.wultra.powerauth.fido2.rest.model.entity.Credential; import com.wultra.powerauth.fido2.rest.model.entity.AssertionChallenge; import com.wultra.powerauth.fido2.rest.model.entity.AuthenticatorDetail; import com.wultra.powerauth.fido2.rest.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 f63c933c4..f39583b54 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,8 @@ 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.Credential; 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; @@ -42,11 +44,7 @@ import org.springframework.transaction.annotation.Transactional; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -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; @@ -62,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(Protocols.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 68ffe5152..88aa17dc8 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 @@ -51,6 +51,7 @@ import com.wultra.security.powerauth.client.model.response.OperationTemplateDetailResponse; 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; @@ -72,6 +73,7 @@ */ @SpringBootTest(classes = Application.class) @ActiveProfiles("test") +@Transactional class Fido2AuthenticatorTest { private final CBORMapper CBOR_MAPPER = new CBORMapper();