From 6e0527c442d31c1c145b59aff7083cb48c48b7e1 Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 21 Apr 2018 00:54:05 +0900 Subject: [PATCH 1/9] Add MultifactorAuthenticationToken to indicate the state of principal which passed first step of multi step (factor) authentication. --- ...actorAuthenticationProviderConfigurer.java | 62 ++++++++++++ .../WebSecurityConfigurerAdapter.java | 9 ++ .../ExceptionHandlingConfigurer.java | 22 ++++- .../SessionManagementConfigurer.java | 26 ++--- ...AuthenticationProviderConfigurerTests.java | 49 ++++++++++ .../AuthenticationTrustResolverImpl.java | 10 ++ .../authentication/MFATokenEvaluator.java | 51 ++++++++++ .../authentication/MFATokenEvaluatorImpl.java | 82 ++++++++++++++++ .../MultiFactorAuthenticationProvider.java | 97 +++++++++++++++++++ .../MultiFactorAuthenticationToken.java | 64 ++++++++++++ .../core/userdetails/MFAUserDetails.java | 29 ++++++ .../MFATokenEvaluatorImplTests.java | 72 ++++++++++++++ ...ultiFactorAuthenticationProviderTests.java | 86 ++++++++++++++++ .../MultiFactorAuthenticationTokenTests.java | 61 ++++++++++++ .../access/ExceptionTranslationFilter.java | 23 ++++- .../HttpSessionSecurityContextRepository.java | 21 +++- .../ExceptionTranslationFilterTests.java | 35 +++++++ 17 files changed, 778 insertions(+), 21 deletions(-) create mode 100644 config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java create mode 100644 config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java create mode 100644 core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java create mode 100644 core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java create mode 100644 core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java create mode 100644 core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java create mode 100644 core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java new file mode 100644 index 00000000000..dd2000da08f --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurer.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.authentication.configurers.mfa; + +import org.springframework.security.authentication.*; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; + +/** + * Allows configuring a {@link MultiFactorAuthenticationProvider} + * + * @param the type of the {@link ProviderManagerBuilder} + * + * @author Yoshikazu Nojima + */ +public class MultiFactorAuthenticationProviderConfigurer> + extends SecurityConfigurerAdapter { + + //~ Instance fields + // ================================================================================================ + private AuthenticationProvider authenticationProvider; + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + + /** + * Constructor + * @param authenticationProvider {@link AuthenticationProvider} to be delegated + */ + public MultiFactorAuthenticationProviderConfigurer(AuthenticationProvider authenticationProvider) { + this.authenticationProvider = authenticationProvider; + } + + + public static MultiFactorAuthenticationProviderConfigurer multiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider){ + return new MultiFactorAuthenticationProviderConfigurer(authenticationProvider); + } + + @Override + public void configure(B builder) { + MultiFactorAuthenticationProvider multiFactorAuthenticationProvider = new MultiFactorAuthenticationProvider(authenticationProvider, mfaTokenEvaluator); + multiFactorAuthenticationProvider = postProcess(multiFactorAuthenticationProvider); + builder.authenticationProvider(multiFactorAuthenticationProvider); + } + + public MultiFactorAuthenticationProviderConfigurer mfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { + this.mfaTokenEvaluator = mfaTokenEvaluator; + return this; + } +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index 5c5976f746e..5de4034f3ce 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -40,6 +40,8 @@ import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MFATokenEvaluatorImpl; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; @@ -113,6 +115,7 @@ public T postProcess(T object) { private boolean authenticationManagerInitialized; private AuthenticationManager authenticationManager; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); private HttpSecurity http; private boolean disableDefaults; @@ -390,6 +393,11 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { this.trustResolver = trustResolver; } + @Autowired(required = false) + public void setMfaTokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){ + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + @Autowired(required = false) public void setContentNegotationStrategy( ContentNegotiationStrategy contentNegotiationStrategy) { @@ -419,6 +427,7 @@ private Map, Object> createSharedObjects() { sharedObjects.put(ApplicationContext.class, context); sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy); sharedObjects.put(AuthenticationTrustResolver.class, trustResolver); + sharedObjects.put(MFATokenEvaluator.class, mfaTokenEvaluator); return sharedObjects; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java index 72d03597145..ad741bd50be 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurer.java @@ -15,8 +15,7 @@ */ package org.springframework.security.config.annotation.web.configurers; -import java.util.LinkedHashMap; - +import org.springframework.security.authentication.MFATokenEvaluator; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.AuthenticationEntryPoint; @@ -30,6 +29,8 @@ import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.util.matcher.RequestMatcher; +import java.util.LinkedHashMap; + /** * Adds exception handling for Spring Security related exceptions to an application. All * properties have reasonable defaults, so no additional configuration is required other @@ -67,6 +68,8 @@ public final class ExceptionHandlingConfigurer> private AuthenticationEntryPoint authenticationEntryPoint; + private MFATokenEvaluator mfaTokenEvaluator; + private AccessDeniedHandler accessDeniedHandler; private LinkedHashMap defaultEntryPointMappings = new LinkedHashMap<>(); @@ -151,6 +154,18 @@ public ExceptionHandlingConfigurer authenticationEntryPoint( return this; } + /** + * Specifies the {@link MFATokenEvaluator} to be used + * + * @param mfaTokenEvaluator the {@link MFATokenEvaluator} to be used + * @return the {@link ExceptionHandlingConfigurer} for further customization + */ + public ExceptionHandlingConfigurer mfaTokenEvaluator( + MFATokenEvaluator mfaTokenEvaluator) { + this.mfaTokenEvaluator = mfaTokenEvaluator; + return this; + } + /** * Sets a default {@link AuthenticationEntryPoint} to be used which prefers being * invoked for the provided {@link RequestMatcher}. If only a single default @@ -194,6 +209,9 @@ public void configure(H http) { entryPoint, getRequestCache(http)); AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); + if (mfaTokenEvaluator != null) { + exceptionTranslationFilter.setMFATokenEvaluator(mfaTokenEvaluator); + } exceptionTranslationFilter = postProcess(exceptionTranslationFilter); http.addFilter(exceptionTranslationFilter); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index 84b78c1bb9e..25df14ed432 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -15,18 +15,13 @@ */ package org.springframework.security.config.annotation.web.configurers; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.event.GenericApplicationListenerAdapter; import org.springframework.context.event.SmartApplicationListener; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.config.Customizer; +import org.springframework.security.authentication.MFATokenEvaluator; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -35,7 +30,6 @@ import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; @@ -48,15 +42,16 @@ import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.session.ConcurrentSessionFilter; -import org.springframework.security.web.session.InvalidSessionStrategy; -import org.springframework.security.web.session.SessionInformationExpiredStrategy; -import org.springframework.security.web.session.SessionManagementFilter; -import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; -import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; +import org.springframework.security.web.session.*; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Allows configuring session management. * @@ -471,6 +466,11 @@ public void init(H http) { if (trustResolver != null) { httpSecurityRepository.setTrustResolver(trustResolver); } + MFATokenEvaluator mfaTokenEvaluator = http + .getSharedObject(MFATokenEvaluator.class); + if (mfaTokenEvaluator != null) { + httpSecurityRepository.setMFATokenEvaluator(mfaTokenEvaluator); + } http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java new file mode 100644 index 00000000000..5acafecc0ea --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.config.annotation.authentication.configurers.mfa; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MultiFactorAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.springframework.security.config.annotation.authentication.configurers.mfa.MultiFactorAuthenticationProviderConfigurer.multiFactorAuthenticationProvider; + +public class MultiFactorAuthenticationProviderConfigurerTests { + + @Test + public void test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + MFATokenEvaluator mfaTokenEvaluator = mock(MFATokenEvaluator.class); + MultiFactorAuthenticationProviderConfigurer configurer + = multiFactorAuthenticationProvider(delegatedAuthenticationProvider); + configurer.mfaTokenEvaluator(mfaTokenEvaluator); + ProviderManagerBuilder providerManagerBuilder = mock(ProviderManagerBuilder.class); + configurer.configure(providerManagerBuilder); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AuthenticationProvider.class); + verify(providerManagerBuilder).authenticationProvider(argumentCaptor.capture()); + MultiFactorAuthenticationProvider authenticationProvider = (MultiFactorAuthenticationProvider) argumentCaptor.getValue(); + + assertThat(authenticationProvider.getAuthenticationProvider()).isEqualTo(delegatedAuthenticationProvider); + assertThat(authenticationProvider.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator); + } +} diff --git a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java index 53a9aef58bc..855cc4a1f54 100644 --- a/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java +++ b/core/src/main/java/org/springframework/security/authentication/AuthenticationTrustResolverImpl.java @@ -36,6 +36,8 @@ public class AuthenticationTrustResolverImpl implements AuthenticationTrustResol private Class anonymousClass = AnonymousAuthenticationToken.class; private Class rememberMeClass = RememberMeAuthenticationToken.class; + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + // ~ Methods // ======================================================================================================== @@ -52,6 +54,10 @@ public boolean isAnonymous(Authentication authentication) { return false; } + if (mfaTokenEvaluator != null && mfaTokenEvaluator.isMultiFactorAuthentication(authentication)) { + return true; + } + return anonymousClass.isAssignableFrom(authentication.getClass()); } @@ -70,4 +76,8 @@ public void setAnonymousClass(Class anonymousClass) { public void setRememberMeClass(Class rememberMeClass) { this.rememberMeClass = rememberMeClass; } + + public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator){ + this.mfaTokenEvaluator = mfaTokenEvaluator; + } } diff --git a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java new file mode 100644 index 00000000000..cd64dfbc121 --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluator.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.security.core.Authentication; + +/** + * Evaluates Authentication tokens + * + * @author Yoshikazu Nojima + */ +public interface MFATokenEvaluator { + + /** + * Indicates whether the passed Authentication token represents a + * user in the middle of multi factor authentication process. + * + * @param authentication to test (may be null in which case the method + * will always return false) + * + * @return true the passed authentication token represented a principal + * in the middle of multi factor authentication process, false otherwise + */ + boolean isMultiFactorAuthentication(Authentication authentication); + + /** + * Indicates whether the principal associated with the Authentication + * token is allowed to login with only single factor. + * + * @param authentication to test (may be null in which case the method + * will always return false) + * + * @return true the principal associated with thepassed authentication + * token is allowed to login with only single factor, false otherwise + */ + boolean isSingleFactorAuthenticationAllowed(Authentication authentication); +} diff --git a/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java new file mode 100644 index 00000000000..acc98967e9b --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MFATokenEvaluatorImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.MFAUserDetails; + +/** + * Basic implementation of {@link MFATokenEvaluator}. + *

+ * Makes trust decisions based on whether the passed Authentication is an + * instance of a defined class. + *

+ * If {@link #multiFactorClass} is null, the + * corresponding method will always return false. + * + * @author Yoshikazu Nojima + */ +public class MFATokenEvaluatorImpl implements MFATokenEvaluator { + + private Class multiFactorClass = MultiFactorAuthenticationToken.class; + private boolean singleFactorAuthenticationAllowed = true; + + @Override + public boolean isMultiFactorAuthentication(Authentication authentication) { + if ((multiFactorClass == null) || (authentication == null)) { + return false; + } + + return multiFactorClass.isAssignableFrom(authentication.getClass()); + } + + @Override + public boolean isSingleFactorAuthenticationAllowed(Authentication authentication) { + if (singleFactorAuthenticationAllowed && authentication.getPrincipal() instanceof MFAUserDetails) { + MFAUserDetails webAuthnUserDetails = (MFAUserDetails) authentication.getPrincipal(); + return webAuthnUserDetails.isSingleFactorAuthenticationAllowed(); + } + return false; + } + + Class getMultiFactorClass() { + return multiFactorClass; + } + + public void setMultiFactorClass(Class multiFactorClass) { + this.multiFactorClass = multiFactorClass; + } + + /** + * Check if single factor authentication is allowed + * + * @return true if single factor authentication is allowed + */ + public boolean isSingleFactorAuthenticationAllowed() { + return singleFactorAuthenticationAllowed; + } + + /** + * Set single factor authentication is allowed + * + * @param singleFactorAuthenticationAllowed true if single factor authentication is allowed + */ + public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed) { + this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; + } + +} diff --git a/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java new file mode 100644 index 00000000000..b9ecfb5117f --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.util.Assert; + +import java.util.Collections; + +/** + * An {@link AuthenticationProvider} implementation for the first factor(step) of multi factor authentication. + * Authentication itself is delegated to another {@link AuthenticationProvider}. + * + * @author Yoshikazu Nojima + */ +public class MultiFactorAuthenticationProvider implements AuthenticationProvider { + + + // ~ Instance fields + // ================================================================================================ + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + + /** + * {@link AuthenticationProvider} to be delegated + */ + private AuthenticationProvider authenticationProvider; + private MFATokenEvaluator mfaTokenEvaluator; + + /** + * Constructor + * + * @param authenticationProvider {@link AuthenticationProvider} to be delegated + */ + public MultiFactorAuthenticationProvider(AuthenticationProvider authenticationProvider, MFATokenEvaluator mfaTokenEvaluator) { + Assert.notNull(authenticationProvider, "authenticationProvider must be set"); + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must be set"); + this.authenticationProvider = authenticationProvider; + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + + /** + * {@inheritDoc} + */ + @Override + public Authentication authenticate(Authentication authentication) { + if (!supports(authentication.getClass())) { + throw new IllegalArgumentException("Not supported AuthenticationToken " + authentication.getClass() + " was attempted"); + } + + Authentication result = authenticationProvider.authenticate(authentication); + + if (mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(result)) { + return result; + } + + return new MultiFactorAuthenticationToken( + result.getPrincipal(), + result.getCredentials(), + Collections.emptyList() // result.getAuthorities() is not used as not to inherit authorities from result + ); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean supports(Class authentication) { + return authenticationProvider.supports(authentication); + } + + /** + * {@link AuthenticationProvider} to be delegated + */ + public AuthenticationProvider getAuthenticationProvider() { + return authenticationProvider; + } + + public MFATokenEvaluator getMFATokenEvaluator() { + return mfaTokenEvaluator; + } +} diff --git a/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java new file mode 100644 index 00000000000..f0977505b0c --- /dev/null +++ b/core/src/main/java/org/springframework/security/authentication/MultiFactorAuthenticationToken.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; + +import java.util.Collection; + +/** + * Represents a principal in the middle of multi factor (step) authentication + * + * @author Yoshikazu Nojima + */ +public class MultiFactorAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + private Object principal; + private Object credentials; + + // ~ Constructors + // =================================================================================================== + public MultiFactorAuthenticationToken(Object principal, Object credentials, Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + setAuthenticated(true); + } + + // ~ Methods + // ======================================================================================================== + + @Override + public Object getPrincipal() { + return principal; + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + credentials = null; + } + +} diff --git a/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java b/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java new file mode 100644 index 00000000000..0125c7decaf --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/userdetails/MFAUserDetails.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.core.userdetails; + +import org.springframework.security.core.userdetails.UserDetails; + +/** + * A specialized {@link UserDetails} to indicate a user allows single factor authentication + * @author Yoshikazu Nojima + */ +public interface MFAUserDetails extends UserDetails { + + boolean isSingleFactorAuthenticationAllowed(); + +} diff --git a/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java b/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java new file mode 100644 index 00000000000..0e07b4193b1 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.junit.Test; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.MFAUserDetails; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MFATokenEvaluatorImplTests { + + // ~ Methods + // ======================================================================================================== + @Test + public void testCorrectOperationIsAnonymous() { + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new MultiFactorAuthenticationToken("ignored", + "ignored", AuthorityUtils.createAuthorityList("ignored")))).isTrue(); + assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new TestingAuthenticationToken("ignored", + "ignored", AuthorityUtils.createAuthorityList("ignored")))).isFalse(); + } + + @Test + public void testIsSingleFactorAuthenticationAllowedWithNonMFAUserDetailsPrincipal(){ + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken("", "")); + assertThat(result).isFalse(); + } + + @Test + public void testIsSingleFactorAuthenticationAllowedWithMFAUserDetailsPrincipal(){ + MFAUserDetails mfaUserDetails = mock(MFAUserDetails.class); + when(mfaUserDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken(mfaUserDetails, "")); + assertThat(result).isTrue(); + } + + @Test + public void testGettersSetters() { + MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + + assertThat(MultiFactorAuthenticationToken.class).isEqualTo( + mfaTokenEvaluator.getMultiFactorClass()); + mfaTokenEvaluator.setMultiFactorClass(TestingAuthenticationToken.class); + assertThat(mfaTokenEvaluator.getMultiFactorClass()).isEqualTo( + TestingAuthenticationToken.class); + + assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isTrue(); + mfaTokenEvaluator.setSingleFactorAuthenticationAllowed(false); + assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isFalse(); + + } + +} diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java new file mode 100644 index 00000000000..86aadeff2d9 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + + +import org.junit.Test; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.MFAUserDetails; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MultiFactorAuthenticationProviderTests { + + @Test + public void authenticate_with_singleFactorAuthenticationAllowedOption_false_test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + MFAUserDetails userDetails = mock(MFAUserDetails.class); + when(userDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList()); + authenticationToken.setDetails(userDetails); + when(delegatedAuthenticationProvider.supports(any())).thenReturn(true); + when(delegatedAuthenticationProvider.authenticate(any())) + .thenReturn(new UsernamePasswordAuthenticationToken( + "principal", + "credentials", + Collections.singletonList(new SimpleGrantedAuthority("ROLE_DUMMY")) + )); + + MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); + Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("dummy", "dummy")); + + assertThat(result).isInstanceOf(MultiFactorAuthenticationToken.class); + assertThat(result.getPrincipal()).isEqualTo("principal"); + assertThat(result.getCredentials()).isEqualTo("credentials"); + assertThat(result.getAuthorities()).isEmpty(); + + } + + @Test + public void authenticate_with_singleFactorAuthenticationAllowedOption_true_test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + MFAUserDetails userDetails = mock(MFAUserDetails.class); + when(userDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList()); + authenticationToken.setDetails(userDetails); + when(delegatedAuthenticationProvider.supports(any())).thenReturn(true); + when(delegatedAuthenticationProvider.authenticate(any())) + .thenReturn(authenticationToken); + + MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); + Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("dummy", "dummy")); + + assertThat(result).isInstanceOf(UsernamePasswordAuthenticationToken.class); + assertThat(result).isEqualTo(result); + } + + @Test(expected = IllegalArgumentException.class) + public void authenticate_with_invalid_AuthenticationToken_test(){ + AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); + when(delegatedAuthenticationProvider.supports(any())).thenReturn(false); + + MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); + provider.authenticate(new TestingAuthenticationToken("dummy", "dummy")); + } + +} diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java new file mode 100644 index 00000000000..99a9e42cb4c --- /dev/null +++ b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.authentication; + +import org.junit.Test; +import org.springframework.security.core.authority.AuthorityUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MultiFactorAuthenticationTokenTests { + // ~ Methods + // ======================================================================================================== + + @Test + public void authenticatedPropertyContractIsSatisfied() { + MultiFactorAuthenticationToken token = new MultiFactorAuthenticationToken( + "Test", "Password", AuthorityUtils.NO_AUTHORITIES); + + // check default given we passed some GrantedAuthority[]s (well, we passed empty + // list) + assertThat(token.isAuthenticated()).isTrue(); + + // check explicit set to untrusted (we can safely go from trusted to untrusted, + // but not the reverse) + token.setAuthenticated(false); + assertThat(token.isAuthenticated()).isFalse(); + + } + + @Test + public void gettersReturnCorrectData() { + MultiFactorAuthenticationToken token = new MultiFactorAuthenticationToken( + "Test", "Password", + AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); + assertThat(token.getPrincipal()).isEqualTo("Test"); + assertThat(token.getCredentials()).isEqualTo("Password"); + assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_ONE"); + assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_TWO"); + } + + @Test(expected = NoSuchMethodException.class) + public void testNoArgConstructorDoesntExist() throws Exception { + Class clazz = UsernamePasswordAuthenticationToken.class; + clazz.getDeclaredConstructor((Class[]) null); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java index 3e3345250c4..23ee8152181 100644 --- a/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java +++ b/web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java @@ -19,6 +19,8 @@ import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MFATokenEvaluatorImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; @@ -81,6 +83,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer(); private RequestCache requestCache = new HttpSessionRequestCache(); @@ -163,6 +166,8 @@ protected AuthenticationTrustResolver getAuthenticationTrustResolver() { return authenticationTrustResolver; } + protected MFATokenEvaluator getMFATokenEvaluator(){ return mfaTokenEvaluator; } + private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { @@ -204,9 +209,15 @@ else if (exception instanceof AccessDeniedException) { protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContextHolder.getContext().setAuthentication(null); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (mfaTokenEvaluator.isMultiFactorAuthentication(authentication)) { + // no-op if in the middle of multi step authentication + } + else { + // SEC-112: Clear the SecurityContextHolder's Authentication, as the + // existing Authentication is no longer considered valid + SecurityContextHolder.getContext().setAuthentication(null); + } requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); authenticationEntryPoint.commence(request, response, reason); @@ -224,6 +235,12 @@ public void setAuthenticationTrustResolver( this.authenticationTrustResolver = authenticationTrustResolver; } + public void setMFATokenEvaluator( + MFATokenEvaluator mfaTokenEvaluator){ + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must not be null"); + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) { Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null"); this.throwableAnalyzer = throwableAnalyzer; diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java index 85f90e3f590..4c88a4e25b2 100644 --- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java @@ -29,6 +29,8 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.authentication.MFATokenEvaluatorImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.Transient; import org.springframework.security.core.context.SecurityContext; @@ -97,6 +99,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY; private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + private MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); /** * Gets the security context for the current request (if available) and returns it. @@ -331,8 +334,8 @@ final class SaveToSessionResponseWrapper extends /** * Stores the supplied security context in the session (if available) and if it * has changed since it was set at the start of the request. If the - * AuthenticationTrustResolver identifies the current user as anonymous, then the - * context will not be stored. + * AuthenticationTrustResolver identifies the current user as anonymous, but not + * in the middle of multi factor authentication, then the context will not be stored. * * @param context the context object obtained from the SecurityContextHolder after * the request has been processed by the filter chain. @@ -346,7 +349,7 @@ protected void saveContext(SecurityContext context) { HttpSession httpSession = request.getSession(false); // See SEC-776 - if (authentication == null || trustResolver.isAnonymous(authentication)) { + if (authentication == null || trustResolver.isAnonymous(authentication) && !mfaTokenEvaluator.isMultiFactorAuthentication(authentication)) { if (logger.isDebugEnabled()) { logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession."); } @@ -455,4 +458,16 @@ public void setTrustResolver(AuthenticationTrustResolver trustResolver) { Assert.notNull(trustResolver, "trustResolver cannot be null"); this.trustResolver = trustResolver; } + + /** + * Sets the {@link MFATokenEvaluator} to be used. The default is + * {@link MFATokenEvaluatorImpl}. + * + * @param mfaTokenEvaluator the {@link MFATokenEvaluator} to use. Cannot be + * null. + */ + public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator cannot be null"); + this.mfaTokenEvaluator = mfaTokenEvaluator; + } } diff --git a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java index c26c3b98b4d..5b83d6089d4 100644 --- a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java @@ -43,6 +43,7 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.MultiFactorAuthenticationToken; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; @@ -111,6 +112,40 @@ public void testAccessDeniedWhenAnonymous() throws Exception { assertThat(getSavedRequestUrl(request)).isEqualTo("http://localhost/mycontext/secure/page.html"); } + @Test + public void testAccessDeniedWhenMultiFactorAuthentication() throws Exception { + // Setup our HTTP request + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServletPath("/secure/page.html"); + request.setServerPort(80); + request.setScheme("http"); + request.setServerName("www.example.com"); + request.setContextPath("/mycontext"); + request.setRequestURI("/mycontext/secure/page.html"); + + // Setup the FilterChain to thrown an access denied exception + FilterChain fc = mock(FilterChain.class); + doThrow(new AccessDeniedException("")).when(fc).doFilter( + any(HttpServletRequest.class), any(HttpServletResponse.class)); + + // Setup SecurityContextHolder, as filter needs to check if user is + // anonymous + SecurityContextHolder.getContext().setAuthentication( + new MultiFactorAuthenticationToken("ignored", "ignored", AuthorityUtils + .createAuthorityList("IGNORED"))); + + // Test + ExceptionTranslationFilter filter = new ExceptionTranslationFilter(mockEntryPoint); + filter.setAuthenticationTrustResolver(new AuthenticationTrustResolverImpl()); + assertThat(filter.getAuthenticationTrustResolver()).isNotNull(); + + MockHttpServletResponse response = new MockHttpServletResponse(); + filter.doFilter(request, response, fc); + assertThat(response.getRedirectedUrl()).isEqualTo("/mycontext/login.jsp"); + assertThat(getSavedRequestUrl(request)).isEqualTo("http://www.example.com/mycontext/secure/page.html"); + } + + @Test public void testAccessDeniedWithRememberMe() throws Exception { // Setup our HTTP request From 6a85631b83488cadaec3c9aa4e34ee5a72859eed Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 4 May 2019 12:19:36 +0900 Subject: [PATCH 2/9] Implement W3C WebAuthentication specification spec: https://www.w3.org/TR/webauthn-1/ --- .../SessionManagementConfigurer.java | 1 + gradle/dependency-management.gradle | 2 + webauthn/spring-security-webauthn.gradle | 28 + .../WebAuthnAssertionAuthenticationToken.java | 122 +++ .../WebAuthnAuthenticationProvider.java | 305 +++++++ .../webauthn/WebAuthnAuthenticationToken.java | 100 ++ .../webauthn/WebAuthnProcessingFilter.java | 262 ++++++ ...RegistrationRequestValidationResponse.java | 79 ++ .../WebAuthnRegistrationRequestValidator.java | 125 +++ .../authenticator/WebAuthnAuthenticator.java | 85 ++ .../WebAuthnAuthenticatorService.java | 36 + .../challenge/ChallengeRepository.java | 75 ++ .../HttpSessionChallengeRepository.java | 87 ++ ...AuthnAuthenticationProviderConfigurer.java | 76 ++ .../configurers/WebAuthnConfigurerUtil.java | 123 +++ .../configurers/WebAuthnLoginConfigurer.java | 855 ++++++++++++++++++ .../AssertionOptionsEndpointFilter.java | 90 ++ .../endpoint/AssertionOptionsResponse.java | 97 ++ .../AttestationOptionsEndpointFilter.java | 94 ++ .../endpoint/AttestationOptionsResponse.java | 144 +++ .../webauthn/endpoint/ErrorResponse.java | 59 ++ .../endpoint/OptionsEndpointFilterBase.java | 163 ++++ ...WebAuthnPublicKeyCredentialDescriptor.java | 101 +++ ...WebAuthnPublicKeyCredentialUserEntity.java | 78 ++ .../exception/BadAaguidException.java | 31 + .../exception/BadAlgorithmException.java | 33 + .../BadAttestationStatementException.java | 31 + .../exception/BadChallengeException.java | 31 + .../exception/BadCredentialIdException.java | 27 + .../exception/BadOriginException.java | 30 + .../webauthn/exception/BadRpIdException.java | 30 + .../exception/BadSignatureException.java | 31 + .../exception/CertificateException.java | 30 + .../ConstraintViolationException.java | 32 + .../CredentialIdNotFoundException.java | 30 + .../exception/DataConversionException.java | 32 + .../KeyDescriptionValidationException.java | 31 + .../MaliciousCounterValueException.java | 32 + .../exception/MaliciousDataException.java | 30 + .../webauthn/exception/MetadataException.java | 35 + .../exception/MissingChallengeException.java | 31 + .../exception/PublicKeyMismatchException.java | 32 + .../SelfAttestationProhibitedException.java | 32 + .../exception/TokenBindingException.java | 32 + .../TrustAnchorNotFoundException.java | 33 + .../UnexpectedExtensionException.java | 32 + .../exception/UserNotPresentException.java | 33 + .../exception/UserNotVerifiedException.java | 33 + .../exception/ValidationException.java | 32 + .../WebAuthnAuthenticationException.java | 36 + .../webauthn/exception/package-info.java | 20 + .../webauthn/options/AssertionOptions.java | 99 ++ .../webauthn/options/AttestationOptions.java | 144 +++ ...uthenticationExtensionsOptionProvider.java | 21 + .../options/ExtensionOptionProvider.java | 27 + .../options/ExtensionsOptionProvider.java | 72 ++ .../webauthn/options/OptionsProvider.java | 59 ++ .../webauthn/options/OptionsProviderImpl.java | 317 +++++++ .../RegistrationExtensionsOptionProvider.java | 21 + .../StaticExtensionOptionProvider.java | 39 + .../security/webauthn/package-info.java | 20 + .../WebAuthnAuthenticationRequest.java | 156 ++++ .../server/ServerPropertyProvider.java | 39 + .../server/ServerPropertyProviderImpl.java | 58 ++ .../userdetails/WebAuthnUserDetails.java | 38 + .../userdetails/WebAuthnUserDetailsImpl.java | 88 ++ .../WebAuthnUserDetailsService.java | 75 ++ .../webauthn/userdetails/package-info.java | 20 + .../security/webauthn/util/ExceptionUtil.java | 95 ++ .../security/webauthn/util/ServletUtil.java | 43 + .../component/RegistrationValidationTest.java | 104 +++ ...AuthnAssertionAuthenticationTokenTest.java | 60 ++ .../WebAuthnAuthenticationProviderTest.java | 363 ++++++++ .../WebAuthnAuthenticationTokenTest.java | 61 ++ .../WebAuthnProcessingFilterTest.java | 260 ++++++ ...strationRequestValidationResponseTest.java | 56 ++ ...AuthnRegistrationRequestValidatorTest.java | 162 ++++ .../WebAuthnAuthenticatorTest.java | 40 + .../HttpSessionChallengeRepositoryTest.java | 118 +++ ...nticationProviderConfigurerSpringTest.java | 125 +++ ...bAuthnLoginConfigurerSetterSpringTest.java | 135 +++ .../WebAuthnLoginConfigurerSpringTest.java | 229 +++++ .../AttestationOptionsEndpointFilterTest.java | 121 +++ .../webauthn/endpoint/ErrorResponseTest.java | 40 + ...uthnPublicKeyCredentialDescriptorTest.java | 42 + ...uthnPublicKeyCredentialUserEntityTest.java | 35 + .../exception/BadAaguidExceptionTest.java | 36 + .../exception/BadAlgorithmExceptionTest.java | 36 + .../BadAttestationStatementExceptionTest.java | 35 + .../exception/BadChallengeExceptionTest.java | 36 + .../BadCredentialIdExceptionTest.java | 37 + .../exception/BadOriginExceptionTest.java | 35 + .../exception/BadRpIdExceptionTest.java | 36 + .../exception/BadSignatureExceptionTest.java | 35 + .../exception/CertificateExceptionTest.java | 36 + .../ConstraintViolationExceptionTest.java | 36 + .../CredentialIdNotFoundExceptionTest.java | 35 + .../DataConversionExceptionTest.java | 38 + ...KeyDescriptionValidationExceptionTest.java | 36 + .../MaliciousCounterValueExceptionTest.java | 36 + .../exception/MaliciousDataExceptionTest.java | 36 + .../MissingChallengeExceptionTest.java | 36 + .../exception/OptionsExceptionTest.java | 36 + .../PublicKeyMismatchExceptionTest.java | 35 + ...elfAttestationProhibitedExceptionTest.java | 35 + .../exception/TokenBindingExceptionTest.java | 36 + .../TrustAnchorNotFoundExceptionTest.java | 36 + .../UnexpectedExtensionExceptionTest.java | 36 + .../UserNotPresentExceptionTest.java | 36 + .../UserNotVerifiedExceptionTest.java | 36 + .../WebAuthnAuthenticationExceptionTest.java | 37 + .../options/AssertionOptionsTest.java | 51 ++ .../options/AttestationOptionsTest.java | 59 ++ .../options/OptionsProviderImplTest.java | 142 +++ .../WebAuthnAuthenticationRequestTest.java | 109 +++ .../ServerPropertyProviderImplTest.java | 54 ++ .../WebAuthnUserDetailsImplTest.java | 49 + .../webauthn/util/ExceptionUtilTest.java | 70 ++ .../resources/certs/3tier-test-root-CA.der | Bin 0 -> 389 bytes .../resources/certs/3tier-test-root-CA.pem | 38 + 120 files changed, 8881 insertions(+) create mode 100644 webauthn/spring-security-webauthn.gradle create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorService.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationException.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java create mode 100644 webauthn/src/test/java/integration/component/RegistrationValidationTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java create mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java create mode 100644 webauthn/src/test/resources/certs/3tier-test-root-CA.der create mode 100644 webauthn/src/test/resources/certs/3tier-test-root-CA.pem diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index 25df14ed432..b957e39d4b2 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -30,6 +30,7 @@ import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index a33638468b4..926f196c32c 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -21,6 +21,7 @@ dependencyManagement { dependencies { dependency 'cglib:cglib-nodep:3.3.0' dependency 'com.squareup.okhttp3:mockwebserver:3.14.2' + dependency 'com.webauthn4j:webauthn4j-test:0.9.13.RELEASE' dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' @@ -63,6 +64,7 @@ dependencyManagement { dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-impl:2.3.2' dependency 'com.unboundid:unboundid-ldapsdk:4.0.11' + dependency 'com.webauthn4j:webauthn4j-core:0.9.13.RELEASE' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'commons-cli:commons-cli:1.4' dependency 'commons-codec:commons-codec:1.13' diff --git a/webauthn/spring-security-webauthn.gradle b/webauthn/spring-security-webauthn.gradle new file mode 100644 index 00000000000..44933d9afcb --- /dev/null +++ b/webauthn/spring-security-webauthn.gradle @@ -0,0 +1,28 @@ +apply plugin: 'io.spring.convention.spring-module' + +dependencies { + compile project(':spring-security-core') + compile project(':spring-security-config') + compile project(':spring-security-web') + compile springCoreDependency + compile("org.springframework:spring-core") + compile("org.springframework:spring-context") + compile("org.springframework:spring-aop") + compile("org.springframework:spring-jdbc") + compile("org.springframework:spring-web") + + compile("com.webauthn4j:webauthn4j-core") + + provided 'javax.servlet:javax.servlet-api' + + compile project(':spring-security-test') + testCompile("com.webauthn4j:webauthn4j-test") + testCompile("org.skyscreamer:jsonassert") + testCompile("org.springframework:spring-webmvc") + testCompile('junit:junit') + testCompile('org.mockito:mockito-core') + testCompile('org.assertj:assertj-core') + + + testRuntime 'org.hsqldb:hsqldb' +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java new file mode 100644 index 00000000000..1bb086fdd4e --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; + +/** + * An {@link Authentication} implementation for representing WebAuthn assertion like + * {@link UsernamePasswordAuthenticationToken} for password authentication + * + * @author Yoshikazu Nojima + */ +public class WebAuthnAssertionAuthenticationToken extends AbstractAuthenticationToken { + + // ~ Instance fields + // ================================================================================================ + private WebAuthnAuthenticationRequest credentials; + + + // ~ Constructor + // ======================================================================================================== + + /** + * This constructor can be safely used by any code that wishes to create a + * WebAuthnAssertionAuthenticationToken, as the {@link #isAuthenticated()} + * will return false. + * + * @param credentials credential + */ + public WebAuthnAssertionAuthenticationToken(WebAuthnAuthenticationRequest credentials) { + super(null); + this.credentials = credentials; + setAuthenticated(false); + } + + // ~ Methods + // ======================================================================================================== + + /** + * Always null + * + * @return null + */ + @Override + public String getPrincipal() { + return null; + } + + /** + * @return the stored WebAuthn authentication context + */ + @Override + public WebAuthnAuthenticationRequest getCredentials() { + return credentials; + } + + /** + * This object can never be authenticated, call with true result in exception. + * + * @param isAuthenticated only false value allowed + * @throws IllegalArgumentException if isAuthenticated is true + */ + @Override + public void setAuthenticated(boolean isAuthenticated) { + if (isAuthenticated) { + throw new IllegalArgumentException( + "Cannot set this authenticator to trusted"); + } + + super.setAuthenticated(false); + } + + /** + * {@inheritDoc} + */ + @Override + public void eraseCredentials() { + super.eraseCredentials(); + credentials = null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WebAuthnAssertionAuthenticationToken)) return false; + if (!super.equals(o)) return false; + + WebAuthnAssertionAuthenticationToken that = (WebAuthnAssertionAuthenticationToken) o; + + return credentials != null ? credentials.equals(that.credentials) : that.credentials == null; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (credentials != null ? credentials.hashCode() : 0); + return result; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java new file mode 100644 index 00000000000..942ed9ffcb0 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java @@ -0,0 +1,305 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.data.WebAuthnAuthenticationContext; +import com.webauthn4j.util.exception.WebAuthnException; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsChecker; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.security.webauthn.util.ExceptionUtil; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Objects; + +/** + * An {@link AuthenticationProvider} implementation for processing {@link WebAuthnAssertionAuthenticationToken} + * + * @author Yoshikazu Nojima + */ +public class WebAuthnAuthenticationProvider implements AuthenticationProvider { + + //~ Instance fields + // ================================================================================================ + + protected final Log logger = LogFactory.getLog(getClass()); + + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + private WebAuthnUserDetailsService userDetailsService; + private WebAuthnAuthenticatorService authenticatorService; + private WebAuthnAuthenticationContextValidator authenticationContextValidator; + private boolean forcePrincipalAsString = false; + private boolean hideCredentialIdNotFoundExceptions = true; + private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks(); + private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks(); + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + + // ~ Constructor + // ======================================================================================================== + + public WebAuthnAuthenticationProvider( + WebAuthnUserDetailsService userDetailsService, + WebAuthnAuthenticatorService authenticatorService, + WebAuthnAuthenticationContextValidator authenticationContextValidator) { + + Assert.notNull(userDetailsService, "userDetailsService must not be null"); + Assert.notNull(authenticatorService, "authenticatorService must not be null"); + Assert.notNull(authenticationContextValidator, "authenticationContextValidator must not be null"); + + this.userDetailsService = userDetailsService; + this.authenticatorService = authenticatorService; + this.authenticationContextValidator = authenticationContextValidator; + } + + // ~ Methods + // ======================================================================================================== + + /** + * {@inheritDoc} + */ + @Override + public Authentication authenticate(Authentication authentication) { + if (!supports(authentication.getClass())) { + throw new IllegalArgumentException("Only WebAuthnAssertionAuthenticationToken is supported, " + authentication.getClass() + " was attempted"); + } + + WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) authentication; + + WebAuthnAuthenticationRequest credentials = authenticationToken.getCredentials(); + if (credentials == null) { + logger.debug("Authentication failed: no credentials provided"); + + throw new BadCredentialsException(messages.getMessage( + "WebAuthnAuthenticationContextValidator.badCredentials", + "Bad credentials")); + } + + byte[] credentialId = credentials.getCredentialId(); + + WebAuthnUserDetails user = retrieveWebAuthnUserDetails(credentialId); + Authenticator authenticator = user.getAuthenticators().stream() + .filter(item -> Arrays.equals(item.getAttestedCredentialData().getCredentialId(), credentialId)) + .findFirst() + .orElse(null); + + preAuthenticationChecks.check(user); + doAuthenticate(authenticationToken, authenticator, user); + postAuthenticationChecks.check(user); + + //noinspection ConstantConditions + authenticatorService.updateCounter(credentialId, authenticator.getCounter()); + + Serializable principalToReturn = user; + + if (forcePrincipalAsString) { + principalToReturn = user.getUsername(); + } + + WebAuthnAuthenticationToken result = new WebAuthnAuthenticationToken( + principalToReturn, authenticationToken.getCredentials(), + authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(authenticationToken.getDetails()); + + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean supports(Class authentication) { + return WebAuthnAssertionAuthenticationToken.class.isAssignableFrom(authentication); + } + + void doAuthenticate(WebAuthnAssertionAuthenticationToken authenticationToken, Authenticator authenticator, WebAuthnUserDetails user) { + + WebAuthnAuthenticationRequest credentials = authenticationToken.getCredentials(); + + boolean userVerificationRequired = isUserVerificationRequired(user, credentials); + + WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext( + credentials.getCredentialId(), + credentials.getClientDataJSON(), + credentials.getAuthenticatorData(), + credentials.getSignature(), + credentials.getClientExtensionsJSON(), + credentials.getServerProperty(), + userVerificationRequired, + credentials.isUserPresenceRequired(), + credentials.getExpectedAuthenticationExtensionIds() + ); + + try { + authenticationContextValidator.validate(authenticationContext, authenticator); + } catch (WebAuthnException e) { + throw ExceptionUtil.wrapWithAuthenticationException(e); + } + + } + + public boolean isForcePrincipalAsString() { + return forcePrincipalAsString; + } + + public void setForcePrincipalAsString(boolean forcePrincipalAsString) { + this.forcePrincipalAsString = forcePrincipalAsString; + } + + public boolean isHideCredentialIdNotFoundExceptions() { + return hideCredentialIdNotFoundExceptions; + } + + /** + * By default the WebAuthnAuthenticationProvider throws a + * BadCredentialsException if a credentialId is not found or the credential is + * incorrect. Setting this property to false will cause + * CredentialIdNotFoundExceptions to be thrown instead for the former. Note + * this is considered less secure than throwing BadCredentialsException + * for both exceptions. + * + * @param hideCredentialIdNotFoundExceptions set to false if you wish + * CredentialIdNotFoundExceptions to be thrown instead of the non-specific + * BadCredentialsException (defaults to true) + */ + public void setHideCredentialIdNotFoundExceptions(boolean hideCredentialIdNotFoundExceptions) { + this.hideCredentialIdNotFoundExceptions = hideCredentialIdNotFoundExceptions; + } + + protected WebAuthnUserDetailsService getUserDetailsService() { + return userDetailsService; + } + + public void setUserDetailsService(WebAuthnUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + protected UserDetailsChecker getPreAuthenticationChecks() { + return preAuthenticationChecks; + } + + /** + * Sets the policy will be used to verify the status of the loaded + * UserDetails before validation of the credentials takes place. + * + * @param preAuthenticationChecks strategy to be invoked prior to authentication. + */ + public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) { + this.preAuthenticationChecks = preAuthenticationChecks; + } + + protected UserDetailsChecker getPostAuthenticationChecks() { + return postAuthenticationChecks; + } + + public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) { + this.postAuthenticationChecks = postAuthenticationChecks; + } + + WebAuthnUserDetails retrieveWebAuthnUserDetails(byte[] credentialId) { + WebAuthnUserDetails user; + try { + user = userDetailsService.loadUserByCredentialId(credentialId); + } catch (CredentialIdNotFoundException notFound) { + if (hideCredentialIdNotFoundExceptions) { + throw new BadCredentialsException(messages.getMessage( + "WebAuthnAuthenticationProvider.badCredentials", + "Bad credentials")); + } else { + throw notFound; + } + } catch (Exception repositoryProblem) { + throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); + } + + if (user == null) { + throw new InternalAuthenticationServiceException( + "UserDetailsService returned null, which is an interface contract violation"); + } + return user; + } + + boolean isUserVerificationRequired(WebAuthnUserDetails user, WebAuthnAuthenticationRequest credentials) { + + Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication(); + + // If current authentication is authenticated and username matches, return false + if (currentAuthentication != null && currentAuthentication.isAuthenticated() && Objects.equals(currentAuthentication.getName(), user.getUsername())) { + return false; + } else { + return credentials.isUserVerificationRequired(); + } + } + + private class DefaultPreAuthenticationChecks implements UserDetailsChecker { + @Override + public void check(UserDetails user) { + if (!user.isAccountNonLocked()) { + logger.debug("User account is locked"); + + throw new LockedException(messages.getMessage( + "WebAuthnAuthenticationProvider.locked", + "User account is locked")); + } + + if (!user.isEnabled()) { + logger.debug("User account is disabled"); + + throw new DisabledException(messages.getMessage( + "WebAuthnAuthenticationProvider.disabled", + "User is disabled")); + } + + if (!user.isAccountNonExpired()) { + logger.debug("User account is expired"); + + throw new AccountExpiredException(messages.getMessage( + "WebAuthnAuthenticationProvider.expired", + "User account has expired")); + } + } + } + + private class DefaultPostAuthenticationChecks implements UserDetailsChecker { + @Override + public void check(UserDetails user) { + if (!user.isCredentialsNonExpired()) { + logger.debug("User account credentials have expired"); + + throw new CredentialsExpiredException(messages.getMessage( + "WebAuthnAuthenticationProvider.credentialsExpired", + "User credentials have expired")); + } + } + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java new file mode 100644 index 00000000000..bd8e6ef1bc8 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; + +import java.io.Serializable; +import java.util.Collection; + +/** + * An {@link Authentication} implementation that is designed for Web Authentication specification. + * + * @author Yoshikazu Nojima + */ +public class WebAuthnAuthenticationToken extends AbstractAuthenticationToken { + + //~ Instance fields + // ================================================================================================ + private Serializable principal; + private WebAuthnAuthenticationRequest credentials; + + // ~ Constructor + // ======================================================================================================== + + /** + * Constructor + * + * @param principal principal + * @param credentials credentials + * @param authorities the collection of GrantedAuthority for the principal represented by this authentication object. + */ + public WebAuthnAuthenticationToken(Serializable principal, WebAuthnAuthenticationRequest credentials, Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + this.setAuthenticated(true); + } + + // ~ Methods + // ======================================================================================================== + + /** + * {@inheritDoc} + */ + @Override + public Serializable getPrincipal() { + return principal; + } + + /** + * {@inheritDoc} + */ + @Override + public WebAuthnAuthenticationRequest getCredentials() { + return credentials; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WebAuthnAuthenticationToken)) return false; + if (!super.equals(o)) return false; + + WebAuthnAuthenticationToken that = (WebAuthnAuthenticationToken) o; + + if (principal != null ? !principal.equals(that.principal) : that.principal != null) return false; + return credentials != null ? credentials.equals(that.credentials) : that.credentials == null; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (principal != null ? principal.hashCode() : 0); + result = 31 * result + (credentials != null ? credentials.hashCode() : 0); + return result; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java new file mode 100644 index 00000000000..e03b4222e40 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java @@ -0,0 +1,262 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.server.ServerProperty; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.util.Assert; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collections; +import java.util.List; + + +/** + * Processes a WebAuthn authentication form submission. For supporting username/password authentication for first step of + * two step authentication, if credentialId is not found in the HTTP request, this filter try to find username/password + * parameters. + *

+ * Login forms must present WebAuthn parameters (credentialId, clientDataJSON, authenticatorData,signature and + * clientExtensionJSON) or Password authentication parameters (username and password). + * The default parameter names to use are contained in the static fields + * {@link #SPRING_SECURITY_FORM_CREDENTIAL_ID_KEY}, + * {@link #SPRING_SECURITY_FORM_CLIENT_DATA_JSON_KEY}, + * {@link #SPRING_SECURITY_FORM_AUTHENTICATOR_DATA_KEY}, + * {@link #SPRING_SECURITY_FORM_SIGNATURE_KEY}, and + * {@link #SPRING_SECURITY_FORM_CLIENT_EXTENSIONS_JSON_KEY}. + * The parameter names can also be changed by setting the corresponding properties. + *

+ * This filter by default responds to the URL {@code /login}. + * + * @author Yoshikazu Nojima + * @see WebAuthnAuthenticationProvider + */ +public class WebAuthnProcessingFilter extends UsernamePasswordAuthenticationFilter { + + // ~ Static fields/initializers + // ===================================================================================== + public static final String SPRING_SECURITY_FORM_CREDENTIAL_ID_KEY = "credentialId"; + public static final String SPRING_SECURITY_FORM_CLIENT_DATA_JSON_KEY = "clientDataJSON"; + public static final String SPRING_SECURITY_FORM_AUTHENTICATOR_DATA_KEY = "authenticatorData"; + public static final String SPRING_SECURITY_FORM_SIGNATURE_KEY = "signature"; + public static final String SPRING_SECURITY_FORM_CLIENT_EXTENSIONS_JSON_KEY = "clientExtensionsJSON"; + + //~ Instance fields + // ================================================================================================ + private List authorities; + + private String credentialIdParameter = SPRING_SECURITY_FORM_CREDENTIAL_ID_KEY; + private String clientDataJSONParameter = SPRING_SECURITY_FORM_CLIENT_DATA_JSON_KEY; + private String authenticatorDataParameter = SPRING_SECURITY_FORM_AUTHENTICATOR_DATA_KEY; + private String signatureParameter = SPRING_SECURITY_FORM_SIGNATURE_KEY; + private String clientExtensionsJSONParameter = SPRING_SECURITY_FORM_CLIENT_EXTENSIONS_JSON_KEY; + + private ServerPropertyProvider serverPropertyProvider; + + private List expectedAuthenticationExtensionIds = Collections.emptyList(); + + private boolean postOnly = true; + + // ~ Constructors + // =================================================================================================== + + /** + * Constructor + */ + public WebAuthnProcessingFilter() { + super(); + this.authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"); + } + + /** + * Constructor + * + * @param authorities authorities for FirstOfMultiFactorAuthenticationToken + * @param serverPropertyProvider provider for ServerProperty + */ + public WebAuthnProcessingFilter(List authorities, ServerPropertyProvider serverPropertyProvider) { + super(); + Assert.notNull(authorities, "authorities must not be null"); + Assert.notNull(serverPropertyProvider, "serverPropertyProvider must not be null"); + this.authorities = authorities; + this.serverPropertyProvider = serverPropertyProvider; + } + + // ~ Methods + // ======================================================================================================== + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) { + if (postOnly && !HttpMethod.POST.matches(request.getMethod())) { + throw new AuthenticationServiceException( + "Authentication method not supported: " + request.getMethod()); + } + + String username = obtainUsername(request); + String password = obtainPassword(request); + + String credentialId = obtainCredentialId(request); + String clientDataJSON = obtainClientDataJSON(request); + String authenticatorData = obtainAuthenticatorData(request); + String signature = obtainSignatureData(request); + String clientExtensionsJSON = obtainClientExtensionsJSON(request); + + AbstractAuthenticationToken authRequest; + if (StringUtils.isEmpty(credentialId)) { + authRequest = new UsernamePasswordAuthenticationToken(username, password, authorities); + } else { + byte[] rawId = Base64Utils.decodeFromUrlSafeString(credentialId); + byte[] rawClientData = clientDataJSON == null ? null : Base64Utils.decodeFromUrlSafeString(clientDataJSON); + byte[] rawAuthenticatorData = authenticatorData == null ? null : Base64Utils.decodeFromUrlSafeString(authenticatorData); + byte[] signatureBytes = signature == null ? null : Base64Utils.decodeFromUrlSafeString(signature); + + ServerProperty serverProperty = serverPropertyProvider.provide(request); + + WebAuthnAuthenticationRequest webAuthnAuthenticationRequest = new WebAuthnAuthenticationRequest( + rawId, + rawClientData, + rawAuthenticatorData, + signatureBytes, + clientExtensionsJSON, + serverProperty, + true, + expectedAuthenticationExtensionIds + ); + authRequest = new WebAuthnAssertionAuthenticationToken(webAuthnAuthenticationRequest); + } + + // Allow subclasses to set the "details" property + setDetails(request, authRequest); + + return this.getAuthenticationManager().authenticate(authRequest); + } + + /** + * Defines whether only HTTP POST requests will be allowed by this filter. If set to + * true, and an authentication request is received which is not a POST request, an + * exception will be raised immediately and authentication will not be attempted. The + * unsuccessfulAuthentication() method will be called as if handling a failed + * authentication. + *

+ * Defaults to true but may be overridden by subclasses. + * + * @param postOnly Flag to restrict HTTP method to POST. + */ + @Override + public void setPostOnly(boolean postOnly) { + this.postOnly = postOnly; + } + + public String getCredentialIdParameter() { + return credentialIdParameter; + } + + public void setCredentialIdParameter(String credentialIdParameter) { + this.credentialIdParameter = credentialIdParameter; + } + + public String getClientDataJSONParameter() { + return clientDataJSONParameter; + } + + public void setClientDataJSONParameter(String clientDataJSONParameter) { + this.clientDataJSONParameter = clientDataJSONParameter; + } + + public String getAuthenticatorDataParameter() { + return authenticatorDataParameter; + } + + public void setAuthenticatorDataParameter(String authenticatorDataParameter) { + this.authenticatorDataParameter = authenticatorDataParameter; + } + + public String getSignatureParameter() { + return signatureParameter; + } + + public void setSignatureParameter(String signatureParameter) { + this.signatureParameter = signatureParameter; + } + + public String getClientExtensionsJSONParameter() { + return clientExtensionsJSONParameter; + } + + public void setClientExtensionsJSONParameter(String clientExtensionsJSONParameter) { + this.clientExtensionsJSONParameter = clientExtensionsJSONParameter; + } + + public List getExpectedAuthenticationExtensionIds() { + return expectedAuthenticationExtensionIds; + } + + /** + * Sets expected authentication extensionId list + * + * @param expectedAuthenticationExtensionIds list of expected authentication extensionId + */ + public void setExpectedAuthenticationExtensionIds(List expectedAuthenticationExtensionIds) { + this.expectedAuthenticationExtensionIds = expectedAuthenticationExtensionIds; + } + + public ServerPropertyProvider getServerPropertyProvider() { + return serverPropertyProvider; + } + + public void setServerPropertyProvider(ServerPropertyProvider serverPropertyProvider) { + this.serverPropertyProvider = serverPropertyProvider; + } + + + private String obtainClientDataJSON(HttpServletRequest request) { + return request.getParameter(clientDataJSONParameter); + } + + private String obtainCredentialId(HttpServletRequest request) { + return request.getParameter(credentialIdParameter); + } + + private String obtainAuthenticatorData(HttpServletRequest request) { + return request.getParameter(authenticatorDataParameter); + } + + private String obtainSignatureData(HttpServletRequest request) { + return request.getParameter(signatureParameter); + } + + private String obtainClientExtensionsJSON(HttpServletRequest request) { + return request.getParameter(clientExtensionsJSONParameter); + } + + private void setDetails(HttpServletRequest request, + AbstractAuthenticationToken authRequest) { + authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java new file mode 100644 index 00000000000..75646baffea --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.data.attestation.AttestationObject; +import com.webauthn4j.data.client.CollectedClientData; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientOutputs; + +import java.util.Objects; + +/** + * Response from {@link WebAuthnRegistrationRequestValidator} + * + * @author Yoshikazu Nojima + */ +public class WebAuthnRegistrationRequestValidationResponse { + + // ~ Instance fields + // ================================================================================================ + + private CollectedClientData collectedClientData; + private AttestationObject attestationObject; + private AuthenticationExtensionsClientOutputs registrationExtensionsClientOutputs; + + // ~ Constructors + // =================================================================================================== + + public WebAuthnRegistrationRequestValidationResponse(CollectedClientData collectedClientData, AttestationObject attestationObject, AuthenticationExtensionsClientOutputs registrationExtensionsClientOutputs) { + this.collectedClientData = collectedClientData; + this.attestationObject = attestationObject; + this.registrationExtensionsClientOutputs = registrationExtensionsClientOutputs; + } + + // ~ Methods + // ======================================================================================================== + + public CollectedClientData getCollectedClientData() { + return collectedClientData; + } + + public AttestationObject getAttestationObject() { + return attestationObject; + } + + public AuthenticationExtensionsClientOutputs getRegistrationExtensionsClientOutputs() { + return registrationExtensionsClientOutputs; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnRegistrationRequestValidationResponse that = (WebAuthnRegistrationRequestValidationResponse) o; + return Objects.equals(collectedClientData, that.collectedClientData) && + Objects.equals(attestationObject, that.attestationObject) && + Objects.equals(registrationExtensionsClientOutputs, that.registrationExtensionsClientOutputs); + } + + @Override + public int hashCode() { + + return Objects.hash(collectedClientData, attestationObject, registrationExtensionsClientOutputs); + } +} + diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java new file mode 100644 index 00000000000..805f7df16f0 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.data.WebAuthnRegistrationContext; +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.util.Base64UrlUtil; +import com.webauthn4j.util.exception.WebAuthnException; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidationResponse; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.util.ExceptionUtil; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Set; + +/** + * A validator for WebAuthn registration request + * + * @author Yoshikazu Nojima + */ +public class WebAuthnRegistrationRequestValidator { + + // ~ Instance fields + // ================================================================================================ + private WebAuthnRegistrationContextValidator registrationContextValidator; + private ServerPropertyProvider serverPropertyProvider; + + private List expectedRegistrationExtensionIds; + + // ~ Constructors + // =================================================================================================== + + /** + * Constructor + * + * @param registrationContextValidator validator for {@link WebAuthnRegistrationContext} + * @param serverPropertyProvider provider for {@link ServerProperty} + */ + public WebAuthnRegistrationRequestValidator(WebAuthnRegistrationContextValidator registrationContextValidator, ServerPropertyProvider serverPropertyProvider) { + + Assert.notNull(registrationContextValidator, "registrationContextValidator must not be null"); + Assert.notNull(serverPropertyProvider, "serverPropertyProvider must not be null"); + + this.registrationContextValidator = registrationContextValidator; + this.serverPropertyProvider = serverPropertyProvider; + } + + // ~ Methods + // ======================================================================================================== + + public WebAuthnRegistrationRequestValidationResponse validate( + HttpServletRequest httpServletRequest, + String clientDataBase64url, + String attestationObjectBase64url, + Set transports, + String clientExtensionsJSON + ) { + Assert.notNull(httpServletRequest, "httpServletRequest must not be null"); + Assert.hasText(clientDataBase64url, "clientDataBase64url must have text"); + Assert.hasText(attestationObjectBase64url, "attestationObjectBase64url must have text"); + if (transports != null) { + transports.forEach(transport -> Assert.hasText(transport, "each transport must have text")); + } + + WebAuthnRegistrationContext registrationContext = + createRegistrationContext(httpServletRequest, clientDataBase64url, attestationObjectBase64url, transports, clientExtensionsJSON); + + try { + WebAuthnRegistrationContextValidationResponse response = registrationContextValidator.validate(registrationContext); + return new WebAuthnRegistrationRequestValidationResponse( + response.getCollectedClientData(), + response.getAttestationObject(), + response.getRegistrationExtensionsClientOutputs()); + } catch (WebAuthnException e) { + throw ExceptionUtil.wrapWithAuthenticationException(e); + } + } + + WebAuthnRegistrationContext createRegistrationContext( + HttpServletRequest request, + String clientDataBase64, + String attestationObjectBase64, + Set transports, + String clientExtensionsJSON) { + + byte[] clientDataBytes = Base64UrlUtil.decode(clientDataBase64); + byte[] attestationObjectBytes = Base64UrlUtil.decode(attestationObjectBase64); + ServerProperty serverProperty = serverPropertyProvider.provide(request); + + return new WebAuthnRegistrationContext( + clientDataBytes, + attestationObjectBytes, + transports, + clientExtensionsJSON, + serverProperty, + false, + false, + expectedRegistrationExtensionIds); + } + + public List getExpectedRegistrationExtensionIds() { + return expectedRegistrationExtensionIds; + } + + public void setExpectedRegistrationExtensionIds(List expectedRegistrationExtensionIds) { + this.expectedRegistrationExtensionIds = expectedRegistrationExtensionIds; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java new file mode 100644 index 00000000000..038d959f656 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java @@ -0,0 +1,85 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.authenticator; + +import com.webauthn4j.authenticator.AuthenticatorImpl; +import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; +import com.webauthn4j.data.attestation.statement.AttestationStatement; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +import java.util.Objects; + +/** + * Models core authenticator information retrieved by a {@link WebAuthnUserDetailsService} + * + * @author Yoshikazu Nojima + * @see WebAuthnUserDetailsService + */ +public class WebAuthnAuthenticator extends AuthenticatorImpl { + + // ~ Instance fields + // ================================================================================================ + private String name; + + // ~ Constructor + // ======================================================================================================== + + /** + * Constructor + * + * @param name authenticator's friendly name + * @param attestedCredentialData attested credential data + * @param attestationStatement attestation statement + * @param counter counter + */ + public WebAuthnAuthenticator(String name, AttestedCredentialData attestedCredentialData, AttestationStatement attestationStatement, long counter) { + super(attestedCredentialData, attestationStatement, counter); + this.setName(name); + } + + // ~ Methods + // ======================================================================================================== + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + WebAuthnAuthenticator that = (WebAuthnAuthenticator) o; + return Objects.equals(name, that.name); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + + return Objects.hash(super.hashCode(), name); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorService.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorService.java new file mode 100644 index 00000000000..1168571f863 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorService.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.authenticator; + +import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; + +/** + * Core interface for manipulating persisted authenticator + */ +public interface WebAuthnAuthenticatorService { + + /** + * Updates Authenticator counter + * + * @param credentialId credentialId + * @param counter counter + * @throws CredentialIdNotFoundException if the authenticator could not be found + */ + @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") + void updateCounter(byte[] credentialId, long counter) throws CredentialIdNotFoundException; + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java new file mode 100644 index 00000000000..b2fc7590571 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.challenge; + +import com.webauthn4j.data.client.challenge.Challenge; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +/** + * An API to allow changing the method in which the expected {@link Challenge} is + * associated to the {@link HttpServletRequest}. For example, it may be stored in + * {@link HttpSession}. + * + * @see HttpSessionChallengeRepository + */ +public interface ChallengeRepository { + + /** + * Generates a {@link Challenge} + * + * @return the {@link Challenge} that was generated. Cannot be null. + */ + Challenge generateChallenge(); + + /** + * Saves the {@link Challenge} using the {@link HttpServletRequest} and + * {@link HttpServletResponse}. If the {@link Challenge} is null, it is the same as + * deleting it. + * + * @param challenge the {@link Challenge} to save or null to delete + * @param request the {@link HttpServletRequest} to use + */ + void saveChallenge(Challenge challenge, HttpServletRequest request); + + /** + * Loads the expected {@link Challenge} from the {@link HttpServletRequest} + * + * @param request the {@link HttpServletRequest} to use + * @return the {@link Challenge} or null if none exists + */ + Challenge loadChallenge(HttpServletRequest request); + + /** + * Loads or generates {@link Challenge} from the {@link HttpServletRequest} + * + * @param request the {@link HttpServletRequest} to use + * @return the {@link Challenge} or null if none exists + */ + default Challenge loadOrGenerateChallenge(HttpServletRequest request) { + Challenge challenge = this.loadChallenge(request); + if (challenge == null) { + challenge = this.generateChallenge(); + this.saveChallenge(challenge, request); + } + return challenge; + } + +} + diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java new file mode 100644 index 00000000000..29cf31afe52 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java @@ -0,0 +1,87 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.challenge; + +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +/** + * A {@link ChallengeRepository} implementation that stores data to HTTP session + *

+ * Class design is based on {@link HttpSessionCsrfTokenRepository} + * + * @author Yoshikazu Nojima + */ +public class HttpSessionChallengeRepository implements ChallengeRepository { + + // ~ Static fields/initializers + // ===================================================================================== + + private static final String DEFAULT_CHALLENGE_ATTR_NAME = HttpSessionChallengeRepository.class + .getName().concat(".CHALLENGE"); + + //~ Instance fields + // ================================================================================================ + private String sessionAttributeName = DEFAULT_CHALLENGE_ATTR_NAME; + + // ~ Methods + // ======================================================================================================== + + @Override + public Challenge generateChallenge() { + return new DefaultChallenge(); + } + + @Override + public void saveChallenge(Challenge challenge, HttpServletRequest request) { + if (challenge == null) { + HttpSession session = request.getSession(false); + if (session != null) { + session.removeAttribute(this.sessionAttributeName); + } + } else { + HttpSession session = request.getSession(); + session.setAttribute(this.sessionAttributeName, challenge); + } + } + + @Override + public Challenge loadChallenge(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session == null) { + return null; + } + return (Challenge) session.getAttribute(this.sessionAttributeName); + } + + /** + * Sets the {@link HttpSession} attribute name that the {@link Challenge} is stored in + * + * @param sessionAttributeName the new attribute name to use + */ + public void setSessionAttributeName(String sessionAttributeName) { + Assert.hasLength(sessionAttributeName, + "sessionAttributename cannot be null or empty"); + this.sessionAttributeName = sessionAttributeName; + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java new file mode 100644 index 00000000000..015adbc748a --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.config.configurers; + +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; +import org.springframework.security.webauthn.WebAuthnAuthenticationProvider; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.util.Assert; + +/** + * Allows configuring a {@link WebAuthnAuthenticationProvider} + * + * @see WebAuthnConfigurer + * @see WebAuthnLoginConfigurer + */ +public class WebAuthnAuthenticationProviderConfigurer< + B extends ProviderManagerBuilder, + U extends WebAuthnUserDetailsService, + A extends WebAuthnAuthenticatorService, + V extends WebAuthnAuthenticationContextValidator> + extends SecurityConfigurerAdapter { + + //~ Instance fields + // ================================================================================================ + private U userDetailsService; + private A authenticatorService; + private V authenticationContextValidator; + + /** + * Constructor + * + * @param userDetailsService {@link WebAuthnUserDetailsService} + * @param authenticatorService {@link WebAuthnAuthenticatorService} + * @param authenticationContextValidator {@link WebAuthnAuthenticationContextValidator} + */ + public WebAuthnAuthenticationProviderConfigurer(U userDetailsService, A authenticatorService, V authenticationContextValidator) { + + Assert.notNull(userDetailsService, "userDetailsService must not be null"); + Assert.notNull(authenticatorService, "authenticatorService must not be null"); + Assert.notNull(authenticationContextValidator, "authenticationContextValidator must not be null"); + + this.userDetailsService = userDetailsService; + this.authenticatorService = authenticatorService; + this.authenticationContextValidator = authenticationContextValidator; + } + + // ~ Methods + // ======================================================================================================== + + @Override + public void configure(B builder) { + WebAuthnAuthenticationProvider authenticationProvider = + new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, authenticationContextValidator); + authenticationProvider = postProcess(authenticationProvider); + builder.authenticationProvider(authenticationProvider); + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java new file mode 100644 index 00000000000..1b83c622b90 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java @@ -0,0 +1,123 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.config.configurers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.springframework.context.ApplicationContext; +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.challenge.HttpSessionChallengeRepository; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.options.OptionsProviderImpl; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +/** + * Internal utility for WebAuthn Configurers + */ +public class WebAuthnConfigurerUtil { + + private WebAuthnConfigurerUtil() { + } + + static > ChallengeRepository getOrCreateChallengeRepository(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + ChallengeRepository challengeRepository; + String[] beanNames = applicationContext.getBeanNamesForType(ChallengeRepository.class); + if (beanNames.length == 0) { + challengeRepository = new HttpSessionChallengeRepository(); + } else { + challengeRepository = applicationContext.getBean(ChallengeRepository.class); + } + return challengeRepository; + } + + public static > OptionsProvider getOrCreateOptionsProvider(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + OptionsProvider optionsProvider; + String[] beanNames = applicationContext.getBeanNamesForType(OptionsProvider.class); + if (beanNames.length == 0) { + WebAuthnUserDetailsService userDetailsService = getWebAuthnUserDetailsService(http); + ChallengeRepository challengeRepository = getOrCreateChallengeRepository(http); + optionsProvider = new OptionsProviderImpl(userDetailsService, challengeRepository); + } else { + optionsProvider = applicationContext.getBean(OptionsProvider.class); + } + return optionsProvider; + } + + + public static > JsonConverter getOrCreateJsonConverter(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + JsonConverter jsonConverter; + String[] beanNames = applicationContext.getBeanNamesForType(JsonConverter.class); + if (beanNames.length == 0) { + ObjectMapper jsonMapper = new ObjectMapper(); + ObjectMapper cborMapper = new ObjectMapper(new CBORFactory()); + jsonConverter = new JsonConverter(jsonMapper, cborMapper); + } else { + jsonConverter = applicationContext.getBean(JsonConverter.class); + } + return jsonConverter; + } + + public static > ServerPropertyProvider getOrCreateServerPropertyProvider(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + ServerPropertyProvider serverPropertyProvider; + String[] beanNames = applicationContext.getBeanNamesForType(ServerPropertyProvider.class); + if (beanNames.length == 0) { + serverPropertyProvider = new ServerPropertyProviderImpl(getOrCreateOptionsProvider(http), getOrCreateChallengeRepository(http)); + } else { + serverPropertyProvider = applicationContext.getBean(ServerPropertyProvider.class); + } + return serverPropertyProvider; + } + + public static > WebAuthnUserDetailsService getWebAuthnUserDetailsService(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + return applicationContext.getBean(WebAuthnUserDetailsService.class); + } + + public static > WebAuthnRegistrationRequestValidator getOrCreateWebAuthnRegistrationRequestValidator(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnRegistrationRequestValidator.class); + if (beanNames.length == 0) { + webAuthnRegistrationRequestValidator = new WebAuthnRegistrationRequestValidator(getOrCreateWebAuthnRegistrationContextValidator(http), getOrCreateServerPropertyProvider(http)); + } else { + webAuthnRegistrationRequestValidator = applicationContext.getBean(WebAuthnRegistrationRequestValidator.class); + } + return webAuthnRegistrationRequestValidator; + } + + public static > WebAuthnRegistrationContextValidator getOrCreateWebAuthnRegistrationContextValidator(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnRegistrationContextValidator.class); + if (beanNames.length == 0) { + webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); + } else { + webAuthnRegistrationContextValidator = applicationContext.getBean(WebAuthnRegistrationContextValidator.class); + } + return webAuthnRegistrationContextValidator; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java new file mode 100644 index 00000000000..c247b5d0532 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java @@ -0,0 +1,855 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.config.configurers; + +import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.data.*; +import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; +import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; +import com.webauthn4j.data.extension.client.ExtensionClientInput; +import com.webauthn4j.data.extension.client.RegistrationExtensionClientInput; +import org.springframework.context.ApplicationContext; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.MFATokenEvaluator; +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; +import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; +import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler; +import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler; +import org.springframework.security.web.session.SessionManagementFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.webauthn.WebAuthnProcessingFilter; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.endpoint.AssertionOptionsEndpointFilter; +import org.springframework.security.webauthn.endpoint.AttestationOptionsEndpointFilter; +import org.springframework.security.webauthn.options.ExtensionOptionProvider; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.options.OptionsProviderImpl; +import org.springframework.security.webauthn.options.StaticExtensionOptionProvider; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Adds WebAuthn authentication. All attributes have reasonable defaults making all + * parameters are optional. If no {@link #loginPage(String)} is specified, a default login + * page will be generated by the framework. + * + *

Security Filters

+ *

+ * The following Filters are populated + * + *

    + *
  • {@link WebAuthnProcessingFilter}
  • + *
  • {@link AttestationOptionsEndpointFilter}
  • + *
  • {@link AssertionOptionsEndpointFilter}
  • + *
+ * + *

Shared Objects Created

+ *

+ * The following shared objects are populated + *

    + *
  • {@link ChallengeRepository}
  • + *
  • {@link OptionsProvider}
  • + *
  • {@link ServerPropertyProvider}
  • + *
+ * + *

Shared Objects Used

+ *

+ * The following shared objects are used: + * + *

    + *
  • {@link AuthenticationManager}
  • + *
  • {@link MFATokenEvaluator}
  • + *
+ * + * @see WebAuthnAuthenticationProviderConfigurer + */ +public final class WebAuthnLoginConfigurer> extends + AbstractAuthenticationFilterConfigurer, WebAuthnProcessingFilter> { + + private final AttestationOptionsEndpointConfig attestationOptionsEndpointConfig = new AttestationOptionsEndpointConfig(); + private final AssertionOptionsEndpointConfig assertionOptionsEndpointConfig = new AssertionOptionsEndpointConfig(); + private final PublicKeyCredParamsConfig publicKeyCredParamsConfig = new PublicKeyCredParamsConfig(); + private final AuthenticatorSelectionCriteriaConfig authenticatorSelectionConfig = new AuthenticatorSelectionCriteriaConfig(); + private final ExtensionsClientInputsConfig registrationExtensionsConfig + = new ExtensionsClientInputsConfig<>(); + private final ExtensionsClientInputsConfig authenticationExtensionsConfig + = new ExtensionsClientInputsConfig<>(); + private final ExpectedRegistrationExtensionIdsConfig + expectedRegistrationExtensionIdsConfig = new ExpectedRegistrationExtensionIdsConfig(); + private final ExpectedAuthenticationExtensionIdsConfig + expectedAuthenticationExtensionIdsConfig = new ExpectedAuthenticationExtensionIdsConfig(); + //~ Instance fields + // ================================================================================================ + private OptionsProvider optionsProvider = null; + private JsonConverter jsonConverter = null; + private ServerPropertyProvider serverPropertyProvider = null; + private WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator; + private String rpId = null; + private String rpName = null; + private String rpIcon = null; + private Long registrationTimeout = null; + private Long authenticationTimeout = null; + private AttestationConveyancePreference attestation = null; + private String usernameParameter = null; + private String passwordParameter = null; + private String credentialIdParameter = null; + private String clientDataJSONParameter = null; + private String authenticatorDataParameter = null; + private String signatureParameter = null; + private String clientExtensionsJSONParameter = null; + + + public WebAuthnLoginConfigurer() { + super(new WebAuthnProcessingFilter(), null); + } + + public static WebAuthnLoginConfigurer webAuthnLogin() { + return new WebAuthnLoginConfigurer<>(); + } + + // ~ Methods + // ======================================================================================================== + @Override + public void init(H http) throws Exception { + super.init(http); + + if (jsonConverter == null) { + jsonConverter = WebAuthnConfigurerUtil.getOrCreateJsonConverter(http); + } + http.setSharedObject(JsonConverter.class, jsonConverter); + + if (optionsProvider == null) { + optionsProvider = WebAuthnConfigurerUtil.getOrCreateOptionsProvider(http); + } + if (optionsProvider instanceof OptionsProviderImpl) { + OptionsProviderImpl optionsProviderImpl = (OptionsProviderImpl) optionsProvider; + configureOptionsProviderImpl(optionsProviderImpl); + optionsProvider = optionsProviderImpl; + } + http.setSharedObject(OptionsProvider.class, optionsProvider); + + if (serverPropertyProvider == null) { + serverPropertyProvider = WebAuthnConfigurerUtil.getOrCreateServerPropertyProvider(http); + } + http.setSharedObject(ServerPropertyProvider.class, serverPropertyProvider); + + if (webAuthnRegistrationRequestValidator == null) { + webAuthnRegistrationRequestValidator = WebAuthnConfigurerUtil.getOrCreateWebAuthnRegistrationRequestValidator(http); + } + http.setSharedObject(WebAuthnRegistrationRequestValidator.class, webAuthnRegistrationRequestValidator); + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(H http) throws Exception { + super.configure(http); + configureParameters(); + + this.getAuthenticationFilter().setServerPropertyProvider(serverPropertyProvider); + + this.attestationOptionsEndpointConfig.configure(http); + this.assertionOptionsEndpointConfig.configure(http); + + this.getAuthenticationFilter().setExpectedAuthenticationExtensionIds(expectedAuthenticationExtensionIdsConfig.expectedAuthenticationExtensionIds); + webAuthnRegistrationRequestValidator.setExpectedRegistrationExtensionIds(expectedRegistrationExtensionIdsConfig.expectedRegistrationExtensionIds); + } + + private void configureOptionsProviderImpl(OptionsProviderImpl optionsProviderImpl) { + if (rpId != null) { + optionsProviderImpl.setRpId(rpId); + } + if (rpName != null) { + optionsProviderImpl.setRpName(rpName); + } + if (rpIcon != null) { + optionsProviderImpl.setRpIcon(rpIcon); + } + optionsProviderImpl.getPubKeyCredParams().addAll(publicKeyCredParamsConfig.publicKeyCredentialParameters); + if (registrationTimeout != null) { + optionsProviderImpl.setRegistrationTimeout(registrationTimeout); + } + if (authenticationTimeout != null) { + optionsProviderImpl.setAuthenticationTimeout(authenticationTimeout); + } + AuthenticatorSelectionCriteria authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria( + this.authenticatorSelectionConfig.authenticatorAttachment, + this.authenticatorSelectionConfig.requireResidentKey, + this.authenticatorSelectionConfig.userVerification); + optionsProviderImpl.setAuthenticatorSelection(authenticatorSelectionCriteria); + optionsProviderImpl.setAttestation(this.attestation); + optionsProviderImpl.getRegistrationExtensions().putAll(registrationExtensionsConfig.extensionsClientInputs); + optionsProviderImpl.getAuthenticationExtensions().putAll(authenticationExtensionsConfig.extensionsClientInputs); + + } + + private void configureParameters() { + if (usernameParameter != null) { + this.getAuthenticationFilter().setUsernameParameter(usernameParameter); + } + if (passwordParameter != null) { + this.getAuthenticationFilter().setPasswordParameter(passwordParameter); + } + if (credentialIdParameter != null) { + this.getAuthenticationFilter().setCredentialIdParameter(credentialIdParameter); + } + if (clientDataJSONParameter != null) { + this.getAuthenticationFilter().setClientDataJSONParameter(clientDataJSONParameter); + } + if (authenticatorDataParameter != null) { + this.getAuthenticationFilter().setAuthenticatorDataParameter(authenticatorDataParameter); + } + if (signatureParameter != null) { + this.getAuthenticationFilter().setSignatureParameter(signatureParameter); + } + if (clientExtensionsJSONParameter != null) { + this.getAuthenticationFilter().setClientExtensionsJSONParameter(clientExtensionsJSONParameter); + } + } + + /** + * Specifies the {@link OptionsProvider} to be used. + * + * @param optionsProvider the {@link OptionsProvider} + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer optionsProvider(OptionsProvider optionsProvider) { + Assert.notNull(optionsProvider, "optionsProvider must not be null"); + this.optionsProvider = optionsProvider; + return this; + } + + /** + * Specifies the {@link JsonConverter} to be used. + * + * @param jsonConverter the {@link JsonConverter} + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer jsonConverter(JsonConverter jsonConverter) { + Assert.notNull(jsonConverter, "jsonConverter must not be null"); + this.jsonConverter = jsonConverter; + return this; + } + + /** + * Specifies the {@link ServerPropertyProvider} to be used. + * + * @param serverPropertyProvider the {@link ServerPropertyProvider} + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer serverPropertyProvider(ServerPropertyProvider serverPropertyProvider) { + Assert.notNull(serverPropertyProvider, "serverPropertyProvider must not be null"); + this.serverPropertyProvider = serverPropertyProvider; + return this; + } + + + /** + * Returns the {@link AttestationOptionsEndpointConfig} for configuring the {@link AttestationOptionsEndpointFilter} + * + * @return the {@link AttestationOptionsEndpointConfig} + */ + public AttestationOptionsEndpointConfig attestationOptionsEndpoint() { + return attestationOptionsEndpointConfig; + } + + /** + * Returns the {@link AssertionOptionsEndpointConfig} for configuring the {@link AssertionOptionsEndpointFilter} + * + * @return the {@link AssertionOptionsEndpointConfig} + */ + public AssertionOptionsEndpointConfig assertionOptionsEndpoint() { + return assertionOptionsEndpointConfig; + } + + /** + * The HTTP parameter to look for the username when performing authentication. Default + * is "username". + * + * @param usernameParameter the HTTP parameter to look for the username when + * performing authentication + * @return the {@link FormLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer usernameParameter(String usernameParameter) { + Assert.hasText(usernameParameter, "usernameParameter must not be null or empty"); + this.usernameParameter = usernameParameter; + return this; + } + + /** + * The HTTP parameter to look for the password when performing authentication. Default + * is "password". + * + * @param passwordParameter the HTTP parameter to look for the password when + * performing authentication + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer passwordParameter(String passwordParameter) { + Assert.hasText(usernameParameter, "passwordParameter must not be null or empty"); + this.passwordParameter = passwordParameter; + return this; + } + + /** + * The HTTP parameter to look for the credentialId when performing authentication. Default + * is "credentialId". + * + * @param credentialIdParameter the HTTP parameter to look for the credentialId when + * performing authentication + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer credentialIdParameter(String credentialIdParameter) { + Assert.hasText(usernameParameter, "credentialIdParameter must not be null or empty"); + this.credentialIdParameter = credentialIdParameter; + return this; + } + + /** + * The HTTP parameter to look for the clientData when performing authentication. Default + * is "clientDataJSON". + * + * @param clientDataJSONParameter the HTTP parameter to look for the clientDataJSON when + * performing authentication + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer clientDataJSONParameter(String clientDataJSONParameter) { + Assert.hasText(usernameParameter, "clientDataJSONParameter must not be null or empty"); + this.clientDataJSONParameter = clientDataJSONParameter; + return this; + } + + /** + * The HTTP parameter to look for the authenticatorData when performing authentication. Default + * is "authenticatorData". + * + * @param authenticatorDataParameter the HTTP parameter to look for the authenticatorData when + * performing authentication + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer authenticatorDataParameter(String authenticatorDataParameter) { + Assert.hasText(usernameParameter, "authenticatorDataParameter must not be null or empty"); + this.authenticatorDataParameter = authenticatorDataParameter; + return this; + } + + /** + * The HTTP parameter to look for the signature when performing authentication. Default + * is "signature". + * + * @param signatureParameter the HTTP parameter to look for the signature when + * performing authentication + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer signatureParameter(String signatureParameter) { + Assert.hasText(usernameParameter, "signatureParameter must not be null or empty"); + this.signatureParameter = signatureParameter; + return this; + } + + /** + * The HTTP parameter to look for the clientExtensionsJSON when performing authentication. Default + * is "clientExtensionsJSON". + * + * @param clientExtensionsJSONParameter the HTTP parameter to look for the clientExtensionsJSON when + * performing authentication + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer clientExtensionsJSONParameter(String clientExtensionsJSONParameter) { + Assert.hasText(clientExtensionsJSONParameter, "clientExtensionsJSONParameter must not be null or empty"); + this.clientExtensionsJSONParameter = clientExtensionsJSONParameter; + return this; + } + + /** + * The relying party id for credential scoping + * + * @param rpId the relying party id + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer rpId(String rpId) { + Assert.hasText(rpId, "rpId parameter must not be null or empty"); + this.rpId = rpId; + return this; + } + + /** + * The relying party name + * + * @param rpName the relying party name + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer rpName(String rpName) { + Assert.hasText(rpName, "rpName parameter must not be null or empty"); + this.rpName = rpName; + return this; + } + + /** + * The relying party icon + * + * @param rpIcon the relying party icon + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer rpIcon(String rpIcon) { + Assert.hasText(rpIcon, "rpIcon parameter must not be null or empty"); + this.rpIcon = rpIcon; + return this; + } + + /** + * Returns the {@link PublicKeyCredParamsConfig} for configuring PublicKeyCredParams + * + * @return the {@link PublicKeyCredParamsConfig} + */ + public WebAuthnLoginConfigurer.PublicKeyCredParamsConfig publicKeyCredParams() { + return this.publicKeyCredParamsConfig; + } + + /** + * The timeout for registration ceremony + * + * @param registrationTimeout the timeout for registration ceremony + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer registrationTimeout(Long registrationTimeout) { + this.registrationTimeout = registrationTimeout; + return this; + } + + /** + * The timeout for authentication ceremony + * + * @param authenticationTimeout the timeout for authentication ceremony + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer authenticationTimeout(Long authenticationTimeout) { + this.authenticationTimeout = authenticationTimeout; + return this; + } + + /** + * Returns the {@link AuthenticatorSelectionCriteriaConfig} for configuring authenticator selection criteria + * + * @return the {@link AuthenticatorSelectionCriteriaConfig} + */ + public AuthenticatorSelectionCriteriaConfig authenticatorSelection() { + return this.authenticatorSelectionConfig; + } + + /** + * The attestation conveyance preference + * + * @param attestation the attestation conveyance preference + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer attestation(AttestationConveyancePreference attestation) { + this.attestation = attestation; + return this; + } + + /** + * Returns the {@link ExtensionsClientInputsConfig} for configuring registration extensions + * + * @return the {@link ExtensionsClientInputsConfig} + */ + public ExtensionsClientInputsConfig registrationExtensions() { + return this.registrationExtensionsConfig; + } + + /** + * Returns the {@link ExtensionsClientInputsConfig} for configuring authentication extensions + * + * @return the {@link ExtensionsClientInputsConfig} + */ + public ExtensionsClientInputsConfig authenticationExtensions() { + return this.authenticationExtensionsConfig; + } + + /** + * Returns the {@link ExpectedRegistrationExtensionIdsConfig} for configuring the expectedRegistrationExtensionId(s) + * + * @return the {@link ExpectedRegistrationExtensionIdsConfig} + */ + public ExpectedRegistrationExtensionIdsConfig expectedRegistrationExtensionIdsConfig() { + return this.expectedRegistrationExtensionIdsConfig; + } + + /** + * Returns the {@link ExpectedAuthenticationExtensionIdsConfig} for configuring the expectedAuthenticationExtensionId(s) + * + * @return the {@link ExpectedAuthenticationExtensionIdsConfig} + */ + public ExpectedAuthenticationExtensionIdsConfig expectedAuthenticationExtensionIds() { + return this.expectedAuthenticationExtensionIdsConfig; + } + + + /** + * Forward Authentication Success Handler + * + * @param forwardUrl the target URL in case of success + * @return he {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer successForwardUrl(String forwardUrl) { + successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl)); + return this; + } + + /** + * Forward Authentication Failure Handler + * + * @param forwardUrl the target URL in case of failure + * @return he {@link WebAuthnLoginConfigurer} for additional customization + */ + public WebAuthnLoginConfigurer failureForwardUrl(String forwardUrl) { + failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl)); + return this; + } + + /** + *

+ * Specifies the URL to send users to if login is required. If used with + * {@link WebSecurityConfigurerAdapter} a default login page will be generated when + * this attribute is not specified. + *

+ * + * @param loginPage login page + * @return the {@link WebAuthnLoginConfigurer} for additional customization + */ + @Override + public WebAuthnLoginConfigurer loginPage(String loginPage) { + return super.loginPage(loginPage); + } + + /** + * Create the {@link RequestMatcher} given a loginProcessingUrl + * + * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the + * loginProcessingUrl + * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl + */ + @Override + protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) { + return new AntPathRequestMatcher(loginProcessingUrl, "POST"); + } + + /** + * Configuration options for the {@link AttestationOptionsEndpointFilter} + */ + public class AttestationOptionsEndpointConfig { + + private String processingUrl = AttestationOptionsEndpointFilter.FILTER_URL; + + private AttestationOptionsEndpointConfig() { + } + + private void configure(H http) { + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter; + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + String[] beanNames = applicationContext.getBeanNamesForType(AttestationOptionsEndpointFilter.class); + if (beanNames.length == 0) { + attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); + attestationOptionsEndpointFilter.setFilterProcessesUrl(processingUrl); + } else { + attestationOptionsEndpointFilter = applicationContext.getBean(AttestationOptionsEndpointFilter.class); + } + + http.addFilterAfter(attestationOptionsEndpointFilter, SessionManagementFilter.class); + + } + + /** + * Sets the URL for the options endpoint + * + * @param processingUrl the URL for the options endpoint + * @return the {@link AttestationOptionsEndpointConfig} for additional customization + */ + public AttestationOptionsEndpointConfig processingUrl(String processingUrl) { + this.processingUrl = processingUrl; + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link WebAuthnLoginConfigurer} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + + } + + /** + * Configuration options for the {@link AssertionOptionsEndpointFilter} + */ + public class AssertionOptionsEndpointConfig { + + private String processingUrl = AssertionOptionsEndpointFilter.FILTER_URL; + + private AssertionOptionsEndpointConfig() { + } + + private void configure(H http) { + AssertionOptionsEndpointFilter assertionOptionsEndpointFilter; + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + String[] beanNames = applicationContext.getBeanNamesForType(AttestationOptionsEndpointFilter.class); + if (beanNames.length == 0) { + assertionOptionsEndpointFilter = new AssertionOptionsEndpointFilter(optionsProvider, jsonConverter); + assertionOptionsEndpointFilter.setFilterProcessesUrl(processingUrl); + } else { + assertionOptionsEndpointFilter = applicationContext.getBean(AssertionOptionsEndpointFilter.class); + } + + http.addFilterAfter(assertionOptionsEndpointFilter, SessionManagementFilter.class); + + } + + /** + * Sets the URL for the options endpoint + * + * @param processingUrl the URL for the options endpoint + * @return the {@link AttestationOptionsEndpointConfig} for additional customization + */ + public AssertionOptionsEndpointConfig processingUrl(String processingUrl) { + this.processingUrl = processingUrl; + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link WebAuthnLoginConfigurer} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + + } + + + /** + * Configuration options for PublicKeyCredParams + */ + public class PublicKeyCredParamsConfig { + + private List publicKeyCredentialParameters = new ArrayList<>(); + + private PublicKeyCredParamsConfig() { + } + + /** + * Add PublicKeyCredParam + * + * @param type the {@link PublicKeyCredentialType} + * @param alg the {@link COSEAlgorithmIdentifier} + * @return the {@link PublicKeyCredParamsConfig} + */ + public PublicKeyCredParamsConfig addPublicKeyCredParams(PublicKeyCredentialType type, COSEAlgorithmIdentifier alg) { + Assert.notNull(type, "type must not be null"); + Assert.notNull(alg, "alg must not be null"); + + publicKeyCredentialParameters.add(new PublicKeyCredentialParameters(type, alg)); + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link WebAuthnLoginConfigurer} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + + } + + public class AuthenticatorSelectionCriteriaConfig { + private AuthenticatorAttachment authenticatorAttachment; + private boolean requireResidentKey = false; + private UserVerificationRequirement userVerification = UserVerificationRequirement.PREFERRED; + + /** + * Sets the authenticator attachment preference + * + * @param authenticatorAttachment the authenticator attachment + * @return the {@link AuthenticatorSelectionCriteriaConfig} for additional customization + */ + public WebAuthnLoginConfigurer.AuthenticatorSelectionCriteriaConfig authenticatorAttachment(AuthenticatorAttachment authenticatorAttachment) { + this.authenticatorAttachment = authenticatorAttachment; + return this; + } + + /** + * Sets the residentKey requirement preference + * + * @param requireResidentKey true if requires a resident key + * @return the {@link AuthenticatorSelectionCriteriaConfig} for additional customization + */ + public WebAuthnLoginConfigurer.AuthenticatorSelectionCriteriaConfig requireResidentKey(boolean requireResidentKey) { + this.requireResidentKey = requireResidentKey; + return this; + } + + /** + * Sets the user verification requirement preference + * + * @param userVerification the user verification preference + * @return the {@link AuthenticatorSelectionCriteriaConfig} for additional customization + */ + public WebAuthnLoginConfigurer.AuthenticatorSelectionCriteriaConfig userVerification(UserVerificationRequirement userVerification) { + this.userVerification = userVerification; + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link WebAuthnLoginConfigurer} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + } + + /** + * Configuration options for AuthenticationExtensionsClientInputs + */ + public class ExtensionsClientInputsConfig { + + private Map> extensionsClientInputs = new HashMap<>(); + + private ExtensionsClientInputsConfig() { + } + + /** + * Put ExtensionOption + * + * @param extensionOption the T + * @return the {@link ExtensionsClientInputsConfig} + */ + public ExtensionsClientInputsConfig put(T extensionOption) { + Assert.notNull(extensionOption, "extensionOption must not be null"); + StaticExtensionOptionProvider extensionOptionProvider = new StaticExtensionOptionProvider<>(extensionOption); + extensionsClientInputs.put(extensionOptionProvider.getIdentifier(), extensionOptionProvider); + return this; + } + + /** + * Put ExtensionOptionProvider + * + * @param extensionOptionProvider the T + * @return the {@link ExtensionsClientInputsConfig} + */ + public ExtensionsClientInputsConfig put(ExtensionOptionProvider extensionOptionProvider) { + Assert.notNull(extensionOptionProvider, "extensionOptionProvider must not be null"); + extensionsClientInputs.put(extensionOptionProvider.getIdentifier(), extensionOptionProvider); + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link WebAuthnLoginConfigurer} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + } + + /** + * Configuration options for expectedRegistrationExtensionIds + */ + public class ExpectedRegistrationExtensionIdsConfig { + + private List expectedRegistrationExtensionIds = null; + + private ExpectedRegistrationExtensionIdsConfig() { + } + + /** + * Add AuthenticationExtensionClientInput + * + * @param expectedRegistrationExtensionId the expected registration extension id + * @return the {@link ExpectedRegistrationExtensionIdsConfig} + */ + public ExpectedRegistrationExtensionIdsConfig add(String expectedRegistrationExtensionId) { + Assert.notNull(expectedRegistrationExtensionId, "expectedRegistrationExtensionId must not be null"); + if (expectedRegistrationExtensionIds == null) { + expectedRegistrationExtensionIds = new ArrayList<>(); + } + expectedRegistrationExtensionIds.add(expectedRegistrationExtensionId); + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link ExpectedRegistrationExtensionIdsConfig} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + } + + /** + * Configuration options for expectedRegistrationExtensionIds + */ + public class ExpectedAuthenticationExtensionIdsConfig { + + private List expectedAuthenticationExtensionIds = null; + + private ExpectedAuthenticationExtensionIdsConfig() { + } + + /** + * Add AuthenticationExtensionClientInput + * + * @param expectedAuthenticationExtensionId the expected authentication extension id + * @return the {@link ExpectedAuthenticationExtensionIdsConfig} + */ + public ExpectedAuthenticationExtensionIdsConfig add(String expectedAuthenticationExtensionId) { + Assert.notNull(expectedAuthenticationExtensionId, "expectedAuthenticationExtensionId must not be null"); + if (expectedAuthenticationExtensionIds == null) { + expectedAuthenticationExtensionIds = new ArrayList<>(); + } + expectedAuthenticationExtensionIds.add(expectedAuthenticationExtensionId); + return this; + } + + /** + * Returns the {@link WebAuthnLoginConfigurer} for further configuration. + * + * @return the {@link WebAuthnLoginConfigurer} + */ + public WebAuthnLoginConfigurer and() { + return WebAuthnLoginConfigurer.this; + } + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java new file mode 100644 index 00000000000..394f318ed01 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.util.Base64UrlUtil; +import org.springframework.security.webauthn.options.AssertionOptions; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A filter for providing WebAuthn option parameters to clients. + * Clients can retrieve {@link AssertionOptions}. + * + * @author Yoshikazu Nojima + */ +public class AssertionOptionsEndpointFilter extends OptionsEndpointFilterBase { + + // ~ Static fields/initializers + // ===================================================================================== + + /** + * Default name of path suffix which will validate this filter. + */ + public static final String FILTER_URL = "/webauthn/assertion/options"; + + + //~ Instance fields + // ================================================================================================ + + protected OptionsProvider optionsProvider; + + + // ~ Constructors + // =================================================================================================== + + public AssertionOptionsEndpointFilter(OptionsProvider optionsProvider, JsonConverter jsonConverter) { + super(jsonConverter); + this.optionsProvider = optionsProvider; + this.filterProcessesUrl = FILTER_URL; + checkConfig(); + } + + + // ~ Methods + // ======================================================================================================== + + @Override + public void checkConfig() { + Assert.notNull(optionsProvider, "optionsProvider must not be null"); + super.checkConfig(); + } + + @Override + protected Serializable processRequest(HttpServletRequest request) { + String loginUsername = getLoginUsername(); + AssertionOptions assertionOptions = optionsProvider.getAssertionOptions(request, loginUsername, null); + List credentials = assertionOptions.getAllowCredentials() == null ? null : + assertionOptions.getAllowCredentials().stream() + .map(credential -> new WebAuthnPublicKeyCredentialDescriptor(credential.getType(), Base64UrlUtil.encodeToString(credential.getId()), credential.getTransports())) + .collect(Collectors.toList()); + + return new AssertionOptionsResponse( + assertionOptions.getChallenge(), + assertionOptions.getTimeout(), + assertionOptions.getRpId(), + credentials, + assertionOptions.getExtensions() + ); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java new file mode 100644 index 00000000000..1827934e7de --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import com.webauthn4j.util.CollectionUtil; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Options for WebAuthn assertion generation + */ +public class AssertionOptionsResponse implements Serializable { + + // ~ Instance fields + // ================================================================================================ + + private Challenge challenge; + private Long timeout; + private String rpId; + private List allowCredentials; + private AuthenticationExtensionsClientInputs extensions; + + // ~ Constructors + // =================================================================================================== + + public AssertionOptionsResponse( + Challenge challenge, + Long timeout, + String rpId, + List allowCredentials, + AuthenticationExtensionsClientInputs extensions) { + this.challenge = challenge; + this.timeout = timeout; + this.rpId = rpId; + this.allowCredentials = CollectionUtil.unmodifiableList(allowCredentials); + this.extensions = extensions; + } + + // ~ Methods + // ======================================================================================================== + + public Challenge getChallenge() { + return challenge; + } + + public Long getTimeout() { + return timeout; + } + + public String getRpId() { + return rpId; + } + + public List getAllowCredentials() { + return allowCredentials; + } + + public AuthenticationExtensionsClientInputs getExtensions() { + return extensions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AssertionOptionsResponse that = (AssertionOptionsResponse) o; + return Objects.equals(challenge, that.challenge) && + Objects.equals(timeout, that.timeout) && + Objects.equals(rpId, that.rpId) && + Objects.equals(allowCredentials, that.allowCredentials) && + Objects.equals(extensions, that.extensions); + } + + @Override + public int hashCode() { + + return Objects.hash(challenge, timeout, rpId, allowCredentials, extensions); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java new file mode 100644 index 00000000000..5e922579a75 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.data.PublicKeyCredentialUserEntity; +import com.webauthn4j.util.Base64UrlUtil; +import org.springframework.security.webauthn.options.AttestationOptions; +import org.springframework.security.webauthn.options.OptionsProvider; + +import javax.servlet.http.HttpServletRequest; +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A filter for providing WebAuthn option parameters to clients. + * Clients can retrieve {@link AttestationOptions}. + * + * @author Yoshikazu Nojima + */ +public class AttestationOptionsEndpointFilter extends OptionsEndpointFilterBase { + + // ~ Static fields/initializers + // ===================================================================================== + + /** + * Default name of path suffix which will validate this filter. + */ + public static final String FILTER_URL = "/webauthn/attestation/options"; + + //~ Instance fields + // ================================================================================================ + + protected OptionsProvider optionsProvider; + + // ~ Constructors + // =================================================================================================== + + public AttestationOptionsEndpointFilter(OptionsProvider optionsProvider, JsonConverter jsonConverter) { + super(jsonConverter); + this.optionsProvider = optionsProvider; + this.filterProcessesUrl = FILTER_URL; + checkConfig(); + } + + + // ~ Methods + // ======================================================================================================== + + protected Serializable processRequest(HttpServletRequest request) { + String loginUsername = getLoginUsername(); + AttestationOptions attestationOptions = optionsProvider.getAttestationOptions(request, loginUsername, null); + + PublicKeyCredentialUserEntity userEntity = attestationOptions.getUser(); + WebAuthnPublicKeyCredentialUserEntity user = userEntity == null ? null : new WebAuthnPublicKeyCredentialUserEntity( + Base64UrlUtil.encodeToString(userEntity.getId()), + userEntity.getName(), + userEntity.getDisplayName(), + userEntity.getIcon() + ); + + List credentials = attestationOptions.getExcludeCredentials() == null ? null : + attestationOptions.getExcludeCredentials().stream() + .map(credential -> new WebAuthnPublicKeyCredentialDescriptor(credential.getType(), Base64UrlUtil.encodeToString(credential.getId()), credential.getTransports())) + .collect(Collectors.toList()); + + return new AttestationOptionsResponse( + attestationOptions.getRp(), + user, + attestationOptions.getChallenge(), + attestationOptions.getPubKeyCredParams(), + attestationOptions.getTimeout(), + credentials, + attestationOptions.getAuthenticatorSelection(), + attestationOptions.getAttestation(), + attestationOptions.getExtensions() + ); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java new file mode 100644 index 00000000000..3ce15557cc5 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + + +import com.webauthn4j.data.*; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import com.webauthn4j.util.CollectionUtil; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Options for WebAuthn attestation generation + */ +@SuppressWarnings("common-java:DuplicatedBlocks") +public class AttestationOptionsResponse implements Serializable { + + // ~ Instance fields + // ================================================================================================ + + private PublicKeyCredentialRpEntity rp; + private WebAuthnPublicKeyCredentialUserEntity user; + private Challenge challenge; + private List pubKeyCredParams; + private Long timeout; + private List excludeCredentials; + private AuthenticatorSelectionCriteria authenticatorSelection; + private AttestationConveyancePreference attestation; + private AuthenticationExtensionsClientInputs extensions; + + // ~ Constructors + // =================================================================================================== + + public AttestationOptionsResponse( + PublicKeyCredentialRpEntity rp, + WebAuthnPublicKeyCredentialUserEntity user, + Challenge challenge, + List pubKeyCredParams, + Long timeout, + List excludeCredentials, + AuthenticatorSelectionCriteria authenticatorSelection, + AttestationConveyancePreference attestation, + AuthenticationExtensionsClientInputs extensions) { + this.rp = rp; + this.user = user; + this.challenge = challenge; + this.pubKeyCredParams = CollectionUtil.unmodifiableList(pubKeyCredParams); + this.timeout = timeout; + this.excludeCredentials = CollectionUtil.unmodifiableList(excludeCredentials); + this.authenticatorSelection = authenticatorSelection; + this.attestation = attestation; + this.extensions = extensions; + } + + /** + * Returns PublicKeyCredentialRpEntity + * + * @return PublicKeyCredentialRpEntity + */ + public PublicKeyCredentialRpEntity getRp() { + return rp; + } + + /** + * If authenticated, returns {@link WebAuthnPublicKeyCredentialUserEntity}, which is a serialized form of {@link PublicKeyCredentialUserEntity} + * Otherwise returns null + * + * @return {@link WebAuthnPublicKeyCredentialUserEntity} + */ + public WebAuthnPublicKeyCredentialUserEntity getUser() { + return user; + } + + /** + * Returns {@link Challenge} + * + * @return {@link Challenge} + */ + public Challenge getChallenge() { + return challenge; + } + + public List getPubKeyCredParams() { + return pubKeyCredParams; + } + + public Long getTimeout() { + return timeout; + } + + public List getExcludeCredentials() { + return excludeCredentials; + } + + public AuthenticatorSelectionCriteria getAuthenticatorSelection() { + return authenticatorSelection; + } + + public AttestationConveyancePreference getAttestation() { + return attestation; + } + + public AuthenticationExtensionsClientInputs getExtensions() { + return extensions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AttestationOptionsResponse that = (AttestationOptionsResponse) o; + return Objects.equals(rp, that.rp) && + Objects.equals(user, that.user) && + Objects.equals(challenge, that.challenge) && + Objects.equals(pubKeyCredParams, that.pubKeyCredParams) && + Objects.equals(timeout, that.timeout) && + Objects.equals(excludeCredentials, that.excludeCredentials) && + Objects.equals(authenticatorSelection, that.authenticatorSelection) && + attestation == that.attestation && + Objects.equals(extensions, that.extensions); + } + + @Override + public int hashCode() { + return Objects.hash(rp, user, challenge, pubKeyCredParams, timeout, excludeCredentials, authenticatorSelection, attestation, extensions); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java new file mode 100644 index 00000000000..01e904e76b8 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import java.util.Objects; + +/** + * Error response of {@link AttestationOptionsEndpointFilter} + * + * @author Yoshikazu Nojima + */ +public class ErrorResponse { + + // ~ Instance fields + // ================================================================================================ + + private String errorMessage; + + // ~ Constructor + // ======================================================================================================== + + public ErrorResponse(String errorMessage) { + this.errorMessage = errorMessage; + } + + // ~ Methods + // ======================================================================================================== + + public String getErrorMessage() { + return errorMessage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ErrorResponse that = (ErrorResponse) o; + return Objects.equals(errorMessage, that.errorMessage); + } + + @Override + public int hashCode() { + return Objects.hash(errorMessage); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java new file mode 100644 index 00000000000..823be397756 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.webauthn4j.converter.util.JsonConverter; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.FilterInvocation; +import org.springframework.util.Assert; +import org.springframework.web.filter.GenericFilterBean; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Serializable; + +/** + * A filter for providing WebAuthn option parameters to clients. + * + * @author Yoshikazu Nojima + */ +public abstract class OptionsEndpointFilterBase extends GenericFilterBean { + + + //~ Instance fields + // ================================================================================================ + + /** + * Url this filter should get activated on. + */ + protected String filterProcessesUrl; + protected JsonConverter jsonConverter; + + private AuthenticationTrustResolver trustResolver; + private MFATokenEvaluator mfaTokenEvaluator; + + + // ~ Constructors + // =================================================================================================== + + public OptionsEndpointFilterBase(JsonConverter jsonConverter) { + this.jsonConverter = jsonConverter; + this.trustResolver = new AuthenticationTrustResolverImpl(); + this.mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + } + + // ~ Methods + // ======================================================================================================== + + @Override + public void afterPropertiesSet() { + checkConfig(); + } + + protected void checkConfig() { + Assert.notNull(filterProcessesUrl, "filterProcessesUrl must not be null"); + Assert.notNull(jsonConverter, "jsonConverter must not be null"); + Assert.notNull(trustResolver, "trustResolver must not be null"); + Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must not be null"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + FilterInvocation fi = new FilterInvocation(request, response, chain); + + if (!processFilter(fi.getRequest())) { + chain.doFilter(request, response); + return; + } + + try { + Serializable options = processRequest(fi.getRequest()); + writeResponse(fi.getResponse(), options); + } catch (RuntimeException e) { + logger.debug(e); + writeErrorResponse(fi.getResponse(), e); + } + + } + + protected abstract Serializable processRequest(HttpServletRequest request); + + public AuthenticationTrustResolver getTrustResolver() { + return trustResolver; + } + + public void setTrustResolver(AuthenticationTrustResolver trustResolver) { + this.trustResolver = trustResolver; + } + + public MFATokenEvaluator getMFATokenEvaluator() { + return mfaTokenEvaluator; + } + + public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { + this.mfaTokenEvaluator = mfaTokenEvaluator; + } + + + /** + * The filter will be used in case the URL of the request contains the FILTER_URL. + * + * @param request request used to determine whether to enable this filter + * @return true if this filter should be used + */ + private boolean processFilter(HttpServletRequest request) { + return (request.getRequestURI().contains(filterProcessesUrl)); + } + + void writeResponse(HttpServletResponse httpServletResponse, Serializable data) throws IOException { + String responseText = jsonConverter.writeValueAsString(data); + httpServletResponse.setContentType("application/json"); + httpServletResponse.getWriter().print(responseText); + } + + void writeErrorResponse(HttpServletResponse httpServletResponse, RuntimeException e) throws IOException { + ErrorResponse errorResponse; + int statusCode; + if (e instanceof InsufficientAuthenticationException) { + errorResponse = new ErrorResponse("Anonymous access is prohibited"); + statusCode = HttpServletResponse.SC_FORBIDDEN; + } else { + errorResponse = new ErrorResponse("The server encountered an internal error"); + statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + } + String errorResponseText = jsonConverter.writeValueAsString(errorResponse); + httpServletResponse.setContentType("application/json"); + httpServletResponse.getWriter().print(errorResponseText); + httpServletResponse.setStatus(statusCode); + } + + String getLoginUsername() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || (trustResolver.isAnonymous(authentication) && !mfaTokenEvaluator.isMultiFactorAuthentication(authentication))) { + return null; + } else { + return authentication.getName(); + } + } + + public void setFilterProcessesUrl(String filterProcessesUrl) { + this.filterProcessesUrl = filterProcessesUrl; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java new file mode 100644 index 00000000000..cbc9a0f5a47 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.webauthn4j.data.AuthenticatorTransport; +import com.webauthn4j.data.PublicKeyCredentialDescriptor; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.webauthn4j.util.CollectionUtil; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Set; + +/** + * JSON serialization friendly variant of {@link PublicKeyCredentialDescriptor} + * + * @author Yoshikazu Nojima + */ +public class WebAuthnPublicKeyCredentialDescriptor implements Serializable { + + // ~ Instance fields + // ================================================================================================ + + @JsonProperty + private PublicKeyCredentialType type; + @JsonProperty + private String id; + private Set transports; + + // ~ Constructor + // ======================================================================================================== + + @JsonCreator + public WebAuthnPublicKeyCredentialDescriptor( + @JsonProperty("type") PublicKeyCredentialType type, + @JsonProperty("id") String id, + @JsonProperty("transports") Set transports) { + this.type = type; + this.id = id; + this.transports = CollectionUtil.unmodifiableSet(transports); + } + + public WebAuthnPublicKeyCredentialDescriptor( + PublicKeyCredentialType type, + String id) { + this.type = type; + this.id = id; + } + + public WebAuthnPublicKeyCredentialDescriptor( + String id) { + this.type = PublicKeyCredentialType.PUBLIC_KEY; + this.id = id; + } + + + // ~ Methods + // ======================================================================================================== + + public PublicKeyCredentialType getType() { + return type; + } + + public String getId() { + return id; + } + + public Set getTransports() { + return transports; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnPublicKeyCredentialDescriptor that = (WebAuthnPublicKeyCredentialDescriptor) o; + return type == that.type && + Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(type, id); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java new file mode 100644 index 00000000000..0d8c6250e93 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.webauthn4j.data.PublicKeyCredentialEntity; +import com.webauthn4j.data.PublicKeyCredentialUserEntity; + +import java.util.Objects; + +/** + * JSON serialization friendly variant of {@link PublicKeyCredentialUserEntity} + * + * @author Yoshikazu Nojima + */ +public class WebAuthnPublicKeyCredentialUserEntity extends PublicKeyCredentialEntity { + + // ~ Instance fields + // ================================================================================================ + + private String id; + private String displayName; + + // ~ Constructor + // ======================================================================================================== + + @JsonCreator + public WebAuthnPublicKeyCredentialUserEntity( + @JsonProperty("id") String id, + @JsonProperty("name") String name, + @JsonProperty("displayName") String displayName, + @JsonProperty("icon") String icon) { + super(name, icon); + this.id = id; + this.displayName = displayName; + } + + // ~ Methods + // ======================================================================================================== + + public String getId() { + return id; + } + + public String getDisplayName() { + return displayName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + WebAuthnPublicKeyCredentialUserEntity that = (WebAuthnPublicKeyCredentialUserEntity) o; + return Objects.equals(id, that.id) && + Objects.equals(displayName, that.displayName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), id, displayName); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java new file mode 100644 index 00000000000..e4cd4318142 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad aaguid is specified + */ +public class BadAaguidException extends ValidationException { + + public BadAaguidException(String message, Throwable cause) { + super(message, cause); + } + + public BadAaguidException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java new file mode 100644 index 00000000000..89f792d5e51 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad algorithm is specified + * + * @author Yoshikazu Nojima + */ +public class BadAlgorithmException extends ValidationException { + + public BadAlgorithmException(String message, Throwable cause) { + super(message, cause); + } + + public BadAlgorithmException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java new file mode 100644 index 00000000000..e30f6c76e5f --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad attestation statement is specified + */ +public class BadAttestationStatementException extends ValidationException { + + public BadAttestationStatementException(String message, Throwable cause) { + super(message, cause); + } + + public BadAttestationStatementException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java new file mode 100644 index 00000000000..c41c0933192 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad challenge is detected + */ +public class BadChallengeException extends ValidationException { + + public BadChallengeException(String message, Throwable cause) { + super(message, cause); + } + + public BadChallengeException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java new file mode 100644 index 00000000000..45a91f5679a --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +public class BadCredentialIdException extends ValidationException { + public BadCredentialIdException(String message, Throwable cause) { + super(message, cause); + } + + public BadCredentialIdException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java new file mode 100644 index 00000000000..61965da944e --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad origin is specified + */ +public class BadOriginException extends ValidationException { + public BadOriginException(String message, Throwable cause) { + super(message, cause); + } + + public BadOriginException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java new file mode 100644 index 00000000000..93ca3be5349 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad rpId is specified + */ +public class BadRpIdException extends ValidationException { + public BadRpIdException(String message, Throwable cause) { + super(message, cause); + } + + public BadRpIdException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java new file mode 100644 index 00000000000..d1ef7f03da1 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + + +/** + * Thrown if bad signature is specified + */ +public class BadSignatureException extends ValidationException { + public BadSignatureException(String message, Throwable cause) { + super(message, cause); + } + + public BadSignatureException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java new file mode 100644 index 00000000000..29523b25803 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if certificate problems happen + */ +public class CertificateException extends ValidationException { + public CertificateException(String message, Throwable cause) { + super(message, cause); + } + + public CertificateException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java new file mode 100644 index 00000000000..0bde4fe1646 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + + +/** + * Thrown if the value violates constraints + */ +public class ConstraintViolationException extends ValidationException { + + public ConstraintViolationException(String message, Throwable cause) { + super(message, cause); + } + + public ConstraintViolationException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java new file mode 100644 index 00000000000..a917febf052 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if an authentication request is rejected because credentialId is not found. + */ +public class CredentialIdNotFoundException extends ValidationException { + public CredentialIdNotFoundException(String message) { + super(message); + } + + public CredentialIdNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java new file mode 100644 index 00000000000..bd050031a0f --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.springframework.security.core.AuthenticationException; + +/** + * Thrown if data conversion failed + */ +public class DataConversionException extends AuthenticationException { + public DataConversionException(String message) { + super(message); + } + + public DataConversionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java new file mode 100644 index 00000000000..29a516160d2 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if bad key description is specified + */ +public class KeyDescriptionValidationException extends ValidationException { + + public KeyDescriptionValidationException(String message, Throwable cause) { + super(message, cause); + } + + public KeyDescriptionValidationException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java new file mode 100644 index 00000000000..b9c2b5cbae5 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if the counter value is lower than expected value + */ +public class MaliciousCounterValueException extends ValidationException { + + public MaliciousCounterValueException(String message, Throwable cause) { + super(message, cause); + } + + public MaliciousCounterValueException(String message) { + super(message); + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java new file mode 100644 index 00000000000..4075ae76297 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if malicious data is specified + */ +public class MaliciousDataException extends ValidationException { + public MaliciousDataException(String message, Throwable cause) { + super(message, cause); + } + + public MaliciousDataException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java new file mode 100644 index 00000000000..4f12962e151 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if an error happen while processing metadata + */ +public class MetadataException extends RuntimeException { + public MetadataException(String message, Throwable cause) { + super(message, cause); + } + + public MetadataException(String message) { + super(message); + } + + public MetadataException(Throwable cause) { + super(cause); + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java new file mode 100644 index 00000000000..d3290663e9c --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + + +/** + * Thrown if the challenge doesn't exist in the session + */ +public class MissingChallengeException extends ValidationException { + public MissingChallengeException(String message, Throwable cause) { + super(message, cause); + } + + public MissingChallengeException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java new file mode 100644 index 00000000000..b519ecb14e3 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if the public key in the first certificate in x5c doesn't matches the credentialPublicKey in the attestedCredentialData + * + * @author Yoshikazu Nojima + */ +public class PublicKeyMismatchException extends ValidationException { + public PublicKeyMismatchException(String message, Throwable cause) { + super(message, cause); + } + + public PublicKeyMismatchException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java new file mode 100644 index 00000000000..a656daa8a36 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if self attestation is specified while prohibited + * + * @author Yoshikazu Nojima + */ +public class SelfAttestationProhibitedException extends ValidationException { + public SelfAttestationProhibitedException(String message, Throwable cause) { + super(message, cause); + } + + public SelfAttestationProhibitedException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java new file mode 100644 index 00000000000..e99ec1c1954 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if tokenBinding problems happen + * + * @author Yoshikazu Nojima + */ +public class TokenBindingException extends ValidationException { + public TokenBindingException(String message, Throwable cause) { + super(message, cause); + } + + public TokenBindingException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java new file mode 100644 index 00000000000..94b8d5ad13c --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if no trust anchor chained to the attestation certificate is found + * + * @author Yoshikazu Nojima + */ +public class TrustAnchorNotFoundException extends ValidationException { + + public TrustAnchorNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public TrustAnchorNotFoundException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java new file mode 100644 index 00000000000..587f8c4c97f --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if unexpected extension is contained + * + * @author Yoshikazu Nojima + */ +public class UnexpectedExtensionException extends ValidationException { + public UnexpectedExtensionException(String msg, Throwable cause) { + super(msg, cause); + } + + public UnexpectedExtensionException(String msg) { + super(msg); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java new file mode 100644 index 00000000000..effa1a2151e --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if user is to be present but not present + * + * @author Yoshikazu Nojima + */ +public class UserNotPresentException extends ValidationException { + + public UserNotPresentException(String message, Throwable cause) { + super(message, cause); + } + + public UserNotPresentException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java new file mode 100644 index 00000000000..88d4c451e06 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if user is to be verified but not verified + * + * @author Yoshikazu Nojima + */ +public class UserNotVerifiedException extends ValidationException { + + public UserNotVerifiedException(String message, Throwable cause) { + super(message, cause); + } + + public UserNotVerifiedException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java new file mode 100644 index 00000000000..883ab6458f3 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +/** + * Thrown if WebAuthn request validation error happen + * + * @author Yoshikazu Nojima + */ +public class ValidationException extends WebAuthnAuthenticationException { + public ValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationException(String message) { + super(message); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationException.java new file mode 100644 index 00000000000..425af670bec --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.springframework.security.core.AuthenticationException; + +/** + * A specialized {@link AuthenticationException} for WebAuthn + * + * @author Yoshikazu Nojima + */ +public class WebAuthnAuthenticationException extends AuthenticationException { + + public WebAuthnAuthenticationException(String message, Throwable cause) { + super(message, cause); + } + + public WebAuthnAuthenticationException(String message) { + super(message); + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java new file mode 100644 index 00000000000..324fa5d98e3 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Exception classes for WebAuthn + */ +package org.springframework.security.webauthn.exception; diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java new file mode 100644 index 00000000000..edbbf690c6c --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.PublicKeyCredentialDescriptor; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import com.webauthn4j.util.CollectionUtil; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Options for WebAuthn assertion generation + */ +public class AssertionOptions implements Serializable { + + // ~ Instance fields + // ================================================================================================ + + private Challenge challenge; + private Long timeout; + private String rpId; + private List allowCredentials; + private AuthenticationExtensionsClientInputs extensions; + + // ~ Constructors + // =================================================================================================== + + public AssertionOptions( + Challenge challenge, + Long timeout, + String rpId, + List allowCredentials, + AuthenticationExtensionsClientInputs extensions) { + this.challenge = challenge; + this.timeout = timeout; + this.rpId = rpId; + this.allowCredentials = CollectionUtil.unmodifiableList(allowCredentials); + this.extensions = extensions; + } + + // ~ Methods + // ======================================================================================================== + + public Challenge getChallenge() { + return challenge; + } + + public Long getTimeout() { + return timeout; + } + + public String getRpId() { + return rpId; + } + + public List getAllowCredentials() { + return allowCredentials; + } + + public AuthenticationExtensionsClientInputs getExtensions() { + return extensions; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AssertionOptions that = (AssertionOptions) o; + return Objects.equals(challenge, that.challenge) && + Objects.equals(timeout, that.timeout) && + Objects.equals(rpId, that.rpId) && + Objects.equals(allowCredentials, that.allowCredentials) && + Objects.equals(extensions, that.extensions); + } + + @Override + public int hashCode() { + + return Objects.hash(challenge, timeout, rpId, allowCredentials, extensions); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java new file mode 100644 index 00000000000..db878a29452 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + + +import com.webauthn4j.data.*; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import com.webauthn4j.util.CollectionUtil; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Options for WebAuthn attestation generation + */ +@SuppressWarnings("common-java:DuplicatedBlocks") +public class AttestationOptions implements Serializable { + + // ~ Instance fields + // ================================================================================================ + + private PublicKeyCredentialRpEntity rp; + private PublicKeyCredentialUserEntity user; + private Challenge challenge; + private List pubKeyCredParams; + private Long timeout; + private List excludeCredentials; + private AuthenticatorSelectionCriteria authenticatorSelection; + private AttestationConveyancePreference attestation; + private AuthenticationExtensionsClientInputs extensions; + + // ~ Constructors + // =================================================================================================== + + public AttestationOptions( + PublicKeyCredentialRpEntity rp, + PublicKeyCredentialUserEntity user, + Challenge challenge, + List pubKeyCredParams, + Long timeout, + List excludeCredentials, + AuthenticatorSelectionCriteria authenticatorSelection, + AttestationConveyancePreference attestation, + AuthenticationExtensionsClientInputs extensions) { + this.rp = rp; + this.user = user; + this.challenge = challenge; + this.pubKeyCredParams = CollectionUtil.unmodifiableList(pubKeyCredParams); + this.timeout = timeout; + this.excludeCredentials = CollectionUtil.unmodifiableList(excludeCredentials); + this.authenticatorSelection = authenticatorSelection; + this.attestation = attestation; + this.extensions = extensions; + } + + /** + * Returns PublicKeyCredentialRpEntity + * + * @return PublicKeyCredentialRpEntity + */ + public PublicKeyCredentialRpEntity getRp() { + return rp; + } + + /** + * If authenticated, returns {@link PublicKeyCredentialUserEntity} + * Otherwise returns null + * + * @return {@link PublicKeyCredentialUserEntity} + */ + public PublicKeyCredentialUserEntity getUser() { + return user; + } + + /** + * Returns {@link Challenge} + * + * @return {@link Challenge} + */ + public Challenge getChallenge() { + return challenge; + } + + public List getPubKeyCredParams() { + return pubKeyCredParams; + } + + public Long getTimeout() { + return timeout; + } + + public List getExcludeCredentials() { + return excludeCredentials; + } + + public AuthenticatorSelectionCriteria getAuthenticatorSelection() { + return authenticatorSelection; + } + + public AttestationConveyancePreference getAttestation() { + return attestation; + } + + public AuthenticationExtensionsClientInputs getExtensions() { + return extensions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AttestationOptions that = (AttestationOptions) o; + return Objects.equals(rp, that.rp) && + Objects.equals(user, that.user) && + Objects.equals(challenge, that.challenge) && + Objects.equals(pubKeyCredParams, that.pubKeyCredParams) && + Objects.equals(timeout, that.timeout) && + Objects.equals(excludeCredentials, that.excludeCredentials) && + Objects.equals(authenticatorSelection, that.authenticatorSelection) && + attestation == that.attestation && + Objects.equals(extensions, that.extensions); + } + + @Override + public int hashCode() { + return Objects.hash(rp, user, challenge, pubKeyCredParams, timeout, excludeCredentials, authenticatorSelection, attestation, extensions); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java new file mode 100644 index 00000000000..21afe802cc8 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java @@ -0,0 +1,21 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; + +public class AuthenticationExtensionsOptionProvider extends ExtensionsOptionProvider { +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java new file mode 100644 index 00000000000..7e2aa4fabfa --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.extension.client.ExtensionClientInput; + +import javax.servlet.http.HttpServletRequest; + +public interface ExtensionOptionProvider { + + T provide(HttpServletRequest request); + + String getIdentifier(); +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java new file mode 100644 index 00000000000..7bf9817ac43 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.extension.ExtensionInput; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import com.webauthn4j.data.extension.client.ExtensionClientInput; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ExtensionsOptionProvider implements Iterable> { + + private Map> providers = new HashMap<>(); + + public AuthenticationExtensionsClientInputs provide(HttpServletRequest request) { + + Map extensionOptions = + providers.values().stream() + .map(provider -> provider.provide(request)) + .collect(Collectors.toMap(ExtensionInput::getIdentifier, extensionOption -> extensionOption)); + + return new AuthenticationExtensionsClientInputs<>(extensionOptions); + } + + public void put(T extensionOption) { + put(new StaticExtensionOptionProvider<>(extensionOption)); + } + + public void put(ExtensionOptionProvider extensionOptionProvider) { + providers.put(extensionOptionProvider.getIdentifier(), extensionOptionProvider); + } + + public void putAll(Map> extensionsClientInputs) { + extensionsClientInputs.forEach((key, value) -> providers.put(key, value)); + } + + @Override + public Iterator> iterator() { + return providers.values().iterator(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionsOptionProvider that = (ExtensionsOptionProvider) o; + return Objects.equals(providers, that.providers); + } + + @Override + public int hashCode() { + return Objects.hash(providers); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java new file mode 100644 index 00000000000..756403d1192 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.client.challenge.Challenge; + +import javax.servlet.http.HttpServletRequest; + +/** + * Provides {@link Options} and effective rpId for {@link HttpServletRequest} + * + * @author Yoshikazu Nojima + */ +public interface OptionsProvider { + + /** + * provides {@link AttestationOptions}. If username is null, user, credentials are not populated. + * + * @param request request + * @param username username + * @param challenge if null, new challenge is generated. Otherwise, specified challenge is used. + * @return {@link AttestationOptions} instance + */ + AttestationOptions getAttestationOptions(HttpServletRequest request, String username, Challenge challenge); + + /** + * provides {@link AssertionOptions}. If username is null, credentials are not populated. + * + * @param request request + * @param username username + * @param challenge if null, new challenge is generated. Otherwise, specified challenge is used. + * @return {@link AssertionOptions} instance + */ + AssertionOptions getAssertionOptions(HttpServletRequest request, String username, Challenge challenge); + + /** + * returns effective rpId based on request origin and configured rpId. + * + * @param request request + * @return effective rpId + */ + String getEffectiveRpId(HttpServletRequest request); + + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java new file mode 100644 index 00000000000..61150f480e3 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java @@ -0,0 +1,317 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.data.*; +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.Challenge; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.security.webauthn.util.ServletUtil; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * An {@link OptionsProvider} implementation + * + * @author Yoshikazu Nojima + */ +public class OptionsProviderImpl implements OptionsProvider { + + //~ Instance fields + // ================================================================================================ + private String rpId = null; + private String rpName = null; + private String rpIcon = null; + private List pubKeyCredParams = new ArrayList<>(); + private AuthenticatorSelectionCriteria authenticatorSelection = new AuthenticatorSelectionCriteria(null, false, UserVerificationRequirement.PREFERRED); + private AttestationConveyancePreference attestation = AttestationConveyancePreference.NONE; + private Long registrationTimeout = null; + private Long authenticationTimeout = null; + private RegistrationExtensionsOptionProvider registrationExtensions = new RegistrationExtensionsOptionProvider(); + private AuthenticationExtensionsOptionProvider authenticationExtensions = new AuthenticationExtensionsOptionProvider(); + + private WebAuthnUserDetailsService userDetailsService; + private ChallengeRepository challengeRepository; + + // ~ Constructors + // =================================================================================================== + + public OptionsProviderImpl(WebAuthnUserDetailsService userDetailsService, ChallengeRepository challengeRepository) { + + Assert.notNull(userDetailsService, "userDetailsService must not be null"); + Assert.notNull(challengeRepository, "challengeRepository must not be null"); + + this.userDetailsService = userDetailsService; + this.challengeRepository = challengeRepository; + } + + + // ~ Methods + // ======================================================================================================== + + + /** + * {@inheritDoc} + */ + @Override + public AttestationOptions getAttestationOptions(HttpServletRequest request, String username, Challenge challenge) { + + PublicKeyCredentialUserEntity user; + Collection authenticators; + + try { + WebAuthnUserDetails userDetails = userDetailsService.loadUserByUsername(username); + authenticators = userDetails.getAuthenticators(); + user = new PublicKeyCredentialUserEntity(userDetails.getUserHandle(), username, null); + } catch (UsernameNotFoundException e) { + authenticators = Collections.emptyList(); + user = null; + } + + List credentials = authenticators.stream() + .map(authenticator -> new PublicKeyCredentialDescriptor( + PublicKeyCredentialType.PUBLIC_KEY, + authenticator.getAttestedCredentialData().getCredentialId(), + null + )) + .collect(Collectors.toList()); + + PublicKeyCredentialRpEntity relyingParty = new PublicKeyCredentialRpEntity(getEffectiveRpId(request), rpName, rpIcon); + if (challenge == null) { + challenge = challengeRepository.loadOrGenerateChallenge(request); + } else { + challengeRepository.saveChallenge(challenge, request); + } + + return new AttestationOptions(relyingParty, user, challenge, pubKeyCredParams, registrationTimeout, + credentials, authenticatorSelection, attestation, registrationExtensions.provide(request)); + } + + public AssertionOptions getAssertionOptions(HttpServletRequest request, String username, Challenge challenge) { + + Collection authenticators; + try { + WebAuthnUserDetails userDetails = userDetailsService.loadUserByUsername(username); + authenticators = userDetails.getAuthenticators(); + } catch (UsernameNotFoundException e) { + authenticators = Collections.emptyList(); + } + + String effectiveRpId = getEffectiveRpId(request); + + List credentials = authenticators.stream() + .map(authenticator -> new PublicKeyCredentialDescriptor( + PublicKeyCredentialType.PUBLIC_KEY, + authenticator.getAttestedCredentialData().getCredentialId(), + null + )) + .collect(Collectors.toList()); + + if (challenge == null) { + challenge = challengeRepository.loadOrGenerateChallenge(request); + } else { + challengeRepository.saveChallenge(challenge, request); + } + + return new AssertionOptions(challenge, authenticationTimeout, effectiveRpId, credentials, authenticationExtensions.provide(request)); + } + + + /** + * returns effective rpId based on request origin and configured rpId. + * + * @param request request + * @return effective rpId + */ + public String getEffectiveRpId(HttpServletRequest request) { + String effectiveRpId; + if (this.rpId != null) { + effectiveRpId = this.rpId; + } else { + Origin origin = ServletUtil.getOrigin(request); + effectiveRpId = origin.getHost(); + } + return effectiveRpId; + } + + /** + * returns configured rpId + * + * @return rpId + */ + public String getRpId() { + return rpId; + } + + /** + * configures rpId + * + * @param rpId rpId + */ + public void setRpId(String rpId) { + this.rpId = rpId; + } + + /** + * returns rpName + * + * @return rpName + */ + public String getRpName() { + return rpName; + } + + /** + * configures rpName + * + * @param rpName rpName + */ + public void setRpName(String rpName) { + Assert.hasText(rpName, "rpName parameter must not be empty or null"); + this.rpName = rpName; + } + + /** + * returns rpIcon + * + * @return rpIcon + */ + public String getRpIcon() { + return rpIcon; + } + + /** + * configures rpIcon + * + * @param rpIcon rpIcon + */ + public void setRpIcon(String rpIcon) { + Assert.hasText(rpIcon, "rpIcon parameter must not be empty or null"); + this.rpIcon = rpIcon; + } + + /** + * returns {@link PublicKeyCredentialParameters} list + * + * @return {@link PublicKeyCredentialParameters} list + */ + public List getPubKeyCredParams() { + return pubKeyCredParams; + } + + /** + * configures pubKeyCredParams + * + * @param pubKeyCredParams {@link PublicKeyCredentialParameters} list + */ + public void setPubKeyCredParams(List pubKeyCredParams) { + this.pubKeyCredParams = pubKeyCredParams; + } + + /** + * returns the registration timeout + * + * @return the registration timeout + */ + public Long getRegistrationTimeout() { + return registrationTimeout; + } + + /** + * configures the registration timeout + * + * @param registrationTimeout registration timeout + */ + public void setRegistrationTimeout(Long registrationTimeout) { + Assert.isTrue(registrationTimeout >= 0, "registrationTimeout must be within unsigned long."); + this.registrationTimeout = registrationTimeout; + } + + /** + * returns the authentication timeout + * + * @return the authentication timeout + */ + public Long getAuthenticationTimeout() { + return authenticationTimeout; + } + + /** + * configures the authentication timeout + * + * @param authenticationTimeout authentication timeout + */ + public void setAuthenticationTimeout(Long authenticationTimeout) { + Assert.isTrue(registrationTimeout >= 0, "registrationTimeout must be within unsigned long."); + this.authenticationTimeout = authenticationTimeout; + } + + /** + * returns the {@link AuthenticatorSelectionCriteria} + * + * @return the {@link AuthenticatorSelectionCriteria} + */ + public AuthenticatorSelectionCriteria getAuthenticatorSelection() { + return authenticatorSelection; + } + + /** + * configures the {@link AuthenticatorSelectionCriteria} + * + * @param authenticatorSelection the {@link AuthenticatorSelectionCriteria} + */ + public void setAuthenticatorSelection(AuthenticatorSelectionCriteria authenticatorSelection) { + this.authenticatorSelection = authenticatorSelection; + } + + /** + * returns the {@link AttestationConveyancePreference} + * + * @return the {@link AttestationConveyancePreference} + */ + public AttestationConveyancePreference getAttestation() { + return attestation; + } + + /** + * configures the {@link AttestationConveyancePreference} + * + * @param attestation the {@link AttestationConveyancePreference} + */ + public void setAttestation(AttestationConveyancePreference attestation) { + this.attestation = attestation; + } + + public RegistrationExtensionsOptionProvider getRegistrationExtensions() { + return registrationExtensions; + } + + public AuthenticationExtensionsOptionProvider getAuthenticationExtensions() { + return authenticationExtensions; + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java new file mode 100644 index 00000000000..ab9d084d4f7 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java @@ -0,0 +1,21 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.extension.client.RegistrationExtensionClientInput; + +public class RegistrationExtensionsOptionProvider extends ExtensionsOptionProvider { +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java new file mode 100644 index 00000000000..1c9f07ea349 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.extension.client.ExtensionClientInput; + +import javax.servlet.http.HttpServletRequest; + +public class StaticExtensionOptionProvider implements ExtensionOptionProvider { + + private T extensionOption; + + public StaticExtensionOptionProvider(T extensionOption) { + this.extensionOption = extensionOption; + } + + @Override + public T provide(HttpServletRequest request) { + return extensionOption; + } + + @Override + public String getIdentifier() { + return extensionOption.getIdentifier(); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/package-info.java b/webauthn/src/main/java/org/springframework/security/webauthn/package-info.java new file mode 100644 index 00000000000..7550d9bfccd --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Spring Security WebAuthn classes + */ +package org.springframework.security.webauthn; diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java b/webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java new file mode 100644 index 00000000000..711a1410b64 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.request; + +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.util.ArrayUtil; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + + +/** + * Internal data transfer object to represent WebAuthn authentication request + * + * @author Yoshikazu Nojima + */ +public class WebAuthnAuthenticationRequest implements Serializable { + + //~ Instance fields + // ================================================================================================ + // user inputs + private final byte[] credentialId; + private final byte[] clientDataJSON; + private final byte[] authenticatorData; + private final byte[] signature; + private final String clientExtensionsJSON; + private final ServerProperty serverProperty; + private final boolean userVerificationRequired; + private final boolean userPresenceRequired; + private List expectedAuthenticationExtensionIds; + + @SuppressWarnings("squid:S00107") + public WebAuthnAuthenticationRequest( + byte[] credentialId, + byte[] clientDataJSON, + byte[] authenticatorData, + byte[] signature, + String clientExtensionsJSON, + ServerProperty serverProperty, + boolean userVerificationRequired, + boolean userPresenceRequired, + List expectedAuthenticationExtensionIds) { + + this.credentialId = credentialId; + this.clientDataJSON = clientDataJSON; + this.authenticatorData = authenticatorData; + this.signature = signature; + this.clientExtensionsJSON = clientExtensionsJSON; + this.serverProperty = serverProperty; + this.userVerificationRequired = userVerificationRequired; + this.userPresenceRequired = userPresenceRequired; + this.expectedAuthenticationExtensionIds = expectedAuthenticationExtensionIds; + } + + @SuppressWarnings("squid:S00107") + public WebAuthnAuthenticationRequest( + byte[] credentialId, + byte[] clientDataJSON, + byte[] authenticatorData, + byte[] signature, + String clientExtensionsJSON, + ServerProperty serverProperty, + boolean userVerificationRequired, + List expectedAuthenticationExtensionIds) { + + this( + credentialId, + clientDataJSON, + authenticatorData, + signature, + clientExtensionsJSON, + serverProperty, + userVerificationRequired, + true, + expectedAuthenticationExtensionIds + ); + } + + public byte[] getCredentialId() { + return ArrayUtil.clone(credentialId); + } + + public byte[] getClientDataJSON() { + return ArrayUtil.clone(clientDataJSON); + } + + public byte[] getAuthenticatorData() { + return ArrayUtil.clone(authenticatorData); + } + + public byte[] getSignature() { + return ArrayUtil.clone(signature); + } + + public String getClientExtensionsJSON() { + return clientExtensionsJSON; + } + + public ServerProperty getServerProperty() { + return serverProperty; + } + + public boolean isUserVerificationRequired() { + return userVerificationRequired; + } + + public boolean isUserPresenceRequired() { + return userPresenceRequired; + } + + public List getExpectedAuthenticationExtensionIds() { + return expectedAuthenticationExtensionIds; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnAuthenticationRequest that = (WebAuthnAuthenticationRequest) o; + return userVerificationRequired == that.userVerificationRequired && + userPresenceRequired == that.userPresenceRequired && + Arrays.equals(credentialId, that.credentialId) && + Arrays.equals(clientDataJSON, that.clientDataJSON) && + Arrays.equals(authenticatorData, that.authenticatorData) && + Arrays.equals(signature, that.signature) && + Objects.equals(clientExtensionsJSON, that.clientExtensionsJSON) && + Objects.equals(serverProperty, that.serverProperty) && + Objects.equals(expectedAuthenticationExtensionIds, that.expectedAuthenticationExtensionIds); + } + + @Override + public int hashCode() { + int result = Objects.hash(clientExtensionsJSON, serverProperty, userVerificationRequired, userPresenceRequired, expectedAuthenticationExtensionIds); + result = 31 * result + Arrays.hashCode(credentialId); + result = 31 * result + Arrays.hashCode(clientDataJSON); + result = 31 * result + Arrays.hashCode(authenticatorData); + result = 31 * result + Arrays.hashCode(signature); + return result; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java new file mode 100644 index 00000000000..56de5464c21 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.server; + + +import com.webauthn4j.server.ServerProperty; + +import javax.servlet.http.HttpServletRequest; + +/** + * Provides {@link ServerProperty} instance associated with {@link HttpServletRequest} + * + * @author Yoshikazu Nojima + */ +public interface ServerPropertyProvider { + + /** + * Provides {@link ServerProperty} + * + * @param request http servlet request + * @return the {@link ServerProperty} + */ + ServerProperty provide(HttpServletRequest request); + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java new file mode 100644 index 00000000000..d431c9436a7 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.server; + +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.server.ServerProperty; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.util.ServletUtil; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; + +/** + * {@inheritDoc} + */ +public class ServerPropertyProviderImpl implements ServerPropertyProvider { + + //~ Instance fields + // ================================================================================================ + private OptionsProvider optionsProvider; + private ChallengeRepository challengeRepository; + + public ServerPropertyProviderImpl(OptionsProvider optionsProvider, ChallengeRepository challengeRepository) { + + Assert.notNull(optionsProvider, "optionsProvider must not be null"); + Assert.notNull(challengeRepository, "challengeRepository must not be null"); + + this.optionsProvider = optionsProvider; + this.challengeRepository = challengeRepository; + } + + public ServerProperty provide(HttpServletRequest request) { + + Origin origin = ServletUtil.getOrigin(request); + String effectiveRpId = optionsProvider.getEffectiveRpId(request); + Challenge challenge = challengeRepository.loadOrGenerateChallenge(request); + + return new ServerProperty(origin, effectiveRpId, challenge, null); // tokenBinding is not supported by Servlet API as of 4.0 + } + + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java new file mode 100644 index 00000000000..9c203d63c7a --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.userdetails; + +import com.webauthn4j.authenticator.Authenticator; +import org.springframework.security.core.userdetails.MFAUserDetails; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * An extended {@link UserDetails} interface for WebAuthn + * + * @author Yoshikazu Nojima + */ +public interface WebAuthnUserDetails extends MFAUserDetails { + + @SuppressWarnings("squid:S1452") + Collection getAuthenticators(); + + void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed); + + byte[] getUserHandle(); +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java new file mode 100644 index 00000000000..80ca481f78c --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.userdetails; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.util.ArrayUtil; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.util.Collection; +import java.util.List; + +/** + * A {@link WebAuthnUserDetails} implementation + * + * @author Yoshikazu Nojima + */ +@SuppressWarnings("squid:S2160") +public class WebAuthnUserDetailsImpl extends User implements WebAuthnUserDetails { + + // ~ Instance fields + // ================================================================================================ + private boolean singleFactorAuthenticationAllowed = false; + private byte[] userHandle; + private List authenticators; + + public WebAuthnUserDetailsImpl( + byte[] userHandle, String username, String password, List authenticators, + Collection authorities) { + this(userHandle, username, password, authenticators, false, authorities); + } + + public WebAuthnUserDetailsImpl( + byte[] userHandle, String username, String password, List authenticators, + boolean singleFactorAuthenticationAllowed, Collection authorities) { + this(userHandle, username, password, authenticators, singleFactorAuthenticationAllowed, + true, true, true, true, + authorities); + } + + @SuppressWarnings("squid:S00107") + public WebAuthnUserDetailsImpl( + byte[] userHandle, String username, String password, List authenticators, + boolean singleFactorAuthenticationAllowed, boolean enabled, boolean accountNonExpired, + boolean credentialsNonExpired, boolean accountNonLocked, + Collection authorities) { + super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); + this.userHandle = userHandle; + this.authenticators = authenticators; + this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; + } + + @Override + public byte[] getUserHandle() { + return ArrayUtil.clone(userHandle); + } + + @Override + public List getAuthenticators() { + return this.authenticators; + } + + @Override + public boolean isSingleFactorAuthenticationAllowed() { + return singleFactorAuthenticationAllowed; + } + + @Override + public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed) { + this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; + } + + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java new file mode 100644 index 00000000000..fcb23a55fed --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.userdetails; + +import com.webauthn4j.authenticator.Authenticator; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; + +/** + * Specialized {@link UserDetailsService} for WebAuthn + * + * @author Yoshikazu Nojima + */ +public interface WebAuthnUserDetailsService extends UserDetailsService { + + /** + * Locates a user based on the username. + * + * @param username the username identifying the user whose data is required + * @return a fully populated {@link WebAuthnUserDetails} instance (never null) + * @throws UsernameNotFoundException if the user could not be found + */ + @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") + WebAuthnUserDetails loadUserByUsername(String username) throws UsernameNotFoundException; + + /** + * Locates a user based on the credentialId. + * + * @param credentialId credentialId + * @return fully populated {@link WebAuthnUserDetails} instance (never null), + * which must returns the authenticator in getAuthenticators result. + * @throws CredentialIdNotFoundException if the authenticator could not be found + */ + @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") + WebAuthnUserDetails loadUserByCredentialId(byte[] credentialId) throws CredentialIdNotFoundException; + + /** + * Adds {@link Authenticator} to the user record + * + * @param username the username identifying the user + * @param authenticator the authenticator to be added + */ + void addAuthenticator(String username, Authenticator authenticator); + + /** + * Removes {@link Authenticator} from the user record + * + * @param username the username identifying the user + * @param authenticator the authenticator to be removed + */ + void removeAuthenticator(String username, Authenticator authenticator); + + /** + * Removes {@link Authenticator} from the user record + * + * @param username the username identifying the user + * @param credentialId the credentialId identifying the authenticator + */ + void removeAuthenticator(String username, byte[] credentialId); +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java new file mode 100644 index 00000000000..7def00d0f80 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * WebAuthn specific UserDetails classes + */ +package org.springframework.security.webauthn.userdetails; diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java b/webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java new file mode 100644 index 00000000000..86d1905faac --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.util; + +import com.webauthn4j.util.exception.WebAuthnException; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.webauthn.exception.*; + +/** + * Internal utility to handle exceptions + * + * @author Yoshikazu Nojima + */ +public class ExceptionUtil { + + private ExceptionUtil() { + } + + /** + * Wraps WebAuthnAuthentication to proper {@link RuntimeException} (mainly {@link AuthenticationException} subclass. + * + * @param e exception to be wrapped + * @return wrapping exception + */ + @SuppressWarnings("squid:S3776") + public static RuntimeException wrapWithAuthenticationException(WebAuthnException e) { + // ValidationExceptions + if (e instanceof com.webauthn4j.validator.exception.BadAaguidException) { + return new BadAaguidException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadAlgorithmException) { + return new BadAlgorithmException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadAttestationStatementException) { + if (e instanceof com.webauthn4j.validator.exception.KeyDescriptionValidationException) { + return new KeyDescriptionValidationException(e.getMessage(), e); + } else { + return new BadAttestationStatementException(e.getMessage(), e); + } + } else if (e instanceof com.webauthn4j.validator.exception.BadChallengeException) { + return new BadChallengeException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadOriginException) { + return new BadOriginException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadRpIdException) { + return new BadRpIdException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadSignatureException) { + return new BadSignatureException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.CertificateException) { + return new CertificateException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.ConstraintViolationException) { + return new ConstraintViolationException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.MaliciousCounterValueException) { + return new MaliciousCounterValueException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.MaliciousDataException) { + return new MaliciousDataException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.MissingChallengeException) { + return new MissingChallengeException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.PublicKeyMismatchException) { + return new PublicKeyMismatchException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.SelfAttestationProhibitedException) { + return new SelfAttestationProhibitedException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.TokenBindingException) { + return new TokenBindingException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.TrustAnchorNotFoundException) { + return new TrustAnchorNotFoundException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.UnexpectedExtensionException) { + return new UnexpectedExtensionException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.UserNotPresentException) { + return new UserNotPresentException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.UserNotVerifiedException) { + return new UserNotVerifiedException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.ValidationException) { + return new ValidationException("WebAuthn validation error", e); + } + // DataConversionException + else if (e instanceof com.webauthn4j.converter.exception.DataConversionException) { + return new DataConversionException("WebAuthn data conversion error", e); + } else { + return new AuthenticationServiceException(null, e); + } + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java b/webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java new file mode 100644 index 00000000000..f0b7cc229e5 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.util; + + +import com.webauthn4j.data.client.Origin; + +import javax.servlet.ServletRequest; + +/** + * Internal utility to handle servlet + * + * @author Yoshikazu Nojima + */ +public class ServletUtil { + + private ServletUtil() { + } + + /** + * Returns {@link Origin} corresponding {@link ServletRequest} url + * + * @param request http servlet request + * @return the {@link Origin} + */ + public static Origin getOrigin(ServletRequest request) { + return new Origin(request.getScheme(), request.getServerName(), request.getServerPort()); + } +} diff --git a/webauthn/src/test/java/integration/component/RegistrationValidationTest.java b/webauthn/src/test/java/integration/component/RegistrationValidationTest.java new file mode 100644 index 00000000000..9c95e1a886a --- /dev/null +++ b/webauthn/src/test/java/integration/component/RegistrationValidationTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package integration.component; + +import com.webauthn4j.data.*; +import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.test.authenticator.webauthn.PackedAuthenticator; +import com.webauthn4j.test.authenticator.webauthn.WebAuthnAuthenticatorAdaptor; +import com.webauthn4j.test.client.ClientPlatform; +import com.webauthn4j.util.Base64UrlUtil; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidationResponse; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; +import org.springframework.security.webauthn.server.ServerPropertyProvider; + +import java.util.Collections; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for WebAuthnRegistrationContextValidator + */ +public class RegistrationValidationTest { + + String rpId = "example.com"; + Challenge challenge = new DefaultChallenge(); + private Origin origin = new Origin("http://localhost"); + private WebAuthnAuthenticatorAdaptor webAuthnModelAuthenticatorAdaptor = new WebAuthnAuthenticatorAdaptor(new PackedAuthenticator()); + private ClientPlatform clientPlatform = new ClientPlatform(origin, webAuthnModelAuthenticatorAdaptor); + private ServerPropertyProvider serverPropertyProvider = mock(ServerPropertyProvider.class); + private WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( + WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(), serverPropertyProvider + ); + + @Test + public void validate_test() { + ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, null); + when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + + + AuthenticatorSelectionCriteria authenticatorSelectionCriteria = + new AuthenticatorSelectionCriteria(AuthenticatorAttachment.CROSS_PLATFORM, true, UserVerificationRequirement.REQUIRED); + + PublicKeyCredentialParameters publicKeyCredentialParameters = new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256); + + PublicKeyCredentialUserEntity publicKeyCredentialUserEntity = new PublicKeyCredentialUserEntity(); + + PublicKeyCredentialCreationOptions credentialCreationOptions = new PublicKeyCredentialCreationOptions( + new PublicKeyCredentialRpEntity(rpId, "example.com"), + publicKeyCredentialUserEntity, + challenge, + Collections.singletonList(publicKeyCredentialParameters), + null, + null, + authenticatorSelectionCriteria, + AttestationConveyancePreference.NONE, + null + ); + + AuthenticatorAttestationResponse registrationRequest = clientPlatform.create(credentialCreationOptions).getAuthenticatorResponse(); + + MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest.setScheme("https"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setServerPort(443); + + String clientDataBase64 = Base64UrlUtil.encodeToString(registrationRequest.getClientDataJSON()); + String attestationObjectBase64 = Base64UrlUtil.encodeToString(registrationRequest.getAttestationObject()); + Set transports = Collections.emptySet(); + String clientExtensionsJSON = null; + + WebAuthnRegistrationRequestValidationResponse response + = target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON); + + assertThat(response.getAttestationObject()).isNotNull(); + assertThat(response.getCollectedClientData()).isNotNull(); + assertThat(response.getRegistrationExtensionsClientOutputs()).isNull(); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java new file mode 100644 index 00000000000..102c2d41198 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import org.junit.Test; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class WebAuthnAssertionAuthenticationTokenTest { + + @Test(expected = IllegalArgumentException.class) + public void setAuthenticated_with_true_test() { + WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); + token.setAuthenticated(true); + assertThat(token.isAuthenticated()).isTrue(); + } + + @Test + public void setAuthenticated_with_false_test() { + WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); + token.setAuthenticated(false); + assertThat(token.isAuthenticated()).isFalse(); + } + + @Test + public void eraseCredentials_test() { + WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); + token.eraseCredentials(); + assertThat(token.getCredentials()).isNull(); + } + + @Test + public void equals_hashCode_test() { + WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAssertionAuthenticationToken tokenA = new WebAuthnAssertionAuthenticationToken(request); + WebAuthnAssertionAuthenticationToken tokenB = new WebAuthnAssertionAuthenticationToken(request); + + assertThat(tokenA).isEqualTo(tokenB); + assertThat(tokenA).hasSameHashCodeAs(tokenB); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java new file mode 100644 index 00000000000..a8e140d9926 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java @@ -0,0 +1,363 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.authenticator.AuthenticatorImpl; +import com.webauthn4j.data.WebAuthnAuthenticationContext; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetailsChecker; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.exception.BadChallengeException; +import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsImpl; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Test for WebAuthnAuthenticationProvider + */ +public class WebAuthnAuthenticationProviderTest { + + private WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); + + private WebAuthnAuthenticatorService authenticatorService = mock(WebAuthnAuthenticatorService.class); + + private WebAuthnAuthenticationContextValidator authenticationContextValidator = mock(WebAuthnAuthenticationContextValidator.class); + + private WebAuthnAuthenticationProvider authenticationProvider + = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, authenticationContextValidator); + + @Before + public void setup() { + authenticationProvider = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, authenticationContextValidator); + } + + /** + * Verifies that an unsupported authentication token will be rejected. + */ + @Test(expected = IllegalArgumentException.class) + public void authenticate_with_invalid_authenticationToken() { + Authentication token = new UsernamePasswordAuthenticationToken("username", "password"); + authenticationProvider.authenticate(token); + } + + /** + * Verifies that the authentication token without credentials will be rejected. + */ + @Test(expected = BadCredentialsException.class) + public void authenticate_with_authenticationToken_without_credentials() { + Authentication token = new WebAuthnAssertionAuthenticationToken(null); + authenticationProvider.authenticate(token); + } + + + /** + * Verifies that authentication process passes successfully if input is correct. + */ + @Test + public void authenticate_test() { + //Given + byte[] credentialId = new byte[32]; + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); + WebAuthnUserDetailsImpl user = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + Collections.singletonList(grantedAuthority)); + when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + + //When + WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + when(credential.getCredentialId()).thenReturn(credentialId); + when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(user); + Authentication token = new WebAuthnAssertionAuthenticationToken(credential); + Authentication authenticatedToken = authenticationProvider.authenticate(token); + + ArgumentCaptor captor = ArgumentCaptor.forClass(WebAuthnAuthenticationContext.class); + verify(authenticationContextValidator).validate(captor.capture(), any()); + WebAuthnAuthenticationContext authenticationContext = captor.getValue(); + + assertThat(authenticationContext.getExpectedExtensionIds()).isEqualTo(credential.getExpectedAuthenticationExtensionIds()); + + assertThat(authenticatedToken.getPrincipal()).isInstanceOf(WebAuthnUserDetailsImpl.class); + assertThat(authenticatedToken.getCredentials()).isEqualTo(credential); + assertThat(authenticatedToken.getAuthorities().toArray()).containsExactly(grantedAuthority); + } + + /** + * Verifies that authentication process passes successfully if input is correct. + */ + @Test + public void authenticate_with_forcePrincipalAsString_option_test() { + //Given + byte[] credentialId = new byte[32]; + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); + WebAuthnUserDetailsImpl user = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + Collections.singletonList(grantedAuthority)); + when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + + //When + WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + when(credential.getCredentialId()).thenReturn(credentialId); + when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(user); + Authentication token = new WebAuthnAssertionAuthenticationToken(credential); + authenticationProvider.setForcePrincipalAsString(true); + Authentication authenticatedToken = authenticationProvider.authenticate(token); + + assertThat(authenticatedToken.getPrincipal()).isInstanceOf(String.class); + assertThat(authenticatedToken.getCredentials()).isEqualTo(credential); + assertThat(authenticatedToken.getAuthorities().toArray()).containsExactly(grantedAuthority); + } + + /** + * Verifies that validation fails if ValidationException is thrown from authenticationContextValidator + */ + @Test(expected = BadChallengeException.class) + public void authenticate_with_BadChallengeException_from_authenticationContextValidator_test() { + //Given + byte[] credentialId = new byte[32]; + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); + WebAuthnUserDetailsImpl user = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + Collections.singletonList(grantedAuthority)); + when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + + doThrow(com.webauthn4j.validator.exception.BadChallengeException.class).when(authenticationContextValidator).validate(any(), any()); + + //When + WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + when(credential.getCredentialId()).thenReturn(credentialId); + when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(user); + Authentication token = new WebAuthnAssertionAuthenticationToken(credential); + authenticationProvider.authenticate(token); + } + + + @Test + public void retrieveWebAuthnUserDetails_test() { + byte[] credentialId = new byte[0]; + WebAuthnUserDetails expectedUser = mock(WebAuthnUserDetails.class); + + //Given + when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(expectedUser); + + //When + WebAuthnUserDetails userDetails = authenticationProvider.retrieveWebAuthnUserDetails(credentialId); + + //Then + assertThat(userDetails).isEqualTo(expectedUser); + + } + + @Test(expected = BadCredentialsException.class) + public void retrieveWebAuthnUserDetails_test_with_CredentialIdNotFoundException() { + byte[] credentialId = new byte[0]; + + //Given + when(userDetailsService.loadUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); + + //When + authenticationProvider.retrieveWebAuthnUserDetails(credentialId); + } + + @Test(expected = CredentialIdNotFoundException.class) + public void retrieveWebAuthnUserDetails_test_with_CredentialIdNotFoundException_and_hideCredentialIdNotFoundExceptions_option_false() { + byte[] credentialId = new byte[0]; + + //Given + when(userDetailsService.loadUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); + + //When + authenticationProvider.setHideCredentialIdNotFoundExceptions(false); + authenticationProvider.retrieveWebAuthnUserDetails(credentialId); + } + + @Test(expected = InternalAuthenticationServiceException.class) + public void retrieveWebAuthnUserDetails_test_with_RuntimeException_from_webAuthnAuthenticatorService() { + byte[] credentialId = new byte[0]; + + //Given + when(userDetailsService.loadUserByCredentialId(credentialId)).thenThrow(RuntimeException.class); + + //When + authenticationProvider.setHideCredentialIdNotFoundExceptions(false); + authenticationProvider.retrieveWebAuthnUserDetails(credentialId); + } + + @Test(expected = InternalAuthenticationServiceException.class) + public void retrieveWebAuthnUserDetails_test_with_null_from_webAuthnAuthenticatorService() { + byte[] credentialId = new byte[0]; + + //Given + when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(null); + + //When + authenticationProvider.setHideCredentialIdNotFoundExceptions(false); + authenticationProvider.retrieveWebAuthnUserDetails(credentialId); + } + + @Test + public void getter_setter_test() { + WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); + UserDetailsChecker preAuthenticationChecker = mock(UserDetailsChecker.class); + UserDetailsChecker postAuthenticationChecker = mock(UserDetailsChecker.class); + + authenticationProvider.setForcePrincipalAsString(true); + assertThat(authenticationProvider.isForcePrincipalAsString()).isTrue(); + authenticationProvider.setHideCredentialIdNotFoundExceptions(true); + assertThat(authenticationProvider.isHideCredentialIdNotFoundExceptions()).isTrue(); + + authenticationProvider.setUserDetailsService(userDetailsService); + assertThat(authenticationProvider.getUserDetailsService()).isEqualTo(userDetailsService); + + authenticationProvider.setPreAuthenticationChecks(preAuthenticationChecker); + assertThat(authenticationProvider.getPreAuthenticationChecks()).isEqualTo(preAuthenticationChecker); + authenticationProvider.setPostAuthenticationChecks(postAuthenticationChecker); + assertThat(authenticationProvider.getPostAuthenticationChecks()).isEqualTo(postAuthenticationChecker); + + } + + @Test + public void userDetailsChecker_check_test() { + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + Authenticator authenticator = new AuthenticatorImpl(null, null, 0); + WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + Collections.singletonList(grantedAuthority)); + authenticationProvider.getPreAuthenticationChecks().check(userDetails); + } + + @Test(expected = DisabledException.class) + public void userDetailsChecker_check_with_disabled_userDetails_test() { + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + Authenticator authenticator = new AuthenticatorImpl(null, null, 0); + WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + true, + false, + true, + true, + true, + Collections.singletonList(grantedAuthority)); + authenticationProvider.getPreAuthenticationChecks().check(userDetails); + } + + @Test(expected = AccountExpiredException.class) + public void userDetailsChecker_check_with_expired_userDetails_test() { + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + Authenticator authenticator = new AuthenticatorImpl(null, null, 0); + WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + true, + true, + false, + true, + true, + Collections.singletonList(grantedAuthority)); + authenticationProvider.getPreAuthenticationChecks().check(userDetails); + } + + @Test(expected = CredentialsExpiredException.class) + public void userDetailsChecker_check_with_credentials_expired_userDetails_test() { + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + Authenticator authenticator = new AuthenticatorImpl(null, null, 0); + WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + true, + true, + true, + false, + true, + Collections.singletonList(grantedAuthority)); + authenticationProvider.getPostAuthenticationChecks().check(userDetails); + } + + @Test(expected = LockedException.class) + public void userDetailsChecker_check_with_locked_userDetails_test() { + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + Authenticator authenticator = new AuthenticatorImpl(null, null, 0); + WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + new byte[0], + "dummy", + "dummy", + Collections.singletonList(authenticator), + true, + true, + true, + true, + false, + Collections.singletonList(grantedAuthority)); + authenticationProvider.getPreAuthenticationChecks().check(userDetails); + } + + @Test + public void isUserVerificationRequired_test() { + WebAuthnUserDetails webAuthnUserDetails = mock(WebAuthnUserDetails.class); + when(webAuthnUserDetails.getUsername()).thenReturn("john.doe"); + WebAuthnAuthenticationRequest credentials = mock(WebAuthnAuthenticationRequest.class); + when(credentials.isUserVerificationRequired()).thenReturn(true); + SecurityContext securityContext = mock(SecurityContext.class); + Authentication authentication = mock(Authentication.class); + when(authentication.isAuthenticated()).thenReturn(true); + when(authentication.getName()).thenReturn("john.doe"); + when(securityContext.getAuthentication()).thenReturn(authentication); + SecurityContextHolder.setContext(securityContext); + assertThat(authenticationProvider.isUserVerificationRequired(webAuthnUserDetails, credentials)).isFalse(); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java new file mode 100644 index 00000000000..e679d4641ce --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import org.junit.Test; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Test for WebAuthnAuthenticationToken + */ +public class WebAuthnAuthenticationTokenTest { + + /** + * Verifies that constructor with 3 args yields authenticated authenticator. + */ + @Test + public void webAuthnAuthenticationToken() { + WebAuthnAuthenticationToken webAuthnAuthenticationToken = new WebAuthnAuthenticationToken(null, null, null); + assertThat(webAuthnAuthenticationToken.isAuthenticated()).isTrue(); + } + + /** + * Verifies that getter returns constructor parameters + */ + @Test + public void test_methods() { + WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationToken webAuthnAuthenticationToken = new WebAuthnAuthenticationToken("username", credential, null); + + assertThat(webAuthnAuthenticationToken.getPrincipal()).isEqualTo("username"); + assertThat(webAuthnAuthenticationToken.getCredentials()).isEqualTo(credential); + } + + @Test + public void equals_hashCode_test() { + WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationToken tokenA = new WebAuthnAuthenticationToken("username", credential, null); + WebAuthnAuthenticationToken tokenB = new WebAuthnAuthenticationToken("username", credential, null); + assertThat(tokenA).isEqualTo(tokenB); + assertThat(tokenA).hasSameHashCodeAs(tokenB); + } + + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java new file mode 100644 index 00000000000..15f6d1507d7 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java @@ -0,0 +1,260 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.data.extension.client.FIDOAppIDExtensionClientInput; +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.util.Base64UrlUtil; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; +import org.springframework.security.webauthn.server.ServerPropertyProvider; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +/** + * Test for WebAuthnProcessingFilter + */ +public class WebAuthnProcessingFilterTest { + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + private ServerPropertyProvider serverPropertyProvider; + private AuthenticationManager authenticationManager; + private MockHttpServletRequest mockHttpServletRequest; + private MockHttpServletResponse mockHttpServletResponse; + + @Spy + private WebAuthnProcessingFilter target; + + private ArgumentCaptor captor = ArgumentCaptor.forClass(Authentication.class); + + @Before + public void setup() { + serverPropertyProvider = mock(ServerPropertyProvider.class); + authenticationManager = mock(AuthenticationManager.class); + mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletResponse = new MockHttpServletResponse(); + + target.setAuthenticationManager(authenticationManager); + target.setServerPropertyProvider(serverPropertyProvider); + } + + @Test + public void attemptAuthentication_test_with_username_password() { + + mockHttpServletRequest.setMethod("POST"); + mockHttpServletRequest.setParameter("username", "username"); + mockHttpServletRequest.setParameter("password", "password"); + + when(authenticationManager.authenticate(captor.capture())).thenReturn(null); + target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); + + Authentication authenticationToken = captor.getValue(); + assertThat(authenticationToken.getPrincipal()).isEqualTo("username"); + assertThat(authenticationToken.getCredentials()).isEqualTo("password"); + + } + + @Test + public void attemptAuthentication_test_with_credential() { + + String credentialId = "AAhdofeLeQWG6Y6gwwytZKNCDFB1WaIgqDsOwVYR5UavKQhAti4ic9_Dz-_CQEPpN0To6hiDRSCvmFHXaG6HK5yvvhm4DJRVJXzSvZiq5NefbXSYIr2uUaKbsoBe1lulhNdL9dRt6Dkkp38uq02YIR5CDaoxD-HQgMsS667aWlhHVKE884Sq0d1VVgGTDb1ds-Py_H7CDqk9SDErb8-XtQ9L"; + String clientDataJSON = "eyJjaGFsbGVuZ2UiOiJGT3JHWklmSFJfeURaSklydTVPdXBBIiwiaGFzaEFsZyI6IlMyNTYiLCJvcmlnaW4iOiJsb2NhbGhvc3QifQ"; + String authenticatorData = "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABaQ"; + String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; + String clientExtensionsJSON = ""; + + ServerProperty serverProperty = mock(ServerProperty.class); + + + //Given + mockHttpServletRequest.setMethod("POST"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setParameter("credentialId", credentialId); + mockHttpServletRequest.setParameter("clientDataJSON", clientDataJSON); + mockHttpServletRequest.setParameter("authenticatorData", authenticatorData); + mockHttpServletRequest.setParameter("signature", signature); + mockHttpServletRequest.setParameter("clientExtensionsJSON", clientExtensionsJSON); + + when(authenticationManager.authenticate(captor.capture())).thenReturn(null); + when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + + //When + target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); + + //Then + WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); + verify(serverPropertyProvider).provide(mockHttpServletRequest); + assertThat(authenticationToken.getPrincipal()).isNull(); + assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationRequest.class); + assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); + assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); + assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); + assertThat(authenticationToken.getCredentials().getSignature()).isEqualTo(Base64UrlUtil.decode(signature)); + assertThat(authenticationToken.getCredentials().getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(authenticationToken.getCredentials().getServerProperty()).isEqualTo(serverProperty); + + } + + @Test + public void attemptAuthentication_test_with_get_method() { + + String credentialId = "AAhdofeLeQWG6Y6gwwytZKNCDFB1WaIgqDsOwVYR5UavKQhAti4ic9_Dz-_CQEPpN0To6hiDRSCvmFHXaG6HK5yvvhm4DJRVJXzSvZiq5NefbXSYIr2uUaKbsoBe1lulhNdL9dRt6Dkkp38uq02YIR5CDaoxD-HQgMsS667aWlhHVKE884Sq0d1VVgGTDb1ds-Py_H7CDqk9SDErb8-XtQ9L"; + String clientDataJSON = "eyJjaGFsbGVuZ2UiOiJGT3JHWklmSFJfeURaSklydTVPdXBBIiwiaGFzaEFsZyI6IlMyNTYiLCJvcmlnaW4iOiJsb2NhbGhvc3QifQ"; + String authenticatorData = "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABaQ"; + String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; + String clientExtensionsJSON = ""; + + ServerProperty serverProperty = mock(ServerProperty.class); + + //Given + target.setPostOnly(false); + mockHttpServletRequest.setMethod("GET"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setParameter("credentialId", credentialId); + mockHttpServletRequest.setParameter("clientDataJSON", clientDataJSON); + mockHttpServletRequest.setParameter("authenticatorData", authenticatorData); + mockHttpServletRequest.setParameter("signature", signature); + mockHttpServletRequest.setParameter("clientExtensionsJSON", clientExtensionsJSON); + + when(authenticationManager.authenticate(captor.capture())).thenReturn(null); + when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + + //When + target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); + + //Then + WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); + verify(serverPropertyProvider).provide(mockHttpServletRequest); + assertThat(authenticationToken.getPrincipal()).isNull(); + assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationRequest.class); + assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); + assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); + assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); + assertThat(authenticationToken.getCredentials().getSignature()).isEqualTo(Base64UrlUtil.decode(signature)); + assertThat(authenticationToken.getCredentials().getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(authenticationToken.getCredentials().getServerProperty()).isEqualTo(serverProperty); + + } + + + @Test + public void attemptAuthentication_test_with_customized_parameter() { + + String usernameParameter = "param_username"; + String passwordParameter = "param_password"; + String credentialIdParameter = "param_credentialId"; + String clientDataJSONParameter = "param_clientDataJSON"; + String authenticatorDataParameter = "param_authenticatorData"; + String signatureParameter = "param_signature"; + String clientExtensionsJSONParameter = "param_clientExtensionsJSON"; + + String credentialId = "AAhdofeLeQWG6Y6gwwytZKNCDFB1WaIgqDsOwVYR5UavKQhAti4ic9_Dz-_CQEPpN0To6hiDRSCvmFHXaG6HK5yvvhm4DJRVJXzSvZiq5NefbXSYIr2uUaKbsoBe1lulhNdL9dRt6Dkkp38uq02YIR5CDaoxD-HQgMsS667aWlhHVKE884Sq0d1VVgGTDb1ds-Py_H7CDqk9SDErb8-XtQ9L"; + String clientDataJSON = "eyJjaGFsbGVuZ2UiOiJGT3JHWklmSFJfeURaSklydTVPdXBBIiwiaGFzaEFsZyI6IlMyNTYiLCJvcmlnaW4iOiJsb2NhbGhvc3QifQ"; + String authenticatorData = "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABaQ"; + String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; + String clientExtensionsJSON = ""; + + ServerProperty serverProperty = mock(ServerProperty.class); + + //Given + target.setUsernameParameter(usernameParameter); + target.setPasswordParameter(passwordParameter); + target.setCredentialIdParameter(credentialIdParameter); + target.setClientDataJSONParameter(clientDataJSONParameter); + target.setAuthenticatorDataParameter(authenticatorDataParameter); + target.setSignatureParameter(signatureParameter); + target.setClientExtensionsJSONParameter(clientExtensionsJSONParameter); + target.setExpectedAuthenticationExtensionIds(Collections.singletonList(FIDOAppIDExtensionClientInput.ID)); + + mockHttpServletRequest.setMethod("POST"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setParameter(credentialIdParameter, credentialId); + mockHttpServletRequest.setParameter(clientDataJSONParameter, clientDataJSON); + mockHttpServletRequest.setParameter(authenticatorDataParameter, authenticatorData); + mockHttpServletRequest.setParameter(signatureParameter, signature); + mockHttpServletRequest.setParameter(clientExtensionsJSONParameter, clientExtensionsJSON); + + when(authenticationManager.authenticate(captor.capture())).thenReturn(null); + when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + + //When + target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); + + //Then + assertThat(target.getUsernameParameter()).isEqualTo(usernameParameter); + assertThat(target.getPasswordParameter()).isEqualTo(passwordParameter); + assertThat(target.getCredentialIdParameter()).isEqualTo(credentialIdParameter); + assertThat(target.getClientDataJSONParameter()).isEqualTo(clientDataJSONParameter); + assertThat(target.getAuthenticatorDataParameter()).isEqualTo(authenticatorDataParameter); + assertThat(target.getSignatureParameter()).isEqualTo(signatureParameter); + assertThat(target.getClientExtensionsJSONParameter()).isEqualTo(clientExtensionsJSONParameter); + assertThat(target.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList(FIDOAppIDExtensionClientInput.ID)); + assertThat(target.getServerPropertyProvider()).isEqualTo(serverPropertyProvider); + + + WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); + verify(serverPropertyProvider).provide(mockHttpServletRequest); + assertThat(authenticationToken.getPrincipal()).isNull(); + assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationRequest.class); + assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); + assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); + assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); + assertThat(authenticationToken.getCredentials().getSignature()).isEqualTo(Base64UrlUtil.decode(signature)); + assertThat(authenticationToken.getCredentials().getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(authenticationToken.getCredentials().getServerProperty()).isEqualTo(serverProperty); + + + } + + + @Test(expected = AuthenticationServiceException.class) + public void attemptAuthentication_test_with_wrong_port() { + + //Given + mockHttpServletRequest.setMethod("GET"); + when(authenticationManager.authenticate(captor.capture())).thenReturn(null); + + //When + target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); + } + + @Test + public void constructor_test() { + ServerPropertyProvider serverPropertyProvider = mock(ServerPropertyProvider.class); + WebAuthnProcessingFilter webAuthnProcessingFilter = new WebAuthnProcessingFilter(AuthorityUtils.NO_AUTHORITIES, serverPropertyProvider); + assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(serverPropertyProvider); + } + + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java new file mode 100644 index 00000000000..9de3ef32d06 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + + +import com.webauthn4j.data.attestation.AttestationObject; +import com.webauthn4j.data.client.ClientDataType; +import com.webauthn4j.data.client.CollectedClientData; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientOutputs; +import com.webauthn4j.test.TestDataUtil; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebAuthnRegistrationRequestValidationResponseTest { + + @Test + public void equals_hashCode_test() { + CollectedClientData clientData = TestDataUtil.createClientData(ClientDataType.CREATE); + AttestationObject attestationObject = TestDataUtil.createAttestationObjectWithFIDOU2FAttestationStatement(); + AuthenticationExtensionsClientOutputs clientExtensions = new AuthenticationExtensionsClientOutputs(); + WebAuthnRegistrationRequestValidationResponse instanceA = + new WebAuthnRegistrationRequestValidationResponse(clientData, attestationObject, clientExtensions); + WebAuthnRegistrationRequestValidationResponse instanceB = + new WebAuthnRegistrationRequestValidationResponse(clientData, attestationObject, clientExtensions); + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceB).hasSameHashCodeAs(instanceB); + } + + @Test + public void getter_test() { + CollectedClientData clientData = TestDataUtil.createClientData(ClientDataType.CREATE); + AttestationObject attestationObject = TestDataUtil.createAttestationObjectWithFIDOU2FAttestationStatement(); + AuthenticationExtensionsClientOutputs clientExtensions = new AuthenticationExtensionsClientOutputs(); + WebAuthnRegistrationRequestValidationResponse instance = + new WebAuthnRegistrationRequestValidationResponse(clientData, attestationObject, clientExtensions); + + assertThat(instance.getCollectedClientData()).isEqualTo(clientData); + assertThat(instance.getAttestationObject()).isEqualTo(attestationObject); + assertThat(instance.getRegistrationExtensionsClientOutputs()).isEqualTo(clientExtensions); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java new file mode 100644 index 00000000000..1d210c93653 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java @@ -0,0 +1,162 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.data.WebAuthnRegistrationContext; +import com.webauthn4j.data.attestation.AttestationObject; +import com.webauthn4j.data.client.CollectedClientData; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientOutputs; +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.util.Base64UrlUtil; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidationResponse; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.webauthn.exception.BadAttestationStatementException; +import org.springframework.security.webauthn.server.ServerPropertyProvider; + +import java.util.Collections; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Test for WebAuthnRegistrationContextValidator + */ +public class WebAuthnRegistrationRequestValidatorTest { + + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private WebAuthnRegistrationContextValidator registrationContextValidator; + + @Mock + private ServerPropertyProvider serverPropertyProvider; + + + @Test + public void validate_test() { + WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( + registrationContextValidator, serverPropertyProvider + ); + + ServerProperty serverProperty = mock(ServerProperty.class); + when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + + CollectedClientData collectedClientData = mock(CollectedClientData.class); + AttestationObject attestationObject = mock(AttestationObject.class); + AuthenticationExtensionsClientOutputs clientExtensionOutputs = new AuthenticationExtensionsClientOutputs(); + when(registrationContextValidator.validate(any())).thenReturn( + new WebAuthnRegistrationContextValidationResponse(collectedClientData, attestationObject, clientExtensionOutputs)); + + MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest.setScheme("https"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setServerPort(443); + String clientDataBase64 = "clientDataBase64"; + String attestationObjectBase64 = "attestationObjectBase64"; + Set transports = Collections.emptySet(); + String clientExtensionsJSON = "clientExtensionsJSON"; + + target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationContext.class); + verify(registrationContextValidator).validate(argumentCaptor.capture()); + WebAuthnRegistrationContext registrationContext = argumentCaptor.getValue(); + + assertThat(registrationContext.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); + assertThat(registrationContext.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); + assertThat(registrationContext.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(registrationContext.getServerProperty()).isEqualTo(serverProperty); + assertThat(registrationContext.getExpectedExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); + } + + @Test + public void validate_with_transports_null_test() { + WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( + registrationContextValidator, serverPropertyProvider + ); + + ServerProperty serverProperty = mock(ServerProperty.class); + when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + + CollectedClientData collectedClientData = mock(CollectedClientData.class); + AttestationObject attestationObject = mock(AttestationObject.class); + AuthenticationExtensionsClientOutputs clientExtensionOutputs = new AuthenticationExtensionsClientOutputs(); + when(registrationContextValidator.validate(any())).thenReturn( + new WebAuthnRegistrationContextValidationResponse(collectedClientData, attestationObject, clientExtensionOutputs)); + + MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest.setScheme("https"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setServerPort(443); + String clientDataBase64 = "clientDataBase64"; + String attestationObjectBase64 = "attestationObjectBase64"; + String clientExtensionsJSON = "clientExtensionsJSON"; + + target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, null, clientExtensionsJSON); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationContext.class); + verify(registrationContextValidator).validate(argumentCaptor.capture()); + WebAuthnRegistrationContext registrationContext = argumentCaptor.getValue(); + + assertThat(registrationContext.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); + assertThat(registrationContext.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); + assertThat(registrationContext.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(registrationContext.getServerProperty()).isEqualTo(serverProperty); + assertThat(registrationContext.getExpectedExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); + } + + + @Test + public void getter_setter_test() { + WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( + registrationContextValidator, serverPropertyProvider + ); + target.setExpectedRegistrationExtensionIds(Collections.singletonList("appId")); + assertThat(target.getExpectedRegistrationExtensionIds()).containsExactly("appId"); + + } + + @Test(expected = BadAttestationStatementException.class) + public void validate_caught_exception_test() { + WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( + registrationContextValidator, serverPropertyProvider + ); + when(registrationContextValidator.validate(any())).thenThrow(new com.webauthn4j.validator.exception.BadAttestationStatementException("dummy")); + + MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest.setScheme("https"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setServerPort(443); + String clientDataBase64 = "clientDataBase64"; + String attestationObjectBase64 = "attestationObjectBase64"; + Set transports = Collections.emptySet(); + String clientExtensionsJSON = "clientExtensionsJSON"; + + target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON); + + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java new file mode 100644 index 00000000000..9e8de4218dd --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.authenticator; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebAuthnAuthenticatorTest { + + @Test + public void equals_hashCode_test() { + WebAuthnAuthenticator instanceA = new WebAuthnAuthenticator("authenticator", null, null, 0); + WebAuthnAuthenticator instanceB = new WebAuthnAuthenticator("authenticator", null, null, 0); + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceA).hasSameHashCodeAs(instanceB); + } + + @Test + public void get_set_name_test() { + WebAuthnAuthenticator instance = new WebAuthnAuthenticator("authenticator", null, null, 0); + assertThat(instance.getName()).isEqualTo("authenticator"); + instance.setName("newName"); + assertThat(instance.getName()).isEqualTo("newName"); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java new file mode 100644 index 00000000000..71db1013bc0 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.challenge; + +import com.webauthn4j.data.client.challenge.Challenge; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; + +import javax.servlet.http.HttpSession; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for HttpSessionChallengeRepository + */ +public class HttpSessionChallengeRepositoryTest { + + private HttpSessionChallengeRepository target = new HttpSessionChallengeRepository(); + + @Test + public void generateChallenge_test() { + Challenge challenge = target.generateChallenge(); + assertThat(challenge).isNotNull(); + assertThat(challenge.getValue()).hasSize(16); + } + + @Test + public void saveChallenge_test() { + MockHttpServletRequest request = new MockHttpServletRequest(); + String attrName = ".test-challenge"; + + target.setSessionAttributeName(attrName); + Challenge challenge = target.generateChallenge(); + target.saveChallenge(challenge, request); + + HttpSession session = request.getSession(); + assertThat((Challenge) session.getAttribute(attrName)).isEqualTo(challenge); + } + + @Test + public void saveChallenge_test_with_null() { + MockHttpSession session = new MockHttpSession(); + MockHttpServletRequest prevRequest = new MockHttpServletRequest(); + prevRequest.setSession(session); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setSession(session); + + Challenge challenge = target.generateChallenge(); + target.saveChallenge(challenge, prevRequest); + target.saveChallenge(null, request); + Challenge loadedChallenge = target.loadChallenge(request); + + assertThat(loadedChallenge).isNull(); + } + + @Test + public void saveChallenge_test_without_prev_request() { + MockHttpServletRequest request = new MockHttpServletRequest(); + + target.saveChallenge(null, request); + Challenge loadedChallenge = target.loadChallenge(request); + + assertThat(loadedChallenge).isNull(); + } + + + @Test + public void loadChallenge_test() { + MockHttpSession session = new MockHttpSession(); + MockHttpServletRequest prevRequest = new MockHttpServletRequest(); + prevRequest.setSession(session); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setSession(session); + String attrName = ".test-challenge"; + + target.setSessionAttributeName(attrName); + Challenge challenge = target.generateChallenge(); + target.saveChallenge(challenge, prevRequest); + Challenge loadedChallenge = target.loadChallenge(request); + + assertThat(loadedChallenge).isEqualTo(challenge); + } + + @Test + public void loadChallenge_test_without_previous_request() { + MockHttpServletRequest request = new MockHttpServletRequest(); + + Challenge loadedChallenge = target.loadChallenge(request); + + assertThat(loadedChallenge).isNull(); + } + + @Test + public void loadOrGenerateChallenge_test_without_previous_request() { + MockHttpServletRequest request = new MockHttpServletRequest(); + + Challenge loadedChallenge = target.loadOrGenerateChallenge(request); + + assertThat(loadedChallenge).isNotNull(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java new file mode 100644 index 00000000000..f2725535f6d --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.config.configurers; + +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.webauthn.WebAuthnAuthenticationProvider; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.challenge.HttpSessionChallengeRepository; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.options.OptionsProviderImpl; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; + +@RunWith(SpringRunner.class) +public class WebAuthnAuthenticationProviderConfigurerSpringTest { + + @Autowired + ProviderManager providerManager; + + @Test + public void test() { + assertThat(providerManager.getProviders()).extracting("class").contains(WebAuthnAuthenticationProvider.class); + } + + + @EnableWebSecurity + static class Config extends WebSecurityConfigurerAdapter { + + @Autowired + private WebAuthnUserDetailsService webAuthnUserDetailsService; + + @Autowired + private WebAuthnAuthenticatorService webAuthnAuthenticatorService; + + @Configuration + static class BeanConfig { + @Bean + public WebAuthnUserDetailsService webAuthnUserDetailsService(){ + return mock(WebAuthnUserDetailsService.class); + } + + @Bean + public WebAuthnAuthenticatorService webAuthnAuthenticatorService(){ + return mock(WebAuthnAuthenticatorService.class); + } + + @Bean + public ChallengeRepository challengeRepository() { + return new HttpSessionChallengeRepository(); + } + + @Bean + public OptionsProvider optionsProvider(WebAuthnUserDetailsService webAuthnUserDetailsService, ChallengeRepository challengeRepository) { + OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(webAuthnUserDetailsService, challengeRepository); + optionsProviderImpl.setRpId("example.com"); + return optionsProviderImpl; + } + + @Bean + public ServerPropertyProvider serverPropertyProvider(OptionsProvider optionsProvider, ChallengeRepository challengeRepository) { + return new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + } + + + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManager(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + // Authentication + http.apply(webAuthnLogin()); + + // Authorization + http.authorizeRequests() + .antMatchers("/login").permitAll() + .anyRequest().authenticated(); + } + + @Override + public void configure(AuthenticationManagerBuilder builder) throws Exception { + builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(webAuthnUserDetailsService, webAuthnAuthenticatorService, new WebAuthnAuthenticationContextValidator())); + } + + } + + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java new file mode 100644 index 00000000000..5e619e3e739 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.config.configurers; + + +import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.test.TestDataUtil; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.webauthn.WebAuthnProcessingFilter; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.options.OptionsProviderImpl; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Collection; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; + +@RunWith(SpringRunner.class) +public class WebAuthnLoginConfigurerSetterSpringTest { + + @Autowired + FilterChainProxy springSecurityFilterChain; + + private MockMvc mvc; + + @Autowired + private WebAuthnUserDetailsService webAuthnUserDetailsService; + + @Autowired + private ServerPropertyProvider serverPropertyProvider; + + @SuppressWarnings("unchecked") + @Before + public void setup() { + WebAuthnUserDetails mockUserDetails = mock(WebAuthnUserDetails.class); + Collection authenticators = Collections.singletonList(TestDataUtil.createAuthenticator()); + when(mockUserDetails.getAuthenticators()).thenReturn(authenticators); + when(mockUserDetails.getUserHandle()).thenReturn(new byte[32]); + doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadUserByUsername(null); + when(webAuthnUserDetailsService.loadUserByUsername(anyString())).thenReturn(mockUserDetails); + } + + @Test + public void configured_filter_test() { + WebAuthnProcessingFilter webAuthnProcessingFilter = (WebAuthnProcessingFilter) springSecurityFilterChain.getFilterChains().get(0).getFilters().stream().filter(item -> item instanceof WebAuthnProcessingFilter).findFirst().orElse(null); + assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(serverPropertyProvider); + } + + @EnableWebSecurity + static class Config extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + + // Authentication + http.apply(webAuthnLogin()); + + // Authorization + http.authorizeRequests() + .antMatchers("/login").permitAll() + .anyRequest().authenticated(); + } + + @Configuration + static class BeanConfig { + + @Bean + public WebAuthnUserDetailsService webAuthnUserDetailsService(){ + return mock(WebAuthnUserDetailsService.class); + } + + @Bean + public JsonConverter jsonConverter() { + return new JsonConverter(); + } + + @Bean + public ChallengeRepository challengeRepository() { + ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + when(challengeRepository.loadOrGenerateChallenge(any())).thenReturn(new DefaultChallenge("aFglXMZdQTKD4krvNzJBzA")); + return challengeRepository; + } + + @Bean + public OptionsProvider optionsProvider(WebAuthnUserDetailsService webAuthnUserDetailsService, ChallengeRepository challengeRepository) { + OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(webAuthnUserDetailsService, challengeRepository); + optionsProviderImpl.setRpId("example.com"); + return optionsProviderImpl; + } + + @Bean + public ServerPropertyProvider serverPropertyProvider(OptionsProvider optionsProvider, ChallengeRepository challengeRepository) { + return new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + } + + } + + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java new file mode 100644 index 00000000000..3155c12de79 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java @@ -0,0 +1,229 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.config.configurers; + + +import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.data.extension.client.FIDOAppIDExtensionClientInput; +import com.webauthn4j.data.extension.client.SupportedExtensionsExtensionClientInput; +import com.webauthn4j.test.TestDataUtil; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.webauthn.WebAuthnProcessingFilter; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Collection; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; +import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +public class WebAuthnLoginConfigurerSpringTest { + + @Autowired + FilterChainProxy springSecurityFilterChain; + + private MockMvc mvc; + + @Autowired + private WebAuthnUserDetailsService webAuthnUserDetailsService; + + @SuppressWarnings("unchecked") + @Before + public void setup() { + WebAuthnUserDetails mockUserDetails = mock(WebAuthnUserDetails.class); + Collection authenticators = Collections.singletonList(TestDataUtil.createAuthenticator()); + when(mockUserDetails.getAuthenticators()).thenReturn(authenticators); + when(mockUserDetails.getUserHandle()).thenReturn(new byte[32]); + doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadUserByUsername(null); + when(webAuthnUserDetailsService.loadUserByUsername(anyString())).thenReturn(mockUserDetails); + } + + @Test + public void configured_filter_test() { + WebAuthnProcessingFilter webAuthnProcessingFilter = (WebAuthnProcessingFilter) springSecurityFilterChain.getFilterChains().get(0).getFilters().stream().filter(item -> item instanceof WebAuthnProcessingFilter).findFirst().orElse(null); + assertThat(webAuthnProcessingFilter).isNotNull(); + } + + + @Test + public void rootPath_with_anonymous_user_test() throws Exception { + mvc = MockMvcBuilders.standaloneSetup() + .addFilter(springSecurityFilterChain) + .build(); + + mvc + .perform(get("/").with(anonymous())) + .andExpect(unauthenticated()) + .andExpect(status().is3xxRedirection()); + } + + @Test + public void attestationOptionsEndpointPath_with_anonymous_user_test() throws Exception { + mvc = MockMvcBuilders.standaloneSetup() + .addFilter(springSecurityFilterChain) + .build(); + + mvc + .perform(get("/webauthn/attestation/options").with(anonymous())) + .andExpect(unauthenticated()) + .andExpect(content().json("{\"rp\":{\"name\":\"example\",\"icon\":\"dummy\",\"id\":\"example.com\"},\"challenge\":\"aFglXMZdQTKD4krvNzJBzA\",\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7},{\"type\":\"public-key\",\"alg\":-65535}],\"timeout\":10000,\"excludeCredentials\":[],\"authenticatorSelection\":{\"requireResidentKey\":false,\"userVerification\":\"preferred\"},\"extensions\":{\"exts\":true}}")) + .andExpect(status().isOk()); + } + + @Test + public void assertionOptionsEndpointPath_with_anonymous_user_test() throws Exception { + mvc = MockMvcBuilders.standaloneSetup() + .addFilter(springSecurityFilterChain) + .build(); + + mvc + .perform(get("/webauthn/assertion/options").with(anonymous())) + .andExpect(unauthenticated()) + .andExpect(content().json("{\"challenge\":\"aFglXMZdQTKD4krvNzJBzA\",\"timeout\":20000,\"rpId\":\"example.com\",\"allowCredentials\":[],\"extensions\":{\"appid\":\"\"}}")) + .andExpect(status().isOk()); + } + + @Test + public void rootPath_with_authenticated_user_test() throws Exception { + mvc = MockMvcBuilders.standaloneSetup() + .defaultRequest(get("/").with(user("john"))) + .addFilter(springSecurityFilterChain) + .build(); + + mvc + .perform(get("/")) + .andExpect(authenticated()) + .andExpect(status().isNotFound()); + + } + + @Test + public void assertionOptionsEndpointPath_with_authenticated_user_test() throws Exception { + mvc = MockMvcBuilders.standaloneSetup() + .addFilter(springSecurityFilterChain) + .build(); + + mvc + .perform(get("/webauthn/assertion/options").with(user("john"))) + .andExpect(authenticated()) + .andExpect(content().json("{\"challenge\":\"aFglXMZdQTKD4krvNzJBzA\",\"timeout\":20000,\"rpId\":\"example.com\",\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}],\"extensions\":{\"appid\":\"\"}}")) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class Config extends WebSecurityConfigurerAdapter { + + @Autowired + private JsonConverter jsonConverter; + + @Override + protected void configure(HttpSecurity http) throws Exception { + + // Authentication + http.apply(webAuthnLogin()) + .rpId("example.com") + .rpIcon("dummy") + .rpName("example") + .publicKeyCredParams() + .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256) + .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.RS1) + .and() + .registrationTimeout(10000L) + .authenticationTimeout(20000L) + .registrationExtensions() + .put(new SupportedExtensionsExtensionClientInput(true)) + .and() + .authenticationExtensions() + .put(new FIDOAppIDExtensionClientInput("")) + .and() + .usernameParameter("username") + .passwordParameter("password") + .credentialIdParameter("credentialId") + .clientDataJSONParameter("clientDataJSON") + .authenticatorDataParameter("authenticatorData") + .signatureParameter("signature") + .clientExtensionsJSONParameter("clientExtensionsJSON") + .successForwardUrl("/") + .failureForwardUrl("/login") + .loginPage("/login") + .attestationOptionsEndpoint() + .processingUrl("/webauthn/attestation/options") + .and() + .assertionOptionsEndpoint() + .processingUrl("/webauthn/assertion/options") + .and() + .jsonConverter(jsonConverter); + + // Authorization + http.authorizeRequests() + .antMatchers("/login").permitAll() + .anyRequest().authenticated(); + } + + @Configuration + static class BeanConfig { + + @Bean + public WebAuthnUserDetailsService webAuthnUserDetailsService(){ + return mock(WebAuthnUserDetailsService.class); + } + + @Bean + public JsonConverter jsonConverter() { + return new JsonConverter(); + } + + @Bean + public ChallengeRepository challengeRepository() { + ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + when(challengeRepository.loadOrGenerateChallenge(any())).thenReturn(new DefaultChallenge("aFglXMZdQTKD4krvNzJBzA")); + return challengeRepository; + } + + } + + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java new file mode 100644 index 00000000000..8938dc64d89 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import com.webauthn4j.converter.util.JsonConverter; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.*; +import org.springframework.security.webauthn.options.AttestationOptions; +import org.springframework.security.webauthn.options.OptionsProvider; + +import javax.servlet.ServletException; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.security.webauthn.endpoint.AttestationOptionsEndpointFilter.FILTER_URL; + +public class AttestationOptionsEndpointFilterTest { + + private JsonConverter jsonConverter = new JsonConverter(); + + @Test + public void getter_setter_test() { + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(mock(OptionsProvider.class), jsonConverter); + MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + attestationOptionsEndpointFilter.setMFATokenEvaluator(mfaTokenEvaluator); + attestationOptionsEndpointFilter.setTrustResolver(trustResolver); + assertThat(attestationOptionsEndpointFilter.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator); + assertThat(attestationOptionsEndpointFilter.getTrustResolver()).isEqualTo(trustResolver); + } + + @Test + public void afterPropertiesSet_test() { + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(mock(OptionsProvider.class), jsonConverter); + assertThatCode(attestationOptionsEndpointFilter::afterPropertiesSet).doesNotThrowAnyException(); + } + + @Test + public void doFilter_test() throws IOException, ServletException { + OptionsProvider optionsProvider = mock(OptionsProvider.class); + AttestationOptions attestationOptions = + new AttestationOptions(null, null, null, null, null, null, null, null, null); + when(optionsProvider.getAttestationOptions(any(), any(), any())).thenReturn(attestationOptions); + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); + MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + attestationOptionsEndpointFilter.setMFATokenEvaluator(mfaTokenEvaluator); + attestationOptionsEndpointFilter.setTrustResolver(trustResolver); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI(FILTER_URL); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + + attestationOptionsEndpointFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + public void doFilter_with_error_test() throws IOException, ServletException { + OptionsProvider optionsProvider = mock(OptionsProvider.class); + doThrow(new RuntimeException()).when(optionsProvider).getAttestationOptions(any(), any(), any()); + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); + MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); + AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + attestationOptionsEndpointFilter.setMFATokenEvaluator(mfaTokenEvaluator); + attestationOptionsEndpointFilter.setTrustResolver(trustResolver); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI(FILTER_URL); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockFilterChain filterChain = new MockFilterChain(); + + attestationOptionsEndpointFilter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); + } + + @Test + public void writeErrorResponse_with_RuntimeException_test() throws IOException { + OptionsProvider optionsProvider = mock(OptionsProvider.class); + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); + + MockHttpServletResponse response = new MockHttpServletResponse(); + RuntimeException exception = new RuntimeException(); + attestationOptionsEndpointFilter.writeErrorResponse(response, exception); + assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The server encountered an internal error\"}"); + } + + @Test + public void writeErrorResponse_with_InsufficientAuthenticationException_test() throws IOException { + OptionsProvider optionsProvider = mock(OptionsProvider.class); + AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); + + MockHttpServletResponse response = new MockHttpServletResponse(); + InsufficientAuthenticationException exception = new InsufficientAuthenticationException(null); + attestationOptionsEndpointFilter.writeErrorResponse(response, exception); + assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"Anonymous access is prohibited\"}"); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java new file mode 100644 index 00000000000..c5db3668a92 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ErrorResponseTest { + + @Test + public void constructor_test() { + ErrorResponse errorResponse = new ErrorResponse("message"); + assertThat(errorResponse.getErrorMessage()).isEqualTo("message"); + } + + @Test + public void equals_hashCode_test() { + ErrorResponse instanceA = new ErrorResponse("message"); + ErrorResponse instanceB = new ErrorResponse("message"); + + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceA).hasSameHashCodeAs(instanceB); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java new file mode 100644 index 00000000000..8c2cabbb49f --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + + +import com.webauthn4j.data.PublicKeyCredentialType; +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class WebAuthnPublicKeyCredentialDescriptorTest { + + @Test + public void constructor_test() { + WebAuthnPublicKeyCredentialDescriptor descriptor = new WebAuthnPublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, ""); + assertThat(descriptor.getType()).isEqualTo(PublicKeyCredentialType.PUBLIC_KEY); + assertThat(descriptor.getId()).isEqualTo(""); + } + + @Test + public void equals_hashCode_test() { + WebAuthnPublicKeyCredentialDescriptor instanceA = new WebAuthnPublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, ""); + WebAuthnPublicKeyCredentialDescriptor instanceB = new WebAuthnPublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, ""); + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceA).hasSameHashCodeAs(instanceB); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java new file mode 100644 index 00000000000..223f675db5d --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.endpoint; + + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebAuthnPublicKeyCredentialUserEntityTest { + + @Test + public void equals_hashCode_test() { + WebAuthnPublicKeyCredentialUserEntity instanceA = new WebAuthnPublicKeyCredentialUserEntity("", "john", "dummy", "dummy"); + WebAuthnPublicKeyCredentialUserEntity instanceB = new WebAuthnPublicKeyCredentialUserEntity("", "john", "dummy", "dummy"); + + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceA).hasSameHashCodeAs(instanceB); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java new file mode 100644 index 00000000000..429ddd5a28f --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadAaguidExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new BadAaguidException("dummy", cause); + new BadAaguidException("dummy"); + }).doesNotThrowAnyException(); + + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java new file mode 100644 index 00000000000..a1ef55b27f5 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadAlgorithmExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new BadAlgorithmException("dummy", cause); + new BadAlgorithmException("dummy"); + }).doesNotThrowAnyException(); + + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java new file mode 100644 index 00000000000..635cea4f7b9 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadAttestationStatementExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new BadAttestationStatementException("dummy", cause); + new BadAttestationStatementException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java new file mode 100644 index 00000000000..43a1584f824 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadChallengeExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new BadChallengeException("dummy", cause); + new BadChallengeException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java new file mode 100644 index 00000000000..ee69e1101a0 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadCredentialIdExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new BadCredentialIdException("dummy", cause); + new BadCredentialIdException("dummy"); + }).doesNotThrowAnyException(); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java new file mode 100644 index 00000000000..f0686fe8025 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadOriginExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new BadOriginException("dummy", cause); + new BadOriginException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java new file mode 100644 index 00000000000..c2f28eea882 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadRpIdExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new BadRpIdException("dummy", cause); + new BadRpIdException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java new file mode 100644 index 00000000000..4ba32976523 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class BadSignatureExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new BadSignatureException("dummy", cause); + new BadSignatureException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java new file mode 100644 index 00000000000..60746e19302 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class CertificateExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new CertificateException("dummy", cause); + new CertificateException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java new file mode 100644 index 00000000000..ecc7f744d36 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class ConstraintViolationExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new ConstraintViolationException("dummy", cause); + new ConstraintViolationException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java new file mode 100644 index 00000000000..6299c67c0e7 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class CredentialIdNotFoundExceptionTest { + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new CredentialIdNotFoundException("dummy", cause); + new CredentialIdNotFoundException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java new file mode 100644 index 00000000000..29d739ac676 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class DataConversionExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new DataConversionException("dummy", cause); + new DataConversionException("dummy"); + }).doesNotThrowAnyException(); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java new file mode 100644 index 00000000000..3cc91492c95 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class KeyDescriptionValidationExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new KeyDescriptionValidationException("dummy", cause); + new KeyDescriptionValidationException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java new file mode 100644 index 00000000000..e621e03ed0b --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class MaliciousCounterValueExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new MaliciousCounterValueException("dummy", cause); + new MaliciousCounterValueException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java new file mode 100644 index 00000000000..b2cbbf7ff0d --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class MaliciousDataExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new MaliciousDataException("dummy", cause); + new MaliciousDataException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java new file mode 100644 index 00000000000..b2774ef2702 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class MissingChallengeExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new MissingChallengeException("dummy", cause); + new MissingChallengeException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java new file mode 100644 index 00000000000..916f2f919c5 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class OptionsExceptionTest { + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new MetadataException("dummy", cause); + new MetadataException("dummy"); + new MetadataException(cause); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java new file mode 100644 index 00000000000..b219de53d9f --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class PublicKeyMismatchExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new PublicKeyMismatchException("dummy", cause); + new PublicKeyMismatchException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java new file mode 100644 index 00000000000..8873a044aea --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class SelfAttestationProhibitedExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + assertThatCode(() -> { + new SelfAttestationProhibitedException("dummy", cause); + new SelfAttestationProhibitedException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java new file mode 100644 index 00000000000..b8b7571cd98 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class TokenBindingExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new TokenBindingException("dummy", cause); + new TokenBindingException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java new file mode 100644 index 00000000000..285e70747ed --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class TrustAnchorNotFoundExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new TrustAnchorNotFoundException("dummy", cause); + new TrustAnchorNotFoundException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java new file mode 100644 index 00000000000..944638ff0e5 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class UnexpectedExtensionExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new UnexpectedExtensionException("dummy", cause); + new UnexpectedExtensionException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java new file mode 100644 index 00000000000..1bb5d55a91d --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class UserNotPresentExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new UserNotPresentException("dummy", cause); + new UserNotPresentException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java new file mode 100644 index 00000000000..9ecd822ba9e --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class UserNotVerifiedExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new UserNotVerifiedException("dummy", cause); + new UserNotVerifiedException("dummy"); + }).doesNotThrowAnyException(); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java new file mode 100644 index 00000000000..1815566d458 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.exception; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThatCode; + +@SuppressWarnings("ThrowableNotThrown") +public class WebAuthnAuthenticationExceptionTest { + + private RuntimeException cause = new RuntimeException(); + + @Test + public void test() { + + assertThatCode(() -> { + new WebAuthnAuthenticationException("dummy", cause); + new WebAuthnAuthenticationException("dummy"); + }).doesNotThrowAnyException(); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java new file mode 100644 index 00000000000..a0610a5b1fa --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.PublicKeyCredentialDescriptor; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AssertionOptionsTest { + + @Test + public void equals_hashCode_test() { + String rpId = "rpId"; + Challenge challenge = new DefaultChallenge(); + Long authenticationTimeout = 1000L; + List credentialIds = Collections.singletonList(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, new byte[32], null)); + AuthenticationExtensionsClientInputs authenticationExtensionsClientInputs = new AuthenticationExtensionsClientInputs<>(); + + AssertionOptions instanceA = + new AssertionOptions(challenge, authenticationTimeout, rpId, credentialIds, authenticationExtensionsClientInputs); + AssertionOptions instanceB = + new AssertionOptions(challenge, authenticationTimeout, rpId, credentialIds, authenticationExtensionsClientInputs); + + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceA).hasSameHashCodeAs(instanceB); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java new file mode 100644 index 00000000000..85914b79250 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + +import com.webauthn4j.data.*; +import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; +import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; +import com.webauthn4j.data.extension.client.RegistrationExtensionClientInput; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AttestationOptionsTest { + + @Test + public void equals_hashCode_test() { + PublicKeyCredentialRpEntity rpEntity = new PublicKeyCredentialRpEntity("rpId", "rpName", "rpIcon"); + PublicKeyCredentialUserEntity userEntity = new PublicKeyCredentialUserEntity("userHandle".getBytes(), "username", null); + Challenge challenge = new DefaultChallenge(); + List pubKeyCredParams = Collections.singletonList(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256)); + Long registrationTimeout = 1000L; + Long authenticationTimeout = 1000L; + List credentialIds = Collections.singletonList(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, new byte[32], null)); + AuthenticatorSelectionCriteria authenticatorSelection = new AuthenticatorSelectionCriteria(null, false, UserVerificationRequirement.PREFERRED); + AttestationConveyancePreference attestation = AttestationConveyancePreference.NONE; + AuthenticationExtensionsClientInputs registrationExtensionsClientInputs = new AuthenticationExtensionsClientInputs<>(); + AuthenticationExtensionsClientInputs authenticationExtensionsClientInputs = new AuthenticationExtensionsClientInputs<>(); + AttestationOptions instanceA = + new AttestationOptions(rpEntity, userEntity, challenge, pubKeyCredParams, registrationTimeout, + credentialIds, authenticatorSelection, attestation, registrationExtensionsClientInputs); + AttestationOptions instanceB = + new AttestationOptions(rpEntity, userEntity, challenge, pubKeyCredParams, registrationTimeout, + credentialIds, authenticatorSelection, attestation, registrationExtensionsClientInputs); + + assertThat(instanceA).isEqualTo(instanceB); + assertThat(instanceA).hasSameHashCodeAs(instanceB); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java new file mode 100644 index 00000000000..75c964be40f --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.options; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.data.PublicKeyCredentialDescriptor; +import com.webauthn4j.data.PublicKeyCredentialParameters; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import org.assertj.core.util.Lists; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class OptionsProviderImplTest { + + @Test + public void getAttestationOptions_test() { + Challenge challenge = new DefaultChallenge(); + byte[] credentialId = new byte[]{0x01, 0x23, 0x45}; + WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); + WebAuthnUserDetails userDetails = mock(WebAuthnUserDetails.class); + Authenticator authenticator = mock(Authenticator.class, RETURNS_DEEP_STUBS); + List authenticators = Collections.singletonList(authenticator); + ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + + when(userDetailsService.loadUserByUsername(any())).thenReturn(userDetails); + doReturn(new byte[0]).when(userDetails).getUserHandle(); + doReturn(authenticators).when(userDetails).getAuthenticators(); + when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + when(challengeRepository.loadOrGenerateChallenge(mockRequest)).thenReturn(challenge); + + OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(userDetailsService, challengeRepository); + optionsProviderImpl.setRpId("example.com"); + optionsProviderImpl.setRpName("rpName"); + optionsProviderImpl.setRpIcon("data://dummy"); + + AttestationOptions attestationOptions = optionsProviderImpl.getAttestationOptions(mockRequest, "dummy", null); + assertThat(attestationOptions.getRp().getId()).isEqualTo("example.com"); + assertThat(attestationOptions.getRp().getName()).isEqualTo("rpName"); + assertThat(attestationOptions.getRp().getIcon()).isEqualTo("data://dummy"); + assertThat(attestationOptions.getChallenge()).isEqualTo(challenge); + assertThat(attestationOptions.getExcludeCredentials()).containsExactly(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, credentialId, null)); + + } + + @Test + public void getAttestationOptions_with_challenge_test() { + Challenge challenge = new DefaultChallenge(); + byte[] credentialId = new byte[]{0x01, 0x23, 0x45}; + WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); + WebAuthnUserDetails userDetails = mock(WebAuthnUserDetails.class); + Authenticator authenticator = mock(Authenticator.class, RETURNS_DEEP_STUBS); + List authenticators = Collections.singletonList(authenticator); + ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + + when(userDetailsService.loadUserByUsername(any())).thenReturn(userDetails); + doReturn(new byte[0]).when(userDetails).getUserHandle(); + doReturn(authenticators).when(userDetails).getAuthenticators(); + when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + + OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(userDetailsService, challengeRepository); + optionsProviderImpl.setRpId("example.com"); + optionsProviderImpl.setRpName("rpName"); + optionsProviderImpl.setRpIcon("data://dummy"); + + AttestationOptions attestationOptions = optionsProviderImpl.getAttestationOptions(mockRequest, "dummy", challenge); + assertThat(attestationOptions.getRp().getId()).isEqualTo("example.com"); + assertThat(attestationOptions.getRp().getName()).isEqualTo("rpName"); + assertThat(attestationOptions.getRp().getIcon()).isEqualTo("data://dummy"); + assertThat(attestationOptions.getChallenge()).isEqualTo(challenge); + assertThat(attestationOptions.getExcludeCredentials()).containsExactly(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, credentialId, null)); + + } + + @Test + public void getEffectiveRpId() { + WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); + ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + OptionsProviderImpl optionsProvider = new OptionsProviderImpl(userDetailsService, challengeRepository); + optionsProvider.setRpId(null); + MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); + httpServletRequest.setScheme("https"); + httpServletRequest.setServerName("example.com"); + httpServletRequest.setServerPort(8080); + assertThat(optionsProvider.getEffectiveRpId(httpServletRequest)).isEqualTo("example.com"); + + } + + @Test + public void getter_setter_test() { + WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); + ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + OptionsProviderImpl optionsProvider = new OptionsProviderImpl(userDetailsService, challengeRepository); + + optionsProvider.setRpId("example.com"); + assertThat(optionsProvider.getRpId()).isEqualTo("example.com"); + optionsProvider.setRpName("example"); + assertThat(optionsProvider.getRpName()).isEqualTo("example"); + optionsProvider.setRpIcon("data://dummy"); + assertThat(optionsProvider.getRpIcon()).isEqualTo("data://dummy"); + List publicKeyCredParams = Lists.emptyList(); + optionsProvider.setPubKeyCredParams(publicKeyCredParams); + assertThat(optionsProvider.getPubKeyCredParams()).isEqualTo(publicKeyCredParams); + optionsProvider.setRegistrationTimeout(10000L); + assertThat(optionsProvider.getRegistrationTimeout()).isEqualTo(10000L); + optionsProvider.setAuthenticationTimeout(20000L); + assertThat(optionsProvider.getAuthenticationTimeout()).isEqualTo(20000L); + assertThat(optionsProvider.getRegistrationExtensions()).isEqualTo(new RegistrationExtensionsOptionProvider()); + assertThat(optionsProvider.getAuthenticationExtensions()).isEqualTo(new AuthenticationExtensionsOptionProvider()); + + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java new file mode 100644 index 00000000000..bc21bbd7a44 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.request; + +import com.webauthn4j.converter.AuthenticatorDataConverter; +import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.data.client.ClientDataType; +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.test.TestDataUtil; +import org.junit.Test; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebAuthnAuthenticationRequestTest { + + private CborConverter cborConverter = new CborConverter(); + + @Test + public void getter_test() { + Challenge challenge = new DefaultChallenge(); + byte[] clientDataJSON = TestDataUtil.createClientDataJSON(ClientDataType.GET); + byte[] authenticatorData = new AuthenticatorDataConverter(cborConverter).convert(TestDataUtil.createAuthenticatorData()); + ServerProperty serverProperty = new ServerProperty( + new Origin("https://example.com"), + "example.com", + challenge, + new byte[]{0x43, 0x21} + ); + WebAuthnAuthenticationRequest request = new WebAuthnAuthenticationRequest( + new byte[]{0x01, 0x23}, + clientDataJSON, + authenticatorData, + new byte[]{0x45, 0x56}, + "", + serverProperty, + true, + true, + Collections.singletonList("uvi") + ); + assertThat(request.getCredentialId()).isEqualTo(new byte[]{0x01, 0x23}); + assertThat(request.getClientDataJSON()).isEqualTo(clientDataJSON); + assertThat(request.getAuthenticatorData()).isEqualTo(authenticatorData); + assertThat(request.getSignature()).isEqualTo(new byte[]{0x45, 0x56}); + assertThat(request.getClientExtensionsJSON()).isEqualTo(""); + assertThat(request.getServerProperty()).isEqualTo(serverProperty); + assertThat(request.isUserVerificationRequired()).isEqualTo(true); + assertThat(request.isUserPresenceRequired()).isEqualTo(true); + assertThat(request.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList("uvi")); + } + + @Test + public void equals_hashCode_test() { + Challenge challenge = new DefaultChallenge(); + byte[] clientDataJSON = TestDataUtil.createClientDataJSON(ClientDataType.GET); + byte[] authenticatorData = new AuthenticatorDataConverter(cborConverter).convert(TestDataUtil.createAuthenticatorData()); + WebAuthnAuthenticationRequest requestA = new WebAuthnAuthenticationRequest( + new byte[]{0x01, 0x23}, + clientDataJSON, + authenticatorData, + new byte[]{0x45, 0x56}, + "", + new ServerProperty( + new Origin("https://example.com"), + "example.com", + challenge, + new byte[]{0x43, 0x21} + ), + true, + Collections.singletonList("uvi") + ); + WebAuthnAuthenticationRequest requestB = new WebAuthnAuthenticationRequest( + new byte[]{0x01, 0x23}, + clientDataJSON, + authenticatorData, + new byte[]{0x45, 0x56}, + "", + new ServerProperty( + new Origin("https://example.com"), + "example.com", + challenge, + new byte[]{0x43, 0x21} + ), + true, + Collections.singletonList("uvi") + ); + + assertThat(requestA).isEqualTo(requestB); + assertThat(requestA).hasSameHashCodeAs(requestB); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java new file mode 100644 index 00000000000..83e47c5ddb2 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.server; + +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.server.ServerProperty; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.options.OptionsProvider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ServerPropertyProviderImplTest { + + private ChallengeRepository challengeRepository = mock(ChallengeRepository.class); + private OptionsProvider optionsProvider = mock(OptionsProvider.class); + private ServerPropertyProviderImpl target = new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + + @Test + public void provide_test() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setScheme("https"); + request.setServerName("origin.example.com"); + request.setServerPort(443); + Challenge mockChallenge = new DefaultChallenge(); + when(challengeRepository.loadOrGenerateChallenge(request)).thenReturn(mockChallenge); + when(optionsProvider.getEffectiveRpId(request)).thenReturn("rpid.example.com"); + + ServerProperty serverProperty = target.provide(request); + + assertThat(serverProperty.getRpId()).isEqualTo("rpid.example.com"); + assertThat(serverProperty.getOrigin()).isEqualTo(new Origin("https://origin.example.com")); + assertThat(serverProperty.getChallenge()).isEqualTo(mockChallenge); + } +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java new file mode 100644 index 00000000000..e558f93000a --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.userdetails; + + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.authenticator.AuthenticatorImpl; +import org.junit.Test; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebAuthnUserDetailsImplTest { + + @Test + public void getter_setter_test() { + GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); + Authenticator authenticator = new AuthenticatorImpl(null, null, 0); + WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + new byte[32], + "dummy", + "dummy", + Collections.singletonList(authenticator), + Collections.singletonList(grantedAuthority)); + + userDetails.setSingleFactorAuthenticationAllowed(true); + assertThat(userDetails.getUserHandle()).isEqualTo(new byte[32]); + assertThat(userDetails.isSingleFactorAuthenticationAllowed()).isTrue(); + assertThat(userDetails.getAuthenticators()).isEqualTo(Collections.singletonList(authenticator)); + } + +} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java new file mode 100644 index 00000000000..a4e54b7676f --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.util; + +import com.webauthn4j.util.exception.WebAuthnException; +import org.junit.Test; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.webauthn.exception.*; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ExceptionUtilTest { + + @Test + public void wrapWithAuthenticationException_test() { + + Map map = new HashMap<>(); + map.put(new com.webauthn4j.validator.exception.BadAaguidException("dummy"), BadAaguidException.class); + map.put(new com.webauthn4j.validator.exception.BadAlgorithmException("dummy"), BadAlgorithmException.class); + map.put(new com.webauthn4j.validator.exception.BadAttestationStatementException("dummy"), BadAttestationStatementException.class); + map.put(new com.webauthn4j.validator.exception.KeyDescriptionValidationException("dummy"), KeyDescriptionValidationException.class); + map.put(new com.webauthn4j.validator.exception.BadChallengeException("dummy"), BadChallengeException.class); + map.put(new com.webauthn4j.validator.exception.BadOriginException("dummy"), BadOriginException.class); + map.put(new com.webauthn4j.validator.exception.BadRpIdException("dummy"), BadRpIdException.class); + map.put(new com.webauthn4j.validator.exception.BadSignatureException("dummy"), BadSignatureException.class); + map.put(new com.webauthn4j.validator.exception.CertificateException("dummy"), CertificateException.class); + map.put(new com.webauthn4j.validator.exception.ConstraintViolationException("dummy"), ConstraintViolationException.class); + map.put(new com.webauthn4j.validator.exception.MaliciousCounterValueException("dummy"), MaliciousCounterValueException.class); + map.put(new com.webauthn4j.validator.exception.MaliciousDataException("dummy"), MaliciousDataException.class); + map.put(new com.webauthn4j.validator.exception.MissingChallengeException("dummy"), MissingChallengeException.class); + map.put(new com.webauthn4j.validator.exception.PublicKeyMismatchException("dummy"), PublicKeyMismatchException.class); + map.put(new com.webauthn4j.validator.exception.SelfAttestationProhibitedException("dummy"), SelfAttestationProhibitedException.class); + map.put(new com.webauthn4j.validator.exception.TokenBindingException("dummy"), TokenBindingException.class); + map.put(new com.webauthn4j.validator.exception.TrustAnchorNotFoundException("dummy"), TrustAnchorNotFoundException.class); + map.put(new com.webauthn4j.validator.exception.UnexpectedExtensionException("dummy"), UnexpectedExtensionException.class); + map.put(new com.webauthn4j.validator.exception.UserNotPresentException("dummy"), UserNotPresentException.class); + map.put(new com.webauthn4j.validator.exception.UserNotVerifiedException("dummy"), UserNotVerifiedException.class); + map.put(new UnknownValidationException("dummy"), ValidationException.class); + map.put(new com.webauthn4j.converter.exception.DataConversionException("dummy"), DataConversionException.class); + map.put(new WebAuthnException("dummy"), AuthenticationServiceException.class); + + for (Map.Entry entry : map.entrySet()) { + assertThat(ExceptionUtil.wrapWithAuthenticationException(entry.getKey())).isInstanceOf(entry.getValue()); + } + } + + static class UnknownValidationException extends com.webauthn4j.validator.exception.ValidationException { + + UnknownValidationException(String message) { + super(message); + } + } +} diff --git a/webauthn/src/test/resources/certs/3tier-test-root-CA.der b/webauthn/src/test/resources/certs/3tier-test-root-CA.der new file mode 100644 index 0000000000000000000000000000000000000000..cad0759b845378370e47421d1d83dc9f42ac0902 GIT binary patch literal 389 zcmXqLVr(>MVpL}m;FYLbIa^c5u3=SZ!<9K5-3DB299nH2=WJP+nG9SEg$xAPm_u2( zcsPSI5{n9a5|i`{^$c`CV$3|M#RWx~dFi^vsmY~9nI)CF<*7-Dr6n183MHwQ?Fo4K%(o(OMZ0R4+s553x%{uT|M}c^uWQjG0IsQmJpcdz literal 0 HcmV?d00001 diff --git a/webauthn/src/test/resources/certs/3tier-test-root-CA.pem b/webauthn/src/test/resources/certs/3tier-test-root-CA.pem new file mode 100644 index 00000000000..0972044392b --- /dev/null +++ b/webauthn/src/test/resources/certs/3tier-test-root-CA.pem @@ -0,0 +1,38 @@ +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 0d:18:7e:a9:9b:29:2c:3e:80:aa:55:80:d4:9c:88:8b + Signature Algorithm: ecdsa-with-SHA256 + Issuer: O=SharpLab., CN=spring-security-webauthn test root CA + Validity + Not Before: Sep 22 03:18:29 2017 GMT + Not After : Aug 29 03:18:29 2117 GMT + Subject: O=SharpLab., CN=spring-security-webauthn test root CA + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:cd:77:2e:b9:c5:ba:54:35:e1:33:41:ca:b5:0a: + 01:7a:c8:6a:46:c3:c7:94:0f:7f:94:76:5b:f9:ca: + 38:eb:68:2d:ba:0f:e6:55:89:4a:98:28:fa:c1:3a: + 75:8d:17:19:39:17:bc:e9:1e:56:65:46:8c:33:41: + 40:84:dd:ff:22 + ASN1 OID: prime256v1 + NIST CURVE: P-256 + Signature Algorithm: ecdsa-with-SHA256 + 30:45:02:20:36:9e:79:a0:04:ea:80:df:32:86:4d:c2:01:4b: + 01:cb:09:7a:99:1a:a4:22:2b:79:9a:c7:2d:59:dc:f2:35:9f: + 02:21:00:a5:96:b3:76:e2:2e:bc:50:e9:e6:4d:78:61:a8:87: + 25:56:46:b1:bb:84:6d:0f:ea:b7:fc:f3:8e:de:8a:a2:e2 +-----BEGIN CERTIFICATE----- +MIIBgTCCAScCEA0YfqmbKSw+gKpVgNSciIswCgYIKoZIzj0EAwIwRDESMBAGA1UE +CgwJU2hhcnBMYWIuMS4wLAYDVQQDDCVzcHJpbmctc2VjdXJpdHktd2ViYXV0aG4g +dGVzdCByb290IENBMCAXDTE3MDkyMjAzMTgyOVoYDzIxMTcwODI5MDMxODI5WjBE +MRIwEAYDVQQKDAlTaGFycExhYi4xLjAsBgNVBAMMJXNwcmluZy1zZWN1cml0eS13 +ZWJhdXRobiB0ZXN0IHJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN +dy65xbpUNeEzQcq1CgF6yGpGw8eUD3+Udlv5yjjraC26D+ZViUqYKPrBOnWNFxk5 +F7zpHlZlRowzQUCE3f8iMAoGCCqGSM49BAMCA0gAMEUCIDaeeaAE6oDfMoZNwgFL +AcsJepkapCIreZrHLVnc8jWfAiEApZazduIuvFDp5k14YaiHJVZGsbuEbQ/qt/zz +jt6KouI= +-----END CERTIFICATE----- From d7981e32517256ff354a390f0ec390eebce1e5f4 Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 4 May 2019 21:22:58 +0900 Subject: [PATCH 3/9] Add WebAuthn sample application --- ...ecurity-samples-javaconfig-webauthn.gradle | 17 ++ .../webauthn/sample/SampleWebApplication.java | 34 ++++ .../app/config/WebSecurityBeanConfig.java | 72 ++++++++ .../sample/app/config/WebSecurityConfig.java | 118 +++++++++++++ .../app/web/AuthenticatorCreateForm.java | 71 ++++++++ .../sample/app/web/UserCreateForm.java | 84 +++++++++ .../app/web/WebAuthnSampleController.java | 153 ++++++++++++++++ .../domain/entity/AuthenticatorEntity.java | 131 ++++++++++++++ .../sample/domain/entity/UserEntity.java | 136 +++++++++++++++ .../WebAuthnSampleBusinessException.java | 29 +++ .../WebAuthnUserDetailsServiceImpl.java | 98 +++++++++++ .../src/main/resources/static/css/tiny.css | 44 +++++ .../src/main/resources/static/js/base64url.js | 63 +++++++ .../src/main/resources/static/js/webauthn.js | 165 ++++++++++++++++++ .../templates/dashboard/dashboard.html | 44 +++++ .../templates/login/authenticator-login.html | 61 +++++++ .../main/resources/templates/login/login.html | 79 +++++++++ .../resources/templates/signup/signup.html | 108 ++++++++++++ 18 files changed, 1507 insertions(+) create mode 100644 samples/javaconfig/webauthn/spring-security-samples-javaconfig-webauthn.gradle create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/SampleWebApplication.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/UserCreateForm.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java create mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java create mode 100644 samples/javaconfig/webauthn/src/main/resources/static/css/tiny.css create mode 100644 samples/javaconfig/webauthn/src/main/resources/static/js/base64url.js create mode 100644 samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js create mode 100644 samples/javaconfig/webauthn/src/main/resources/templates/dashboard/dashboard.html create mode 100644 samples/javaconfig/webauthn/src/main/resources/templates/login/authenticator-login.html create mode 100644 samples/javaconfig/webauthn/src/main/resources/templates/login/login.html create mode 100644 samples/javaconfig/webauthn/src/main/resources/templates/signup/signup.html diff --git a/samples/javaconfig/webauthn/spring-security-samples-javaconfig-webauthn.gradle b/samples/javaconfig/webauthn/spring-security-samples-javaconfig-webauthn.gradle new file mode 100644 index 00000000000..3f0a64ea0f9 --- /dev/null +++ b/samples/javaconfig/webauthn/spring-security-samples-javaconfig-webauthn.gradle @@ -0,0 +1,17 @@ +plugins { + id "io.spring.convention.spring-sample-boot" +} + +dependencies { + compile project(':spring-security-webauthn') + compile('org.springframework.boot:spring-boot-starter-web') + + compile("org.slf4j:jcl-over-slf4j") + compile('ch.qos.logback:logback-classic') + compile('org.thymeleaf:thymeleaf-spring5') + + compile('org.webjars:bootstrap:4.1.1') + compile('org.webjars:jquery:3.3.1') + compile('org.webjars:font-awesome:5.8.2') + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/SampleWebApplication.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/SampleWebApplication.java new file mode 100644 index 00000000000..f64b5b95ee5 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/SampleWebApplication.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +/** + * SampleWebApplication + */ +@SpringBootApplication +@ComponentScan("org.springframework.security.webauthn.sample.app") +@ComponentScan("org.springframework.security.webauthn.sample.domain") +public class SampleWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleWebApplication.class, args); + } +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java new file mode 100644 index 00000000000..c17677d6037 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.app.config; + +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; +import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.challenge.HttpSessionChallengeRepository; +import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.options.OptionsProviderImpl; +import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +@Configuration +public class WebSecurityBeanConfig { + + @Bean + public WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator(ServerPropertyProvider serverPropertyProvider) { + WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); + return new WebAuthnRegistrationRequestValidator(webAuthnRegistrationContextValidator, serverPropertyProvider); + } + + @Bean + public ServerPropertyProvider serverPropertyProvider(WebAuthnUserDetailsService webAuthnUserDetailsService) { + ChallengeRepository challengeRepository = new HttpSessionChallengeRepository(); + OptionsProvider optionsProvider = new OptionsProviderImpl(webAuthnUserDetailsService, challengeRepository); + return new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + } + + @Bean + public WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator() { + return new WebAuthnAuthenticationContextValidator(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + // Not to register DaoAuthenticationProvider to ProviderManager, + // initialize DaoAuthenticationProvider manually instead of using DaoAuthenticationConfigurer. + @Bean + public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setPasswordEncoder(passwordEncoder); + daoAuthenticationProvider.setUserDetailsService(userDetailsService); + return daoAuthenticationProvider; + } + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java new file mode 100644 index 00000000000..0aac1b735d9 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.app.config; + +import com.webauthn4j.data.AttestationConveyancePreference; +import com.webauthn4j.data.PublicKeyCredentialType; +import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configurers.mfa.MultiFactorAuthenticationProviderConfigurer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.config.configurers.WebAuthnAuthenticationProviderConfigurer; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; + + +/** + * Security Configuration + */ +@Configuration +@Import(value = WebSecurityBeanConfig.class) +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private DaoAuthenticationProvider daoAuthenticationProvider; + + @Autowired + private WebAuthnUserDetailsService userDetailsService; + + @Autowired + private WebAuthnAuthenticatorService authenticatorService; + + @Autowired + private WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator; + + @Override + public void configure(AuthenticationManagerBuilder builder) throws Exception { + builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(userDetailsService, authenticatorService, webAuthnAuthenticationContextValidator)); + builder.apply(new MultiFactorAuthenticationProviderConfigurer<>(daoAuthenticationProvider)); + } + + @Override + public void configure(WebSecurity web) { + // ignore static resources + web.ignoring().antMatchers( + "/favicon.ico", + "/webjars/**", + "/js/**", + "/css/**"); + } + + /** + * Configure SecurityFilterChain + */ + @Override + protected void configure(HttpSecurity http) throws Exception { + + // WebAuthn Login + http.apply(webAuthnLogin()) + .rpName("Spring Security WebAuthn Sample") + .attestation(AttestationConveyancePreference.NONE) + .publicKeyCredParams() + .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.RS256) // Windows Hello + .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256) // FIDO U2F Key, etc + .and() + .loginPage("/login") + .usernameParameter("username") + .passwordParameter("password") + .credentialIdParameter("credentialId") + .clientDataJSONParameter("clientDataJSON") + .authenticatorDataParameter("authenticatorData") + .signatureParameter("signature") + .clientExtensionsJSONParameter("clientExtensionsJSON") + .loginProcessingUrl("/login") + .successHandler(new SimpleUrlAuthenticationSuccessHandler("/dashboard")); + + // Logout + http.logout() + .logoutUrl("/logout"); + // Authorization + http.authorizeRequests() + .mvcMatchers("/").permitAll() + .mvcMatchers("/signup").permitAll() + .mvcMatchers("/login").permitAll() + .mvcMatchers("/h2-console/**").denyAll() + .anyRequest().fullyAuthenticated(); + + http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); + + } + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java new file mode 100644 index 00000000000..578e57fbbe7 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.app.web; + +import com.webauthn4j.data.AuthenticatorTransport; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import java.util.Set; + +public class AuthenticatorCreateForm { + + @NotNull + @Valid + private String clientDataJSON; + + @NotNull + @Valid + private String attestationObject; + + private Set transports; + + @NotNull + private String clientExtensions; + + public String getClientDataJSON() { + return clientDataJSON; + } + + public void setClientDataJSON(String clientDataJSON) { + this.clientDataJSON = clientDataJSON; + } + + public String getAttestationObject() { + return attestationObject; + } + + public void setAttestationObject(String attestationObject) { + this.attestationObject = attestationObject; + } + + public Set getTransports() { + return transports; + } + + public void setTransports(Set transports) { + this.transports = transports; + } + + public String getClientExtensions() { + return clientExtensions; + } + + public void setClientExtensions(String clientExtensions) { + this.clientExtensions = clientExtensions; + } +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/UserCreateForm.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/UserCreateForm.java new file mode 100644 index 00000000000..44cfc84450a --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/UserCreateForm.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.app.web; + + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * Form for User + */ +public class UserCreateForm { + + @NotNull + private String userHandle; + + @NotEmpty + private String username; + + @NotEmpty + private String password; + + @Valid + @NotNull + private AuthenticatorCreateForm authenticator; + + private boolean singleFactorAuthenticationAllowed; + + public String getUserHandle() { + return userHandle; + } + + public void setUserHandle(String userHandle) { + this.userHandle = userHandle; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public AuthenticatorCreateForm getAuthenticator() { + return authenticator; + } + + public void setAuthenticator(AuthenticatorCreateForm authenticator) { + this.authenticator = authenticator; + } + + public boolean isSingleFactorAuthenticationAllowed() { + return singleFactorAuthenticationAllowed; + } + + public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed) { + this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; + } + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java new file mode 100644 index 00000000000..e3dfeb7cc2a --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.app.web; + +import com.webauthn4j.util.UUIDUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.MultiFactorAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidationResponse; +import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; +import org.springframework.security.webauthn.exception.ValidationException; +import org.springframework.security.webauthn.sample.domain.entity.AuthenticatorEntity; +import org.springframework.security.webauthn.sample.domain.entity.UserEntity; +import org.springframework.security.webauthn.sample.domain.exception.WebAuthnSampleBusinessException; +import org.springframework.security.webauthn.sample.domain.service.WebAuthnUserDetailsServiceImpl; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.util.Base64Utils; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Login controller + */ +@SuppressWarnings("SameReturnValue") +@Controller +public class WebAuthnSampleController { + + private static final String REDIRECT_LOGIN = "redirect:/login"; + private static final String REDIRECT_SIGNUP = "redirect:/signup"; + + private static final String VIEW_SIGNUP_SIGNUP = "signup/signup"; + private static final String VIEW_LOGIN_LOGIN = "login/login"; + private static final String VIEW_LOGIN_AUTHENTICATOR_LOGIN = "login/authenticator-login"; + + private static final String VIEW_DASHBOARD_DASHBOARD = "dashboard/dashboard"; + + @Autowired + private WebAuthnUserDetailsServiceImpl webAuthnUserDetailsService; + + @Autowired + private WebAuthnRegistrationRequestValidator registrationRequestValidator; + + @Autowired + private PasswordEncoder passwordEncoder; + + @RequestMapping(value = "/") + public String index(Model model) { + return REDIRECT_SIGNUP; + } + + @RequestMapping(value = "/dashboard") + public String dashboard(Model model) { + return VIEW_DASHBOARD_DASHBOARD; + } + + @RequestMapping(value = "/signup", method = RequestMethod.GET) + public String template(Model model) { + UserCreateForm userCreateForm = new UserCreateForm(); + UUID userHandle = UUID.randomUUID(); + String userHandleStr = java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(UUIDUtil.convertUUIDToBytes(userHandle)); + userCreateForm.setUserHandle(userHandleStr); + model.addAttribute("userForm", userCreateForm); + return VIEW_SIGNUP_SIGNUP; + } + + @RequestMapping(value = "/signup", method = RequestMethod.POST) + public String create(HttpServletRequest request, HttpServletResponse response, @Valid @ModelAttribute("userForm") UserCreateForm userCreateForm, BindingResult result, Model model, RedirectAttributes redirectAttributes) { + + if (result.hasErrors()) { + return VIEW_SIGNUP_SIGNUP; + } + WebAuthnRegistrationRequestValidationResponse webAuthnRegistrationRequestValidationResponse; + try { + webAuthnRegistrationRequestValidationResponse = registrationRequestValidator.validate( + request, + userCreateForm.getAuthenticator().getClientDataJSON(), + userCreateForm.getAuthenticator().getAttestationObject(), + null, //TODO + userCreateForm.getAuthenticator().getClientExtensions() + ); + } catch (ValidationException e) { + return VIEW_SIGNUP_SIGNUP; + } + + UserEntity destination = new UserEntity(); + + destination.setUserHandle(Base64Utils.decodeFromUrlSafeString(userCreateForm.getUserHandle())); + destination.setUsername(userCreateForm.getUsername()); + destination.setPassword(passwordEncoder.encode(userCreateForm.getPassword())); + + List authenticators = new ArrayList<>(); + AuthenticatorEntity authenticator = new AuthenticatorEntity(); + AuthenticatorCreateForm sourceAuthenticator = userCreateForm.getAuthenticator(); + authenticator.setUser(destination); + authenticator.setName(null); // sample application doesn't name authenticator + authenticator.setAttestationStatement(webAuthnRegistrationRequestValidationResponse.getAttestationObject().getAttestationStatement()); + authenticator.setAttestedCredentialData(webAuthnRegistrationRequestValidationResponse.getAttestationObject().getAuthenticatorData().getAttestedCredentialData()); + authenticator.setCounter(webAuthnRegistrationRequestValidationResponse.getAttestationObject().getAuthenticatorData().getSignCount()); + authenticator.setTransports(sourceAuthenticator.getTransports()); + authenticators.add(authenticator); + + destination.setAuthenticators(authenticators); + destination.setLocked(false); + destination.setSingleFactorAuthenticationAllowed(userCreateForm.isSingleFactorAuthenticationAllowed()); + + UserEntity user = destination; + try { + webAuthnUserDetailsService.createUser(user); + } catch (WebAuthnSampleBusinessException ex) { + return VIEW_SIGNUP_SIGNUP; + } + + return REDIRECT_LOGIN; + } + + @RequestMapping(value = "/login", method = RequestMethod.GET) + public String login() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication instanceof MultiFactorAuthenticationToken) { + return VIEW_LOGIN_AUTHENTICATOR_LOGIN; + } else { + return VIEW_LOGIN_LOGIN; + } + } + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java new file mode 100644 index 00000000000..1549c2f2913 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java @@ -0,0 +1,131 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.domain.entity; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.data.AuthenticatorTransport; +import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; +import com.webauthn4j.data.attestation.statement.AttestationStatement; +import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput; +import com.webauthn4j.data.extension.client.RegistrationExtensionClientOutput; +import java.util.Map; +import java.util.Set; + +/** + * Authenticator model + */ +public class AuthenticatorEntity implements Authenticator { + + private Integer id; + + private String name; + + private UserEntity user; + + private long counter; + + private Set transports; + + private AttestedCredentialData attestedCredentialData; + + private AttestationStatement attestationStatement; + + private Map clientExtensions; + + private Map authenticatorExtensions; + + public String getFormat() { + return attestationStatement.getFormat(); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } + + public long getCounter() { + return counter; + } + + public void setCounter(long counter) { + this.counter = counter; + } + + @Override + public Set getTransports() { + return transports; + } + + public void setTransports(Set transports) { + this.transports = transports; + } + + public AttestedCredentialData getAttestedCredentialData() { + return attestedCredentialData; + } + + public void setAttestedCredentialData(AttestedCredentialData attestedCredentialData) { + this.attestedCredentialData = attestedCredentialData; + } + + public AttestationStatement getAttestationStatement() { + return attestationStatement; + } + + public void setAttestationStatement(AttestationStatement attestationStatement) { + this.attestationStatement = attestationStatement; + } + + @Override + public Map getClientExtensions() { + return clientExtensions; + } + + @Override + public void setClientExtensions(Map clientExtensions) { + this.clientExtensions = clientExtensions; + } + + @Override + public Map getAuthenticatorExtensions() { + return authenticatorExtensions; + } + + @Override + public void setAuthenticatorExtensions(Map authenticatorExtensions) { + this.authenticatorExtensions = authenticatorExtensions; + } +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java new file mode 100644 index 00000000000..beb593a86b4 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java @@ -0,0 +1,136 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.domain.entity; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * User model + */ +public class UserEntity implements WebAuthnUserDetails { + + private Integer id; + private byte[] userHandle; + private String username; + + private List authenticators; + + private String password; + + private boolean locked; + + private boolean singleFactorAuthenticationAllowed; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Override + public byte[] getUserHandle() { + return userHandle; + } + + public void setUserHandle(byte[] userHandle) { + this.userHandle = userHandle; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public List getAuthenticators() { + return authenticators; + } + + public void setAuthenticators(List authenticators) { + this.authenticators = authenticators; + } + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + @Override + public boolean isSingleFactorAuthenticationAllowed() { + return singleFactorAuthenticationAllowed; + } + + @Override + public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed) { + this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return !isLocked(); + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + /** + * return String representation + */ + @Override + public String toString() { + return username; + } + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java new file mode 100644 index 00000000000..71898c21c4a --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.domain.exception; + +/** + * Business Exception for WebAuthn Sample + */ +@SuppressWarnings("squid:MaximumInheritanceDepth") +public class WebAuthnSampleBusinessException extends RuntimeException { + + public WebAuthnSampleBusinessException(String message) { + super(message); + } + +} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java new file mode 100644 index 00000000000..0bc639d5db8 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.sample.domain.service; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.util.Base64UrlUtil; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; +import org.springframework.security.webauthn.sample.domain.entity.AuthenticatorEntity; +import org.springframework.security.webauthn.sample.domain.entity.UserEntity; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * {@inheritDoc} + */ +@Component +@Transactional +public class WebAuthnUserDetailsServiceImpl implements WebAuthnUserDetailsService, WebAuthnAuthenticatorService { + + private List users = new ArrayList<>(); + + /** + * {@inheritDoc} + */ + @Override + public WebAuthnUserDetails loadUserByUsername(String username) { + return users + .stream() + .filter(user -> user.getUsername().equals(username)) + .findFirst() + .orElseThrow(() -> new UsernameNotFoundException(String.format("UserEntity with username'%s' is not found.", username))); + } + + @Override + public WebAuthnUserDetails loadUserByCredentialId(byte[] credentialId) { + return users + .stream() + .filter(user -> user.getAuthenticators().stream().anyMatch(authenticator -> Arrays.equals(authenticator.getAttestedCredentialData().getCredentialId(), credentialId))) + .findFirst() + .orElseThrow(() -> new CredentialIdNotFoundException(String.format("AuthenticatorEntity with credentialId'%s' is not found.", Base64UrlUtil.encodeToString(credentialId)))); + } + + @Override + public void addAuthenticator(String username, Authenticator authenticator) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAuthenticator(String username, Authenticator authenticator) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAuthenticator(String username, byte[] credentialId) { + throw new UnsupportedOperationException(); + } + + public UserEntity createUser(UserEntity user) { + users.add(user); + return user; + } + + @Override + public void updateCounter(byte[] credentialId, long counter) throws CredentialIdNotFoundException { + + AuthenticatorEntity authenticator = users + .stream() + .flatMap(user -> user.getAuthenticators().stream()) + .filter(entry -> Arrays.equals(entry.getAttestedCredentialData().getCredentialId(), credentialId)) + .findFirst() + .orElseThrow(() -> new CredentialIdNotFoundException(String.format("AuthenticatorEntity with credentialId'%s' is not found.", Base64UrlUtil.encodeToString(credentialId)))); + + authenticator.setCounter(counter); + } + +} diff --git a/samples/javaconfig/webauthn/src/main/resources/static/css/tiny.css b/samples/javaconfig/webauthn/src/main/resources/static/css/tiny.css new file mode 100644 index 00000000000..4815ab563bb --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/static/css/tiny.css @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.content-wrapper{ + max-width: 400px; + width: 100%; + padding: 15px; + margin: auto; +} + +.login-form input#username { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.login-form input#password { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.signup-form input#username { + margin-bottom: 2em; +} +.signup-form input#password{ + margin-bottom: 2em; +} +.signup-form input#authenticator { + margin-bottom: 4em; +} + diff --git a/samples/javaconfig/webauthn/src/main/resources/static/js/base64url.js b/samples/javaconfig/webauthn/src/main/resources/static/js/base64url.js new file mode 100644 index 00000000000..ee540d04f68 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/static/js/base64url.js @@ -0,0 +1,63 @@ +"use strict"; + +(function (exports) { + + var lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + var reverseLookup = new Uint8Array(256); + + for (var i = 0; i < lookup.length; i++) { + reverseLookup[lookup.charCodeAt(i)] = i; + } + + function decodeBase64url(base64url) { + var base64urlLength = base64url.length; + + var placeHolderLength = base64url.charAt(base64urlLength - 2) === '=' ? 2 : base64url.charAt(base64urlLength - 1) === '=' ? 1 : 0; + var bufferLength = (base64urlLength * 3 / 4) - placeHolderLength; + + var arrayBuffer = new ArrayBuffer(bufferLength); + var uint8Array = new Uint8Array(arrayBuffer); + + var j = 0; + for (var i = 0; i < base64urlLength; i+=4) { + var tmp0 = reverseLookup[base64url.charCodeAt(i)]; + var tmp1 = reverseLookup[base64url.charCodeAt(i+1)]; + var tmp2 = reverseLookup[base64url.charCodeAt(i+2)]; + var tmp3 = reverseLookup[base64url.charCodeAt(i+3)]; + + uint8Array[j++] = (tmp0 << 2) | (tmp1 >> 4); + uint8Array[j++] = ((tmp1 & 15) << 4) | (tmp2 >> 2); + uint8Array[j++] = ((tmp2 & 3) << 6) | (tmp3 & 63); + } + + return arrayBuffer; + } + + function encodeBase64url(arrayBuffer) { + var uint8Array = new Uint8Array(arrayBuffer); + var length = uint8Array.length; + var base64url = ""; + + for (var i = 0; i < length; i+=3) { + base64url += lookup[uint8Array[i] >> 2]; + base64url += lookup[((uint8Array[i] & 3) << 4) | (uint8Array[i + 1] >> 4)]; + base64url += lookup[((uint8Array[i + 1] & 15) << 2) | (uint8Array[i + 2] >> 6)]; + base64url += lookup[uint8Array[i + 2] & 63]; + } + + switch (length % 3) { + case 1: + base64url = base64url.substring(0, base64url.length - 2); + break; + case 2: + base64url = base64url.substring(0, base64url.length - 1); + break; + } + return base64url; + } + + exports.decodeBase64url = decodeBase64url; + exports.encodeBase64url = encodeBase64url; + +}(typeof exports === 'undefined' ? (this.base64url = {}) : exports)); + diff --git a/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js b/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js new file mode 100644 index 00000000000..0181826f8fe --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js @@ -0,0 +1,165 @@ + +function createCredential(residentKeyRequirement){ + + return fetchAttestationOptions().then(function (options) { + var username = $("#username").val(); + var userHandle = $("#userHandle").val(); + + var authenticatorSelection = options["authenticatorSelection"]; + authenticatorSelection["requireResidentKey"] = residentKeyRequirement; + var publicKeyCredentialCreationOptions = { + rp: options["rp"], + user: { + id: base64url.decodeBase64url(userHandle), + name: username, + displayName: username + }, + challenge: base64url.decodeBase64url(options["challenge"]), + pubKeyCredParams: options["pubKeyCredParams"], + timeout: options["timeout"], + excludeCredentials: options["excludeCredentials"].map(function(credential){ + return { + type: credential["type"], + id: base64url.decodeBase64url(credential["id"]) + } + }), + authenticatorSelection: authenticatorSelection, + attestation: options["attestation"], + extensions: options["extensions"] + }; + + var credentialCreationOptions = { + publicKey: publicKeyCredentialCreationOptions + }; + + return navigator.credentials.create(credentialCreationOptions); + }); +} + +function getCredential(userVerification){ + return fetchAssertionOptions().then(function (options) { + var publicKeyCredentialRequestOptions = { + challenge: base64url.decodeBase64url(options["challenge"]), + timeout: options["timeout"], + rpId: options["rpId"], + allowCredentials: options["allowCredentials"].map(function(credential){ + return { + type: credential["type"], + id: base64url.decodeBase64url(credential["id"]) + } + }), + userVerification: userVerification, + extensions: options["extensions"] + }; + + var credentialRequestOptions = { + publicKey: publicKeyCredentialRequestOptions + }; + + return navigator.credentials.get(credentialRequestOptions); + }); +} + +function fetchAttestationOptions(){ + return $.ajax({ + type: 'GET', + url: './webauthn/attestation/options', + dataType: 'json' + }).done(function(options){ + console.log(options); + return options; + }).fail(function(error){ + console.log(error); + }); +} + +function fetchAssertionOptions(){ + return $.ajax({ + type: 'GET', + url: './webauthn/assertion/options', + dataType: 'json' + }).done(function(options){ + console.log(options); + return options; + }).fail(function(error){ + console.log(error); + }); +} + +$(document).ready(function() { + + var dialog = $("#resident-key-requirement-dialog"); + + var onResidentKeyRequirementDialogClosing = function(residentKeyRequirement){ + createCredential(residentKeyRequirement).then(function (credential) { + console.log(credential); + $('#clientDataJSON').val(base64url.encodeBase64url(credential.response.clientDataJSON)); + $('#attestationObject').val(base64url.encodeBase64url(credential.response.attestationObject)); + $('#clientExtensions').val(JSON.stringify(credential.getClientExtensionResults())); + $('#authenticator').text('Authenticator registered'); + $('#authenticator').prop('disabled', true); + $('#submit').prop('disabled', false); + dialog.modal('hide'); + }).catch(function (e) { + console.error("Error:%s, Message:%s", e.name, e.message); + dialog.modal('hide'); + }); + }; + + $('#resident-key-requirement-dialog-yes').click(function () { + onResidentKeyRequirementDialogClosing(true); + }); + $('#resident-key-requirement-dialog-no').click(function () { + onResidentKeyRequirementDialogClosing(false); + }); + $('#resident-key-requirement-dialog-close').click(function () { + dialog.modal('hide'); + }); + + $('#authenticator').click(function(){ + dialog.modal('show'); + }); + + $('#fast-login').click(function(){ + getCredential("required").then(function (credential) { + console.log(credential); + $("#credentialId").val(credential.id); + $("#clientDataJSON").val(base64url.encodeBase64url(credential.response.clientDataJSON)); + $("#authenticatorData").val(base64url.encodeBase64url(credential.response.authenticatorData)); + $("#signature").val(base64url.encodeBase64url(credential.response.signature)); + $("#clientExtensions").val(JSON.stringify(credential.getClientExtensionResults())); + $('#login-form').submit(); + }).catch(function (e) { + console.error("Error:%s, Message:%s", e.name, e.message); + }); + return false; + }); + $('#retry').click(function(){ + getCredential("preferred").then(function (credential) { + console.log(credential); + $("#credentialId").val(credential.id); + $("#clientDataJSON").val(base64url.encodeBase64url(credential.response.clientDataJSON)); + $("#authenticatorData").val(base64url.encodeBase64url(credential.response.authenticatorData)); + $("#signature").val(base64url.encodeBase64url(credential.response.signature)); + $("#clientExtensions").val(JSON.stringify(credential.getClientExtensionResults())); + $('#login-form').submit(); + }).catch(function (e) { + console.error("Error:%s, Message:%s", e.name, e.message); + }); + return false; + }); + + if($('#login-authenticator-login-view').length>0){ + return getCredential("preferred").then(function (credential) { + console.log(credential); + $("#credentialId").val(credential.id); + $("#clientDataJSON").val(base64url.encodeBase64url(credential.response.clientDataJSON)); + $("#authenticatorData").val(base64url.encodeBase64url(credential.response.authenticatorData)); + $("#signature").val(base64url.encodeBase64url(credential.response.signature)); + $("#clientExtensions").val(JSON.stringify(credential.getClientExtensionResults())); + $('#login-form').submit(); + }).catch(function (e) { + console.error("Error:%s, Message:%s", e.name, e.message); + }); + } +}); diff --git a/samples/javaconfig/webauthn/src/main/resources/templates/dashboard/dashboard.html b/samples/javaconfig/webauthn/src/main/resources/templates/dashboard/dashboard.html new file mode 100644 index 00000000000..1d7d3c1e143 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/templates/dashboard/dashboard.html @@ -0,0 +1,44 @@ + + + + + Spring Security WebAuthn Sample + + + + + + + + + + + + + + + + + + + + + +
+

Spring Security WebAuthn Sample

+ +

Dashboard

+ +

Login success

+ +
+
+ +
+
+ +
+ + + + diff --git a/samples/javaconfig/webauthn/src/main/resources/templates/login/authenticator-login.html b/samples/javaconfig/webauthn/src/main/resources/templates/login/authenticator-login.html new file mode 100644 index 00000000000..de4ab3d430e --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/templates/login/authenticator-login.html @@ -0,0 +1,61 @@ + + + + + Spring Security WebAuthn Sample + + + + + + + + + + + + + + + + + + + + + +
+

Spring Security WebAuthn Sample

+ +

Login

+ + + +
+
+ +
+ +
+ +
+ + + + + + +
+ + +
+
+ +
+
+ +
+ + + + diff --git a/samples/javaconfig/webauthn/src/main/resources/templates/login/login.html b/samples/javaconfig/webauthn/src/main/resources/templates/login/login.html new file mode 100644 index 00000000000..db6dd1a9473 --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/templates/login/login.html @@ -0,0 +1,79 @@ + + + + + Spring Security WebAuthn Sample + + + + + + + + + + + + + + + + + + + + + +
+

Spring Security WebAuthn Sample

+ +

Login

+ + + + + + + Sign up + +
+ + + + diff --git a/samples/javaconfig/webauthn/src/main/resources/templates/signup/signup.html b/samples/javaconfig/webauthn/src/main/resources/templates/signup/signup.html new file mode 100644 index 00000000000..df822ef7e2b --- /dev/null +++ b/samples/javaconfig/webauthn/src/main/resources/templates/signup/signup.html @@ -0,0 +1,108 @@ + + + + + Spring Security WebAuthn Sample + + + + + + + + + + + + + + + + + + + + + +
+

Spring Security WebAuthn Sample

+ +

Sign up

+ + + + + + Login + +
+ + +
+ +
+ + + From e7ba6bfcdd6594d57915a2406c23c1fa74619b2a Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 13 Jul 2019 00:01:36 +0900 Subject: [PATCH 4/9] Redesign not to expose WebAuthn4J types --- .../app/config/WebSecurityBeanConfig.java | 52 +- .../sample/app/config/WebSecurityConfig.java | 16 +- .../app/web/AuthenticatorCreateForm.java | 8 +- .../app/web/WebAuthnSampleController.java | 118 ++-- .../domain/entity/AuthenticatorEntity.java | 131 ---- .../sample/domain/entity/UserEntity.java | 136 ---- .../WebAuthnUserDetailsServiceImpl.java | 98 --- .../src/main/resources/static/js/webauthn.js | 134 ++-- .../webauthn/WebAuthn4JWebAuthnManager.java | 263 ++++++++ .../WebAuthnAssertionAuthenticationToken.java | 7 +- ...t.java => WebAuthnAuthenticationData.java} | 35 +- .../WebAuthnAuthenticationProvider.java | 84 ++- .../webauthn/WebAuthnAuthenticationToken.java | 7 +- .../webauthn/WebAuthnDataConverter.java | 79 +++ .../security/webauthn/WebAuthnManager.java | 28 + .../webauthn/WebAuthnOptionWebHelper.java | 60 ++ .../webauthn/WebAuthnProcessingFilter.java | 21 +- .../webauthn/WebAuthnRegistrationData.java | 70 ++ .../webauthn/WebAuthnRegistrationRequest.java | 81 +++ ...RegistrationRequestValidationResponse.java | 79 --- .../WebAuthnRegistrationRequestValidator.java | 104 +-- .../authenticator/WebAuthnAuthenticator.java | 58 +- .../WebAuthnAuthenticatorImpl.java | 120 ++++ .../WebAuthnAuthenticatorTransport.java | 86 +++ ...tpSessionWebAuthnChallengeRepository.java} | 20 +- .../WebAuthnChallenge.java} | 12 +- .../challenge/WebAuthnChallengeImpl.java | 81 +++ ....java => WebAuthnChallengeRepository.java} | 35 +- ...AuthnAuthenticationProviderConfigurer.java | 23 +- .../configurers/WebAuthnConfigurerUtil.java | 105 +-- .../configurers/WebAuthnLoginConfigurer.java | 606 ++---------------- .../AssertionOptionsEndpointFilter.java | 90 --- .../endpoint/AssertionOptionsResponse.java | 97 --- .../AttestationOptionsEndpointFilter.java | 94 --- .../endpoint/AttestationOptionsResponse.java | 144 ----- .../webauthn/endpoint/ErrorResponse.java | 59 -- .../endpoint/OptionsEndpointFilterBase.java | 163 ----- ...WebAuthnPublicKeyCredentialDescriptor.java | 101 --- ...WebAuthnPublicKeyCredentialUserEntity.java | 78 --- .../webauthn/options/AssertionOptions.java | 99 --- .../webauthn/options/AttestationOptions.java | 144 ----- .../options/ExtensionsOptionProvider.java | 72 --- .../webauthn/options/OptionsProvider.java | 59 -- .../webauthn/options/OptionsProviderImpl.java | 317 --------- .../StaticExtensionOptionProvider.java | 39 -- .../EffectiveRpIdProvider.java} | 15 +- .../server/ServerPropertyProviderImpl.java | 58 -- .../webauthn/server/WebAuthnOrigin.java | 152 +++++ .../server/WebAuthnServerProperty.java | 102 +++ ...va => WebAuthnServerPropertyProvider.java} | 12 +- .../WebAuthnServerPropertyProviderImpl.java | 52 ++ ...WebAuthnAndPasswordUserDetailsManager.java | 107 ++++ ...Impl.java => WebAuthnAndPasswordUser.java} | 37 +- .../WebAuthnAndPasswordUserDetails.java | 16 +- ...ebAuthnAndPasswordUserDetailsService.java} | 7 +- .../webauthn/userdetails/WebAuthnUser.java | 168 +++++ .../userdetails/WebAuthnUserDetails.java | 72 ++- .../WebAuthnUserDetailsChecker.java | 30 + .../WebAuthnUserDetailsService.java | 31 +- .../security/webauthn/util/ExceptionUtil.java | 95 --- .../security/webauthn/util/ServletUtil.java | 43 -- .../component/RegistrationValidationTest.java | 35 +- ...ava => WebAuthn4JWebAuthnManagerTest.java} | 45 +- ...AuthnAssertionAuthenticationTokenTest.java | 9 +- ...va => WebAuthnAuthenticationDataTest.java} | 52 +- .../WebAuthnAuthenticationProviderTest.java | 88 ++- .../WebAuthnAuthenticationTokenTest.java | 5 +- .../WebAuthnProcessingFilterTest.java | 43 +- ...strationRequestValidationResponseTest.java | 56 -- ...AuthnRegistrationRequestValidatorTest.java | 110 ++-- ...ava => WebAuthnAuthenticatorImplTest.java} | 15 +- ...ssionWebAuthnChallengeRepositoryTest.java} | 27 +- ...nticationProviderConfigurerSpringTest.java | 82 +-- ...bAuthnLoginConfigurerSetterSpringTest.java | 45 +- .../WebAuthnLoginConfigurerSpringTest.java | 95 +-- .../AttestationOptionsEndpointFilterTest.java | 121 ---- .../webauthn/endpoint/ErrorResponseTest.java | 40 -- ...uthnPublicKeyCredentialDescriptorTest.java | 42 -- ...uthnPublicKeyCredentialUserEntityTest.java | 35 - .../options/AssertionOptionsTest.java | 51 -- .../options/AttestationOptionsTest.java | 59 -- .../options/OptionsProviderImplTest.java | 142 ---- ...bAuthnServerPropertyProviderImplTest.java} | 27 +- ....java => WebAuthnAndPasswordUserTest.java} | 10 +- 84 files changed, 2324 insertions(+), 4220 deletions(-) delete mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java delete mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java delete mode 100644 samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java rename webauthn/src/main/java/org/springframework/security/webauthn/{request/WebAuthnAuthenticationRequest.java => WebAuthnAuthenticationData.java} (81%) create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnDataConverter.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnManager.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnOptionWebHelper.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequest.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java rename webauthn/src/main/java/org/springframework/security/webauthn/challenge/{HttpSessionChallengeRepository.java => HttpSessionWebAuthnChallengeRepository.java} (79%) rename webauthn/src/main/java/org/springframework/security/webauthn/{options/RegistrationExtensionsOptionProvider.java => challenge/WebAuthnChallenge.java} (71%) create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java rename webauthn/src/main/java/org/springframework/security/webauthn/challenge/{ChallengeRepository.java => WebAuthnChallengeRepository.java} (55%) delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java rename webauthn/src/main/java/org/springframework/security/webauthn/{options/ExtensionOptionProvider.java => server/EffectiveRpIdProvider.java} (69%) delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java rename webauthn/src/main/java/org/springframework/security/webauthn/server/{ServerPropertyProvider.java => WebAuthnServerPropertyProvider.java} (73%) create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/InMemoryWebAuthnAndPasswordUserDetailsManager.java rename webauthn/src/main/java/org/springframework/security/webauthn/userdetails/{WebAuthnUserDetailsImpl.java => WebAuthnAndPasswordUser.java} (61%) rename samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java => webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetails.java (63%) rename webauthn/src/main/java/org/springframework/security/webauthn/{options/AuthenticationExtensionsOptionProvider.java => userdetails/WebAuthnAndPasswordUserDetailsService.java} (70%) create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUser.java create mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsChecker.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java rename webauthn/src/test/java/org/springframework/security/webauthn/{util/ExceptionUtilTest.java => WebAuthn4JWebAuthnManagerTest.java} (64%) rename webauthn/src/test/java/org/springframework/security/webauthn/{request/WebAuthnAuthenticationRequestTest.java => WebAuthnAuthenticationDataTest.java} (56%) delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java rename webauthn/src/test/java/org/springframework/security/webauthn/authenticator/{WebAuthnAuthenticatorTest.java => WebAuthnAuthenticatorImplTest.java} (66%) rename webauthn/src/test/java/org/springframework/security/webauthn/challenge/{HttpSessionChallengeRepositoryTest.java => HttpSessionWebAuthnChallengeRepositoryTest.java} (76%) delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java rename webauthn/src/test/java/org/springframework/security/webauthn/server/{ServerPropertyProviderImplTest.java => WebAuthnServerPropertyProviderImplTest.java} (54%) rename webauthn/src/test/java/org/springframework/security/webauthn/userdetails/{WebAuthnUserDetailsImplTest.java => WebAuthnAndPasswordUserTest.java} (78%) diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java index c17677d6037..4bab8c8f580 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityBeanConfig.java @@ -16,44 +16,60 @@ package org.springframework.security.webauthn.sample.app.config; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.challenge.HttpSessionChallengeRepository; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.security.webauthn.options.OptionsProviderImpl; -import org.springframework.security.webauthn.server.ServerPropertyProvider; -import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.*; +import org.springframework.security.webauthn.challenge.HttpSessionWebAuthnChallengeRepository; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.security.webauthn.server.EffectiveRpIdProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProviderImpl; +import org.springframework.security.webauthn.userdetails.InMemoryWebAuthnAndPasswordUserDetailsManager; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; @Configuration public class WebSecurityBeanConfig { @Bean - public WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator(ServerPropertyProvider serverPropertyProvider) { - WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); - return new WebAuthnRegistrationRequestValidator(webAuthnRegistrationContextValidator, serverPropertyProvider); + public WebAuthnServerPropertyProvider webAuthnServerPropertyProvider(EffectiveRpIdProvider effectiveRpIdProvider, WebAuthnChallengeRepository challengeRepository){ + return new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, challengeRepository); } @Bean - public ServerPropertyProvider serverPropertyProvider(WebAuthnUserDetailsService webAuthnUserDetailsService) { - ChallengeRepository challengeRepository = new HttpSessionChallengeRepository(); - OptionsProvider optionsProvider = new OptionsProviderImpl(webAuthnUserDetailsService, challengeRepository); - return new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + public WebAuthnChallengeRepository webAuthnChallengeRepository(){ + return new HttpSessionWebAuthnChallengeRepository(); } @Bean - public WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator() { - return new WebAuthnAuthenticationContextValidator(); + public InMemoryWebAuthnAndPasswordUserDetailsManager webAuthnUserDetailsService(){ + return new InMemoryWebAuthnAndPasswordUserDetailsManager(); } + @Bean + public WebAuthnOptionWebHelper webAuthnOptionWebHelper(WebAuthnChallengeRepository challengeRepository, WebAuthnUserDetailsService userDetailsService){ + return new WebAuthnOptionWebHelper(challengeRepository, userDetailsService); + } + + @Bean + public WebAuthnManager webAuthnAuthenticationManager(){ + return new WebAuthn4JWebAuthnManager(); + } + + @Bean + public WebAuthnDataConverter webAuthnDataConverter(){ + return new WebAuthnDataConverter(); + } + + @Bean + public WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator(WebAuthnManager webAuthnManager, WebAuthnServerPropertyProvider webAuthnServerPropertyProvider){ + return new WebAuthnRegistrationRequestValidator(webAuthnManager, webAuthnServerPropertyProvider); + } + + @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java index 0aac1b735d9..f087847197f 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/config/WebSecurityConfig.java @@ -16,10 +16,6 @@ package org.springframework.security.webauthn.sample.app.config; -import com.webauthn4j.data.AttestationConveyancePreference; -import com.webauthn4j.data.PublicKeyCredentialType; -import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -32,6 +28,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; +import org.springframework.security.webauthn.WebAuthnManager; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; import org.springframework.security.webauthn.config.configurers.WebAuthnAuthenticationProviderConfigurer; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; @@ -57,11 +54,11 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private WebAuthnAuthenticatorService authenticatorService; @Autowired - private WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator; + private WebAuthnManager webAuthnManager; @Override public void configure(AuthenticationManagerBuilder builder) throws Exception { - builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(userDetailsService, authenticatorService, webAuthnAuthenticationContextValidator)); + builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(userDetailsService, authenticatorService, webAuthnManager)); builder.apply(new MultiFactorAuthenticationProviderConfigurer<>(daoAuthenticationProvider)); } @@ -83,12 +80,6 @@ protected void configure(HttpSecurity http) throws Exception { // WebAuthn Login http.apply(webAuthnLogin()) - .rpName("Spring Security WebAuthn Sample") - .attestation(AttestationConveyancePreference.NONE) - .publicKeyCredParams() - .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.RS256) // Windows Hello - .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256) // FIDO U2F Key, etc - .and() .loginPage("/login") .usernameParameter("username") .passwordParameter("password") @@ -115,4 +106,5 @@ protected void configure(HttpSecurity http) throws Exception { } + } diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java index 578e57fbbe7..9201335d8c2 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/AuthenticatorCreateForm.java @@ -16,8 +16,6 @@ package org.springframework.security.webauthn.sample.app.web; -import com.webauthn4j.data.AuthenticatorTransport; - import javax.validation.Valid; import javax.validation.constraints.NotNull; import java.util.Set; @@ -32,7 +30,7 @@ public class AuthenticatorCreateForm { @Valid private String attestationObject; - private Set transports; + private Set transports; @NotNull private String clientExtensions; @@ -53,11 +51,11 @@ public void setAttestationObject(String attestationObject) { this.attestationObject = attestationObject; } - public Set getTransports() { + public Set getTransports() { return transports; } - public void setTransports(Set transports) { + public void setTransports(Set transports) { this.transports = transports; } diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java index e3dfeb7cc2a..7e6795eb811 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java @@ -16,19 +16,27 @@ package org.springframework.security.webauthn.sample.app.web; +import com.webauthn4j.util.Base64UrlUtil; import com.webauthn4j.util.UUIDUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.MultiFactorAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidationResponse; +import org.springframework.security.webauthn.WebAuthnDataConverter; +import org.springframework.security.webauthn.WebAuthnOptionWebHelper; +import org.springframework.security.webauthn.WebAuthnRegistrationRequest; import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorTransport; +import org.springframework.security.webauthn.exception.DataConversionException; import org.springframework.security.webauthn.exception.ValidationException; -import org.springframework.security.webauthn.sample.domain.entity.AuthenticatorEntity; -import org.springframework.security.webauthn.sample.domain.entity.UserEntity; -import org.springframework.security.webauthn.sample.domain.exception.WebAuthnSampleBusinessException; -import org.springframework.security.webauthn.sample.domain.service.WebAuthnUserDetailsServiceImpl; +import org.springframework.security.webauthn.userdetails.InMemoryWebAuthnAndPasswordUserDetailsManager; +import org.springframework.security.webauthn.userdetails.WebAuthnAndPasswordUser; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.Base64Utils; @@ -36,14 +44,11 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; /** * Login controller @@ -52,6 +57,8 @@ @Controller public class WebAuthnSampleController { + private final Log logger = LogFactory.getLog(getClass()); + private static final String REDIRECT_LOGIN = "redirect:/login"; private static final String REDIRECT_SIGNUP = "redirect:/signup"; @@ -62,14 +69,26 @@ public class WebAuthnSampleController { private static final String VIEW_DASHBOARD_DASHBOARD = "dashboard/dashboard"; @Autowired - private WebAuthnUserDetailsServiceImpl webAuthnUserDetailsService; + private InMemoryWebAuthnAndPasswordUserDetailsManager webAuthnUserDetailsService; @Autowired private WebAuthnRegistrationRequestValidator registrationRequestValidator; + @Autowired + private WebAuthnOptionWebHelper webAuthnOptionWebHelper; + + @Autowired + private WebAuthnDataConverter webAuthnDataConverter; + @Autowired private PasswordEncoder passwordEncoder; + @ModelAttribute + public void addAttributes(Model model, HttpServletRequest request) { + model.addAttribute("webAuthnChallenge", webAuthnOptionWebHelper.getChallenge(request)); + model.addAttribute("webAuthnCredentialIds", webAuthnOptionWebHelper.getCredentialIds()); + } + @RequestMapping(value = "/") public String index(Model model) { return REDIRECT_SIGNUP; @@ -91,49 +110,70 @@ public String template(Model model) { } @RequestMapping(value = "/signup", method = RequestMethod.POST) - public String create(HttpServletRequest request, HttpServletResponse response, @Valid @ModelAttribute("userForm") UserCreateForm userCreateForm, BindingResult result, Model model, RedirectAttributes redirectAttributes) { + public String create(HttpServletRequest request, @Valid @ModelAttribute("userForm") UserCreateForm userCreateForm, BindingResult result, Model model) { if (result.hasErrors()) { return VIEW_SIGNUP_SIGNUP; } - WebAuthnRegistrationRequestValidationResponse webAuthnRegistrationRequestValidationResponse; + + WebAuthnRegistrationRequest webAuthnRegistrationRequest = new WebAuthnRegistrationRequest( + request, + userCreateForm.getAuthenticator().getClientDataJSON(), + userCreateForm.getAuthenticator().getAttestationObject(), + userCreateForm.getAuthenticator().getTransports(), + userCreateForm.getAuthenticator().getClientExtensions() + ); try { - webAuthnRegistrationRequestValidationResponse = registrationRequestValidator.validate( - request, - userCreateForm.getAuthenticator().getClientDataJSON(), - userCreateForm.getAuthenticator().getAttestationObject(), - null, //TODO - userCreateForm.getAuthenticator().getClientExtensions() - ); - } catch (ValidationException e) { + registrationRequestValidator.validate(webAuthnRegistrationRequest); + } + catch (ValidationException e){ + logger.debug("WebAuthn registration request validation failed.", e); return VIEW_SIGNUP_SIGNUP; } + catch (DataConversionException e){ + logger.debug("WebAuthn registration request data conversion failed.", e); + return VIEW_SIGNUP_SIGNUP; + } + + AuthenticatorCreateForm sourceAuthenticator = userCreateForm.getAuthenticator(); - UserEntity destination = new UserEntity(); + byte[] attestationObject = Base64UrlUtil.decode(sourceAuthenticator.getAttestationObject()); + byte[] authenticatorData = webAuthnDataConverter.extractAuthenticatorData(attestationObject); + byte[] attestedCredentialData = webAuthnDataConverter.extractAttestedCredentialData(authenticatorData); + byte[] credentialId = webAuthnDataConverter.extractCredentialId(attestedCredentialData); + long signCount = webAuthnDataConverter.extractSignCount(authenticatorData); + Set transports; + if (sourceAuthenticator.getTransports() == null) { + transports = null; + } + else { + transports = sourceAuthenticator.getTransports().stream() + .map(WebAuthnAuthenticatorTransport::create) + .collect(Collectors.toSet()); + } - destination.setUserHandle(Base64Utils.decodeFromUrlSafeString(userCreateForm.getUserHandle())); - destination.setUsername(userCreateForm.getUsername()); - destination.setPassword(passwordEncoder.encode(userCreateForm.getPassword())); + List authenticators = new ArrayList<>(); + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl( + credentialId, + null, + attestationObject, + signCount, + transports, + sourceAuthenticator.getClientExtensions()); - List authenticators = new ArrayList<>(); - AuthenticatorEntity authenticator = new AuthenticatorEntity(); - AuthenticatorCreateForm sourceAuthenticator = userCreateForm.getAuthenticator(); - authenticator.setUser(destination); - authenticator.setName(null); // sample application doesn't name authenticator - authenticator.setAttestationStatement(webAuthnRegistrationRequestValidationResponse.getAttestationObject().getAttestationStatement()); - authenticator.setAttestedCredentialData(webAuthnRegistrationRequestValidationResponse.getAttestationObject().getAuthenticatorData().getAttestedCredentialData()); - authenticator.setCounter(webAuthnRegistrationRequestValidationResponse.getAttestationObject().getAuthenticatorData().getSignCount()); - authenticator.setTransports(sourceAuthenticator.getTransports()); authenticators.add(authenticator); - destination.setAuthenticators(authenticators); - destination.setLocked(false); - destination.setSingleFactorAuthenticationAllowed(userCreateForm.isSingleFactorAuthenticationAllowed()); + byte[] userHandle = Base64Utils.decodeFromUrlSafeString(userCreateForm.getUserHandle()); + String username = userCreateForm.getUsername(); + String password = passwordEncoder.encode(userCreateForm.getPassword()); + List authorities = Collections.emptyList(); + boolean singleFactorAuthenticationAllowed = userCreateForm.isSingleFactorAuthenticationAllowed(); + WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser(userHandle, username, password, authenticators, singleFactorAuthenticationAllowed, authorities); + - UserEntity user = destination; try { webAuthnUserDetailsService.createUser(user); - } catch (WebAuthnSampleBusinessException ex) { + } catch (IllegalArgumentException ex) { return VIEW_SIGNUP_SIGNUP; } diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java deleted file mode 100644 index 1549c2f2913..00000000000 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/AuthenticatorEntity.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.sample.domain.entity; - -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.data.AuthenticatorTransport; -import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; -import com.webauthn4j.data.attestation.statement.AttestationStatement; -import com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput; -import com.webauthn4j.data.extension.client.RegistrationExtensionClientOutput; -import java.util.Map; -import java.util.Set; - -/** - * Authenticator model - */ -public class AuthenticatorEntity implements Authenticator { - - private Integer id; - - private String name; - - private UserEntity user; - - private long counter; - - private Set transports; - - private AttestedCredentialData attestedCredentialData; - - private AttestationStatement attestationStatement; - - private Map clientExtensions; - - private Map authenticatorExtensions; - - public String getFormat() { - return attestationStatement.getFormat(); - } - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public UserEntity getUser() { - return user; - } - - public void setUser(UserEntity user) { - this.user = user; - } - - public long getCounter() { - return counter; - } - - public void setCounter(long counter) { - this.counter = counter; - } - - @Override - public Set getTransports() { - return transports; - } - - public void setTransports(Set transports) { - this.transports = transports; - } - - public AttestedCredentialData getAttestedCredentialData() { - return attestedCredentialData; - } - - public void setAttestedCredentialData(AttestedCredentialData attestedCredentialData) { - this.attestedCredentialData = attestedCredentialData; - } - - public AttestationStatement getAttestationStatement() { - return attestationStatement; - } - - public void setAttestationStatement(AttestationStatement attestationStatement) { - this.attestationStatement = attestationStatement; - } - - @Override - public Map getClientExtensions() { - return clientExtensions; - } - - @Override - public void setClientExtensions(Map clientExtensions) { - this.clientExtensions = clientExtensions; - } - - @Override - public Map getAuthenticatorExtensions() { - return authenticatorExtensions; - } - - @Override - public void setAuthenticatorExtensions(Map authenticatorExtensions) { - this.authenticatorExtensions = authenticatorExtensions; - } -} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java deleted file mode 100644 index beb593a86b4..00000000000 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/entity/UserEntity.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.sample.domain.entity; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * User model - */ -public class UserEntity implements WebAuthnUserDetails { - - private Integer id; - private byte[] userHandle; - private String username; - - private List authenticators; - - private String password; - - private boolean locked; - - private boolean singleFactorAuthenticationAllowed; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - @Override - public byte[] getUserHandle() { - return userHandle; - } - - public void setUserHandle(byte[] userHandle) { - this.userHandle = userHandle; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - @Override - public List getAuthenticators() { - return authenticators; - } - - public void setAuthenticators(List authenticators) { - this.authenticators = authenticators; - } - - @Override - public Collection getAuthorities() { - return Collections.emptyList(); - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public boolean isLocked() { - return locked; - } - - public void setLocked(boolean locked) { - this.locked = locked; - } - - @Override - public boolean isSingleFactorAuthenticationAllowed() { - return singleFactorAuthenticationAllowed; - } - - @Override - public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed) { - this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return !isLocked(); - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - /** - * return String representation - */ - @Override - public String toString() { - return username; - } - -} diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java deleted file mode 100644 index 0bc639d5db8..00000000000 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/service/WebAuthnUserDetailsServiceImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.sample.domain.service; - -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.util.Base64UrlUtil; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; -import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; -import org.springframework.security.webauthn.sample.domain.entity.AuthenticatorEntity; -import org.springframework.security.webauthn.sample.domain.entity.UserEntity; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * {@inheritDoc} - */ -@Component -@Transactional -public class WebAuthnUserDetailsServiceImpl implements WebAuthnUserDetailsService, WebAuthnAuthenticatorService { - - private List users = new ArrayList<>(); - - /** - * {@inheritDoc} - */ - @Override - public WebAuthnUserDetails loadUserByUsername(String username) { - return users - .stream() - .filter(user -> user.getUsername().equals(username)) - .findFirst() - .orElseThrow(() -> new UsernameNotFoundException(String.format("UserEntity with username'%s' is not found.", username))); - } - - @Override - public WebAuthnUserDetails loadUserByCredentialId(byte[] credentialId) { - return users - .stream() - .filter(user -> user.getAuthenticators().stream().anyMatch(authenticator -> Arrays.equals(authenticator.getAttestedCredentialData().getCredentialId(), credentialId))) - .findFirst() - .orElseThrow(() -> new CredentialIdNotFoundException(String.format("AuthenticatorEntity with credentialId'%s' is not found.", Base64UrlUtil.encodeToString(credentialId)))); - } - - @Override - public void addAuthenticator(String username, Authenticator authenticator) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeAuthenticator(String username, Authenticator authenticator) { - throw new UnsupportedOperationException(); - } - - @Override - public void removeAuthenticator(String username, byte[] credentialId) { - throw new UnsupportedOperationException(); - } - - public UserEntity createUser(UserEntity user) { - users.add(user); - return user; - } - - @Override - public void updateCounter(byte[] credentialId, long counter) throws CredentialIdNotFoundException { - - AuthenticatorEntity authenticator = users - .stream() - .flatMap(user -> user.getAuthenticators().stream()) - .filter(entry -> Arrays.equals(entry.getAttestedCredentialData().getCredentialId(), credentialId)) - .findFirst() - .orElseThrow(() -> new CredentialIdNotFoundException(String.format("AuthenticatorEntity with credentialId'%s' is not found.", Base64UrlUtil.encodeToString(credentialId)))); - - authenticator.setCounter(counter); - } - -} diff --git a/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js b/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js index 0181826f8fe..d64375f06c0 100644 --- a/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js +++ b/samples/javaconfig/webauthn/src/main/resources/static/js/webauthn.js @@ -1,89 +1,73 @@ function createCredential(residentKeyRequirement){ - return fetchAttestationOptions().then(function (options) { - var username = $("#username").val(); - var userHandle = $("#userHandle").val(); - - var authenticatorSelection = options["authenticatorSelection"]; - authenticatorSelection["requireResidentKey"] = residentKeyRequirement; - var publicKeyCredentialCreationOptions = { - rp: options["rp"], - user: { - id: base64url.decodeBase64url(userHandle), - name: username, - displayName: username + var username = $("#username").val(); + var userHandle = $("#userHandle").val(); + var challenge = $("meta[name=webAuthnChallenge]").attr("content"); + var credentialIds = $("meta[name=webAuthnCredentialId]") + .map(function(i, element){ return $(element).attr("content")}) + .get(); + + var publicKeyCredentialCreationOptions = { + rp: { + name: "Spring Security WebAuthn Sample" + }, + user: { + id: base64url.decodeBase64url(userHandle), + name: username, + displayName: username + }, + challenge: base64url.decodeBase64url(challenge), + pubKeyCredParams: [ + { + "type": "public-key", + "alg": -7 //ES256 }, - challenge: base64url.decodeBase64url(options["challenge"]), - pubKeyCredParams: options["pubKeyCredParams"], - timeout: options["timeout"], - excludeCredentials: options["excludeCredentials"].map(function(credential){ - return { - type: credential["type"], - id: base64url.decodeBase64url(credential["id"]) - } - }), - authenticatorSelection: authenticatorSelection, - attestation: options["attestation"], - extensions: options["extensions"] - }; + { + type: "public-key", + alg: -257 //RS256 + } + ], + excludeCredentials: credentialIds.map(function(credentialId){ + return { + type: "public-key", + id: base64url.decodeBase64url(credentialId) + } + }), + authenticatorSelection: { + requireResidentKey: residentKeyRequirement + }, + attestation: "none" + }; - var credentialCreationOptions = { - publicKey: publicKeyCredentialCreationOptions - }; + var credentialCreationOptions = { + publicKey: publicKeyCredentialCreationOptions + }; - return navigator.credentials.create(credentialCreationOptions); - }); + return navigator.credentials.create(credentialCreationOptions); } function getCredential(userVerification){ - return fetchAssertionOptions().then(function (options) { - var publicKeyCredentialRequestOptions = { - challenge: base64url.decodeBase64url(options["challenge"]), - timeout: options["timeout"], - rpId: options["rpId"], - allowCredentials: options["allowCredentials"].map(function(credential){ - return { - type: credential["type"], - id: base64url.decodeBase64url(credential["id"]) - } - }), - userVerification: userVerification, - extensions: options["extensions"] - }; - - var credentialRequestOptions = { - publicKey: publicKeyCredentialRequestOptions - }; - - return navigator.credentials.get(credentialRequestOptions); - }); -} + var challenge = $("meta[name=webAuthnChallenge]").attr("content"); + var credentialIds = $("meta[name=webAuthnCredentialId]") + .map(function(i, element){ return $(element).attr("content")}) + .get(); + var publicKeyCredentialRequestOptions = { + challenge: base64url.decodeBase64url(challenge), + allowCredentials: credentialIds.map(function(credentialId){ + return { + type: "public-key", + id: base64url.decodeBase64url(credentialId) + } + }), + userVerification: userVerification + }; -function fetchAttestationOptions(){ - return $.ajax({ - type: 'GET', - url: './webauthn/attestation/options', - dataType: 'json' - }).done(function(options){ - console.log(options); - return options; - }).fail(function(error){ - console.log(error); - }); -} + var credentialRequestOptions = { + publicKey: publicKeyCredentialRequestOptions + }; -function fetchAssertionOptions(){ - return $.ajax({ - type: 'GET', - url: './webauthn/assertion/options', - dataType: 'json' - }).done(function(options){ - console.log(options); - return options; - }).fail(function(error){ - console.log(error); - }); + return navigator.credentials.get(credentialRequestOptions); } $(document).ready(function() { diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java new file mode 100644 index 00000000000..b931827a183 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java @@ -0,0 +1,263 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.authenticator.Authenticator; +import com.webauthn4j.authenticator.AuthenticatorImpl; +import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.data.AuthenticatorTransport; +import com.webauthn4j.data.WebAuthnAuthenticationContext; +import com.webauthn4j.data.WebAuthnRegistrationContext; +import com.webauthn4j.data.attestation.AttestationObject; +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.DefaultChallenge; +import com.webauthn4j.server.ServerProperty; +import com.webauthn4j.util.exception.WebAuthnException; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.exception.*; +import org.springframework.security.webauthn.server.WebAuthnOrigin; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +public class WebAuthn4JWebAuthnManager implements WebAuthnManager { + + // ~ Instance fields + // ================================================================================================ + private WebAuthnRegistrationContextValidator registrationContextValidator; + private WebAuthnAuthenticationContextValidator authenticationContextValidator; + + private String rpId; + + private CborConverter cborConverter; + + public WebAuthn4JWebAuthnManager( + WebAuthnRegistrationContextValidator registrationContextValidator, + WebAuthnAuthenticationContextValidator authenticationContextValidator, + WebAuthnDataConverter webAuthnDataConverter) { + this.registrationContextValidator = registrationContextValidator; + this.authenticationContextValidator = authenticationContextValidator; + this.cborConverter = new CborConverter(webAuthnDataConverter.getJsonMapper(), webAuthnDataConverter.getCborMapper()); + } + + public WebAuthn4JWebAuthnManager( + WebAuthnRegistrationContextValidator registrationContextValidator, + WebAuthnAuthenticationContextValidator authenticationContextValidator) { + this(registrationContextValidator, authenticationContextValidator, new WebAuthnDataConverter()); + } + + public WebAuthn4JWebAuthnManager(WebAuthnDataConverter webAuthnDataConverter) { + this(WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(), + new WebAuthnAuthenticationContextValidator(), webAuthnDataConverter); + } + + public WebAuthn4JWebAuthnManager() { + this(new WebAuthnDataConverter()); + } + + /** + * Wraps WebAuthnAuthentication to proper {@link RuntimeException} (mainly {@link AuthenticationException} subclass. + * + * @param e exception to be wrapped + * @return wrapping exception + */ + @SuppressWarnings("squid:S3776") + static RuntimeException wrapWithAuthenticationException(WebAuthnException e) { + // ValidationExceptions + if (e instanceof com.webauthn4j.validator.exception.BadAaguidException) { + return new BadAaguidException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadAlgorithmException) { + return new BadAlgorithmException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadAttestationStatementException) { + if (e instanceof com.webauthn4j.validator.exception.KeyDescriptionValidationException) { + return new KeyDescriptionValidationException(e.getMessage(), e); + } else { + return new BadAttestationStatementException(e.getMessage(), e); + } + } else if (e instanceof com.webauthn4j.validator.exception.BadChallengeException) { + return new BadChallengeException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadOriginException) { + return new BadOriginException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadRpIdException) { + return new BadRpIdException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.BadSignatureException) { + return new BadSignatureException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.CertificateException) { + return new CertificateException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.ConstraintViolationException) { + return new ConstraintViolationException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.MaliciousCounterValueException) { + return new MaliciousCounterValueException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.MaliciousDataException) { + return new MaliciousDataException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.MissingChallengeException) { + return new MissingChallengeException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.PublicKeyMismatchException) { + return new PublicKeyMismatchException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.SelfAttestationProhibitedException) { + return new SelfAttestationProhibitedException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.TokenBindingException) { + return new TokenBindingException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.TrustAnchorNotFoundException) { + return new TrustAnchorNotFoundException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.UnexpectedExtensionException) { + return new UnexpectedExtensionException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.UserNotPresentException) { + return new UserNotPresentException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.UserNotVerifiedException) { + return new UserNotVerifiedException(e.getMessage(), e); + } else if (e instanceof com.webauthn4j.validator.exception.ValidationException) { + return new ValidationException("WebAuthn validation error", e); + } + // DataConversionException + else if (e instanceof com.webauthn4j.converter.exception.DataConversionException) { + return new DataConversionException("WebAuthn data conversion error", e); + } else { + return new AuthenticationServiceException(null, e); + } + } + + public void verifyRegistrationData( + WebAuthnRegistrationData registrationData + ) { + + Assert.notNull(registrationData.getClientDataJSON(), "clientDataJSON must not be null"); + Assert.notNull(registrationData.getAttestationObject(), "attestationObject must not be null"); + if (registrationData.getTransports() != null) { + registrationData.getTransports().forEach(transport -> Assert.hasText(transport, "each transport must have text")); + } + Assert.notNull(registrationData.getServerProperty(), "serverProperty must not be null"); + + WebAuthnRegistrationContext registrationContext = createRegistrationContext(registrationData); + + try { + registrationContextValidator.validate(registrationContext); + } catch (WebAuthnException e) { + throw wrapWithAuthenticationException(e); + } + } + + @Override + public void verifyAuthenticationData(WebAuthnAuthenticationData authenticationData, WebAuthnAuthenticator webAuthnAuthenticator) { + + //TODO: null check + + WebAuthnAuthenticationContext authenticationContext = createWebAuthnAuthenticationContext(authenticationData); + + AttestationObject attestationObject = cborConverter.readValue(webAuthnAuthenticator.getAttestationObject(), AttestationObject.class); + + Set transports; + if (webAuthnAuthenticator.getTransports() == null) { + transports = Collections.emptySet(); + } else { + transports = webAuthnAuthenticator.getTransports().stream() + .map(transport -> AuthenticatorTransport.create(transport.getValue())) + .collect(Collectors.toSet()); + } + + Authenticator authenticator = new AuthenticatorImpl( + attestationObject.getAuthenticatorData().getAttestedCredentialData(), + attestationObject.getAttestationStatement(), + webAuthnAuthenticator.getCounter(), + transports + ); + + try { + authenticationContextValidator.validate(authenticationContext, authenticator); + } catch (WebAuthnException e) { + throw wrapWithAuthenticationException(e); + } + + } + + @Override + public String getEffectiveRpId(HttpServletRequest request) { + String effectiveRpId; + if (this.rpId != null) { + effectiveRpId = this.rpId; + } else { + WebAuthnOrigin origin = WebAuthnOrigin.create(request); + effectiveRpId = origin.getHost(); + } + return effectiveRpId; + } + + public String getRpId() { + return rpId; + } + + public void setRpId(String rpId) { + this.rpId = rpId; + } + + private WebAuthnRegistrationContext createRegistrationContext(WebAuthnRegistrationData webAuthnRegistrationData) { + + byte[] clientDataBytes = webAuthnRegistrationData.getClientDataJSON(); + byte[] attestationObjectBytes = webAuthnRegistrationData.getAttestationObject(); + Set transports = webAuthnRegistrationData.getTransports(); + String clientExtensionsJSON = webAuthnRegistrationData.getClientExtensionsJSON(); + ServerProperty serverProperty = convertToServerProperty(webAuthnRegistrationData.getServerProperty()); + + return new WebAuthnRegistrationContext( + clientDataBytes, + attestationObjectBytes, + transports, + clientExtensionsJSON, + serverProperty, + false, + false, + webAuthnRegistrationData.getExpectedRegistrationExtensionIds()); + } + + private WebAuthnAuthenticationContext createWebAuthnAuthenticationContext(WebAuthnAuthenticationData webAuthnAuthenticationData) { + + ServerProperty serverProperty = convertToServerProperty(webAuthnAuthenticationData.getServerProperty()); + + return new WebAuthnAuthenticationContext( + webAuthnAuthenticationData.getCredentialId(), + webAuthnAuthenticationData.getClientDataJSON(), + webAuthnAuthenticationData.getAuthenticatorData(), + webAuthnAuthenticationData.getSignature(), + webAuthnAuthenticationData.getClientExtensionsJSON(), + serverProperty, + webAuthnAuthenticationData.isUserVerificationRequired(), + webAuthnAuthenticationData.isUserPresenceRequired(), + webAuthnAuthenticationData.getExpectedAuthenticationExtensionIds() + ); + } + + private Origin convertToOrigin(WebAuthnOrigin webAuthnOrigin) { + return new Origin(webAuthnOrigin.getScheme(), webAuthnOrigin.getHost(), webAuthnOrigin.getPort()); + } + + private ServerProperty convertToServerProperty(WebAuthnServerProperty webAuthnServerProperty) { + return new ServerProperty( + convertToOrigin(webAuthnServerProperty.getOrigin()), + webAuthnServerProperty.getRpId(), + new DefaultChallenge(webAuthnServerProperty.getChallenge().getValue()), + webAuthnServerProperty.getTokenBindingId()); + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java index 1bb086fdd4e..e5689b83e8b 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationToken.java @@ -19,7 +19,6 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; /** * An {@link Authentication} implementation for representing WebAuthn assertion like @@ -31,7 +30,7 @@ public class WebAuthnAssertionAuthenticationToken extends AbstractAuthentication // ~ Instance fields // ================================================================================================ - private WebAuthnAuthenticationRequest credentials; + private WebAuthnAuthenticationData credentials; // ~ Constructor @@ -44,7 +43,7 @@ public class WebAuthnAssertionAuthenticationToken extends AbstractAuthentication * * @param credentials credential */ - public WebAuthnAssertionAuthenticationToken(WebAuthnAuthenticationRequest credentials) { + public WebAuthnAssertionAuthenticationToken(WebAuthnAuthenticationData credentials) { super(null); this.credentials = credentials; setAuthenticated(false); @@ -67,7 +66,7 @@ public String getPrincipal() { * @return the stored WebAuthn authentication context */ @Override - public WebAuthnAuthenticationRequest getCredentials() { + public WebAuthnAuthenticationData getCredentials() { return credentials; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java similarity index 81% rename from webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java rename to webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java index 711a1410b64..baf1f90614b 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequest.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package org.springframework.security.webauthn.request; +package org.springframework.security.webauthn; -import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.ArrayUtil; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; import java.io.Serializable; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -30,7 +31,7 @@ * * @author Yoshikazu Nojima */ -public class WebAuthnAuthenticationRequest implements Serializable { +public class WebAuthnAuthenticationData implements Serializable { //~ Instance fields // ================================================================================================ @@ -40,42 +41,43 @@ public class WebAuthnAuthenticationRequest implements Serializable { private final byte[] authenticatorData; private final byte[] signature; private final String clientExtensionsJSON; - private final ServerProperty serverProperty; + + private final WebAuthnServerProperty serverProperty; private final boolean userVerificationRequired; private final boolean userPresenceRequired; - private List expectedAuthenticationExtensionIds; + private final List expectedAuthenticationExtensionIds; @SuppressWarnings("squid:S00107") - public WebAuthnAuthenticationRequest( + public WebAuthnAuthenticationData( byte[] credentialId, byte[] clientDataJSON, byte[] authenticatorData, byte[] signature, String clientExtensionsJSON, - ServerProperty serverProperty, + WebAuthnServerProperty serverProperty, boolean userVerificationRequired, boolean userPresenceRequired, List expectedAuthenticationExtensionIds) { - this.credentialId = credentialId; - this.clientDataJSON = clientDataJSON; - this.authenticatorData = authenticatorData; - this.signature = signature; + this.credentialId = ArrayUtil.clone(credentialId); + this.clientDataJSON = ArrayUtil.clone(clientDataJSON); + this.authenticatorData = ArrayUtil.clone(authenticatorData); + this.signature = ArrayUtil.clone(signature); this.clientExtensionsJSON = clientExtensionsJSON; this.serverProperty = serverProperty; this.userVerificationRequired = userVerificationRequired; this.userPresenceRequired = userPresenceRequired; - this.expectedAuthenticationExtensionIds = expectedAuthenticationExtensionIds; + this.expectedAuthenticationExtensionIds = Collections.unmodifiableList(expectedAuthenticationExtensionIds); } @SuppressWarnings("squid:S00107") - public WebAuthnAuthenticationRequest( + public WebAuthnAuthenticationData( byte[] credentialId, byte[] clientDataJSON, byte[] authenticatorData, byte[] signature, String clientExtensionsJSON, - ServerProperty serverProperty, + WebAuthnServerProperty serverProperty, boolean userVerificationRequired, List expectedAuthenticationExtensionIds) { @@ -112,7 +114,7 @@ public String getClientExtensionsJSON() { return clientExtensionsJSON; } - public ServerProperty getServerProperty() { + public WebAuthnServerProperty getServerProperty() { return serverProperty; } @@ -128,11 +130,12 @@ public List getExpectedAuthenticationExtensionIds() { return expectedAuthenticationExtensionIds; } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - WebAuthnAuthenticationRequest that = (WebAuthnAuthenticationRequest) o; + WebAuthnAuthenticationData that = (WebAuthnAuthenticationData) o; return userVerificationRequired == that.userVerificationRequired && userPresenceRequired == that.userPresenceRequired && Arrays.equals(credentialId, that.credentialId) && diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java index 942ed9ffcb0..25a58c89555 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationProvider.java @@ -16,10 +16,6 @@ package org.springframework.security.webauthn; -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.data.WebAuthnAuthenticationContext; -import com.webauthn4j.util.exception.WebAuthnException; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.support.MessageSourceAccessor; @@ -29,14 +25,12 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsChecker; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsChecker; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import org.springframework.security.webauthn.util.ExceptionUtil; import org.springframework.util.Assert; import java.io.Serializable; @@ -58,11 +52,11 @@ public class WebAuthnAuthenticationProvider implements AuthenticationProvider { protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private WebAuthnUserDetailsService userDetailsService; private WebAuthnAuthenticatorService authenticatorService; - private WebAuthnAuthenticationContextValidator authenticationContextValidator; + private WebAuthnManager webAuthnManager; private boolean forcePrincipalAsString = false; private boolean hideCredentialIdNotFoundExceptions = true; - private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks(); - private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks(); + private WebAuthnUserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks(); + private WebAuthnUserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks(); private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); // ~ Constructor @@ -71,15 +65,15 @@ public class WebAuthnAuthenticationProvider implements AuthenticationProvider { public WebAuthnAuthenticationProvider( WebAuthnUserDetailsService userDetailsService, WebAuthnAuthenticatorService authenticatorService, - WebAuthnAuthenticationContextValidator authenticationContextValidator) { + WebAuthnManager webAuthnManager) { Assert.notNull(userDetailsService, "userDetailsService must not be null"); Assert.notNull(authenticatorService, "authenticatorService must not be null"); - Assert.notNull(authenticationContextValidator, "authenticationContextValidator must not be null"); + Assert.notNull(webAuthnManager, "webAuthnManager must not be null"); this.userDetailsService = userDetailsService; this.authenticatorService = authenticatorService; - this.authenticationContextValidator = authenticationContextValidator; + this.webAuthnManager = webAuthnManager; } // ~ Methods @@ -96,7 +90,7 @@ public Authentication authenticate(Authentication authentication) { WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) authentication; - WebAuthnAuthenticationRequest credentials = authenticationToken.getCredentials(); + WebAuthnAuthenticationData credentials = authenticationToken.getCredentials(); if (credentials == null) { logger.debug("Authentication failed: no credentials provided"); @@ -108,8 +102,8 @@ public Authentication authenticate(Authentication authentication) { byte[] credentialId = credentials.getCredentialId(); WebAuthnUserDetails user = retrieveWebAuthnUserDetails(credentialId); - Authenticator authenticator = user.getAuthenticators().stream() - .filter(item -> Arrays.equals(item.getAttestedCredentialData().getCredentialId(), credentialId)) + WebAuthnAuthenticator authenticator = user.getAuthenticators().stream() + .filter(item -> Arrays.equals(item.getCredentialId(), credentialId)) .findFirst() .orElse(null); @@ -142,29 +136,23 @@ public boolean supports(Class authentication) { return WebAuthnAssertionAuthenticationToken.class.isAssignableFrom(authentication); } - void doAuthenticate(WebAuthnAssertionAuthenticationToken authenticationToken, Authenticator authenticator, WebAuthnUserDetails user) { - - WebAuthnAuthenticationRequest credentials = authenticationToken.getCredentials(); - - boolean userVerificationRequired = isUserVerificationRequired(user, credentials); - - WebAuthnAuthenticationContext authenticationContext = new WebAuthnAuthenticationContext( - credentials.getCredentialId(), - credentials.getClientDataJSON(), - credentials.getAuthenticatorData(), - credentials.getSignature(), - credentials.getClientExtensionsJSON(), - credentials.getServerProperty(), + void doAuthenticate(WebAuthnAssertionAuthenticationToken authenticationToken, WebAuthnAuthenticator webAuthnAuthenticator, WebAuthnUserDetails user) { + + WebAuthnAuthenticationData webAuthnAuthenticationData = authenticationToken.getCredentials(); + boolean userVerificationRequired = isUserVerificationRequired(user, webAuthnAuthenticationData); + webAuthnAuthenticationData = new WebAuthnAuthenticationData( + webAuthnAuthenticationData.getCredentialId(), + webAuthnAuthenticationData.getClientDataJSON(), + webAuthnAuthenticationData.getAuthenticatorData(), + webAuthnAuthenticationData.getSignature(), + webAuthnAuthenticationData.getClientExtensionsJSON(), + webAuthnAuthenticationData.getServerProperty(), userVerificationRequired, - credentials.isUserPresenceRequired(), - credentials.getExpectedAuthenticationExtensionIds() + webAuthnAuthenticationData.isUserPresenceRequired(), + webAuthnAuthenticationData.getExpectedAuthenticationExtensionIds() ); - try { - authenticationContextValidator.validate(authenticationContext, authenticator); - } catch (WebAuthnException e) { - throw ExceptionUtil.wrapWithAuthenticationException(e); - } + webAuthnManager.verifyAuthenticationData(webAuthnAuthenticationData, webAuthnAuthenticator); } @@ -204,32 +192,32 @@ public void setUserDetailsService(WebAuthnUserDetailsService userDetailsService) this.userDetailsService = userDetailsService; } - protected UserDetailsChecker getPreAuthenticationChecks() { + protected WebAuthnUserDetailsChecker getPreAuthenticationChecks() { return preAuthenticationChecks; } /** * Sets the policy will be used to verify the status of the loaded - * UserDetails before validation of the credentials takes place. + * WebAuthnUserDetails before validation of the credentials takes place. * * @param preAuthenticationChecks strategy to be invoked prior to authentication. */ - public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) { + public void setPreAuthenticationChecks(WebAuthnUserDetailsChecker preAuthenticationChecks) { this.preAuthenticationChecks = preAuthenticationChecks; } - protected UserDetailsChecker getPostAuthenticationChecks() { + protected WebAuthnUserDetailsChecker getPostAuthenticationChecks() { return postAuthenticationChecks; } - public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) { + public void setPostAuthenticationChecks(WebAuthnUserDetailsChecker postAuthenticationChecks) { this.postAuthenticationChecks = postAuthenticationChecks; } WebAuthnUserDetails retrieveWebAuthnUserDetails(byte[] credentialId) { WebAuthnUserDetails user; try { - user = userDetailsService.loadUserByCredentialId(credentialId); + user = userDetailsService.loadWebAuthnUserByCredentialId(credentialId); } catch (CredentialIdNotFoundException notFound) { if (hideCredentialIdNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( @@ -249,7 +237,7 @@ WebAuthnUserDetails retrieveWebAuthnUserDetails(byte[] credentialId) { return user; } - boolean isUserVerificationRequired(WebAuthnUserDetails user, WebAuthnAuthenticationRequest credentials) { + boolean isUserVerificationRequired(WebAuthnUserDetails user, WebAuthnAuthenticationData credentials) { Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication(); @@ -261,9 +249,9 @@ boolean isUserVerificationRequired(WebAuthnUserDetails user, WebAuthnAuthenticat } } - private class DefaultPreAuthenticationChecks implements UserDetailsChecker { + private class DefaultPreAuthenticationChecks implements WebAuthnUserDetailsChecker { @Override - public void check(UserDetails user) { + public void check(WebAuthnUserDetails user) { if (!user.isAccountNonLocked()) { logger.debug("User account is locked"); @@ -290,9 +278,9 @@ public void check(UserDetails user) { } } - private class DefaultPostAuthenticationChecks implements UserDetailsChecker { + private class DefaultPostAuthenticationChecks implements WebAuthnUserDetailsChecker { @Override - public void check(UserDetails user) { + public void check(WebAuthnUserDetails user) { if (!user.isCredentialsNonExpired()) { logger.debug("User account credentials have expired"); diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java index bd8e6ef1bc8..dc79ff982eb 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationToken.java @@ -19,7 +19,6 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; import java.io.Serializable; import java.util.Collection; @@ -34,7 +33,7 @@ public class WebAuthnAuthenticationToken extends AbstractAuthenticationToken { //~ Instance fields // ================================================================================================ private Serializable principal; - private WebAuthnAuthenticationRequest credentials; + private WebAuthnAuthenticationData credentials; // ~ Constructor // ======================================================================================================== @@ -46,7 +45,7 @@ public class WebAuthnAuthenticationToken extends AbstractAuthenticationToken { * @param credentials credentials * @param authorities the collection of GrantedAuthority for the principal represented by this authentication object. */ - public WebAuthnAuthenticationToken(Serializable principal, WebAuthnAuthenticationRequest credentials, Collection authorities) { + public WebAuthnAuthenticationToken(Serializable principal, WebAuthnAuthenticationData credentials, Collection authorities) { super(authorities); this.principal = principal; this.credentials = credentials; @@ -68,7 +67,7 @@ public Serializable getPrincipal() { * {@inheritDoc} */ @Override - public WebAuthnAuthenticationRequest getCredentials() { + public WebAuthnAuthenticationData getCredentials() { return credentials; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnDataConverter.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnDataConverter.java new file mode 100644 index 00000000000..301242620cc --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnDataConverter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.webauthn4j.converter.AttestationObjectConverter; +import com.webauthn4j.converter.AttestedCredentialDataConverter; +import com.webauthn4j.converter.AuthenticatorDataConverter; +import com.webauthn4j.converter.util.CborConverter; +import com.webauthn4j.converter.util.JsonConverter; +import org.springframework.util.Assert; + +public class WebAuthnDataConverter { + + private ObjectMapper jsonMapper; + private ObjectMapper cborMapper; + + private AttestationObjectConverter attestationObjectConverter; + private AuthenticatorDataConverter authenticatorDataConverter; + private AttestedCredentialDataConverter attestedCredentialDataConverter; + + public WebAuthnDataConverter(ObjectMapper jsonMapper, ObjectMapper cborMapper) { + Assert.notNull(jsonMapper, "jsonMapper must not be null"); + Assert.notNull(cborMapper, "cborMapper must not be null"); + + this.jsonMapper = jsonMapper; + this.cborMapper = cborMapper; + JsonConverter jsonConverter = new JsonConverter(jsonMapper, cborMapper); + CborConverter cborConverter = jsonConverter.getCborConverter(); + + this.attestationObjectConverter = new AttestationObjectConverter(cborConverter); + this.authenticatorDataConverter = new AuthenticatorDataConverter(cborConverter); + this.attestedCredentialDataConverter = new AttestedCredentialDataConverter(cborConverter); + } + + public WebAuthnDataConverter() { + this(new ObjectMapper(), new ObjectMapper(new CBORFactory())); + } + + + public byte[] extractAuthenticatorData(byte[] attestationObject) { + return attestationObjectConverter.extractAuthenticatorData(attestationObject); + } + + public byte[] extractAttestedCredentialData(byte[] authenticatorData) { + return authenticatorDataConverter.extractAttestedCredentialData(authenticatorData); + } + + public byte[] extractCredentialId(byte[] attestedCredentialData) { + return attestedCredentialDataConverter.extractCredentialId(attestedCredentialData); + } + + public long extractSignCount(byte[] authenticatorData) { + return authenticatorDataConverter.extractSignCount(authenticatorData); + } + + public ObjectMapper getJsonMapper() { + return jsonMapper; + } + + public ObjectMapper getCborMapper() { + return cborMapper; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnManager.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnManager.java new file mode 100644 index 00000000000..3e9e454539a --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnManager.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.server.EffectiveRpIdProvider; + +public interface WebAuthnManager extends EffectiveRpIdProvider { + + void verifyRegistrationData(WebAuthnRegistrationData registrationData); + + void verifyAuthenticationData(WebAuthnAuthenticationData authenticationData, WebAuthnAuthenticator authenticator); + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnOptionWebHelper.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnOptionWebHelper.java new file mode 100644 index 00000000000..c38371403ed --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnOptionWebHelper.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.util.Base64UrlUtil; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class WebAuthnOptionWebHelper { + + private WebAuthnChallengeRepository challengeRepository; + private WebAuthnUserDetailsService userDetailsService; + + public WebAuthnOptionWebHelper(WebAuthnChallengeRepository challengeRepository, WebAuthnUserDetailsService userDetailsService) { + this.challengeRepository = challengeRepository; + this.userDetailsService = userDetailsService; + } + + public String getChallenge(HttpServletRequest request) { + return Base64UrlUtil.encodeToString(challengeRepository.loadOrGenerateChallenge(request).getValue()); + } + + public List getCredentialIds() { + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + if (username == null) { + return Collections.emptyList(); + } else { + try { + WebAuthnUserDetails webAuthnUserDetails = userDetailsService.loadWebAuthnUserByUsername(username); + return webAuthnUserDetails.getAuthenticators().stream() + .map(authenticator -> Base64UrlUtil.encodeToString(authenticator.getCredentialId())) + .collect(Collectors.toList()); + } catch (UsernameNotFoundException e) { + return Collections.emptyList(); + } + } + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java index e03b4222e40..fc6b3ae9cd8 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java @@ -16,7 +16,6 @@ package org.springframework.security.webauthn; -import com.webauthn4j.server.ServerProperty; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationServiceException; @@ -25,8 +24,8 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; -import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import org.springframework.util.Assert; import org.springframework.util.Base64Utils; import org.springframework.util.StringUtils; @@ -77,7 +76,7 @@ public class WebAuthnProcessingFilter extends UsernamePasswordAuthenticationFilt private String signatureParameter = SPRING_SECURITY_FORM_SIGNATURE_KEY; private String clientExtensionsJSONParameter = SPRING_SECURITY_FORM_CLIENT_EXTENSIONS_JSON_KEY; - private ServerPropertyProvider serverPropertyProvider; + private WebAuthnServerPropertyProvider serverPropertyProvider; private List expectedAuthenticationExtensionIds = Collections.emptyList(); @@ -100,7 +99,7 @@ public WebAuthnProcessingFilter() { * @param authorities authorities for FirstOfMultiFactorAuthenticationToken * @param serverPropertyProvider provider for ServerProperty */ - public WebAuthnProcessingFilter(List authorities, ServerPropertyProvider serverPropertyProvider) { + public WebAuthnProcessingFilter(List authorities, WebAuthnServerPropertyProvider serverPropertyProvider) { super(); Assert.notNull(authorities, "authorities must not be null"); Assert.notNull(serverPropertyProvider, "serverPropertyProvider must not be null"); @@ -136,19 +135,19 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ byte[] rawAuthenticatorData = authenticatorData == null ? null : Base64Utils.decodeFromUrlSafeString(authenticatorData); byte[] signatureBytes = signature == null ? null : Base64Utils.decodeFromUrlSafeString(signature); - ServerProperty serverProperty = serverPropertyProvider.provide(request); + WebAuthnServerProperty webAuthnServerProperty = serverPropertyProvider.provide(request); - WebAuthnAuthenticationRequest webAuthnAuthenticationRequest = new WebAuthnAuthenticationRequest( + WebAuthnAuthenticationData webAuthnAuthenticationData = new WebAuthnAuthenticationData( rawId, rawClientData, rawAuthenticatorData, signatureBytes, clientExtensionsJSON, - serverProperty, + webAuthnServerProperty, true, expectedAuthenticationExtensionIds ); - authRequest = new WebAuthnAssertionAuthenticationToken(webAuthnAuthenticationRequest); + authRequest = new WebAuthnAssertionAuthenticationToken(webAuthnAuthenticationData); } // Allow subclasses to set the "details" property @@ -226,11 +225,11 @@ public void setExpectedAuthenticationExtensionIds(List expectedAuthentic this.expectedAuthenticationExtensionIds = expectedAuthenticationExtensionIds; } - public ServerPropertyProvider getServerPropertyProvider() { + public WebAuthnServerPropertyProvider getServerPropertyProvider() { return serverPropertyProvider; } - public void setServerPropertyProvider(ServerPropertyProvider serverPropertyProvider) { + public void setServerPropertyProvider(WebAuthnServerPropertyProvider serverPropertyProvider) { this.serverPropertyProvider = serverPropertyProvider; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java new file mode 100644 index 00000000000..2900b8e2ebd --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import com.webauthn4j.util.ArrayUtil; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class WebAuthnRegistrationData { + + private final byte[] clientDataJSON; + private final byte[] attestationObject; + private final Set transports; + private final String clientExtensionsJSON; + + private final WebAuthnServerProperty serverProperty; + private final List expectedRegistrationExtensionIds; + + public WebAuthnRegistrationData(byte[] clientDataJSON, byte[] attestationObject, Set transports, String clientExtensionsJSON, + WebAuthnServerProperty serverProperty, + List expectedRegistrationExtensionIds) { + this.clientDataJSON = ArrayUtil.clone(clientDataJSON); + this.attestationObject = ArrayUtil.clone(attestationObject); + this.transports = transports == null ? null : Collections.unmodifiableSet(transports); + this.clientExtensionsJSON = clientExtensionsJSON; + this.serverProperty = serverProperty; + this.expectedRegistrationExtensionIds = expectedRegistrationExtensionIds == null ? null : Collections.unmodifiableList(expectedRegistrationExtensionIds); + } + + public byte[] getClientDataJSON() { + return ArrayUtil.clone(clientDataJSON); + } + + public byte[] getAttestationObject() { + return ArrayUtil.clone(attestationObject); + } + + public Set getTransports() { + return transports; + } + + public String getClientExtensionsJSON() { + return clientExtensionsJSON; + } + + public WebAuthnServerProperty getServerProperty() { + return serverProperty; + } + + public List getExpectedRegistrationExtensionIds() { + return expectedRegistrationExtensionIds; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequest.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequest.java new file mode 100644 index 00000000000..742530b6c8d --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +public class WebAuthnRegistrationRequest { + + private HttpServletRequest httpServletRequest; + private String clientDataBase64Url; + private String attestationObjectBase64Url; + private Set transports; + private String clientExtensionsJSON; + + public WebAuthnRegistrationRequest( + HttpServletRequest httpServletRequest, + String clientDataBase64Url, + String attestationObjectBase64Url, + Set transports, + String clientExtensionsJSON) { + this.httpServletRequest = httpServletRequest; + this.clientDataBase64Url = clientDataBase64Url; + this.attestationObjectBase64Url = attestationObjectBase64Url; + this.transports = transports == null ? null : Collections.unmodifiableSet(transports); + this.clientExtensionsJSON = clientExtensionsJSON; + } + + public HttpServletRequest getHttpServletRequest() { + return httpServletRequest; + } + + public String getClientDataBase64Url() { + return clientDataBase64Url; + } + + public String getAttestationObjectBase64Url() { + return attestationObjectBase64Url; + } + + public Set getTransports() { + return transports; + } + + public String getClientExtensionsJSON() { + return clientExtensionsJSON; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnRegistrationRequest that = (WebAuthnRegistrationRequest) o; + return Objects.equals(httpServletRequest, that.httpServletRequest) && + Objects.equals(clientDataBase64Url, that.clientDataBase64Url) && + Objects.equals(attestationObjectBase64Url, that.attestationObjectBase64Url) && + Objects.equals(transports, that.transports) && + Objects.equals(clientExtensionsJSON, that.clientExtensionsJSON); + } + + @Override + public int hashCode() { + return Objects.hash(httpServletRequest, clientDataBase64Url, attestationObjectBase64Url, transports, clientExtensionsJSON); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java deleted file mode 100644 index 75646baffea..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponse.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import com.webauthn4j.data.attestation.AttestationObject; -import com.webauthn4j.data.client.CollectedClientData; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientOutputs; - -import java.util.Objects; - -/** - * Response from {@link WebAuthnRegistrationRequestValidator} - * - * @author Yoshikazu Nojima - */ -public class WebAuthnRegistrationRequestValidationResponse { - - // ~ Instance fields - // ================================================================================================ - - private CollectedClientData collectedClientData; - private AttestationObject attestationObject; - private AuthenticationExtensionsClientOutputs registrationExtensionsClientOutputs; - - // ~ Constructors - // =================================================================================================== - - public WebAuthnRegistrationRequestValidationResponse(CollectedClientData collectedClientData, AttestationObject attestationObject, AuthenticationExtensionsClientOutputs registrationExtensionsClientOutputs) { - this.collectedClientData = collectedClientData; - this.attestationObject = attestationObject; - this.registrationExtensionsClientOutputs = registrationExtensionsClientOutputs; - } - - // ~ Methods - // ======================================================================================================== - - public CollectedClientData getCollectedClientData() { - return collectedClientData; - } - - public AttestationObject getAttestationObject() { - return attestationObject; - } - - public AuthenticationExtensionsClientOutputs getRegistrationExtensionsClientOutputs() { - return registrationExtensionsClientOutputs; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WebAuthnRegistrationRequestValidationResponse that = (WebAuthnRegistrationRequestValidationResponse) o; - return Objects.equals(collectedClientData, that.collectedClientData) && - Objects.equals(attestationObject, that.attestationObject) && - Objects.equals(registrationExtensionsClientOutputs, that.registrationExtensionsClientOutputs); - } - - @Override - public int hashCode() { - - return Objects.hash(collectedClientData, attestationObject, registrationExtensionsClientOutputs); - } -} - diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java index 805f7df16f0..0cd18c0ca6d 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java @@ -16,103 +16,44 @@ package org.springframework.security.webauthn; -import com.webauthn4j.data.WebAuthnRegistrationContext; -import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.Base64UrlUtil; -import com.webauthn4j.util.exception.WebAuthnException; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidationResponse; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; -import org.springframework.security.webauthn.server.ServerPropertyProvider; -import org.springframework.security.webauthn.util.ExceptionUtil; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import org.springframework.util.Assert; -import javax.servlet.http.HttpServletRequest; import java.util.List; -import java.util.Set; -/** - * A validator for WebAuthn registration request - * - * @author Yoshikazu Nojima - */ public class WebAuthnRegistrationRequestValidator { - // ~ Instance fields - // ================================================================================================ - private WebAuthnRegistrationContextValidator registrationContextValidator; - private ServerPropertyProvider serverPropertyProvider; + private WebAuthnManager webAuthnManager; + private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; private List expectedRegistrationExtensionIds; - // ~ Constructors - // =================================================================================================== + public WebAuthnRegistrationRequestValidator( + WebAuthnManager webAuthnManager, + WebAuthnServerPropertyProvider webAuthnServerPropertyProvider) { - /** - * Constructor - * - * @param registrationContextValidator validator for {@link WebAuthnRegistrationContext} - * @param serverPropertyProvider provider for {@link ServerProperty} - */ - public WebAuthnRegistrationRequestValidator(WebAuthnRegistrationContextValidator registrationContextValidator, ServerPropertyProvider serverPropertyProvider) { + this.webAuthnManager = webAuthnManager; + this.webAuthnServerPropertyProvider = webAuthnServerPropertyProvider; + } - Assert.notNull(registrationContextValidator, "registrationContextValidator must not be null"); - Assert.notNull(serverPropertyProvider, "serverPropertyProvider must not be null"); + public void validate(WebAuthnRegistrationRequest registrationRequest) { - this.registrationContextValidator = registrationContextValidator; - this.serverPropertyProvider = serverPropertyProvider; - } + Assert.notNull(registrationRequest, "target must not be null"); + Assert.notNull(registrationRequest.getHttpServletRequest(), "httpServletRequest must not be null"); - // ~ Methods - // ======================================================================================================== - - public WebAuthnRegistrationRequestValidationResponse validate( - HttpServletRequest httpServletRequest, - String clientDataBase64url, - String attestationObjectBase64url, - Set transports, - String clientExtensionsJSON - ) { - Assert.notNull(httpServletRequest, "httpServletRequest must not be null"); - Assert.hasText(clientDataBase64url, "clientDataBase64url must have text"); - Assert.hasText(attestationObjectBase64url, "attestationObjectBase64url must have text"); - if (transports != null) { - transports.forEach(transport -> Assert.hasText(transport, "each transport must have text")); - } - - WebAuthnRegistrationContext registrationContext = - createRegistrationContext(httpServletRequest, clientDataBase64url, attestationObjectBase64url, transports, clientExtensionsJSON); - - try { - WebAuthnRegistrationContextValidationResponse response = registrationContextValidator.validate(registrationContext); - return new WebAuthnRegistrationRequestValidationResponse( - response.getCollectedClientData(), - response.getAttestationObject(), - response.getRegistrationExtensionsClientOutputs()); - } catch (WebAuthnException e) { - throw ExceptionUtil.wrapWithAuthenticationException(e); - } - } + WebAuthnServerProperty webAuthnServerProperty = webAuthnServerPropertyProvider.provide(registrationRequest.getHttpServletRequest()); - WebAuthnRegistrationContext createRegistrationContext( - HttpServletRequest request, - String clientDataBase64, - String attestationObjectBase64, - Set transports, - String clientExtensionsJSON) { - - byte[] clientDataBytes = Base64UrlUtil.decode(clientDataBase64); - byte[] attestationObjectBytes = Base64UrlUtil.decode(attestationObjectBase64); - ServerProperty serverProperty = serverPropertyProvider.provide(request); - - return new WebAuthnRegistrationContext( - clientDataBytes, - attestationObjectBytes, - transports, - clientExtensionsJSON, - serverProperty, - false, - false, + WebAuthnRegistrationData webAuthnRegistrationData = new WebAuthnRegistrationData( + Base64UrlUtil.decode(registrationRequest.getClientDataBase64Url()), + Base64UrlUtil.decode(registrationRequest.getAttestationObjectBase64Url()), + registrationRequest.getTransports(), + registrationRequest.getClientExtensionsJSON(), + webAuthnServerProperty, expectedRegistrationExtensionIds); + + webAuthnManager.verifyRegistrationData(webAuthnRegistrationData); } public List getExpectedRegistrationExtensionIds() { @@ -122,4 +63,5 @@ public List getExpectedRegistrationExtensionIds() { public void setExpectedRegistrationExtensionIds(List expectedRegistrationExtensionIds) { this.expectedRegistrationExtensionIds = expectedRegistrationExtensionIds; } + } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java index 038d959f656..8218f843195 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java @@ -16,12 +16,9 @@ package org.springframework.security.webauthn.authenticator; -import com.webauthn4j.authenticator.AuthenticatorImpl; -import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; -import com.webauthn4j.data.attestation.statement.AttestationStatement; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import java.util.Objects; +import java.util.Set; /** * Models core authenticator information retrieved by a {@link WebAuthnUserDetailsService} @@ -29,57 +26,18 @@ * @author Yoshikazu Nojima * @see WebAuthnUserDetailsService */ -public class WebAuthnAuthenticator extends AuthenticatorImpl { +public interface WebAuthnAuthenticator { - // ~ Instance fields - // ================================================================================================ - private String name; + byte[] getCredentialId(); - // ~ Constructor - // ======================================================================================================== + byte[] getAttestationObject(); - /** - * Constructor - * - * @param name authenticator's friendly name - * @param attestedCredentialData attested credential data - * @param attestationStatement attestation statement - * @param counter counter - */ - public WebAuthnAuthenticator(String name, AttestedCredentialData attestedCredentialData, AttestationStatement attestationStatement, long counter) { - super(attestedCredentialData, attestationStatement, counter); - this.setName(name); - } + long getCounter(); - // ~ Methods - // ======================================================================================================== + void setCounter(long counter); - public String getName() { - return name; - } + Set getTransports(); - public void setName(String name) { - this.name = name; - } + String getClientExtensions(); - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - WebAuthnAuthenticator that = (WebAuthnAuthenticator) o; - return Objects.equals(name, that.name); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - - return Objects.hash(super.hashCode(), name); - } } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java new file mode 100644 index 00000000000..2f78f3ef9c4 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java @@ -0,0 +1,120 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.authenticator; + + +import com.webauthn4j.util.ArrayUtil; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; + +public class WebAuthnAuthenticatorImpl implements WebAuthnAuthenticator { + + // ~ Instance fields + // ================================================================================================ + private byte[] credentialId; + private String name; + private byte[] attestationObject; + private long counter; + private Set transports; + private String clientExtensions; + + // ~ Constructor + // ======================================================================================================== + + /** + * Constructor + * + * @param credentialId credential id + * @param name authenticator's friendly name + * @param attestationObject attestation object + * @param counter counter + * @param transports transports + */ + public WebAuthnAuthenticatorImpl( + byte[] credentialId, + String name, + byte[] attestationObject, + long counter, + Set transports, + String clientExtensions) { + this.credentialId = credentialId; + this.name = name; + this.attestationObject = ArrayUtil.clone(attestationObject); + this.counter = counter; + this.transports = transports; + this.clientExtensions = clientExtensions; + } + + // ~ Methods + // ======================================================================================================== + + @Override + public byte[] getCredentialId() { + return credentialId; + } + + public String getName() { + return name; + } + + @Override + public byte[] getAttestationObject() { + return attestationObject; + } + + public long getCounter() { + return counter; + } + + @Override + public void setCounter(long counter) { + this.counter = counter; + } + + @Override + public Set getTransports() { + return transports; + } + + @Override + public String getClientExtensions() { + return clientExtensions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnAuthenticatorImpl that = (WebAuthnAuthenticatorImpl) o; + return counter == that.counter && + Arrays.equals(credentialId, that.credentialId) && + Objects.equals(name, that.name) && + Arrays.equals(attestationObject, that.attestationObject) && + Objects.equals(transports, that.transports) && + Objects.equals(clientExtensions, that.clientExtensions); + } + + @Override + public int hashCode() { + int result = Objects.hash(name, counter, transports, clientExtensions); + result = 31 * result + Arrays.hashCode(credentialId); + result = 31 * result + Arrays.hashCode(attestationObject); + return result; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java new file mode 100644 index 00000000000..6591f64ec06 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.authenticator; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; + +/** + * Authenticators may implement various transports for communicating with clients. + * This enumeration defines hints as to how clients might communicate with a particular authenticator in order to + * obtain an assertion for a specific credential. Note that these hints represent the WebAuthn Relying Party's + * best belief as to how an authenticator may be reached. + * + * @see + * 5.10.4. Authenticator Transport Enumeration (enum WebAuthnAuthenticatorTransport) + */ +public enum WebAuthnAuthenticatorTransport { + + /** + * Indicates the respective authenticator can be contacted over removable USB. + */ + USB("usb"), + + /** + * Indicates the respective authenticator can be contacted over Near Field Communication (NFC). + */ + NFC("nfc"), + + /** + * Indicates the respective authenticator can be contacted over Bluetooth Smart + * (Bluetooth Low Energy / BLE). + */ + BLE("ble"); + + private String value; + + WebAuthnAuthenticatorTransport(String value) { + this.value = value; + } + + public static WebAuthnAuthenticatorTransport create(String value) { + if (value == null) { + return null; + } + switch (value) { + case "usb": + return USB; + case "nfc": + return NFC; + case "ble": + return BLE; + default: + throw new IllegalArgumentException("value '" + value + "' is out of range"); + } + } + + @JsonCreator + private static WebAuthnAuthenticatorTransport deserialize(String value) throws InvalidFormatException { + try { + return create(value); + } catch (IllegalArgumentException e) { + throw new InvalidFormatException(null, "value is out of range", value, WebAuthnAuthenticatorTransport.class); + } + } + + @JsonValue + public String getValue() { + return value; + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java similarity index 79% rename from webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java rename to webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java index 29cf31afe52..0da1a46c7d1 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepository.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java @@ -16,8 +16,6 @@ package org.springframework.security.webauthn.challenge; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.util.Assert; @@ -25,18 +23,18 @@ import javax.servlet.http.HttpSession; /** - * A {@link ChallengeRepository} implementation that stores data to HTTP session + * A {@link WebAuthnChallengeRepository} implementation that stores data to HTTP session *

* Class design is based on {@link HttpSessionCsrfTokenRepository} * * @author Yoshikazu Nojima */ -public class HttpSessionChallengeRepository implements ChallengeRepository { +public class HttpSessionWebAuthnChallengeRepository implements WebAuthnChallengeRepository { // ~ Static fields/initializers // ===================================================================================== - private static final String DEFAULT_CHALLENGE_ATTR_NAME = HttpSessionChallengeRepository.class + private static final String DEFAULT_CHALLENGE_ATTR_NAME = HttpSessionWebAuthnChallengeRepository.class .getName().concat(".CHALLENGE"); //~ Instance fields @@ -47,12 +45,12 @@ public class HttpSessionChallengeRepository implements ChallengeRepository { // ======================================================================================================== @Override - public Challenge generateChallenge() { - return new DefaultChallenge(); + public WebAuthnChallenge generateChallenge() { + return new WebAuthnChallengeImpl(); } @Override - public void saveChallenge(Challenge challenge, HttpServletRequest request) { + public void saveChallenge(WebAuthnChallenge challenge, HttpServletRequest request) { if (challenge == null) { HttpSession session = request.getSession(false); if (session != null) { @@ -65,16 +63,16 @@ public void saveChallenge(Challenge challenge, HttpServletRequest request) { } @Override - public Challenge loadChallenge(HttpServletRequest request) { + public WebAuthnChallenge loadChallenge(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } - return (Challenge) session.getAttribute(this.sessionAttributeName); + return (WebAuthnChallenge) session.getAttribute(this.sessionAttributeName); } /** - * Sets the {@link HttpSession} attribute name that the {@link Challenge} is stored in + * Sets the {@link HttpSession} attribute name that the {@link WebAuthnChallenge} is stored in * * @param sessionAttributeName the new attribute name to use */ diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java similarity index 71% rename from webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java rename to webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java index ab9d084d4f7..a7b58fdabe1 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/RegistrationExtensionsOptionProvider.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java @@ -13,9 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.webauthn.options; -import com.webauthn4j.data.extension.client.RegistrationExtensionClientInput; +package org.springframework.security.webauthn.challenge; -public class RegistrationExtensionsOptionProvider extends ExtensionsOptionProvider { +public interface WebAuthnChallenge { + + /** + * Gets the challenge value. Cannot be null. + * + * @return the challenge value + */ + byte[] getValue(); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java new file mode 100644 index 00000000000..ffe11305479 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.challenge; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.webauthn4j.util.ArrayUtil; +import com.webauthn4j.util.Base64UrlUtil; +import org.springframework.util.Assert; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.UUID; + +public class WebAuthnChallengeImpl implements WebAuthnChallenge { + private final byte[] value; + + /** + * Creates a new instance + * + * @param value the value of the challenge + */ + public WebAuthnChallengeImpl(byte[] value) { + Assert.notNull(value, "value cannot be null"); + this.value = ArrayUtil.clone(value); + } + + public WebAuthnChallengeImpl(String base64urlString) { + Assert.notNull(base64urlString, "base64urlString cannot be null"); + this.value = Base64UrlUtil.decode(base64urlString); + } + + public WebAuthnChallengeImpl() { + UUID uuid = UUID.randomUUID(); + long hi = uuid.getMostSignificantBits(); + long lo = uuid.getLeastSignificantBits(); + this.value = ByteBuffer.allocate(16).putLong(hi).putLong(lo).array(); + } + + @JsonCreator + public WebAuthnChallengeImpl create(String base64url) { + return new WebAuthnChallengeImpl(Base64UrlUtil.decode(base64url)); + } + + @JsonValue + public String toBase64UrlString() { + return Base64UrlUtil.encodeToString(getValue()); + } + + @Override + public byte[] getValue() { + return ArrayUtil.clone(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnChallengeImpl that = (WebAuthnChallengeImpl) o; + return Arrays.equals(value, that.value); + } + + @Override + public int hashCode() { + return Arrays.hashCode(value); + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java similarity index 55% rename from webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java rename to webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java index b2fc7590571..b6cf462b257 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/ChallengeRepository.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java @@ -16,54 +16,53 @@ package org.springframework.security.webauthn.challenge; -import com.webauthn4j.data.client.challenge.Challenge; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** - * An API to allow changing the method in which the expected {@link Challenge} is + * An API to allow changing the method in which the expected {@link WebAuthnChallenge} is * associated to the {@link HttpServletRequest}. For example, it may be stored in * {@link HttpSession}. * - * @see HttpSessionChallengeRepository + * @see HttpSessionWebAuthnChallengeRepository */ -public interface ChallengeRepository { +public interface WebAuthnChallengeRepository { /** - * Generates a {@link Challenge} + * Generates a {@link WebAuthnChallenge} * - * @return the {@link Challenge} that was generated. Cannot be null. + * @return the {@link WebAuthnChallenge} that was generated. Cannot be null. */ - Challenge generateChallenge(); + WebAuthnChallenge generateChallenge(); /** - * Saves the {@link Challenge} using the {@link HttpServletRequest} and - * {@link HttpServletResponse}. If the {@link Challenge} is null, it is the same as + * Saves the {@link WebAuthnChallenge} using the {@link HttpServletRequest} and + * {@link HttpServletResponse}. If the {@link WebAuthnChallenge} is null, it is the same as * deleting it. * - * @param challenge the {@link Challenge} to save or null to delete + * @param challenge the {@link WebAuthnChallenge} to save or null to delete * @param request the {@link HttpServletRequest} to use */ - void saveChallenge(Challenge challenge, HttpServletRequest request); + void saveChallenge(WebAuthnChallenge challenge, HttpServletRequest request); /** - * Loads the expected {@link Challenge} from the {@link HttpServletRequest} + * Loads the expected {@link WebAuthnChallenge} from the {@link HttpServletRequest} * * @param request the {@link HttpServletRequest} to use - * @return the {@link Challenge} or null if none exists + * @return the {@link WebAuthnChallenge} or null if none exists */ - Challenge loadChallenge(HttpServletRequest request); + WebAuthnChallenge loadChallenge(HttpServletRequest request); /** - * Loads or generates {@link Challenge} from the {@link HttpServletRequest} + * Loads or generates {@link WebAuthnChallenge} from the {@link HttpServletRequest} * * @param request the {@link HttpServletRequest} to use - * @return the {@link Challenge} or null if none exists + * @return the {@link WebAuthnChallenge} or null if none exists */ - default Challenge loadOrGenerateChallenge(HttpServletRequest request) { - Challenge challenge = this.loadChallenge(request); + default WebAuthnChallenge loadOrGenerateChallenge(HttpServletRequest request) { + WebAuthnChallenge challenge = this.loadChallenge(request); if (challenge == null) { challenge = this.generateChallenge(); this.saveChallenge(challenge, request); diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java index 015adbc748a..28c0aa41221 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurer.java @@ -16,50 +16,47 @@ package org.springframework.security.webauthn.config.configurers; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; import org.springframework.security.webauthn.WebAuthnAuthenticationProvider; +import org.springframework.security.webauthn.WebAuthnManager; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; import org.springframework.util.Assert; /** * Allows configuring a {@link WebAuthnAuthenticationProvider} - * - * @see WebAuthnConfigurer - * @see WebAuthnLoginConfigurer */ public class WebAuthnAuthenticationProviderConfigurer< B extends ProviderManagerBuilder, U extends WebAuthnUserDetailsService, A extends WebAuthnAuthenticatorService, - V extends WebAuthnAuthenticationContextValidator> + V extends WebAuthnManager> extends SecurityConfigurerAdapter { //~ Instance fields // ================================================================================================ private U userDetailsService; private A authenticatorService; - private V authenticationContextValidator; + private V webAuthnAuthenticationManager; /** * Constructor * - * @param userDetailsService {@link WebAuthnUserDetailsService} - * @param authenticatorService {@link WebAuthnAuthenticatorService} - * @param authenticationContextValidator {@link WebAuthnAuthenticationContextValidator} + * @param userDetailsService {@link WebAuthnUserDetailsService} + * @param authenticatorService {@link WebAuthnAuthenticatorService} + * @param webAuthnAuthenticationManager {@link WebAuthnManager} */ - public WebAuthnAuthenticationProviderConfigurer(U userDetailsService, A authenticatorService, V authenticationContextValidator) { + public WebAuthnAuthenticationProviderConfigurer(U userDetailsService, A authenticatorService, V webAuthnAuthenticationManager) { Assert.notNull(userDetailsService, "userDetailsService must not be null"); Assert.notNull(authenticatorService, "authenticatorService must not be null"); - Assert.notNull(authenticationContextValidator, "authenticationContextValidator must not be null"); + Assert.notNull(webAuthnAuthenticationManager, "webAuthnAuthenticationManager must not be null"); this.userDetailsService = userDetailsService; this.authenticatorService = authenticatorService; - this.authenticationContextValidator = authenticationContextValidator; + this.webAuthnAuthenticationManager = webAuthnAuthenticationManager; } // ~ Methods @@ -68,7 +65,7 @@ public WebAuthnAuthenticationProviderConfigurer(U userDetailsService, A authenti @Override public void configure(B builder) { WebAuthnAuthenticationProvider authenticationProvider = - new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, authenticationContextValidator); + new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, webAuthnAuthenticationManager); authenticationProvider = postProcess(authenticationProvider); builder.authenticationProvider(authenticationProvider); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java index 1b83c622b90..a0b7c951cfd 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnConfigurerUtil.java @@ -18,17 +18,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.converter.util.JsonConverter; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; import org.springframework.context.ApplicationContext; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; -import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.challenge.HttpSessionChallengeRepository; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.security.webauthn.options.OptionsProviderImpl; -import org.springframework.security.webauthn.server.ServerPropertyProvider; -import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.WebAuthn4JWebAuthnManager; +import org.springframework.security.webauthn.WebAuthnDataConverter; +import org.springframework.security.webauthn.WebAuthnManager; +import org.springframework.security.webauthn.challenge.HttpSessionWebAuthnChallengeRepository; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProviderImpl; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; /** @@ -39,85 +41,90 @@ public class WebAuthnConfigurerUtil { private WebAuthnConfigurerUtil() { } - static > ChallengeRepository getOrCreateChallengeRepository(H http) { + public static > WebAuthnManager getOrCreateWebAuthnAuthenticationManager(H http) { ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - ChallengeRepository challengeRepository; - String[] beanNames = applicationContext.getBeanNamesForType(ChallengeRepository.class); + WebAuthnManager webAuthnManager; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnManager.class); if (beanNames.length == 0) { - challengeRepository = new HttpSessionChallengeRepository(); + WebAuthnDataConverter webAuthnDataConverter = getOrCreateWebAuthnDataConverter(http); + webAuthnManager = new WebAuthn4JWebAuthnManager( + getOrCreateWebAuthnRegistrationContextValidator(http), + getOrCreateWebAuthnAuthenticationContextValidator(http), + webAuthnDataConverter + ); } else { - challengeRepository = applicationContext.getBean(ChallengeRepository.class); + webAuthnManager = applicationContext.getBean(WebAuthnManager.class); } - return challengeRepository; + return webAuthnManager; } - public static > OptionsProvider getOrCreateOptionsProvider(H http) { + public static > WebAuthnServerPropertyProvider getOrCreateServerPropertyProvider(H http) { ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - OptionsProvider optionsProvider; - String[] beanNames = applicationContext.getBeanNamesForType(OptionsProvider.class); + WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnServerPropertyProvider.class); if (beanNames.length == 0) { - WebAuthnUserDetailsService userDetailsService = getWebAuthnUserDetailsService(http); - ChallengeRepository challengeRepository = getOrCreateChallengeRepository(http); - optionsProvider = new OptionsProviderImpl(userDetailsService, challengeRepository); + webAuthnServerPropertyProvider = new WebAuthnServerPropertyProviderImpl(getOrCreateWebAuthnAuthenticationManager(http), getOrCreateChallengeRepository(http)); } else { - optionsProvider = applicationContext.getBean(OptionsProvider.class); + webAuthnServerPropertyProvider = applicationContext.getBean(WebAuthnServerPropertyProvider.class); } - return optionsProvider; + return webAuthnServerPropertyProvider; } + public static > WebAuthnUserDetailsService getWebAuthnUserDetailsService(H http) { + ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); + return applicationContext.getBean(WebAuthnUserDetailsService.class); + } - public static > JsonConverter getOrCreateJsonConverter(H http) { + static > WebAuthnDataConverter getOrCreateWebAuthnDataConverter(H http) { ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - JsonConverter jsonConverter; + WebAuthnDataConverter webAuthnDataConverter; String[] beanNames = applicationContext.getBeanNamesForType(JsonConverter.class); if (beanNames.length == 0) { ObjectMapper jsonMapper = new ObjectMapper(); ObjectMapper cborMapper = new ObjectMapper(new CBORFactory()); - jsonConverter = new JsonConverter(jsonMapper, cborMapper); + webAuthnDataConverter = new WebAuthnDataConverter(jsonMapper, cborMapper); } else { - jsonConverter = applicationContext.getBean(JsonConverter.class); + webAuthnDataConverter = applicationContext.getBean(WebAuthnDataConverter.class); } - return jsonConverter; + return webAuthnDataConverter; } - public static > ServerPropertyProvider getOrCreateServerPropertyProvider(H http) { + private static > WebAuthnRegistrationContextValidator getOrCreateWebAuthnRegistrationContextValidator(H http) { ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - ServerPropertyProvider serverPropertyProvider; - String[] beanNames = applicationContext.getBeanNamesForType(ServerPropertyProvider.class); + WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnRegistrationContextValidator.class); if (beanNames.length == 0) { - serverPropertyProvider = new ServerPropertyProviderImpl(getOrCreateOptionsProvider(http), getOrCreateChallengeRepository(http)); + webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); } else { - serverPropertyProvider = applicationContext.getBean(ServerPropertyProvider.class); + webAuthnRegistrationContextValidator = applicationContext.getBean(WebAuthnRegistrationContextValidator.class); } - return serverPropertyProvider; - } - - public static > WebAuthnUserDetailsService getWebAuthnUserDetailsService(H http) { - ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - return applicationContext.getBean(WebAuthnUserDetailsService.class); + return webAuthnRegistrationContextValidator; } - public static > WebAuthnRegistrationRequestValidator getOrCreateWebAuthnRegistrationRequestValidator(H http) { + private static > WebAuthnAuthenticationContextValidator getOrCreateWebAuthnAuthenticationContextValidator(H http) { ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator; - String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnRegistrationRequestValidator.class); + WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnAuthenticationContextValidator.class); if (beanNames.length == 0) { - webAuthnRegistrationRequestValidator = new WebAuthnRegistrationRequestValidator(getOrCreateWebAuthnRegistrationContextValidator(http), getOrCreateServerPropertyProvider(http)); + WebAuthnDataConverter webAuthnDataConverter = getOrCreateWebAuthnDataConverter(http); + JsonConverter jsonConverter = new JsonConverter(webAuthnDataConverter.getJsonMapper(), webAuthnDataConverter.getCborMapper()); + CborConverter cborConverter = new CborConverter(webAuthnDataConverter.getJsonMapper(), webAuthnDataConverter.getCborMapper()); + webAuthnAuthenticationContextValidator = new WebAuthnAuthenticationContextValidator(jsonConverter, cborConverter); } else { - webAuthnRegistrationRequestValidator = applicationContext.getBean(WebAuthnRegistrationRequestValidator.class); + webAuthnAuthenticationContextValidator = applicationContext.getBean(WebAuthnAuthenticationContextValidator.class); } - return webAuthnRegistrationRequestValidator; + return webAuthnAuthenticationContextValidator; } - public static > WebAuthnRegistrationContextValidator getOrCreateWebAuthnRegistrationContextValidator(H http) { + private static > WebAuthnChallengeRepository getOrCreateChallengeRepository(H http) { ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator; - String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnRegistrationContextValidator.class); + WebAuthnChallengeRepository webAuthnChallengeRepository; + String[] beanNames = applicationContext.getBeanNamesForType(WebAuthnChallengeRepository.class); if (beanNames.length == 0) { - webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); + webAuthnChallengeRepository = new HttpSessionWebAuthnChallengeRepository(); } else { - webAuthnRegistrationContextValidator = applicationContext.getBean(WebAuthnRegistrationContextValidator.class); + webAuthnChallengeRepository = applicationContext.getBean(WebAuthnChallengeRepository.class); } - return webAuthnRegistrationContextValidator; + return webAuthnChallengeRepository; } } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java index c247b5d0532..999b934bffb 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurer.java @@ -13,15 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.security.webauthn.config.configurers; -import com.webauthn4j.converter.util.JsonConverter; -import com.webauthn4j.data.*; -import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; -import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; -import com.webauthn4j.data.extension.client.ExtensionClientInput; -import com.webauthn4j.data.extension.client.RegistrationExtensionClientInput; -import org.springframework.context.ApplicationContext; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.MFATokenEvaluator; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; @@ -31,26 +26,14 @@ import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler; import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler; -import org.springframework.security.web.session.SessionManagementFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.security.webauthn.WebAuthnDataConverter; import org.springframework.security.webauthn.WebAuthnProcessingFilter; -import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.endpoint.AssertionOptionsEndpointFilter; -import org.springframework.security.webauthn.endpoint.AttestationOptionsEndpointFilter; -import org.springframework.security.webauthn.options.ExtensionOptionProvider; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.security.webauthn.options.OptionsProviderImpl; -import org.springframework.security.webauthn.options.StaticExtensionOptionProvider; -import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import org.springframework.util.Assert; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** * Adds WebAuthn authentication. All attributes have reasonable defaults making all @@ -63,17 +46,14 @@ * *

    *
  • {@link WebAuthnProcessingFilter}
  • - *
  • {@link AttestationOptionsEndpointFilter}
  • - *
  • {@link AssertionOptionsEndpointFilter}
  • *
* *

Shared Objects Created

*

* The following shared objects are populated *

    - *
  • {@link ChallengeRepository}
  • - *
  • {@link OptionsProvider}
  • - *
  • {@link ServerPropertyProvider}
  • + *
  • {@link WebAuthnChallengeRepository}
  • + *
  • {@link WebAuthnServerPropertyProvider}
  • *
* *

Shared Objects Used

@@ -90,30 +70,11 @@ public final class WebAuthnLoginConfigurer> extends AbstractAuthenticationFilterConfigurer, WebAuthnProcessingFilter> { - private final AttestationOptionsEndpointConfig attestationOptionsEndpointConfig = new AttestationOptionsEndpointConfig(); - private final AssertionOptionsEndpointConfig assertionOptionsEndpointConfig = new AssertionOptionsEndpointConfig(); - private final PublicKeyCredParamsConfig publicKeyCredParamsConfig = new PublicKeyCredParamsConfig(); - private final AuthenticatorSelectionCriteriaConfig authenticatorSelectionConfig = new AuthenticatorSelectionCriteriaConfig(); - private final ExtensionsClientInputsConfig registrationExtensionsConfig - = new ExtensionsClientInputsConfig<>(); - private final ExtensionsClientInputsConfig authenticationExtensionsConfig - = new ExtensionsClientInputsConfig<>(); - private final ExpectedRegistrationExtensionIdsConfig - expectedRegistrationExtensionIdsConfig = new ExpectedRegistrationExtensionIdsConfig(); - private final ExpectedAuthenticationExtensionIdsConfig - expectedAuthenticationExtensionIdsConfig = new ExpectedAuthenticationExtensionIdsConfig(); //~ Instance fields // ================================================================================================ - private OptionsProvider optionsProvider = null; - private JsonConverter jsonConverter = null; - private ServerPropertyProvider serverPropertyProvider = null; - private WebAuthnRegistrationRequestValidator webAuthnRegistrationRequestValidator; - private String rpId = null; - private String rpName = null; - private String rpIcon = null; - private Long registrationTimeout = null; - private Long authenticationTimeout = null; - private AttestationConveyancePreference attestation = null; + private ObjectMapper jsonMapper = null; + private ObjectMapper cborMapper = null; + private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider = null; private String usernameParameter = null; private String passwordParameter = null; private String credentialIdParameter = null; @@ -137,30 +98,24 @@ public static WebAuthnLoginConfigurer webAuthnLogin() { public void init(H http) throws Exception { super.init(http); - if (jsonConverter == null) { - jsonConverter = WebAuthnConfigurerUtil.getOrCreateJsonConverter(http); - } - http.setSharedObject(JsonConverter.class, jsonConverter); - - if (optionsProvider == null) { - optionsProvider = WebAuthnConfigurerUtil.getOrCreateOptionsProvider(http); - } - if (optionsProvider instanceof OptionsProviderImpl) { - OptionsProviderImpl optionsProviderImpl = (OptionsProviderImpl) optionsProvider; - configureOptionsProviderImpl(optionsProviderImpl); - optionsProvider = optionsProviderImpl; - } - http.setSharedObject(OptionsProvider.class, optionsProvider); - - if (serverPropertyProvider == null) { - serverPropertyProvider = WebAuthnConfigurerUtil.getOrCreateServerPropertyProvider(http); + WebAuthnDataConverter webAuthnDataConverter; + if (jsonMapper == null && cborMapper == null) { + webAuthnDataConverter = WebAuthnConfigurerUtil.getOrCreateWebAuthnDataConverter(http); + } else { + if (jsonMapper == null) { + jsonMapper = new ObjectMapper(); + } + if (cborMapper == null) { + cborMapper = new ObjectMapper(); + } + webAuthnDataConverter = new WebAuthnDataConverter(jsonMapper, cborMapper); } - http.setSharedObject(ServerPropertyProvider.class, serverPropertyProvider); + http.setSharedObject(WebAuthnDataConverter.class, webAuthnDataConverter); - if (webAuthnRegistrationRequestValidator == null) { - webAuthnRegistrationRequestValidator = WebAuthnConfigurerUtil.getOrCreateWebAuthnRegistrationRequestValidator(http); + if (webAuthnServerPropertyProvider == null) { + webAuthnServerPropertyProvider = WebAuthnConfigurerUtil.getOrCreateServerPropertyProvider(http); } - http.setSharedObject(WebAuthnRegistrationRequestValidator.class, webAuthnRegistrationRequestValidator); + http.setSharedObject(WebAuthnServerPropertyProvider.class, webAuthnServerPropertyProvider); } /** @@ -171,40 +126,7 @@ public void configure(H http) throws Exception { super.configure(http); configureParameters(); - this.getAuthenticationFilter().setServerPropertyProvider(serverPropertyProvider); - - this.attestationOptionsEndpointConfig.configure(http); - this.assertionOptionsEndpointConfig.configure(http); - - this.getAuthenticationFilter().setExpectedAuthenticationExtensionIds(expectedAuthenticationExtensionIdsConfig.expectedAuthenticationExtensionIds); - webAuthnRegistrationRequestValidator.setExpectedRegistrationExtensionIds(expectedRegistrationExtensionIdsConfig.expectedRegistrationExtensionIds); - } - - private void configureOptionsProviderImpl(OptionsProviderImpl optionsProviderImpl) { - if (rpId != null) { - optionsProviderImpl.setRpId(rpId); - } - if (rpName != null) { - optionsProviderImpl.setRpName(rpName); - } - if (rpIcon != null) { - optionsProviderImpl.setRpIcon(rpIcon); - } - optionsProviderImpl.getPubKeyCredParams().addAll(publicKeyCredParamsConfig.publicKeyCredentialParameters); - if (registrationTimeout != null) { - optionsProviderImpl.setRegistrationTimeout(registrationTimeout); - } - if (authenticationTimeout != null) { - optionsProviderImpl.setAuthenticationTimeout(authenticationTimeout); - } - AuthenticatorSelectionCriteria authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria( - this.authenticatorSelectionConfig.authenticatorAttachment, - this.authenticatorSelectionConfig.requireResidentKey, - this.authenticatorSelectionConfig.userVerification); - optionsProviderImpl.setAuthenticatorSelection(authenticatorSelectionCriteria); - optionsProviderImpl.setAttestation(this.attestation); - optionsProviderImpl.getRegistrationExtensions().putAll(registrationExtensionsConfig.extensionsClientInputs); - optionsProviderImpl.getAuthenticationExtensions().putAll(authenticationExtensionsConfig.extensionsClientInputs); + this.getAuthenticationFilter().setServerPropertyProvider(webAuthnServerPropertyProvider); } @@ -233,60 +155,41 @@ private void configureParameters() { } /** - * Specifies the {@link OptionsProvider} to be used. + * Specifies the {@link ObjectMapper} to be used to serialize JSON. * - * @param optionsProvider the {@link OptionsProvider} + * @param jsonMapper the {@link ObjectMapper} * @return the {@link WebAuthnLoginConfigurer} for additional customization */ - public WebAuthnLoginConfigurer optionsProvider(OptionsProvider optionsProvider) { - Assert.notNull(optionsProvider, "optionsProvider must not be null"); - this.optionsProvider = optionsProvider; + public WebAuthnLoginConfigurer jsonMapper(ObjectMapper jsonMapper) { + Assert.notNull(jsonMapper, "jsonMapper must not be null"); + this.jsonMapper = jsonMapper; return this; } /** - * Specifies the {@link JsonConverter} to be used. + * Specifies the {@link ObjectMapper} to be used to serialize CBOR. * - * @param jsonConverter the {@link JsonConverter} + * @param cborMapper the {@link ObjectMapper} * @return the {@link WebAuthnLoginConfigurer} for additional customization */ - public WebAuthnLoginConfigurer jsonConverter(JsonConverter jsonConverter) { - Assert.notNull(jsonConverter, "jsonConverter must not be null"); - this.jsonConverter = jsonConverter; + public WebAuthnLoginConfigurer cborMapper(ObjectMapper cborMapper) { + Assert.notNull(cborMapper, "cborMapper must not be null"); + this.cborMapper = cborMapper; return this; } /** - * Specifies the {@link ServerPropertyProvider} to be used. + * Specifies the {@link WebAuthnServerPropertyProvider} to be used. * - * @param serverPropertyProvider the {@link ServerPropertyProvider} + * @param webAuthnServerPropertyProvider the {@link WebAuthnServerPropertyProvider} * @return the {@link WebAuthnLoginConfigurer} for additional customization */ - public WebAuthnLoginConfigurer serverPropertyProvider(ServerPropertyProvider serverPropertyProvider) { - Assert.notNull(serverPropertyProvider, "serverPropertyProvider must not be null"); - this.serverPropertyProvider = serverPropertyProvider; + public WebAuthnLoginConfigurer serverPropertyProvider(WebAuthnServerPropertyProvider webAuthnServerPropertyProvider) { + Assert.notNull(webAuthnServerPropertyProvider, "webAuthnServerPropertyProvider must not be null"); + this.webAuthnServerPropertyProvider = webAuthnServerPropertyProvider; return this; } - - /** - * Returns the {@link AttestationOptionsEndpointConfig} for configuring the {@link AttestationOptionsEndpointFilter} - * - * @return the {@link AttestationOptionsEndpointConfig} - */ - public AttestationOptionsEndpointConfig attestationOptionsEndpoint() { - return attestationOptionsEndpointConfig; - } - - /** - * Returns the {@link AssertionOptionsEndpointConfig} for configuring the {@link AssertionOptionsEndpointFilter} - * - * @return the {@link AssertionOptionsEndpointConfig} - */ - public AssertionOptionsEndpointConfig assertionOptionsEndpoint() { - return assertionOptionsEndpointConfig; - } - /** * The HTTP parameter to look for the username when performing authentication. Default * is "username". @@ -310,7 +213,7 @@ public WebAuthnLoginConfigurer usernameParameter(String usernameParameter) { * @return the {@link WebAuthnLoginConfigurer} for additional customization */ public WebAuthnLoginConfigurer passwordParameter(String passwordParameter) { - Assert.hasText(usernameParameter, "passwordParameter must not be null or empty"); + Assert.hasText(passwordParameter, "passwordParameter must not be null or empty"); this.passwordParameter = passwordParameter; return this; } @@ -324,7 +227,7 @@ public WebAuthnLoginConfigurer passwordParameter(String passwordParameter) { * @return the {@link WebAuthnLoginConfigurer} for additional customization */ public WebAuthnLoginConfigurer credentialIdParameter(String credentialIdParameter) { - Assert.hasText(usernameParameter, "credentialIdParameter must not be null or empty"); + Assert.hasText(credentialIdParameter, "credentialIdParameter must not be null or empty"); this.credentialIdParameter = credentialIdParameter; return this; } @@ -338,7 +241,7 @@ public WebAuthnLoginConfigurer credentialIdParameter(String credentialIdParam * @return the {@link WebAuthnLoginConfigurer} for additional customization */ public WebAuthnLoginConfigurer clientDataJSONParameter(String clientDataJSONParameter) { - Assert.hasText(usernameParameter, "clientDataJSONParameter must not be null or empty"); + Assert.hasText(clientDataJSONParameter, "clientDataJSONParameter must not be null or empty"); this.clientDataJSONParameter = clientDataJSONParameter; return this; } @@ -352,7 +255,7 @@ public WebAuthnLoginConfigurer clientDataJSONParameter(String clientDataJSONP * @return the {@link WebAuthnLoginConfigurer} for additional customization */ public WebAuthnLoginConfigurer authenticatorDataParameter(String authenticatorDataParameter) { - Assert.hasText(usernameParameter, "authenticatorDataParameter must not be null or empty"); + Assert.hasText(authenticatorDataParameter, "authenticatorDataParameter must not be null or empty"); this.authenticatorDataParameter = authenticatorDataParameter; return this; } @@ -366,7 +269,7 @@ public WebAuthnLoginConfigurer authenticatorDataParameter(String authenticato * @return the {@link WebAuthnLoginConfigurer} for additional customization */ public WebAuthnLoginConfigurer signatureParameter(String signatureParameter) { - Assert.hasText(usernameParameter, "signatureParameter must not be null or empty"); + Assert.hasText(signatureParameter, "signatureParameter must not be null or empty"); this.signatureParameter = signatureParameter; return this; } @@ -385,130 +288,6 @@ public WebAuthnLoginConfigurer clientExtensionsJSONParameter(String clientExt return this; } - /** - * The relying party id for credential scoping - * - * @param rpId the relying party id - * @return the {@link WebAuthnLoginConfigurer} for additional customization - */ - public WebAuthnLoginConfigurer rpId(String rpId) { - Assert.hasText(rpId, "rpId parameter must not be null or empty"); - this.rpId = rpId; - return this; - } - - /** - * The relying party name - * - * @param rpName the relying party name - * @return the {@link WebAuthnLoginConfigurer} for additional customization - */ - public WebAuthnLoginConfigurer rpName(String rpName) { - Assert.hasText(rpName, "rpName parameter must not be null or empty"); - this.rpName = rpName; - return this; - } - - /** - * The relying party icon - * - * @param rpIcon the relying party icon - * @return the {@link WebAuthnLoginConfigurer} for additional customization - */ - public WebAuthnLoginConfigurer rpIcon(String rpIcon) { - Assert.hasText(rpIcon, "rpIcon parameter must not be null or empty"); - this.rpIcon = rpIcon; - return this; - } - - /** - * Returns the {@link PublicKeyCredParamsConfig} for configuring PublicKeyCredParams - * - * @return the {@link PublicKeyCredParamsConfig} - */ - public WebAuthnLoginConfigurer.PublicKeyCredParamsConfig publicKeyCredParams() { - return this.publicKeyCredParamsConfig; - } - - /** - * The timeout for registration ceremony - * - * @param registrationTimeout the timeout for registration ceremony - * @return the {@link WebAuthnLoginConfigurer} for additional customization - */ - public WebAuthnLoginConfigurer registrationTimeout(Long registrationTimeout) { - this.registrationTimeout = registrationTimeout; - return this; - } - - /** - * The timeout for authentication ceremony - * - * @param authenticationTimeout the timeout for authentication ceremony - * @return the {@link WebAuthnLoginConfigurer} for additional customization - */ - public WebAuthnLoginConfigurer authenticationTimeout(Long authenticationTimeout) { - this.authenticationTimeout = authenticationTimeout; - return this; - } - - /** - * Returns the {@link AuthenticatorSelectionCriteriaConfig} for configuring authenticator selection criteria - * - * @return the {@link AuthenticatorSelectionCriteriaConfig} - */ - public AuthenticatorSelectionCriteriaConfig authenticatorSelection() { - return this.authenticatorSelectionConfig; - } - - /** - * The attestation conveyance preference - * - * @param attestation the attestation conveyance preference - * @return the {@link WebAuthnLoginConfigurer} for additional customization - */ - public WebAuthnLoginConfigurer attestation(AttestationConveyancePreference attestation) { - this.attestation = attestation; - return this; - } - - /** - * Returns the {@link ExtensionsClientInputsConfig} for configuring registration extensions - * - * @return the {@link ExtensionsClientInputsConfig} - */ - public ExtensionsClientInputsConfig registrationExtensions() { - return this.registrationExtensionsConfig; - } - - /** - * Returns the {@link ExtensionsClientInputsConfig} for configuring authentication extensions - * - * @return the {@link ExtensionsClientInputsConfig} - */ - public ExtensionsClientInputsConfig authenticationExtensions() { - return this.authenticationExtensionsConfig; - } - - /** - * Returns the {@link ExpectedRegistrationExtensionIdsConfig} for configuring the expectedRegistrationExtensionId(s) - * - * @return the {@link ExpectedRegistrationExtensionIdsConfig} - */ - public ExpectedRegistrationExtensionIdsConfig expectedRegistrationExtensionIdsConfig() { - return this.expectedRegistrationExtensionIdsConfig; - } - - /** - * Returns the {@link ExpectedAuthenticationExtensionIdsConfig} for configuring the expectedAuthenticationExtensionId(s) - * - * @return the {@link ExpectedAuthenticationExtensionIdsConfig} - */ - public ExpectedAuthenticationExtensionIdsConfig expectedAuthenticationExtensionIds() { - return this.expectedAuthenticationExtensionIdsConfig; - } - - /** * Forward Authentication Success Handler * @@ -558,298 +337,5 @@ protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingU return new AntPathRequestMatcher(loginProcessingUrl, "POST"); } - /** - * Configuration options for the {@link AttestationOptionsEndpointFilter} - */ - public class AttestationOptionsEndpointConfig { - - private String processingUrl = AttestationOptionsEndpointFilter.FILTER_URL; - - private AttestationOptionsEndpointConfig() { - } - - private void configure(H http) { - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter; - ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - String[] beanNames = applicationContext.getBeanNamesForType(AttestationOptionsEndpointFilter.class); - if (beanNames.length == 0) { - attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); - attestationOptionsEndpointFilter.setFilterProcessesUrl(processingUrl); - } else { - attestationOptionsEndpointFilter = applicationContext.getBean(AttestationOptionsEndpointFilter.class); - } - - http.addFilterAfter(attestationOptionsEndpointFilter, SessionManagementFilter.class); - - } - - /** - * Sets the URL for the options endpoint - * - * @param processingUrl the URL for the options endpoint - * @return the {@link AttestationOptionsEndpointConfig} for additional customization - */ - public AttestationOptionsEndpointConfig processingUrl(String processingUrl) { - this.processingUrl = processingUrl; - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link WebAuthnLoginConfigurer} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - - } - - /** - * Configuration options for the {@link AssertionOptionsEndpointFilter} - */ - public class AssertionOptionsEndpointConfig { - - private String processingUrl = AssertionOptionsEndpointFilter.FILTER_URL; - - private AssertionOptionsEndpointConfig() { - } - - private void configure(H http) { - AssertionOptionsEndpointFilter assertionOptionsEndpointFilter; - ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class); - String[] beanNames = applicationContext.getBeanNamesForType(AttestationOptionsEndpointFilter.class); - if (beanNames.length == 0) { - assertionOptionsEndpointFilter = new AssertionOptionsEndpointFilter(optionsProvider, jsonConverter); - assertionOptionsEndpointFilter.setFilterProcessesUrl(processingUrl); - } else { - assertionOptionsEndpointFilter = applicationContext.getBean(AssertionOptionsEndpointFilter.class); - } - - http.addFilterAfter(assertionOptionsEndpointFilter, SessionManagementFilter.class); - - } - - /** - * Sets the URL for the options endpoint - * - * @param processingUrl the URL for the options endpoint - * @return the {@link AttestationOptionsEndpointConfig} for additional customization - */ - public AssertionOptionsEndpointConfig processingUrl(String processingUrl) { - this.processingUrl = processingUrl; - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link WebAuthnLoginConfigurer} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - - } - - - /** - * Configuration options for PublicKeyCredParams - */ - public class PublicKeyCredParamsConfig { - - private List publicKeyCredentialParameters = new ArrayList<>(); - - private PublicKeyCredParamsConfig() { - } - - /** - * Add PublicKeyCredParam - * - * @param type the {@link PublicKeyCredentialType} - * @param alg the {@link COSEAlgorithmIdentifier} - * @return the {@link PublicKeyCredParamsConfig} - */ - public PublicKeyCredParamsConfig addPublicKeyCredParams(PublicKeyCredentialType type, COSEAlgorithmIdentifier alg) { - Assert.notNull(type, "type must not be null"); - Assert.notNull(alg, "alg must not be null"); - - publicKeyCredentialParameters.add(new PublicKeyCredentialParameters(type, alg)); - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link WebAuthnLoginConfigurer} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - - } - - public class AuthenticatorSelectionCriteriaConfig { - private AuthenticatorAttachment authenticatorAttachment; - private boolean requireResidentKey = false; - private UserVerificationRequirement userVerification = UserVerificationRequirement.PREFERRED; - - /** - * Sets the authenticator attachment preference - * - * @param authenticatorAttachment the authenticator attachment - * @return the {@link AuthenticatorSelectionCriteriaConfig} for additional customization - */ - public WebAuthnLoginConfigurer.AuthenticatorSelectionCriteriaConfig authenticatorAttachment(AuthenticatorAttachment authenticatorAttachment) { - this.authenticatorAttachment = authenticatorAttachment; - return this; - } - - /** - * Sets the residentKey requirement preference - * - * @param requireResidentKey true if requires a resident key - * @return the {@link AuthenticatorSelectionCriteriaConfig} for additional customization - */ - public WebAuthnLoginConfigurer.AuthenticatorSelectionCriteriaConfig requireResidentKey(boolean requireResidentKey) { - this.requireResidentKey = requireResidentKey; - return this; - } - - /** - * Sets the user verification requirement preference - * - * @param userVerification the user verification preference - * @return the {@link AuthenticatorSelectionCriteriaConfig} for additional customization - */ - public WebAuthnLoginConfigurer.AuthenticatorSelectionCriteriaConfig userVerification(UserVerificationRequirement userVerification) { - this.userVerification = userVerification; - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link WebAuthnLoginConfigurer} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - } - - /** - * Configuration options for AuthenticationExtensionsClientInputs - */ - public class ExtensionsClientInputsConfig { - - private Map> extensionsClientInputs = new HashMap<>(); - - private ExtensionsClientInputsConfig() { - } - - /** - * Put ExtensionOption - * - * @param extensionOption the T - * @return the {@link ExtensionsClientInputsConfig} - */ - public ExtensionsClientInputsConfig put(T extensionOption) { - Assert.notNull(extensionOption, "extensionOption must not be null"); - StaticExtensionOptionProvider extensionOptionProvider = new StaticExtensionOptionProvider<>(extensionOption); - extensionsClientInputs.put(extensionOptionProvider.getIdentifier(), extensionOptionProvider); - return this; - } - - /** - * Put ExtensionOptionProvider - * - * @param extensionOptionProvider the T - * @return the {@link ExtensionsClientInputsConfig} - */ - public ExtensionsClientInputsConfig put(ExtensionOptionProvider extensionOptionProvider) { - Assert.notNull(extensionOptionProvider, "extensionOptionProvider must not be null"); - extensionsClientInputs.put(extensionOptionProvider.getIdentifier(), extensionOptionProvider); - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link WebAuthnLoginConfigurer} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - } - - /** - * Configuration options for expectedRegistrationExtensionIds - */ - public class ExpectedRegistrationExtensionIdsConfig { - - private List expectedRegistrationExtensionIds = null; - - private ExpectedRegistrationExtensionIdsConfig() { - } - - /** - * Add AuthenticationExtensionClientInput - * - * @param expectedRegistrationExtensionId the expected registration extension id - * @return the {@link ExpectedRegistrationExtensionIdsConfig} - */ - public ExpectedRegistrationExtensionIdsConfig add(String expectedRegistrationExtensionId) { - Assert.notNull(expectedRegistrationExtensionId, "expectedRegistrationExtensionId must not be null"); - if (expectedRegistrationExtensionIds == null) { - expectedRegistrationExtensionIds = new ArrayList<>(); - } - expectedRegistrationExtensionIds.add(expectedRegistrationExtensionId); - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link ExpectedRegistrationExtensionIdsConfig} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - } - - /** - * Configuration options for expectedRegistrationExtensionIds - */ - public class ExpectedAuthenticationExtensionIdsConfig { - - private List expectedAuthenticationExtensionIds = null; - - private ExpectedAuthenticationExtensionIdsConfig() { - } - - /** - * Add AuthenticationExtensionClientInput - * - * @param expectedAuthenticationExtensionId the expected authentication extension id - * @return the {@link ExpectedAuthenticationExtensionIdsConfig} - */ - public ExpectedAuthenticationExtensionIdsConfig add(String expectedAuthenticationExtensionId) { - Assert.notNull(expectedAuthenticationExtensionId, "expectedAuthenticationExtensionId must not be null"); - if (expectedAuthenticationExtensionIds == null) { - expectedAuthenticationExtensionIds = new ArrayList<>(); - } - expectedAuthenticationExtensionIds.add(expectedAuthenticationExtensionId); - return this; - } - - /** - * Returns the {@link WebAuthnLoginConfigurer} for further configuration. - * - * @return the {@link WebAuthnLoginConfigurer} - */ - public WebAuthnLoginConfigurer and() { - return WebAuthnLoginConfigurer.this; - } - } } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java deleted file mode 100644 index 394f318ed01..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsEndpointFilter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.webauthn4j.converter.util.JsonConverter; -import com.webauthn4j.util.Base64UrlUtil; -import org.springframework.security.webauthn.options.AssertionOptions; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.util.Assert; - -import javax.servlet.http.HttpServletRequest; -import java.io.Serializable; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A filter for providing WebAuthn option parameters to clients. - * Clients can retrieve {@link AssertionOptions}. - * - * @author Yoshikazu Nojima - */ -public class AssertionOptionsEndpointFilter extends OptionsEndpointFilterBase { - - // ~ Static fields/initializers - // ===================================================================================== - - /** - * Default name of path suffix which will validate this filter. - */ - public static final String FILTER_URL = "/webauthn/assertion/options"; - - - //~ Instance fields - // ================================================================================================ - - protected OptionsProvider optionsProvider; - - - // ~ Constructors - // =================================================================================================== - - public AssertionOptionsEndpointFilter(OptionsProvider optionsProvider, JsonConverter jsonConverter) { - super(jsonConverter); - this.optionsProvider = optionsProvider; - this.filterProcessesUrl = FILTER_URL; - checkConfig(); - } - - - // ~ Methods - // ======================================================================================================== - - @Override - public void checkConfig() { - Assert.notNull(optionsProvider, "optionsProvider must not be null"); - super.checkConfig(); - } - - @Override - protected Serializable processRequest(HttpServletRequest request) { - String loginUsername = getLoginUsername(); - AssertionOptions assertionOptions = optionsProvider.getAssertionOptions(request, loginUsername, null); - List credentials = assertionOptions.getAllowCredentials() == null ? null : - assertionOptions.getAllowCredentials().stream() - .map(credential -> new WebAuthnPublicKeyCredentialDescriptor(credential.getType(), Base64UrlUtil.encodeToString(credential.getId()), credential.getTransports())) - .collect(Collectors.toList()); - - return new AssertionOptionsResponse( - assertionOptions.getChallenge(), - assertionOptions.getTimeout(), - assertionOptions.getRpId(), - credentials, - assertionOptions.getExtensions() - ); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java deleted file mode 100644 index 1827934e7de..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AssertionOptionsResponse.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import com.webauthn4j.util.CollectionUtil; - -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -/** - * Options for WebAuthn assertion generation - */ -public class AssertionOptionsResponse implements Serializable { - - // ~ Instance fields - // ================================================================================================ - - private Challenge challenge; - private Long timeout; - private String rpId; - private List allowCredentials; - private AuthenticationExtensionsClientInputs extensions; - - // ~ Constructors - // =================================================================================================== - - public AssertionOptionsResponse( - Challenge challenge, - Long timeout, - String rpId, - List allowCredentials, - AuthenticationExtensionsClientInputs extensions) { - this.challenge = challenge; - this.timeout = timeout; - this.rpId = rpId; - this.allowCredentials = CollectionUtil.unmodifiableList(allowCredentials); - this.extensions = extensions; - } - - // ~ Methods - // ======================================================================================================== - - public Challenge getChallenge() { - return challenge; - } - - public Long getTimeout() { - return timeout; - } - - public String getRpId() { - return rpId; - } - - public List getAllowCredentials() { - return allowCredentials; - } - - public AuthenticationExtensionsClientInputs getExtensions() { - return extensions; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AssertionOptionsResponse that = (AssertionOptionsResponse) o; - return Objects.equals(challenge, that.challenge) && - Objects.equals(timeout, that.timeout) && - Objects.equals(rpId, that.rpId) && - Objects.equals(allowCredentials, that.allowCredentials) && - Objects.equals(extensions, that.extensions); - } - - @Override - public int hashCode() { - - return Objects.hash(challenge, timeout, rpId, allowCredentials, extensions); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java deleted file mode 100644 index 5e922579a75..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.webauthn4j.converter.util.JsonConverter; -import com.webauthn4j.data.PublicKeyCredentialUserEntity; -import com.webauthn4j.util.Base64UrlUtil; -import org.springframework.security.webauthn.options.AttestationOptions; -import org.springframework.security.webauthn.options.OptionsProvider; - -import javax.servlet.http.HttpServletRequest; -import java.io.Serializable; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A filter for providing WebAuthn option parameters to clients. - * Clients can retrieve {@link AttestationOptions}. - * - * @author Yoshikazu Nojima - */ -public class AttestationOptionsEndpointFilter extends OptionsEndpointFilterBase { - - // ~ Static fields/initializers - // ===================================================================================== - - /** - * Default name of path suffix which will validate this filter. - */ - public static final String FILTER_URL = "/webauthn/attestation/options"; - - //~ Instance fields - // ================================================================================================ - - protected OptionsProvider optionsProvider; - - // ~ Constructors - // =================================================================================================== - - public AttestationOptionsEndpointFilter(OptionsProvider optionsProvider, JsonConverter jsonConverter) { - super(jsonConverter); - this.optionsProvider = optionsProvider; - this.filterProcessesUrl = FILTER_URL; - checkConfig(); - } - - - // ~ Methods - // ======================================================================================================== - - protected Serializable processRequest(HttpServletRequest request) { - String loginUsername = getLoginUsername(); - AttestationOptions attestationOptions = optionsProvider.getAttestationOptions(request, loginUsername, null); - - PublicKeyCredentialUserEntity userEntity = attestationOptions.getUser(); - WebAuthnPublicKeyCredentialUserEntity user = userEntity == null ? null : new WebAuthnPublicKeyCredentialUserEntity( - Base64UrlUtil.encodeToString(userEntity.getId()), - userEntity.getName(), - userEntity.getDisplayName(), - userEntity.getIcon() - ); - - List credentials = attestationOptions.getExcludeCredentials() == null ? null : - attestationOptions.getExcludeCredentials().stream() - .map(credential -> new WebAuthnPublicKeyCredentialDescriptor(credential.getType(), Base64UrlUtil.encodeToString(credential.getId()), credential.getTransports())) - .collect(Collectors.toList()); - - return new AttestationOptionsResponse( - attestationOptions.getRp(), - user, - attestationOptions.getChallenge(), - attestationOptions.getPubKeyCredParams(), - attestationOptions.getTimeout(), - credentials, - attestationOptions.getAuthenticatorSelection(), - attestationOptions.getAttestation(), - attestationOptions.getExtensions() - ); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java deleted file mode 100644 index 3ce15557cc5..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/AttestationOptionsResponse.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - - -import com.webauthn4j.data.*; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import com.webauthn4j.util.CollectionUtil; - -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -/** - * Options for WebAuthn attestation generation - */ -@SuppressWarnings("common-java:DuplicatedBlocks") -public class AttestationOptionsResponse implements Serializable { - - // ~ Instance fields - // ================================================================================================ - - private PublicKeyCredentialRpEntity rp; - private WebAuthnPublicKeyCredentialUserEntity user; - private Challenge challenge; - private List pubKeyCredParams; - private Long timeout; - private List excludeCredentials; - private AuthenticatorSelectionCriteria authenticatorSelection; - private AttestationConveyancePreference attestation; - private AuthenticationExtensionsClientInputs extensions; - - // ~ Constructors - // =================================================================================================== - - public AttestationOptionsResponse( - PublicKeyCredentialRpEntity rp, - WebAuthnPublicKeyCredentialUserEntity user, - Challenge challenge, - List pubKeyCredParams, - Long timeout, - List excludeCredentials, - AuthenticatorSelectionCriteria authenticatorSelection, - AttestationConveyancePreference attestation, - AuthenticationExtensionsClientInputs extensions) { - this.rp = rp; - this.user = user; - this.challenge = challenge; - this.pubKeyCredParams = CollectionUtil.unmodifiableList(pubKeyCredParams); - this.timeout = timeout; - this.excludeCredentials = CollectionUtil.unmodifiableList(excludeCredentials); - this.authenticatorSelection = authenticatorSelection; - this.attestation = attestation; - this.extensions = extensions; - } - - /** - * Returns PublicKeyCredentialRpEntity - * - * @return PublicKeyCredentialRpEntity - */ - public PublicKeyCredentialRpEntity getRp() { - return rp; - } - - /** - * If authenticated, returns {@link WebAuthnPublicKeyCredentialUserEntity}, which is a serialized form of {@link PublicKeyCredentialUserEntity} - * Otherwise returns null - * - * @return {@link WebAuthnPublicKeyCredentialUserEntity} - */ - public WebAuthnPublicKeyCredentialUserEntity getUser() { - return user; - } - - /** - * Returns {@link Challenge} - * - * @return {@link Challenge} - */ - public Challenge getChallenge() { - return challenge; - } - - public List getPubKeyCredParams() { - return pubKeyCredParams; - } - - public Long getTimeout() { - return timeout; - } - - public List getExcludeCredentials() { - return excludeCredentials; - } - - public AuthenticatorSelectionCriteria getAuthenticatorSelection() { - return authenticatorSelection; - } - - public AttestationConveyancePreference getAttestation() { - return attestation; - } - - public AuthenticationExtensionsClientInputs getExtensions() { - return extensions; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AttestationOptionsResponse that = (AttestationOptionsResponse) o; - return Objects.equals(rp, that.rp) && - Objects.equals(user, that.user) && - Objects.equals(challenge, that.challenge) && - Objects.equals(pubKeyCredParams, that.pubKeyCredParams) && - Objects.equals(timeout, that.timeout) && - Objects.equals(excludeCredentials, that.excludeCredentials) && - Objects.equals(authenticatorSelection, that.authenticatorSelection) && - attestation == that.attestation && - Objects.equals(extensions, that.extensions); - } - - @Override - public int hashCode() { - return Objects.hash(rp, user, challenge, pubKeyCredParams, timeout, excludeCredentials, authenticatorSelection, attestation, extensions); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java deleted file mode 100644 index 01e904e76b8..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/ErrorResponse.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import java.util.Objects; - -/** - * Error response of {@link AttestationOptionsEndpointFilter} - * - * @author Yoshikazu Nojima - */ -public class ErrorResponse { - - // ~ Instance fields - // ================================================================================================ - - private String errorMessage; - - // ~ Constructor - // ======================================================================================================== - - public ErrorResponse(String errorMessage) { - this.errorMessage = errorMessage; - } - - // ~ Methods - // ======================================================================================================== - - public String getErrorMessage() { - return errorMessage; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ErrorResponse that = (ErrorResponse) o; - return Objects.equals(errorMessage, that.errorMessage); - } - - @Override - public int hashCode() { - return Objects.hash(errorMessage); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java deleted file mode 100644 index 823be397756..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/OptionsEndpointFilterBase.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.webauthn4j.converter.util.JsonConverter; -import org.springframework.security.authentication.*; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.FilterInvocation; -import org.springframework.util.Assert; -import org.springframework.web.filter.GenericFilterBean; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.Serializable; - -/** - * A filter for providing WebAuthn option parameters to clients. - * - * @author Yoshikazu Nojima - */ -public abstract class OptionsEndpointFilterBase extends GenericFilterBean { - - - //~ Instance fields - // ================================================================================================ - - /** - * Url this filter should get activated on. - */ - protected String filterProcessesUrl; - protected JsonConverter jsonConverter; - - private AuthenticationTrustResolver trustResolver; - private MFATokenEvaluator mfaTokenEvaluator; - - - // ~ Constructors - // =================================================================================================== - - public OptionsEndpointFilterBase(JsonConverter jsonConverter) { - this.jsonConverter = jsonConverter; - this.trustResolver = new AuthenticationTrustResolverImpl(); - this.mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - } - - // ~ Methods - // ======================================================================================================== - - @Override - public void afterPropertiesSet() { - checkConfig(); - } - - protected void checkConfig() { - Assert.notNull(filterProcessesUrl, "filterProcessesUrl must not be null"); - Assert.notNull(jsonConverter, "jsonConverter must not be null"); - Assert.notNull(trustResolver, "trustResolver must not be null"); - Assert.notNull(mfaTokenEvaluator, "mfaTokenEvaluator must not be null"); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - FilterInvocation fi = new FilterInvocation(request, response, chain); - - if (!processFilter(fi.getRequest())) { - chain.doFilter(request, response); - return; - } - - try { - Serializable options = processRequest(fi.getRequest()); - writeResponse(fi.getResponse(), options); - } catch (RuntimeException e) { - logger.debug(e); - writeErrorResponse(fi.getResponse(), e); - } - - } - - protected abstract Serializable processRequest(HttpServletRequest request); - - public AuthenticationTrustResolver getTrustResolver() { - return trustResolver; - } - - public void setTrustResolver(AuthenticationTrustResolver trustResolver) { - this.trustResolver = trustResolver; - } - - public MFATokenEvaluator getMFATokenEvaluator() { - return mfaTokenEvaluator; - } - - public void setMFATokenEvaluator(MFATokenEvaluator mfaTokenEvaluator) { - this.mfaTokenEvaluator = mfaTokenEvaluator; - } - - - /** - * The filter will be used in case the URL of the request contains the FILTER_URL. - * - * @param request request used to determine whether to enable this filter - * @return true if this filter should be used - */ - private boolean processFilter(HttpServletRequest request) { - return (request.getRequestURI().contains(filterProcessesUrl)); - } - - void writeResponse(HttpServletResponse httpServletResponse, Serializable data) throws IOException { - String responseText = jsonConverter.writeValueAsString(data); - httpServletResponse.setContentType("application/json"); - httpServletResponse.getWriter().print(responseText); - } - - void writeErrorResponse(HttpServletResponse httpServletResponse, RuntimeException e) throws IOException { - ErrorResponse errorResponse; - int statusCode; - if (e instanceof InsufficientAuthenticationException) { - errorResponse = new ErrorResponse("Anonymous access is prohibited"); - statusCode = HttpServletResponse.SC_FORBIDDEN; - } else { - errorResponse = new ErrorResponse("The server encountered an internal error"); - statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - } - String errorResponseText = jsonConverter.writeValueAsString(errorResponse); - httpServletResponse.setContentType("application/json"); - httpServletResponse.getWriter().print(errorResponseText); - httpServletResponse.setStatus(statusCode); - } - - String getLoginUsername() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null || (trustResolver.isAnonymous(authentication) && !mfaTokenEvaluator.isMultiFactorAuthentication(authentication))) { - return null; - } else { - return authentication.getName(); - } - } - - public void setFilterProcessesUrl(String filterProcessesUrl) { - this.filterProcessesUrl = filterProcessesUrl; - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java deleted file mode 100644 index cbc9a0f5a47..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptor.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.webauthn4j.data.AuthenticatorTransport; -import com.webauthn4j.data.PublicKeyCredentialDescriptor; -import com.webauthn4j.data.PublicKeyCredentialType; -import com.webauthn4j.util.CollectionUtil; - -import java.io.Serializable; -import java.util.Objects; -import java.util.Set; - -/** - * JSON serialization friendly variant of {@link PublicKeyCredentialDescriptor} - * - * @author Yoshikazu Nojima - */ -public class WebAuthnPublicKeyCredentialDescriptor implements Serializable { - - // ~ Instance fields - // ================================================================================================ - - @JsonProperty - private PublicKeyCredentialType type; - @JsonProperty - private String id; - private Set transports; - - // ~ Constructor - // ======================================================================================================== - - @JsonCreator - public WebAuthnPublicKeyCredentialDescriptor( - @JsonProperty("type") PublicKeyCredentialType type, - @JsonProperty("id") String id, - @JsonProperty("transports") Set transports) { - this.type = type; - this.id = id; - this.transports = CollectionUtil.unmodifiableSet(transports); - } - - public WebAuthnPublicKeyCredentialDescriptor( - PublicKeyCredentialType type, - String id) { - this.type = type; - this.id = id; - } - - public WebAuthnPublicKeyCredentialDescriptor( - String id) { - this.type = PublicKeyCredentialType.PUBLIC_KEY; - this.id = id; - } - - - // ~ Methods - // ======================================================================================================== - - public PublicKeyCredentialType getType() { - return type; - } - - public String getId() { - return id; - } - - public Set getTransports() { - return transports; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WebAuthnPublicKeyCredentialDescriptor that = (WebAuthnPublicKeyCredentialDescriptor) o; - return type == that.type && - Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(type, id); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java b/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java deleted file mode 100644 index 0d8c6250e93..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntity.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.webauthn4j.data.PublicKeyCredentialEntity; -import com.webauthn4j.data.PublicKeyCredentialUserEntity; - -import java.util.Objects; - -/** - * JSON serialization friendly variant of {@link PublicKeyCredentialUserEntity} - * - * @author Yoshikazu Nojima - */ -public class WebAuthnPublicKeyCredentialUserEntity extends PublicKeyCredentialEntity { - - // ~ Instance fields - // ================================================================================================ - - private String id; - private String displayName; - - // ~ Constructor - // ======================================================================================================== - - @JsonCreator - public WebAuthnPublicKeyCredentialUserEntity( - @JsonProperty("id") String id, - @JsonProperty("name") String name, - @JsonProperty("displayName") String displayName, - @JsonProperty("icon") String icon) { - super(name, icon); - this.id = id; - this.displayName = displayName; - } - - // ~ Methods - // ======================================================================================================== - - public String getId() { - return id; - } - - public String getDisplayName() { - return displayName; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - WebAuthnPublicKeyCredentialUserEntity that = (WebAuthnPublicKeyCredentialUserEntity) o; - return Objects.equals(id, that.id) && - Objects.equals(displayName, that.displayName); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), id, displayName); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java deleted file mode 100644 index edbbf690c6c..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/AssertionOptions.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - -import com.webauthn4j.data.PublicKeyCredentialDescriptor; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import com.webauthn4j.util.CollectionUtil; - -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -/** - * Options for WebAuthn assertion generation - */ -public class AssertionOptions implements Serializable { - - // ~ Instance fields - // ================================================================================================ - - private Challenge challenge; - private Long timeout; - private String rpId; - private List allowCredentials; - private AuthenticationExtensionsClientInputs extensions; - - // ~ Constructors - // =================================================================================================== - - public AssertionOptions( - Challenge challenge, - Long timeout, - String rpId, - List allowCredentials, - AuthenticationExtensionsClientInputs extensions) { - this.challenge = challenge; - this.timeout = timeout; - this.rpId = rpId; - this.allowCredentials = CollectionUtil.unmodifiableList(allowCredentials); - this.extensions = extensions; - } - - // ~ Methods - // ======================================================================================================== - - public Challenge getChallenge() { - return challenge; - } - - public Long getTimeout() { - return timeout; - } - - public String getRpId() { - return rpId; - } - - public List getAllowCredentials() { - return allowCredentials; - } - - public AuthenticationExtensionsClientInputs getExtensions() { - return extensions; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AssertionOptions that = (AssertionOptions) o; - return Objects.equals(challenge, that.challenge) && - Objects.equals(timeout, that.timeout) && - Objects.equals(rpId, that.rpId) && - Objects.equals(allowCredentials, that.allowCredentials) && - Objects.equals(extensions, that.extensions); - } - - @Override - public int hashCode() { - - return Objects.hash(challenge, timeout, rpId, allowCredentials, extensions); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java deleted file mode 100644 index db878a29452..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/AttestationOptions.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - - -import com.webauthn4j.data.*; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import com.webauthn4j.util.CollectionUtil; - -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -/** - * Options for WebAuthn attestation generation - */ -@SuppressWarnings("common-java:DuplicatedBlocks") -public class AttestationOptions implements Serializable { - - // ~ Instance fields - // ================================================================================================ - - private PublicKeyCredentialRpEntity rp; - private PublicKeyCredentialUserEntity user; - private Challenge challenge; - private List pubKeyCredParams; - private Long timeout; - private List excludeCredentials; - private AuthenticatorSelectionCriteria authenticatorSelection; - private AttestationConveyancePreference attestation; - private AuthenticationExtensionsClientInputs extensions; - - // ~ Constructors - // =================================================================================================== - - public AttestationOptions( - PublicKeyCredentialRpEntity rp, - PublicKeyCredentialUserEntity user, - Challenge challenge, - List pubKeyCredParams, - Long timeout, - List excludeCredentials, - AuthenticatorSelectionCriteria authenticatorSelection, - AttestationConveyancePreference attestation, - AuthenticationExtensionsClientInputs extensions) { - this.rp = rp; - this.user = user; - this.challenge = challenge; - this.pubKeyCredParams = CollectionUtil.unmodifiableList(pubKeyCredParams); - this.timeout = timeout; - this.excludeCredentials = CollectionUtil.unmodifiableList(excludeCredentials); - this.authenticatorSelection = authenticatorSelection; - this.attestation = attestation; - this.extensions = extensions; - } - - /** - * Returns PublicKeyCredentialRpEntity - * - * @return PublicKeyCredentialRpEntity - */ - public PublicKeyCredentialRpEntity getRp() { - return rp; - } - - /** - * If authenticated, returns {@link PublicKeyCredentialUserEntity} - * Otherwise returns null - * - * @return {@link PublicKeyCredentialUserEntity} - */ - public PublicKeyCredentialUserEntity getUser() { - return user; - } - - /** - * Returns {@link Challenge} - * - * @return {@link Challenge} - */ - public Challenge getChallenge() { - return challenge; - } - - public List getPubKeyCredParams() { - return pubKeyCredParams; - } - - public Long getTimeout() { - return timeout; - } - - public List getExcludeCredentials() { - return excludeCredentials; - } - - public AuthenticatorSelectionCriteria getAuthenticatorSelection() { - return authenticatorSelection; - } - - public AttestationConveyancePreference getAttestation() { - return attestation; - } - - public AuthenticationExtensionsClientInputs getExtensions() { - return extensions; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AttestationOptions that = (AttestationOptions) o; - return Objects.equals(rp, that.rp) && - Objects.equals(user, that.user) && - Objects.equals(challenge, that.challenge) && - Objects.equals(pubKeyCredParams, that.pubKeyCredParams) && - Objects.equals(timeout, that.timeout) && - Objects.equals(excludeCredentials, that.excludeCredentials) && - Objects.equals(authenticatorSelection, that.authenticatorSelection) && - attestation == that.attestation && - Objects.equals(extensions, that.extensions); - } - - @Override - public int hashCode() { - return Objects.hash(rp, user, challenge, pubKeyCredParams, timeout, excludeCredentials, authenticatorSelection, attestation, extensions); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java deleted file mode 100644 index 7bf9817ac43..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionsOptionProvider.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.webauthn.options; - -import com.webauthn4j.data.extension.ExtensionInput; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import com.webauthn4j.data.extension.client.ExtensionClientInput; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -public class ExtensionsOptionProvider implements Iterable> { - - private Map> providers = new HashMap<>(); - - public AuthenticationExtensionsClientInputs provide(HttpServletRequest request) { - - Map extensionOptions = - providers.values().stream() - .map(provider -> provider.provide(request)) - .collect(Collectors.toMap(ExtensionInput::getIdentifier, extensionOption -> extensionOption)); - - return new AuthenticationExtensionsClientInputs<>(extensionOptions); - } - - public void put(T extensionOption) { - put(new StaticExtensionOptionProvider<>(extensionOption)); - } - - public void put(ExtensionOptionProvider extensionOptionProvider) { - providers.put(extensionOptionProvider.getIdentifier(), extensionOptionProvider); - } - - public void putAll(Map> extensionsClientInputs) { - extensionsClientInputs.forEach((key, value) -> providers.put(key, value)); - } - - @Override - public Iterator> iterator() { - return providers.values().iterator(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ExtensionsOptionProvider that = (ExtensionsOptionProvider) o; - return Objects.equals(providers, that.providers); - } - - @Override - public int hashCode() { - return Objects.hash(providers); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java deleted file mode 100644 index 756403d1192..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - -import com.webauthn4j.data.client.challenge.Challenge; - -import javax.servlet.http.HttpServletRequest; - -/** - * Provides {@link Options} and effective rpId for {@link HttpServletRequest} - * - * @author Yoshikazu Nojima - */ -public interface OptionsProvider { - - /** - * provides {@link AttestationOptions}. If username is null, user, credentials are not populated. - * - * @param request request - * @param username username - * @param challenge if null, new challenge is generated. Otherwise, specified challenge is used. - * @return {@link AttestationOptions} instance - */ - AttestationOptions getAttestationOptions(HttpServletRequest request, String username, Challenge challenge); - - /** - * provides {@link AssertionOptions}. If username is null, credentials are not populated. - * - * @param request request - * @param username username - * @param challenge if null, new challenge is generated. Otherwise, specified challenge is used. - * @return {@link AssertionOptions} instance - */ - AssertionOptions getAssertionOptions(HttpServletRequest request, String username, Challenge challenge); - - /** - * returns effective rpId based on request origin and configured rpId. - * - * @param request request - * @return effective rpId - */ - String getEffectiveRpId(HttpServletRequest request); - - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java deleted file mode 100644 index 61150f480e3..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/OptionsProviderImpl.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.data.*; -import com.webauthn4j.data.client.Origin; -import com.webauthn4j.data.client.challenge.Challenge; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import org.springframework.security.webauthn.util.ServletUtil; -import org.springframework.util.Assert; - -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -/** - * An {@link OptionsProvider} implementation - * - * @author Yoshikazu Nojima - */ -public class OptionsProviderImpl implements OptionsProvider { - - //~ Instance fields - // ================================================================================================ - private String rpId = null; - private String rpName = null; - private String rpIcon = null; - private List pubKeyCredParams = new ArrayList<>(); - private AuthenticatorSelectionCriteria authenticatorSelection = new AuthenticatorSelectionCriteria(null, false, UserVerificationRequirement.PREFERRED); - private AttestationConveyancePreference attestation = AttestationConveyancePreference.NONE; - private Long registrationTimeout = null; - private Long authenticationTimeout = null; - private RegistrationExtensionsOptionProvider registrationExtensions = new RegistrationExtensionsOptionProvider(); - private AuthenticationExtensionsOptionProvider authenticationExtensions = new AuthenticationExtensionsOptionProvider(); - - private WebAuthnUserDetailsService userDetailsService; - private ChallengeRepository challengeRepository; - - // ~ Constructors - // =================================================================================================== - - public OptionsProviderImpl(WebAuthnUserDetailsService userDetailsService, ChallengeRepository challengeRepository) { - - Assert.notNull(userDetailsService, "userDetailsService must not be null"); - Assert.notNull(challengeRepository, "challengeRepository must not be null"); - - this.userDetailsService = userDetailsService; - this.challengeRepository = challengeRepository; - } - - - // ~ Methods - // ======================================================================================================== - - - /** - * {@inheritDoc} - */ - @Override - public AttestationOptions getAttestationOptions(HttpServletRequest request, String username, Challenge challenge) { - - PublicKeyCredentialUserEntity user; - Collection authenticators; - - try { - WebAuthnUserDetails userDetails = userDetailsService.loadUserByUsername(username); - authenticators = userDetails.getAuthenticators(); - user = new PublicKeyCredentialUserEntity(userDetails.getUserHandle(), username, null); - } catch (UsernameNotFoundException e) { - authenticators = Collections.emptyList(); - user = null; - } - - List credentials = authenticators.stream() - .map(authenticator -> new PublicKeyCredentialDescriptor( - PublicKeyCredentialType.PUBLIC_KEY, - authenticator.getAttestedCredentialData().getCredentialId(), - null - )) - .collect(Collectors.toList()); - - PublicKeyCredentialRpEntity relyingParty = new PublicKeyCredentialRpEntity(getEffectiveRpId(request), rpName, rpIcon); - if (challenge == null) { - challenge = challengeRepository.loadOrGenerateChallenge(request); - } else { - challengeRepository.saveChallenge(challenge, request); - } - - return new AttestationOptions(relyingParty, user, challenge, pubKeyCredParams, registrationTimeout, - credentials, authenticatorSelection, attestation, registrationExtensions.provide(request)); - } - - public AssertionOptions getAssertionOptions(HttpServletRequest request, String username, Challenge challenge) { - - Collection authenticators; - try { - WebAuthnUserDetails userDetails = userDetailsService.loadUserByUsername(username); - authenticators = userDetails.getAuthenticators(); - } catch (UsernameNotFoundException e) { - authenticators = Collections.emptyList(); - } - - String effectiveRpId = getEffectiveRpId(request); - - List credentials = authenticators.stream() - .map(authenticator -> new PublicKeyCredentialDescriptor( - PublicKeyCredentialType.PUBLIC_KEY, - authenticator.getAttestedCredentialData().getCredentialId(), - null - )) - .collect(Collectors.toList()); - - if (challenge == null) { - challenge = challengeRepository.loadOrGenerateChallenge(request); - } else { - challengeRepository.saveChallenge(challenge, request); - } - - return new AssertionOptions(challenge, authenticationTimeout, effectiveRpId, credentials, authenticationExtensions.provide(request)); - } - - - /** - * returns effective rpId based on request origin and configured rpId. - * - * @param request request - * @return effective rpId - */ - public String getEffectiveRpId(HttpServletRequest request) { - String effectiveRpId; - if (this.rpId != null) { - effectiveRpId = this.rpId; - } else { - Origin origin = ServletUtil.getOrigin(request); - effectiveRpId = origin.getHost(); - } - return effectiveRpId; - } - - /** - * returns configured rpId - * - * @return rpId - */ - public String getRpId() { - return rpId; - } - - /** - * configures rpId - * - * @param rpId rpId - */ - public void setRpId(String rpId) { - this.rpId = rpId; - } - - /** - * returns rpName - * - * @return rpName - */ - public String getRpName() { - return rpName; - } - - /** - * configures rpName - * - * @param rpName rpName - */ - public void setRpName(String rpName) { - Assert.hasText(rpName, "rpName parameter must not be empty or null"); - this.rpName = rpName; - } - - /** - * returns rpIcon - * - * @return rpIcon - */ - public String getRpIcon() { - return rpIcon; - } - - /** - * configures rpIcon - * - * @param rpIcon rpIcon - */ - public void setRpIcon(String rpIcon) { - Assert.hasText(rpIcon, "rpIcon parameter must not be empty or null"); - this.rpIcon = rpIcon; - } - - /** - * returns {@link PublicKeyCredentialParameters} list - * - * @return {@link PublicKeyCredentialParameters} list - */ - public List getPubKeyCredParams() { - return pubKeyCredParams; - } - - /** - * configures pubKeyCredParams - * - * @param pubKeyCredParams {@link PublicKeyCredentialParameters} list - */ - public void setPubKeyCredParams(List pubKeyCredParams) { - this.pubKeyCredParams = pubKeyCredParams; - } - - /** - * returns the registration timeout - * - * @return the registration timeout - */ - public Long getRegistrationTimeout() { - return registrationTimeout; - } - - /** - * configures the registration timeout - * - * @param registrationTimeout registration timeout - */ - public void setRegistrationTimeout(Long registrationTimeout) { - Assert.isTrue(registrationTimeout >= 0, "registrationTimeout must be within unsigned long."); - this.registrationTimeout = registrationTimeout; - } - - /** - * returns the authentication timeout - * - * @return the authentication timeout - */ - public Long getAuthenticationTimeout() { - return authenticationTimeout; - } - - /** - * configures the authentication timeout - * - * @param authenticationTimeout authentication timeout - */ - public void setAuthenticationTimeout(Long authenticationTimeout) { - Assert.isTrue(registrationTimeout >= 0, "registrationTimeout must be within unsigned long."); - this.authenticationTimeout = authenticationTimeout; - } - - /** - * returns the {@link AuthenticatorSelectionCriteria} - * - * @return the {@link AuthenticatorSelectionCriteria} - */ - public AuthenticatorSelectionCriteria getAuthenticatorSelection() { - return authenticatorSelection; - } - - /** - * configures the {@link AuthenticatorSelectionCriteria} - * - * @param authenticatorSelection the {@link AuthenticatorSelectionCriteria} - */ - public void setAuthenticatorSelection(AuthenticatorSelectionCriteria authenticatorSelection) { - this.authenticatorSelection = authenticatorSelection; - } - - /** - * returns the {@link AttestationConveyancePreference} - * - * @return the {@link AttestationConveyancePreference} - */ - public AttestationConveyancePreference getAttestation() { - return attestation; - } - - /** - * configures the {@link AttestationConveyancePreference} - * - * @param attestation the {@link AttestationConveyancePreference} - */ - public void setAttestation(AttestationConveyancePreference attestation) { - this.attestation = attestation; - } - - public RegistrationExtensionsOptionProvider getRegistrationExtensions() { - return registrationExtensions; - } - - public AuthenticationExtensionsOptionProvider getAuthenticationExtensions() { - return authenticationExtensions; - } - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java deleted file mode 100644 index 1c9f07ea349..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/StaticExtensionOptionProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.webauthn.options; - -import com.webauthn4j.data.extension.client.ExtensionClientInput; - -import javax.servlet.http.HttpServletRequest; - -public class StaticExtensionOptionProvider implements ExtensionOptionProvider { - - private T extensionOption; - - public StaticExtensionOptionProvider(T extensionOption) { - this.extensionOption = extensionOption; - } - - @Override - public T provide(HttpServletRequest request) { - return extensionOption; - } - - @Override - public String getIdentifier() { - return extensionOption.getIdentifier(); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/EffectiveRpIdProvider.java similarity index 69% rename from webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java rename to webauthn/src/main/java/org/springframework/security/webauthn/server/EffectiveRpIdProvider.java index 7e2aa4fabfa..a6599c8f4ba 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/ExtensionOptionProvider.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/EffectiveRpIdProvider.java @@ -13,15 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.webauthn.options; -import com.webauthn4j.data.extension.client.ExtensionClientInput; +package org.springframework.security.webauthn.server; import javax.servlet.http.HttpServletRequest; -public interface ExtensionOptionProvider { +public interface EffectiveRpIdProvider { - T provide(HttpServletRequest request); - - String getIdentifier(); + /** + * returns effective rpId based on request origin and configured rpId. + * + * @param request request + * @return effective rpId + */ + String getEffectiveRpId(HttpServletRequest request); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java deleted file mode 100644 index d431c9436a7..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProviderImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.server; - -import com.webauthn4j.data.client.Origin; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.server.ServerProperty; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.security.webauthn.util.ServletUtil; -import org.springframework.util.Assert; - -import javax.servlet.http.HttpServletRequest; - -/** - * {@inheritDoc} - */ -public class ServerPropertyProviderImpl implements ServerPropertyProvider { - - //~ Instance fields - // ================================================================================================ - private OptionsProvider optionsProvider; - private ChallengeRepository challengeRepository; - - public ServerPropertyProviderImpl(OptionsProvider optionsProvider, ChallengeRepository challengeRepository) { - - Assert.notNull(optionsProvider, "optionsProvider must not be null"); - Assert.notNull(challengeRepository, "challengeRepository must not be null"); - - this.optionsProvider = optionsProvider; - this.challengeRepository = challengeRepository; - } - - public ServerProperty provide(HttpServletRequest request) { - - Origin origin = ServletUtil.getOrigin(request); - String effectiveRpId = optionsProvider.getEffectiveRpId(request); - Challenge challenge = challengeRepository.loadOrGenerateChallenge(request); - - return new ServerProperty(origin, effectiveRpId, challenge, null); // tokenBinding is not supported by Servlet API as of 4.0 - } - - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java new file mode 100644 index 00000000000..45d74a0e1d1 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.server; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; + +import javax.servlet.ServletRequest; +import java.io.Serializable; +import java.net.URI; +import java.util.Objects; + +/** + * {@link WebAuthnOrigin} contains the fully qualified origin of the requester, as provided to the authenticator + * by the client. + * + * @see 5.10.1. Client Data Used in WebAuthn Signatures - origin + */ +public class WebAuthnOrigin implements Serializable { + + private static final String SCHEME_HTTPS = "https"; + private static final String SCHEME_HTTP = "http"; + private static final String SCHEME_ERROR_MESSAGE = "scheme must be 'http' or 'https'"; + + private String scheme; + private String host; + private int port; + + public WebAuthnOrigin(String scheme, String host, int port) { + if (!Objects.equals(SCHEME_HTTPS, scheme) && !Objects.equals(SCHEME_HTTP, scheme)) { + throw new IllegalArgumentException(SCHEME_ERROR_MESSAGE); + } + + this.scheme = scheme; + this.host = host; + this.port = port; + } + + public WebAuthnOrigin(String originUrl) { + URI uri = URI.create(originUrl); + this.scheme = uri.getScheme(); + this.host = uri.getHost(); + int originPort = uri.getPort(); + + if (originPort == -1) { + if (this.scheme == null) { + throw new IllegalArgumentException(SCHEME_ERROR_MESSAGE); + } + switch (this.scheme) { + case SCHEME_HTTPS: + originPort = 443; + break; + case SCHEME_HTTP: + originPort = 80; + break; + default: + throw new IllegalArgumentException(SCHEME_ERROR_MESSAGE); + } + } + this.port = originPort; + } + + public static WebAuthnOrigin create(ServletRequest request) { + return new WebAuthnOrigin(request.getScheme(), request.getServerName(), request.getServerPort()); + } + + public static WebAuthnOrigin create(String value) { + try { + return new WebAuthnOrigin(value); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("value is out of range: " + e.getMessage()); + } + } + + @JsonCreator + private static WebAuthnOrigin deserialize(String value) throws InvalidFormatException { + try { + return create(value); + } catch (IllegalArgumentException e) { + throw new InvalidFormatException(null, "value is out of range", value, WebAuthnOrigin.class); + } + } + + public String getScheme() { + return scheme; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + @JsonValue + @Override + public String toString() { + String result = this.scheme + "://" + this.host; + switch (this.scheme) { + case SCHEME_HTTPS: + if (this.port != 443) { + result += ":" + this.port; + } + break; + case SCHEME_HTTP: + if (this.port != 80) { + result += ":" + this.port; + } + break; + default: + throw new IllegalStateException(); + } + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WebAuthnOrigin)) return false; + + WebAuthnOrigin origin = (WebAuthnOrigin) o; + + if (port != origin.port) return false; + //noinspection SimplifiableIfStatement + if (!scheme.equals(origin.scheme)) return false; + return host.equals(origin.host); + } + + @Override + public int hashCode() { + int result = scheme.hashCode(); + result = 31 * result + host.hashCode(); + result = 31 * result + port; + return result; + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java new file mode 100644 index 00000000000..214f3dd6740 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.server; + +import com.webauthn4j.util.ArrayUtil; +import org.springframework.security.webauthn.challenge.WebAuthnChallenge; + +import java.util.Arrays; +import java.util.Objects; + +public class WebAuthnServerProperty { + // ~ Instance fields + // ================================================================================================ + + private final WebAuthnOrigin origin; + private final String rpId; + private final WebAuthnChallenge challenge; + private final byte[] tokenBindingId; + + // ~ Constructor + // ======================================================================================================== + + public WebAuthnServerProperty(WebAuthnOrigin origin, String rpId, WebAuthnChallenge challenge, byte[] tokenBindingId) { + this.origin = origin; + this.rpId = rpId; + this.challenge = challenge; + this.tokenBindingId = ArrayUtil.clone(tokenBindingId); + } + + // ~ Methods + // ======================================================================================================== + + /** + * Returns the {@link WebAuthnOrigin} + * + * @return the {@link WebAuthnOrigin} + */ + public WebAuthnOrigin getOrigin() { + return origin; + } + + /** + * Returns the rpId + * + * @return the rpId + */ + public String getRpId() { + return rpId; + } + + /** + * Returns the {@link WebAuthnChallenge} + * + * @return the {@link WebAuthnChallenge} + */ + public WebAuthnChallenge getChallenge() { + return challenge; + } + + /** + * Returns the tokenBindingId + * + * @return the tokenBindingId + */ + public byte[] getTokenBindingId() { + return ArrayUtil.clone(tokenBindingId); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnServerProperty that = (WebAuthnServerProperty) o; + return Objects.equals(origin, that.origin) && + Objects.equals(rpId, that.rpId) && + Objects.equals(challenge, that.challenge) && + Arrays.equals(tokenBindingId, that.tokenBindingId); + } + + @Override + public int hashCode() { + + int result = Objects.hash(origin, rpId, challenge); + result = 31 * result + Arrays.hashCode(tokenBindingId); + return result; + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java similarity index 73% rename from webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java rename to webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java index 56de5464c21..9291e46b7e3 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/server/ServerPropertyProvider.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java @@ -17,23 +17,21 @@ package org.springframework.security.webauthn.server; -import com.webauthn4j.server.ServerProperty; - import javax.servlet.http.HttpServletRequest; /** - * Provides {@link ServerProperty} instance associated with {@link HttpServletRequest} + * Provides {@link WebAuthnServerProperty} instance associated with {@link HttpServletRequest} * * @author Yoshikazu Nojima */ -public interface ServerPropertyProvider { +public interface WebAuthnServerPropertyProvider { /** - * Provides {@link ServerProperty} + * Provides {@link WebAuthnServerProperty} * * @param request http servlet request - * @return the {@link ServerProperty} + * @return the {@link WebAuthnServerProperty} */ - ServerProperty provide(HttpServletRequest request); + WebAuthnServerProperty provide(HttpServletRequest request); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java new file mode 100644 index 00000000000..98f702ad5e0 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.server; + +import org.springframework.security.webauthn.challenge.WebAuthnChallenge; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; + +/** + * {@inheritDoc} + */ +public class WebAuthnServerPropertyProviderImpl implements WebAuthnServerPropertyProvider { + + //~ Instance fields + // ================================================================================================ + private EffectiveRpIdProvider effectiveRpIdProvider; + private WebAuthnChallengeRepository webAuthnChallengeRepository; + + public WebAuthnServerPropertyProviderImpl(EffectiveRpIdProvider effectiveRpIdProvider, WebAuthnChallengeRepository webAuthnChallengeRepository) { + + Assert.notNull(effectiveRpIdProvider, "effectiveRpIdProvider must not be null"); + Assert.notNull(webAuthnChallengeRepository, "webAuthnChallengeRepository must not be null"); + + this.effectiveRpIdProvider = effectiveRpIdProvider; + this.webAuthnChallengeRepository = webAuthnChallengeRepository; + } + + public WebAuthnServerProperty provide(HttpServletRequest request) { + + WebAuthnOrigin origin = WebAuthnOrigin.create(request); + String effectiveRpId = effectiveRpIdProvider.getEffectiveRpId(request); + WebAuthnChallenge challenge = webAuthnChallengeRepository.loadOrGenerateChallenge(request); + + return new WebAuthnServerProperty(origin, effectiveRpId, challenge, null); // tokenBinding is not supported by Servlet API as of 4.0 + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/InMemoryWebAuthnAndPasswordUserDetailsManager.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/InMemoryWebAuthnAndPasswordUserDetailsManager.java new file mode 100644 index 00000000000..7f4bd4bdad2 --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/InMemoryWebAuthnAndPasswordUserDetailsManager.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.userdetails; + +import com.webauthn4j.util.Base64UrlUtil; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; +import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * {@inheritDoc} + */ +public class InMemoryWebAuthnAndPasswordUserDetailsManager implements WebAuthnAndPasswordUserDetailsService, WebAuthnAuthenticatorService { + + private Map users = new HashMap<>(); + + /** + * {@inheritDoc} + */ + @Override + public WebAuthnAndPasswordUserDetails loadUserByUsername(String username) { + WebAuthnAndPasswordUserDetails userDetails = users.get(username); + if (userDetails == null) { + throw new UsernameNotFoundException(String.format("UserEntity with username'%s' is not found.", username)); + } + return clone(userDetails); } + + /** + * {@inheritDoc} + */ + @Override + public WebAuthnAndPasswordUserDetails loadWebAuthnUserByUsername(String username) { + WebAuthnAndPasswordUserDetails userDetails = loadUserByUsername(username); + return clone(userDetails); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") + public WebAuthnAndPasswordUserDetails loadWebAuthnUserByCredentialId(byte[] credentialId) throws CredentialIdNotFoundException { + WebAuthnAndPasswordUserDetails userDetails = users + .entrySet() + .stream() + .filter(entry -> entry.getValue().getAuthenticators().stream().anyMatch(authenticator -> Arrays.equals(authenticator.getCredentialId(), credentialId))) + .findFirst() + .orElseThrow(() -> new CredentialIdNotFoundException(String.format("AuthenticatorEntity with credentialId'%s' is not found.", Base64UrlUtil.encodeToString(credentialId)))) + .getValue(); + return clone(userDetails); + } + + public void createUser(WebAuthnAndPasswordUserDetails user) { + Assert.isTrue(!userExists(user.getUsername()), "user should not exist"); + users.put(user.getUsername(), user); + } + + public void deleteUser(String username) { + users.remove(username); + } + + public boolean userExists(String username) { + WebAuthnAndPasswordUserDetails userDetails = users.get(username); + return userDetails != null; + } + + @Override + @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") + public void updateCounter(byte[] credentialId, long counter) throws CredentialIdNotFoundException { + + WebAuthnAuthenticator authenticator = users + .entrySet() + .stream() + .flatMap(entry -> entry.getValue().getAuthenticators().stream()) + .filter(entry -> Arrays.equals(entry.getCredentialId(), credentialId)) + .findFirst() + .orElseThrow(() -> new CredentialIdNotFoundException(String.format("AuthenticatorEntity with credentialId'%s' is not found.", Base64UrlUtil.encodeToString(credentialId)))); + + authenticator.setCounter(counter); + } + + private WebAuthnAndPasswordUserDetails clone(WebAuthnAndPasswordUserDetails original){ + return new WebAuthnAndPasswordUser(original.getUserHandle(), original.getUsername(), original.getPassword(), new ArrayList<>(original.getAuthenticators()), original.getAuthorities()); + } + +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUser.java similarity index 61% rename from webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java rename to webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUser.java index 80ca481f78c..8bb3ee589bc 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImpl.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUser.java @@ -16,51 +16,47 @@ package org.springframework.security.webauthn.userdetails; -import com.webauthn4j.authenticator.Authenticator; import com.webauthn4j.util.ArrayUtil; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; import java.util.Collection; +import java.util.Collections; import java.util.List; /** - * A {@link WebAuthnUserDetails} implementation + * A {@link WebAuthnAndPasswordUserDetails} implementation * * @author Yoshikazu Nojima */ @SuppressWarnings("squid:S2160") -public class WebAuthnUserDetailsImpl extends User implements WebAuthnUserDetails { +public class WebAuthnAndPasswordUser extends User implements WebAuthnAndPasswordUserDetails { // ~ Instance fields // ================================================================================================ - private boolean singleFactorAuthenticationAllowed = false; + private boolean singleFactorAuthenticationAllowed; private byte[] userHandle; - private List authenticators; + private List authenticators; - public WebAuthnUserDetailsImpl( - byte[] userHandle, String username, String password, List authenticators, + public WebAuthnAndPasswordUser( + byte[] userHandle, String username, String password, List authenticators, Collection authorities) { - this(userHandle, username, password, authenticators, false, authorities); + this(userHandle, username, password, authenticators, false, true, true, true, true, authorities); } - public WebAuthnUserDetailsImpl( - byte[] userHandle, String username, String password, List authenticators, - boolean singleFactorAuthenticationAllowed, Collection authorities) { - this(userHandle, username, password, authenticators, singleFactorAuthenticationAllowed, - true, true, true, true, - authorities); + public WebAuthnAndPasswordUser(byte[] userHandle, String username, String password, List authenticators, boolean singleFactorAuthenticationAllowed, List authorities) { + this(userHandle, username, password, authenticators, singleFactorAuthenticationAllowed, true, true, true, true, authorities); } @SuppressWarnings("squid:S00107") - public WebAuthnUserDetailsImpl( - byte[] userHandle, String username, String password, List authenticators, - boolean singleFactorAuthenticationAllowed, boolean enabled, boolean accountNonExpired, + public WebAuthnAndPasswordUser( + byte[] userHandle, String username, String password, List authenticators, boolean singleFactorAuthenticationAllowed, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); - this.userHandle = userHandle; - this.authenticators = authenticators; + this.userHandle = ArrayUtil.clone(userHandle); + this.authenticators = Collections.unmodifiableList(authenticators); this.singleFactorAuthenticationAllowed = singleFactorAuthenticationAllowed; } @@ -70,7 +66,7 @@ public byte[] getUserHandle() { } @Override - public List getAuthenticators() { + public List getAuthenticators() { return this.authenticators; } @@ -86,3 +82,4 @@ public void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticat } + diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetails.java similarity index 63% rename from samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java rename to webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetails.java index 71898c21c4a..f4e59ca1804 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/domain/exception/WebAuthnSampleBusinessException.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetails.java @@ -14,16 +14,14 @@ * limitations under the License. */ -package org.springframework.security.webauthn.sample.domain.exception; +package org.springframework.security.webauthn.userdetails; -/** - * Business Exception for WebAuthn Sample - */ -@SuppressWarnings("squid:MaximumInheritanceDepth") -public class WebAuthnSampleBusinessException extends RuntimeException { +import org.springframework.security.core.userdetails.MFAUserDetails; + +public interface WebAuthnAndPasswordUserDetails extends WebAuthnUserDetails, MFAUserDetails { + + boolean isSingleFactorAuthenticationAllowed(); - public WebAuthnSampleBusinessException(String message) { - super(message); - } + void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetailsService.java similarity index 70% rename from webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java rename to webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetailsService.java index 21afe802cc8..8fe9840437e 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/options/AuthenticationExtensionsOptionProvider.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserDetailsService.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.webauthn.options; -import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; +package org.springframework.security.webauthn.userdetails; -public class AuthenticationExtensionsOptionProvider extends ExtensionsOptionProvider { +import org.springframework.security.core.userdetails.UserDetailsService; + +public interface WebAuthnAndPasswordUserDetailsService extends UserDetailsService, WebAuthnUserDetailsService { } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUser.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUser.java new file mode 100644 index 00000000000..a378963024c --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUser.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.webauthn.userdetails; + +import com.webauthn4j.util.ArrayUtil; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.SpringSecurityCoreVersion; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.*; + +/** + * A {@link WebAuthnUserDetails} implementation + * + * @author Yoshikazu Nojima + */ +@SuppressWarnings("squid:S2160") +public class WebAuthnUser implements WebAuthnUserDetails { + + private final String username; + private final Set authorities; + private final boolean accountNonExpired; + private final boolean accountNonLocked; + private final boolean credentialsNonExpired; + private final boolean enabled; + // ~ Instance fields + // ================================================================================================ + private byte[] userHandle; + private List authenticators; + + public WebAuthnUser( + byte[] userHandle, String username, List authenticators, + Collection authorities) { + this(userHandle, username, authenticators, + true, true, true, true, + authorities); + } + + @SuppressWarnings("squid:S00107") + public WebAuthnUser( + byte[] userHandle, String username, List authenticators, boolean enabled, boolean accountNonExpired, + boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) { + this.userHandle = ArrayUtil.clone(userHandle); + this.username = username; + this.authenticators = authenticators; + this.enabled = enabled; + this.accountNonExpired = accountNonExpired; + this.credentialsNonExpired = credentialsNonExpired; + this.accountNonLocked = accountNonLocked; + this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities)); + } + + private static SortedSet sortAuthorities( + Collection authorities) { + Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection"); + // Ensure array iteration order is predictable (as per + // UserDetails.getAuthorities() contract and SEC-717) + SortedSet sortedAuthorities = new TreeSet<>( + new AuthorityComparator()); + + for (GrantedAuthority grantedAuthority : authorities) { + Assert.notNull(grantedAuthority, + "GrantedAuthority list cannot contain any null elements"); + sortedAuthorities.add(grantedAuthority); + } + + return sortedAuthorities; + } + + @Override + public byte[] getUserHandle() { + return ArrayUtil.clone(userHandle); + } + + @Override + public String getUsername() { + return username; + } + + @Override + public List getAuthenticators() { + return authenticators; + } + + @Override + public Set getAuthorities() { + return authorities; + } + + @Override + public boolean isAccountNonExpired() { + return accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return credentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebAuthnUser that = (WebAuthnUser) o; + return accountNonExpired == that.accountNonExpired && + accountNonLocked == that.accountNonLocked && + credentialsNonExpired == that.credentialsNonExpired && + enabled == that.enabled && + Arrays.equals(userHandle, that.userHandle) && + Objects.equals(username, that.username) && + Objects.equals(authenticators, that.authenticators) && + Objects.equals(authorities, that.authorities); + } + + @Override + public int hashCode() { + int result = Objects.hash(username, authenticators, authorities, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled); + result = 31 * result + Arrays.hashCode(userHandle); + return result; + } + + private static class AuthorityComparator implements Comparator, + Serializable { + private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; + + public int compare(GrantedAuthority g1, GrantedAuthority g2) { + // Neither should ever be null as each entry is checked before adding it to + // the set. + // If the authority is null, it is a custom authority and should precede + // others. + if (g2.getAuthority() == null) { + return -1; + } + + if (g1.getAuthority() == null) { + return 1; + } + + return g1.getAuthority().compareTo(g2.getAuthority()); + } + } +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java index 9c203d63c7a..66e2fde3b2c 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetails.java @@ -16,10 +16,11 @@ package org.springframework.security.webauthn.userdetails; -import com.webauthn4j.authenticator.Authenticator; -import org.springframework.security.core.userdetails.MFAUserDetails; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import java.io.Serializable; import java.util.Collection; /** @@ -27,12 +28,71 @@ * * @author Yoshikazu Nojima */ -public interface WebAuthnUserDetails extends MFAUserDetails { +public interface WebAuthnUserDetails extends Serializable { + // ~ Methods + // ======================================================================================================== + /** + * Returns the authorities granted to the user. Cannot return null. + * + * @return the authorities, sorted by natural key (never null) + */ + Collection getAuthorities(); + + /** + * Returns the userHandle that identifies the user. Cannot return null. + * + * @return the userHandle (never null) + */ + byte[] getUserHandle(); + + /** + * Returns the username used to authenticate the user. Cannot return null. + * + * @return the username (never null) + */ + String getUsername(); + + /** + * Returns the authenticators used to authenticate the user. + * + * @return the authenticators + */ @SuppressWarnings("squid:S1452") - Collection getAuthenticators(); + Collection getAuthenticators(); - void setSingleFactorAuthenticationAllowed(boolean singleFactorAuthenticationAllowed); + /** + * Indicates whether the user's account has expired. An expired account cannot be + * authenticated. + * + * @return true if the user's account is valid (ie non-expired), + * false if no longer valid (ie expired) + */ + boolean isAccountNonExpired(); + + /** + * Indicates whether the user is locked or unlocked. A locked user cannot be + * authenticated. + * + * @return true if the user is not locked, false otherwise + */ + boolean isAccountNonLocked(); + + /** + * Indicates whether the user's credentials (password) has expired. Expired + * credentials prevent authentication. + * + * @return true if the user's credentials are valid (ie non-expired), + * false if no longer valid (ie expired) + */ + boolean isCredentialsNonExpired(); + + /** + * Indicates whether the user is enabled or disabled. A disabled user cannot be + * authenticated. + * + * @return true if the user is enabled, false otherwise + */ + boolean isEnabled(); - byte[] getUserHandle(); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsChecker.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsChecker.java new file mode 100644 index 00000000000..dc1e156c2fc --- /dev/null +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsChecker.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.webauthn.userdetails; + +import org.springframework.security.core.userdetails.UserDetailsChecker; + +/** + * A {@link WebAuthnUserDetails} equivalent of {@link UserDetailsChecker} + */ +public interface WebAuthnUserDetailsChecker { + /** + * Examines the User + * + * @param toCheck the UserDetails instance whose status should be checked. + */ + void check(WebAuthnUserDetails toCheck); +} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java index fcb23a55fed..a3545feb16b 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsService.java @@ -16,7 +16,6 @@ package org.springframework.security.webauthn.userdetails; -import com.webauthn4j.authenticator.Authenticator; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; @@ -26,7 +25,7 @@ * * @author Yoshikazu Nojima */ -public interface WebAuthnUserDetailsService extends UserDetailsService { +public interface WebAuthnUserDetailsService { /** * Locates a user based on the username. @@ -36,7 +35,7 @@ public interface WebAuthnUserDetailsService extends UserDetailsService { * @throws UsernameNotFoundException if the user could not be found */ @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") - WebAuthnUserDetails loadUserByUsername(String username) throws UsernameNotFoundException; + WebAuthnUserDetails loadWebAuthnUserByUsername(String username) throws UsernameNotFoundException; /** * Locates a user based on the credentialId. @@ -47,29 +46,5 @@ public interface WebAuthnUserDetailsService extends UserDetailsService { * @throws CredentialIdNotFoundException if the authenticator could not be found */ @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") - WebAuthnUserDetails loadUserByCredentialId(byte[] credentialId) throws CredentialIdNotFoundException; - - /** - * Adds {@link Authenticator} to the user record - * - * @param username the username identifying the user - * @param authenticator the authenticator to be added - */ - void addAuthenticator(String username, Authenticator authenticator); - - /** - * Removes {@link Authenticator} from the user record - * - * @param username the username identifying the user - * @param authenticator the authenticator to be removed - */ - void removeAuthenticator(String username, Authenticator authenticator); - - /** - * Removes {@link Authenticator} from the user record - * - * @param username the username identifying the user - * @param credentialId the credentialId identifying the authenticator - */ - void removeAuthenticator(String username, byte[] credentialId); + WebAuthnUserDetails loadWebAuthnUserByCredentialId(byte[] credentialId) throws CredentialIdNotFoundException; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java b/webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java deleted file mode 100644 index 86d1905faac..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/util/ExceptionUtil.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.util; - -import com.webauthn4j.util.exception.WebAuthnException; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.webauthn.exception.*; - -/** - * Internal utility to handle exceptions - * - * @author Yoshikazu Nojima - */ -public class ExceptionUtil { - - private ExceptionUtil() { - } - - /** - * Wraps WebAuthnAuthentication to proper {@link RuntimeException} (mainly {@link AuthenticationException} subclass. - * - * @param e exception to be wrapped - * @return wrapping exception - */ - @SuppressWarnings("squid:S3776") - public static RuntimeException wrapWithAuthenticationException(WebAuthnException e) { - // ValidationExceptions - if (e instanceof com.webauthn4j.validator.exception.BadAaguidException) { - return new BadAaguidException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadAlgorithmException) { - return new BadAlgorithmException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadAttestationStatementException) { - if (e instanceof com.webauthn4j.validator.exception.KeyDescriptionValidationException) { - return new KeyDescriptionValidationException(e.getMessage(), e); - } else { - return new BadAttestationStatementException(e.getMessage(), e); - } - } else if (e instanceof com.webauthn4j.validator.exception.BadChallengeException) { - return new BadChallengeException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadOriginException) { - return new BadOriginException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadRpIdException) { - return new BadRpIdException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadSignatureException) { - return new BadSignatureException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.CertificateException) { - return new CertificateException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.ConstraintViolationException) { - return new ConstraintViolationException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.MaliciousCounterValueException) { - return new MaliciousCounterValueException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.MaliciousDataException) { - return new MaliciousDataException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.MissingChallengeException) { - return new MissingChallengeException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.PublicKeyMismatchException) { - return new PublicKeyMismatchException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.SelfAttestationProhibitedException) { - return new SelfAttestationProhibitedException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.TokenBindingException) { - return new TokenBindingException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.TrustAnchorNotFoundException) { - return new TrustAnchorNotFoundException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.UnexpectedExtensionException) { - return new UnexpectedExtensionException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.UserNotPresentException) { - return new UserNotPresentException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.UserNotVerifiedException) { - return new UserNotVerifiedException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.ValidationException) { - return new ValidationException("WebAuthn validation error", e); - } - // DataConversionException - else if (e instanceof com.webauthn4j.converter.exception.DataConversionException) { - return new DataConversionException("WebAuthn data conversion error", e); - } else { - return new AuthenticationServiceException(null, e); - } - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java b/webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java deleted file mode 100644 index f0b7cc229e5..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/util/ServletUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.util; - - -import com.webauthn4j.data.client.Origin; - -import javax.servlet.ServletRequest; - -/** - * Internal utility to handle servlet - * - * @author Yoshikazu Nojima - */ -public class ServletUtil { - - private ServletUtil() { - } - - /** - * Returns {@link Origin} corresponding {@link ServletRequest} url - * - * @param request http servlet request - * @return the {@link Origin} - */ - public static Origin getOrigin(ServletRequest request) { - return new Origin(request.getScheme(), request.getServerName(), request.getServerPort()); - } -} diff --git a/webauthn/src/test/java/integration/component/RegistrationValidationTest.java b/webauthn/src/test/java/integration/component/RegistrationValidationTest.java index 9c95e1a886a..5347abd247c 100644 --- a/webauthn/src/test/java/integration/component/RegistrationValidationTest.java +++ b/webauthn/src/test/java/integration/component/RegistrationValidationTest.java @@ -21,22 +21,24 @@ import com.webauthn4j.data.client.Origin; import com.webauthn4j.data.client.challenge.Challenge; import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.server.ServerProperty; import com.webauthn4j.test.authenticator.webauthn.PackedAuthenticator; import com.webauthn4j.test.authenticator.webauthn.WebAuthnAuthenticatorAdaptor; import com.webauthn4j.test.client.ClientPlatform; import com.webauthn4j.util.Base64UrlUtil; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidationResponse; -import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; -import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.*; +import org.springframework.security.webauthn.challenge.WebAuthnChallenge; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; +import org.springframework.security.webauthn.server.WebAuthnOrigin; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import java.util.Collections; import java.util.Set; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -48,18 +50,22 @@ public class RegistrationValidationTest { String rpId = "example.com"; Challenge challenge = new DefaultChallenge(); + WebAuthnChallenge webAuthnChallenge = new WebAuthnChallengeImpl(challenge.getValue()); private Origin origin = new Origin("http://localhost"); + private WebAuthnOrigin webAuthnOrigin = new WebAuthnOrigin(origin.toString()); private WebAuthnAuthenticatorAdaptor webAuthnModelAuthenticatorAdaptor = new WebAuthnAuthenticatorAdaptor(new PackedAuthenticator()); private ClientPlatform clientPlatform = new ClientPlatform(origin, webAuthnModelAuthenticatorAdaptor); - private ServerPropertyProvider serverPropertyProvider = mock(ServerPropertyProvider.class); - private WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( - WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(), serverPropertyProvider - ); + private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider = mock(WebAuthnServerPropertyProvider.class); + private WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); + private WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator = new WebAuthnAuthenticationContextValidator(); + private WebAuthnDataConverter webAuthnDataConverter = new WebAuthnDataConverter(); + private WebAuthnManager webAuthnManager = new WebAuthn4JWebAuthnManager(webAuthnRegistrationContextValidator, webAuthnAuthenticationContextValidator, webAuthnDataConverter); + private WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator(webAuthnManager, webAuthnServerPropertyProvider); @Test public void validate_test() { - ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, null); - when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + WebAuthnServerProperty serverProperty = new WebAuthnServerProperty(webAuthnOrigin, rpId, webAuthnChallenge, null); + when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); AuthenticatorSelectionCriteria authenticatorSelectionCriteria = @@ -93,12 +99,7 @@ public void validate_test() { Set transports = Collections.emptySet(); String clientExtensionsJSON = null; - WebAuthnRegistrationRequestValidationResponse response - = target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON); - - assertThat(response.getAttestationObject()).isNotNull(); - assertThat(response.getCollectedClientData()).isNotNull(); - assertThat(response.getRegistrationExtensionsClientOutputs()).isNull(); + target.validate(new WebAuthnRegistrationRequest(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON)); } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java similarity index 64% rename from webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java rename to webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java index a4e54b7676f..b1d2c066308 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/util/ExceptionUtilTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java @@ -14,19 +14,57 @@ * limitations under the License. */ -package org.springframework.security.webauthn.util; +package org.springframework.security.webauthn; import com.webauthn4j.util.exception.WebAuthnException; +import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; +import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; import org.springframework.security.webauthn.exception.*; +import org.springframework.security.webauthn.server.WebAuthnOrigin; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; import java.util.HashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; -public class ExceptionUtilTest { +public class WebAuthn4JWebAuthnManagerTest { + + private WebAuthnRegistrationContextValidator registrationContextValidator = mock(WebAuthnRegistrationContextValidator.class); + private WebAuthnAuthenticationContextValidator authenticationContextValidator = mock(WebAuthnAuthenticationContextValidator.class); + private WebAuthnDataConverter webAuthnDataConverter = new WebAuthnDataConverter(); + private WebAuthnManager target = new WebAuthn4JWebAuthnManager(registrationContextValidator, authenticationContextValidator, webAuthnDataConverter); + + @Test(expected = BadAttestationStatementException.class) + public void verifyRegistrationData_caught_exception_test() { + + doThrow(new com.webauthn4j.validator.exception.BadAttestationStatementException("dummy")) + .when(registrationContextValidator).validate(any()); + + MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); + mockHttpServletRequest.setScheme("https"); + mockHttpServletRequest.setServerName("example.com"); + mockHttpServletRequest.setServerPort(443); + + byte[] clientDataJSON = new byte[0]; //dummy + byte[] attestationObject = new byte[0]; //dummy + WebAuthnServerProperty serverProperty = new WebAuthnServerProperty( + new WebAuthnOrigin("https://example.com"), + "example.com", + new WebAuthnChallengeImpl(), + new byte[]{0x43, 0x21} + ); + + target.verifyRegistrationData(new WebAuthnRegistrationData(clientDataJSON, attestationObject, null, null, serverProperty, null)); + + } @Test public void wrapWithAuthenticationException_test() { @@ -57,7 +95,7 @@ public void wrapWithAuthenticationException_test() { map.put(new WebAuthnException("dummy"), AuthenticationServiceException.class); for (Map.Entry entry : map.entrySet()) { - assertThat(ExceptionUtil.wrapWithAuthenticationException(entry.getKey())).isInstanceOf(entry.getValue()); + assertThat(WebAuthn4JWebAuthnManager.wrapWithAuthenticationException(entry.getKey())).isInstanceOf(entry.getValue()); } } @@ -67,4 +105,5 @@ static class UnknownValidationException extends com.webauthn4j.validator.excepti super(message); } } + } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java index 102c2d41198..33c393b6cde 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java @@ -17,7 +17,6 @@ package org.springframework.security.webauthn; import org.junit.Test; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -26,7 +25,7 @@ public class WebAuthnAssertionAuthenticationTokenTest { @Test(expected = IllegalArgumentException.class) public void setAuthenticated_with_true_test() { - WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); token.setAuthenticated(true); assertThat(token.isAuthenticated()).isTrue(); @@ -34,7 +33,7 @@ public void setAuthenticated_with_true_test() { @Test public void setAuthenticated_with_false_test() { - WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); token.setAuthenticated(false); assertThat(token.isAuthenticated()).isFalse(); @@ -42,7 +41,7 @@ public void setAuthenticated_with_false_test() { @Test public void eraseCredentials_test() { - WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); token.eraseCredentials(); assertThat(token.getCredentials()).isNull(); @@ -50,7 +49,7 @@ public void eraseCredentials_test() { @Test public void equals_hashCode_test() { - WebAuthnAuthenticationRequest request = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); WebAuthnAssertionAuthenticationToken tokenA = new WebAuthnAssertionAuthenticationToken(request); WebAuthnAssertionAuthenticationToken tokenB = new WebAuthnAssertionAuthenticationToken(request); diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java similarity index 56% rename from webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java rename to webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java index bc21bbd7a44..42c043667b3 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/request/WebAuthnAuthenticationRequestTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java @@ -14,38 +14,38 @@ * limitations under the License. */ -package org.springframework.security.webauthn.request; +package org.springframework.security.webauthn; import com.webauthn4j.converter.AuthenticatorDataConverter; import com.webauthn4j.converter.util.CborConverter; import com.webauthn4j.data.client.ClientDataType; -import com.webauthn4j.data.client.Origin; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.server.ServerProperty; import com.webauthn4j.test.TestDataUtil; import org.junit.Test; +import org.springframework.security.webauthn.challenge.WebAuthnChallenge; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; +import org.springframework.security.webauthn.server.WebAuthnOrigin; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; -public class WebAuthnAuthenticationRequestTest { +public class WebAuthnAuthenticationDataTest { private CborConverter cborConverter = new CborConverter(); @Test public void getter_test() { - Challenge challenge = new DefaultChallenge(); + WebAuthnChallenge challenge = new WebAuthnChallengeImpl(); byte[] clientDataJSON = TestDataUtil.createClientDataJSON(ClientDataType.GET); byte[] authenticatorData = new AuthenticatorDataConverter(cborConverter).convert(TestDataUtil.createAuthenticatorData()); - ServerProperty serverProperty = new ServerProperty( - new Origin("https://example.com"), + WebAuthnServerProperty serverProperty = new WebAuthnServerProperty( + new WebAuthnOrigin("https://example.com"), "example.com", challenge, new byte[]{0x43, 0x21} ); - WebAuthnAuthenticationRequest request = new WebAuthnAuthenticationRequest( + WebAuthnAuthenticationData authenticationData = new WebAuthnAuthenticationData( new byte[]{0x01, 0x23}, clientDataJSON, authenticatorData, @@ -56,30 +56,30 @@ public void getter_test() { true, Collections.singletonList("uvi") ); - assertThat(request.getCredentialId()).isEqualTo(new byte[]{0x01, 0x23}); - assertThat(request.getClientDataJSON()).isEqualTo(clientDataJSON); - assertThat(request.getAuthenticatorData()).isEqualTo(authenticatorData); - assertThat(request.getSignature()).isEqualTo(new byte[]{0x45, 0x56}); - assertThat(request.getClientExtensionsJSON()).isEqualTo(""); - assertThat(request.getServerProperty()).isEqualTo(serverProperty); - assertThat(request.isUserVerificationRequired()).isEqualTo(true); - assertThat(request.isUserPresenceRequired()).isEqualTo(true); - assertThat(request.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList("uvi")); + assertThat(authenticationData.getCredentialId()).isEqualTo(new byte[]{0x01, 0x23}); + assertThat(authenticationData.getClientDataJSON()).isEqualTo(clientDataJSON); + assertThat(authenticationData.getAuthenticatorData()).isEqualTo(authenticatorData); + assertThat(authenticationData.getSignature()).isEqualTo(new byte[]{0x45, 0x56}); + assertThat(authenticationData.getClientExtensionsJSON()).isEqualTo(""); + assertThat(authenticationData.getServerProperty()).isEqualTo(serverProperty); + assertThat(authenticationData.isUserVerificationRequired()).isEqualTo(true); + assertThat(authenticationData.isUserPresenceRequired()).isEqualTo(true); + assertThat(authenticationData.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList("uvi")); } @Test public void equals_hashCode_test() { - Challenge challenge = new DefaultChallenge(); + WebAuthnChallenge challenge = new WebAuthnChallengeImpl(); byte[] clientDataJSON = TestDataUtil.createClientDataJSON(ClientDataType.GET); byte[] authenticatorData = new AuthenticatorDataConverter(cborConverter).convert(TestDataUtil.createAuthenticatorData()); - WebAuthnAuthenticationRequest requestA = new WebAuthnAuthenticationRequest( + WebAuthnAuthenticationData requestA = new WebAuthnAuthenticationData( new byte[]{0x01, 0x23}, clientDataJSON, authenticatorData, new byte[]{0x45, 0x56}, "", - new ServerProperty( - new Origin("https://example.com"), + new WebAuthnServerProperty( + new WebAuthnOrigin("https://example.com"), "example.com", challenge, new byte[]{0x43, 0x21} @@ -87,14 +87,14 @@ public void equals_hashCode_test() { true, Collections.singletonList("uvi") ); - WebAuthnAuthenticationRequest requestB = new WebAuthnAuthenticationRequest( + WebAuthnAuthenticationData requestB = new WebAuthnAuthenticationData( new byte[]{0x01, 0x23}, clientDataJSON, authenticatorData, new byte[]{0x45, 0x56}, "", - new ServerProperty( - new Origin("https://example.com"), + new WebAuthnServerProperty( + new WebAuthnOrigin("https://example.com"), "example.com", challenge, new byte[]{0x43, 0x21} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java index a8e140d9926..181355911fe 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java @@ -16,10 +16,6 @@ package org.springframework.security.webauthn; -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.authenticator.AuthenticatorImpl; -import com.webauthn4j.data.WebAuthnAuthenticationContext; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -29,14 +25,14 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; import org.springframework.security.webauthn.exception.BadChallengeException; import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; +import org.springframework.security.webauthn.userdetails.WebAuthnAndPasswordUser; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsImpl; +import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsChecker; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; import java.util.Collections; @@ -54,14 +50,14 @@ public class WebAuthnAuthenticationProviderTest { private WebAuthnAuthenticatorService authenticatorService = mock(WebAuthnAuthenticatorService.class); - private WebAuthnAuthenticationContextValidator authenticationContextValidator = mock(WebAuthnAuthenticationContextValidator.class); + private WebAuthnManager webAuthnManager = mock(WebAuthnManager.class); private WebAuthnAuthenticationProvider authenticationProvider - = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, authenticationContextValidator); + = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, webAuthnManager); @Before public void setup() { - authenticationProvider = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, authenticationContextValidator); + authenticationProvider = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, webAuthnManager); } /** @@ -92,28 +88,28 @@ public void authenticate_test() { byte[] credentialId = new byte[32]; GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); - WebAuthnUserDetailsImpl user = new WebAuthnUserDetailsImpl( + WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", Collections.singletonList(authenticator), Collections.singletonList(grantedAuthority)); - when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + when(authenticator.getCredentialId()).thenReturn(credentialId); //When - WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); when(credential.getCredentialId()).thenReturn(credentialId); - when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(user); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(user); Authentication token = new WebAuthnAssertionAuthenticationToken(credential); Authentication authenticatedToken = authenticationProvider.authenticate(token); - ArgumentCaptor captor = ArgumentCaptor.forClass(WebAuthnAuthenticationContext.class); - verify(authenticationContextValidator).validate(captor.capture(), any()); - WebAuthnAuthenticationContext authenticationContext = captor.getValue(); + ArgumentCaptor captor = ArgumentCaptor.forClass(WebAuthnAuthenticationData.class); + verify(webAuthnManager).verifyAuthenticationData(captor.capture(), any()); + WebAuthnAuthenticationData authenticationData = captor.getValue(); - assertThat(authenticationContext.getExpectedExtensionIds()).isEqualTo(credential.getExpectedAuthenticationExtensionIds()); + assertThat(authenticationData.getExpectedAuthenticationExtensionIds()).isEqualTo(credential.getExpectedAuthenticationExtensionIds()); - assertThat(authenticatedToken.getPrincipal()).isInstanceOf(WebAuthnUserDetailsImpl.class); + assertThat(authenticatedToken.getPrincipal()).isInstanceOf(WebAuthnAndPasswordUser.class); assertThat(authenticatedToken.getCredentials()).isEqualTo(credential); assertThat(authenticatedToken.getAuthorities().toArray()).containsExactly(grantedAuthority); } @@ -127,18 +123,18 @@ public void authenticate_with_forcePrincipalAsString_option_test() { byte[] credentialId = new byte[32]; GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); - WebAuthnUserDetailsImpl user = new WebAuthnUserDetailsImpl( + WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", Collections.singletonList(authenticator), Collections.singletonList(grantedAuthority)); - when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + when(authenticator.getCredentialId()).thenReturn(credentialId); //When - WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); when(credential.getCredentialId()).thenReturn(credentialId); - when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(user); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(user); Authentication token = new WebAuthnAssertionAuthenticationToken(credential); authenticationProvider.setForcePrincipalAsString(true); Authentication authenticatedToken = authenticationProvider.authenticate(token); @@ -157,20 +153,20 @@ public void authenticate_with_BadChallengeException_from_authenticationContextVa byte[] credentialId = new byte[32]; GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); - WebAuthnUserDetailsImpl user = new WebAuthnUserDetailsImpl( + WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", Collections.singletonList(authenticator), Collections.singletonList(grantedAuthority)); - when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); + when(authenticator.getCredentialId()).thenReturn(credentialId); - doThrow(com.webauthn4j.validator.exception.BadChallengeException.class).when(authenticationContextValidator).validate(any(), any()); + doThrow(BadChallengeException.class).when(webAuthnManager).verifyAuthenticationData(any(), any()); //When - WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); when(credential.getCredentialId()).thenReturn(credentialId); - when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(user); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(user); Authentication token = new WebAuthnAssertionAuthenticationToken(credential); authenticationProvider.authenticate(token); } @@ -182,7 +178,7 @@ public void retrieveWebAuthnUserDetails_test() { WebAuthnUserDetails expectedUser = mock(WebAuthnUserDetails.class); //Given - when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(expectedUser); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(expectedUser); //When WebAuthnUserDetails userDetails = authenticationProvider.retrieveWebAuthnUserDetails(credentialId); @@ -197,7 +193,7 @@ public void retrieveWebAuthnUserDetails_test_with_CredentialIdNotFoundException( byte[] credentialId = new byte[0]; //Given - when(userDetailsService.loadUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); //When authenticationProvider.retrieveWebAuthnUserDetails(credentialId); @@ -208,7 +204,7 @@ public void retrieveWebAuthnUserDetails_test_with_CredentialIdNotFoundException_ byte[] credentialId = new byte[0]; //Given - when(userDetailsService.loadUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); //When authenticationProvider.setHideCredentialIdNotFoundExceptions(false); @@ -220,7 +216,7 @@ public void retrieveWebAuthnUserDetails_test_with_RuntimeException_from_webAuthn byte[] credentialId = new byte[0]; //Given - when(userDetailsService.loadUserByCredentialId(credentialId)).thenThrow(RuntimeException.class); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenThrow(RuntimeException.class); //When authenticationProvider.setHideCredentialIdNotFoundExceptions(false); @@ -232,7 +228,7 @@ public void retrieveWebAuthnUserDetails_test_with_null_from_webAuthnAuthenticato byte[] credentialId = new byte[0]; //Given - when(userDetailsService.loadUserByCredentialId(credentialId)).thenReturn(null); + when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(null); //When authenticationProvider.setHideCredentialIdNotFoundExceptions(false); @@ -242,8 +238,8 @@ public void retrieveWebAuthnUserDetails_test_with_null_from_webAuthnAuthenticato @Test public void getter_setter_test() { WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - UserDetailsChecker preAuthenticationChecker = mock(UserDetailsChecker.class); - UserDetailsChecker postAuthenticationChecker = mock(UserDetailsChecker.class); + WebAuthnUserDetailsChecker preAuthenticationChecker = mock(WebAuthnUserDetailsChecker.class); + WebAuthnUserDetailsChecker postAuthenticationChecker = mock(WebAuthnUserDetailsChecker.class); authenticationProvider.setForcePrincipalAsString(true); assertThat(authenticationProvider.isForcePrincipalAsString()).isTrue(); @@ -263,8 +259,8 @@ public void getter_setter_test() { @Test public void userDetailsChecker_check_test() { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - Authenticator authenticator = new AuthenticatorImpl(null, null, 0); - WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", @@ -276,8 +272,8 @@ public void userDetailsChecker_check_test() { @Test(expected = DisabledException.class) public void userDetailsChecker_check_with_disabled_userDetails_test() { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - Authenticator authenticator = new AuthenticatorImpl(null, null, 0); - WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", @@ -294,8 +290,8 @@ public void userDetailsChecker_check_with_disabled_userDetails_test() { @Test(expected = AccountExpiredException.class) public void userDetailsChecker_check_with_expired_userDetails_test() { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - Authenticator authenticator = new AuthenticatorImpl(null, null, 0); - WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", @@ -312,8 +308,8 @@ public void userDetailsChecker_check_with_expired_userDetails_test() { @Test(expected = CredentialsExpiredException.class) public void userDetailsChecker_check_with_credentials_expired_userDetails_test() { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - Authenticator authenticator = new AuthenticatorImpl(null, null, 0); - WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", @@ -330,8 +326,8 @@ public void userDetailsChecker_check_with_credentials_expired_userDetails_test() @Test(expected = LockedException.class) public void userDetailsChecker_check_with_locked_userDetails_test() { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - Authenticator authenticator = new AuthenticatorImpl(null, null, 0); - WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( new byte[0], "dummy", "dummy", @@ -349,7 +345,7 @@ public void userDetailsChecker_check_with_locked_userDetails_test() { public void isUserVerificationRequired_test() { WebAuthnUserDetails webAuthnUserDetails = mock(WebAuthnUserDetails.class); when(webAuthnUserDetails.getUsername()).thenReturn("john.doe"); - WebAuthnAuthenticationRequest credentials = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData credentials = mock(WebAuthnAuthenticationData.class); when(credentials.isUserVerificationRequired()).thenReturn(true); SecurityContext securityContext = mock(SecurityContext.class); Authentication authentication = mock(Authentication.class); diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java index e679d4641ce..4bf68dc1b91 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java @@ -17,7 +17,6 @@ package org.springframework.security.webauthn; import org.junit.Test; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -41,7 +40,7 @@ public void webAuthnAuthenticationToken() { */ @Test public void test_methods() { - WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); WebAuthnAuthenticationToken webAuthnAuthenticationToken = new WebAuthnAuthenticationToken("username", credential, null); assertThat(webAuthnAuthenticationToken.getPrincipal()).isEqualTo("username"); @@ -50,7 +49,7 @@ public void test_methods() { @Test public void equals_hashCode_test() { - WebAuthnAuthenticationRequest credential = mock(WebAuthnAuthenticationRequest.class); + WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); WebAuthnAuthenticationToken tokenA = new WebAuthnAuthenticationToken("username", credential, null); WebAuthnAuthenticationToken tokenB = new WebAuthnAuthenticationToken("username", credential, null); assertThat(tokenA).isEqualTo(tokenB); diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java index 15f6d1507d7..d79e5e53485 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java @@ -17,7 +17,6 @@ package org.springframework.security.webauthn; import com.webauthn4j.data.extension.client.FIDOAppIDExtensionClientInput; -import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.Base64UrlUtil; import org.junit.Before; import org.junit.Rule; @@ -32,8 +31,8 @@ import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.webauthn.request.WebAuthnAuthenticationRequest; -import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import java.util.Collections; @@ -48,7 +47,7 @@ public class WebAuthnProcessingFilterTest { @Rule public MockitoRule mockito = MockitoJUnit.rule(); - private ServerPropertyProvider serverPropertyProvider; + private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; private AuthenticationManager authenticationManager; private MockHttpServletRequest mockHttpServletRequest; private MockHttpServletResponse mockHttpServletResponse; @@ -60,13 +59,13 @@ public class WebAuthnProcessingFilterTest { @Before public void setup() { - serverPropertyProvider = mock(ServerPropertyProvider.class); + webAuthnServerPropertyProvider = mock(WebAuthnServerPropertyProvider.class); authenticationManager = mock(AuthenticationManager.class); mockHttpServletRequest = new MockHttpServletRequest(); mockHttpServletResponse = new MockHttpServletResponse(); target.setAuthenticationManager(authenticationManager); - target.setServerPropertyProvider(serverPropertyProvider); + target.setServerPropertyProvider(webAuthnServerPropertyProvider); } @Test @@ -94,7 +93,7 @@ public void attemptAuthentication_test_with_credential() { String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; String clientExtensionsJSON = ""; - ServerProperty serverProperty = mock(ServerProperty.class); + WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); //Given @@ -107,16 +106,16 @@ public void attemptAuthentication_test_with_credential() { mockHttpServletRequest.setParameter("clientExtensionsJSON", clientExtensionsJSON); when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); //When target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); //Then WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); - verify(serverPropertyProvider).provide(mockHttpServletRequest); + verify(webAuthnServerPropertyProvider).provide(mockHttpServletRequest); assertThat(authenticationToken.getPrincipal()).isNull(); - assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationRequest.class); + assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationData.class); assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); @@ -135,7 +134,7 @@ public void attemptAuthentication_test_with_get_method() { String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; String clientExtensionsJSON = ""; - ServerProperty serverProperty = mock(ServerProperty.class); + WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); //Given target.setPostOnly(false); @@ -148,16 +147,16 @@ public void attemptAuthentication_test_with_get_method() { mockHttpServletRequest.setParameter("clientExtensionsJSON", clientExtensionsJSON); when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); //When target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); //Then WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); - verify(serverPropertyProvider).provide(mockHttpServletRequest); + verify(webAuthnServerPropertyProvider).provide(mockHttpServletRequest); assertThat(authenticationToken.getPrincipal()).isNull(); - assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationRequest.class); + assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationData.class); assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); @@ -185,7 +184,7 @@ public void attemptAuthentication_test_with_customized_parameter() { String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; String clientExtensionsJSON = ""; - ServerProperty serverProperty = mock(ServerProperty.class); + WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); //Given target.setUsernameParameter(usernameParameter); @@ -206,7 +205,7 @@ public void attemptAuthentication_test_with_customized_parameter() { mockHttpServletRequest.setParameter(clientExtensionsJSONParameter, clientExtensionsJSON); when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); //When target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); @@ -220,13 +219,13 @@ public void attemptAuthentication_test_with_customized_parameter() { assertThat(target.getSignatureParameter()).isEqualTo(signatureParameter); assertThat(target.getClientExtensionsJSONParameter()).isEqualTo(clientExtensionsJSONParameter); assertThat(target.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList(FIDOAppIDExtensionClientInput.ID)); - assertThat(target.getServerPropertyProvider()).isEqualTo(serverPropertyProvider); + assertThat(target.getServerPropertyProvider()).isEqualTo(webAuthnServerPropertyProvider); WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); - verify(serverPropertyProvider).provide(mockHttpServletRequest); + verify(webAuthnServerPropertyProvider).provide(mockHttpServletRequest); assertThat(authenticationToken.getPrincipal()).isNull(); - assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationRequest.class); + assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationData.class); assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); @@ -251,9 +250,9 @@ public void attemptAuthentication_test_with_wrong_port() { @Test public void constructor_test() { - ServerPropertyProvider serverPropertyProvider = mock(ServerPropertyProvider.class); - WebAuthnProcessingFilter webAuthnProcessingFilter = new WebAuthnProcessingFilter(AuthorityUtils.NO_AUTHORITIES, serverPropertyProvider); - assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(serverPropertyProvider); + WebAuthnServerPropertyProvider webAuthnServerPropertyProvider = mock(WebAuthnServerPropertyProvider.class); + WebAuthnProcessingFilter webAuthnProcessingFilter = new WebAuthnProcessingFilter(AuthorityUtils.NO_AUTHORITIES, webAuthnServerPropertyProvider); + assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(webAuthnServerPropertyProvider); } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java deleted file mode 100644 index 9de3ef32d06..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidationResponseTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - - -import com.webauthn4j.data.attestation.AttestationObject; -import com.webauthn4j.data.client.ClientDataType; -import com.webauthn4j.data.client.CollectedClientData; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientOutputs; -import com.webauthn4j.test.TestDataUtil; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class WebAuthnRegistrationRequestValidationResponseTest { - - @Test - public void equals_hashCode_test() { - CollectedClientData clientData = TestDataUtil.createClientData(ClientDataType.CREATE); - AttestationObject attestationObject = TestDataUtil.createAttestationObjectWithFIDOU2FAttestationStatement(); - AuthenticationExtensionsClientOutputs clientExtensions = new AuthenticationExtensionsClientOutputs(); - WebAuthnRegistrationRequestValidationResponse instanceA = - new WebAuthnRegistrationRequestValidationResponse(clientData, attestationObject, clientExtensions); - WebAuthnRegistrationRequestValidationResponse instanceB = - new WebAuthnRegistrationRequestValidationResponse(clientData, attestationObject, clientExtensions); - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceB).hasSameHashCodeAs(instanceB); - } - - @Test - public void getter_test() { - CollectedClientData clientData = TestDataUtil.createClientData(ClientDataType.CREATE); - AttestationObject attestationObject = TestDataUtil.createAttestationObjectWithFIDOU2FAttestationStatement(); - AuthenticationExtensionsClientOutputs clientExtensions = new AuthenticationExtensionsClientOutputs(); - WebAuthnRegistrationRequestValidationResponse instance = - new WebAuthnRegistrationRequestValidationResponse(clientData, attestationObject, clientExtensions); - - assertThat(instance.getCollectedClientData()).isEqualTo(clientData); - assertThat(instance.getAttestationObject()).isEqualTo(attestationObject); - assertThat(instance.getRegistrationExtensionsClientOutputs()).isEqualTo(clientExtensions); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java index 1d210c93653..9a217f8512c 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java @@ -16,14 +16,8 @@ package org.springframework.security.webauthn; -import com.webauthn4j.data.WebAuthnRegistrationContext; -import com.webauthn4j.data.attestation.AttestationObject; -import com.webauthn4j.data.client.CollectedClientData; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientOutputs; -import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.Base64UrlUtil; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidationResponse; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -31,8 +25,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.exception.BadAttestationStatementException; -import org.springframework.security.webauthn.server.ServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerProperty; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import java.util.Collections; import java.util.Set; @@ -42,7 +36,7 @@ import static org.mockito.Mockito.*; /** - * Test for WebAuthnRegistrationContextValidator + * Test for {@link WebAuthnRegistrationRequestValidator} */ public class WebAuthnRegistrationRequestValidatorTest { @@ -50,26 +44,25 @@ public class WebAuthnRegistrationRequestValidatorTest { public MockitoRule mockito = MockitoJUnit.rule(); @Mock - private WebAuthnRegistrationContextValidator registrationContextValidator; + private WebAuthnManager webAuthnManager; @Mock - private ServerPropertyProvider serverPropertyProvider; + private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; + private WebAuthnRegistrationRequestValidator target; + + @Before + public void setup() { + target = new WebAuthnRegistrationRequestValidator(webAuthnManager, webAuthnServerPropertyProvider); + } @Test public void validate_test() { - WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( - registrationContextValidator, serverPropertyProvider - ); - ServerProperty serverProperty = mock(ServerProperty.class); - when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); + when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - CollectedClientData collectedClientData = mock(CollectedClientData.class); - AttestationObject attestationObject = mock(AttestationObject.class); - AuthenticationExtensionsClientOutputs clientExtensionOutputs = new AuthenticationExtensionsClientOutputs(); - when(registrationContextValidator.validate(any())).thenReturn( - new WebAuthnRegistrationContextValidationResponse(collectedClientData, attestationObject, clientExtensionOutputs)); + doNothing().when(webAuthnManager).verifyRegistrationData(any()); MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); mockHttpServletRequest.setScheme("https"); @@ -80,33 +73,26 @@ public void validate_test() { Set transports = Collections.emptySet(); String clientExtensionsJSON = "clientExtensionsJSON"; - target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON); + target.validate(new WebAuthnRegistrationRequest(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON)); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationContext.class); - verify(registrationContextValidator).validate(argumentCaptor.capture()); - WebAuthnRegistrationContext registrationContext = argumentCaptor.getValue(); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationData.class); + verify(webAuthnManager).verifyRegistrationData(argumentCaptor.capture()); + WebAuthnRegistrationData registrationData = argumentCaptor.getValue(); - assertThat(registrationContext.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); - assertThat(registrationContext.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); - assertThat(registrationContext.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(registrationContext.getServerProperty()).isEqualTo(serverProperty); - assertThat(registrationContext.getExpectedExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); + assertThat(registrationData.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); + assertThat(registrationData.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); + assertThat(registrationData.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(registrationData.getServerProperty()).isEqualTo(serverProperty); + assertThat(registrationData.getExpectedRegistrationExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); } @Test public void validate_with_transports_null_test() { - WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( - registrationContextValidator, serverPropertyProvider - ); - ServerProperty serverProperty = mock(ServerProperty.class); - when(serverPropertyProvider.provide(any())).thenReturn(serverProperty); + WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); + when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - CollectedClientData collectedClientData = mock(CollectedClientData.class); - AttestationObject attestationObject = mock(AttestationObject.class); - AuthenticationExtensionsClientOutputs clientExtensionOutputs = new AuthenticationExtensionsClientOutputs(); - when(registrationContextValidator.validate(any())).thenReturn( - new WebAuthnRegistrationContextValidationResponse(collectedClientData, attestationObject, clientExtensionOutputs)); + doNothing().when(webAuthnManager).verifyRegistrationData(any()); MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); mockHttpServletRequest.setScheme("https"); @@ -116,47 +102,25 @@ public void validate_with_transports_null_test() { String attestationObjectBase64 = "attestationObjectBase64"; String clientExtensionsJSON = "clientExtensionsJSON"; - target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, null, clientExtensionsJSON); + target.validate(new WebAuthnRegistrationRequest(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, null, clientExtensionsJSON)); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationContext.class); - verify(registrationContextValidator).validate(argumentCaptor.capture()); - WebAuthnRegistrationContext registrationContext = argumentCaptor.getValue(); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationData.class); + verify(webAuthnManager).verifyRegistrationData(argumentCaptor.capture()); + WebAuthnRegistrationData registrationData = argumentCaptor.getValue(); - assertThat(registrationContext.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); - assertThat(registrationContext.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); - assertThat(registrationContext.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(registrationContext.getServerProperty()).isEqualTo(serverProperty); - assertThat(registrationContext.getExpectedExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); + assertThat(registrationData.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); + assertThat(registrationData.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); + assertThat(registrationData.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); + assertThat(registrationData.getServerProperty()).isEqualTo(serverProperty); + assertThat(registrationData.getExpectedRegistrationExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); } @Test public void getter_setter_test() { - WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( - registrationContextValidator, serverPropertyProvider - ); + target.setExpectedRegistrationExtensionIds(Collections.singletonList("appId")); assertThat(target.getExpectedRegistrationExtensionIds()).containsExactly("appId"); } - - @Test(expected = BadAttestationStatementException.class) - public void validate_caught_exception_test() { - WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator( - registrationContextValidator, serverPropertyProvider - ); - when(registrationContextValidator.validate(any())).thenThrow(new com.webauthn4j.validator.exception.BadAttestationStatementException("dummy")); - - MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); - mockHttpServletRequest.setScheme("https"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setServerPort(443); - String clientDataBase64 = "clientDataBase64"; - String attestationObjectBase64 = "attestationObjectBase64"; - Set transports = Collections.emptySet(); - String clientExtensionsJSON = "clientExtensionsJSON"; - - target.validate(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON); - - } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java similarity index 66% rename from webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java rename to webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java index 9e8de4218dd..ca87b543f1d 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java @@ -20,21 +20,22 @@ import static org.assertj.core.api.Assertions.assertThat; -public class WebAuthnAuthenticatorTest { +/** + * Test for {@link WebAuthnAuthenticatorImpl} + */ +public class WebAuthnAuthenticatorImplTest { @Test public void equals_hashCode_test() { - WebAuthnAuthenticator instanceA = new WebAuthnAuthenticator("authenticator", null, null, 0); - WebAuthnAuthenticator instanceB = new WebAuthnAuthenticator("authenticator", null, null, 0); + WebAuthnAuthenticator instanceA = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAuthenticator instanceB = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); assertThat(instanceA).isEqualTo(instanceB); assertThat(instanceA).hasSameHashCodeAs(instanceB); } @Test - public void get_set_name_test() { - WebAuthnAuthenticator instance = new WebAuthnAuthenticator("authenticator", null, null, 0); + public void get_name_test() { + WebAuthnAuthenticatorImpl instance = new WebAuthnAuthenticatorImpl(new byte[0], "authenticator", new byte[0], 0, null, null); assertThat(instance.getName()).isEqualTo("authenticator"); - instance.setName("newName"); - assertThat(instance.getName()).isEqualTo("newName"); } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java similarity index 76% rename from webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java rename to webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java index 71db1013bc0..eb87e42efce 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionChallengeRepositoryTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java @@ -16,7 +16,6 @@ package org.springframework.security.webauthn.challenge; -import com.webauthn4j.data.client.challenge.Challenge; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; @@ -26,15 +25,15 @@ import static org.assertj.core.api.Assertions.assertThat; /** - * Test for HttpSessionChallengeRepository + * Test for {@link HttpSessionWebAuthnChallengeRepository} */ -public class HttpSessionChallengeRepositoryTest { +public class HttpSessionWebAuthnChallengeRepositoryTest { - private HttpSessionChallengeRepository target = new HttpSessionChallengeRepository(); + private HttpSessionWebAuthnChallengeRepository target = new HttpSessionWebAuthnChallengeRepository(); @Test public void generateChallenge_test() { - Challenge challenge = target.generateChallenge(); + WebAuthnChallenge challenge = target.generateChallenge(); assertThat(challenge).isNotNull(); assertThat(challenge.getValue()).hasSize(16); } @@ -45,11 +44,11 @@ public void saveChallenge_test() { String attrName = ".test-challenge"; target.setSessionAttributeName(attrName); - Challenge challenge = target.generateChallenge(); + WebAuthnChallenge challenge = target.generateChallenge(); target.saveChallenge(challenge, request); HttpSession session = request.getSession(); - assertThat((Challenge) session.getAttribute(attrName)).isEqualTo(challenge); + assertThat((WebAuthnChallenge) session.getAttribute(attrName)).isEqualTo(challenge); } @Test @@ -61,10 +60,10 @@ public void saveChallenge_test_with_null() { MockHttpServletRequest request = new MockHttpServletRequest(); request.setSession(session); - Challenge challenge = target.generateChallenge(); + WebAuthnChallenge challenge = target.generateChallenge(); target.saveChallenge(challenge, prevRequest); target.saveChallenge(null, request); - Challenge loadedChallenge = target.loadChallenge(request); + WebAuthnChallenge loadedChallenge = target.loadChallenge(request); assertThat(loadedChallenge).isNull(); } @@ -74,7 +73,7 @@ public void saveChallenge_test_without_prev_request() { MockHttpServletRequest request = new MockHttpServletRequest(); target.saveChallenge(null, request); - Challenge loadedChallenge = target.loadChallenge(request); + WebAuthnChallenge loadedChallenge = target.loadChallenge(request); assertThat(loadedChallenge).isNull(); } @@ -91,9 +90,9 @@ public void loadChallenge_test() { String attrName = ".test-challenge"; target.setSessionAttributeName(attrName); - Challenge challenge = target.generateChallenge(); + WebAuthnChallenge challenge = target.generateChallenge(); target.saveChallenge(challenge, prevRequest); - Challenge loadedChallenge = target.loadChallenge(request); + WebAuthnChallenge loadedChallenge = target.loadChallenge(request); assertThat(loadedChallenge).isEqualTo(challenge); } @@ -102,7 +101,7 @@ public void loadChallenge_test() { public void loadChallenge_test_without_previous_request() { MockHttpServletRequest request = new MockHttpServletRequest(); - Challenge loadedChallenge = target.loadChallenge(request); + WebAuthnChallenge loadedChallenge = target.loadChallenge(request); assertThat(loadedChallenge).isNull(); } @@ -111,7 +110,7 @@ public void loadChallenge_test_without_previous_request() { public void loadOrGenerateChallenge_test_without_previous_request() { MockHttpServletRequest request = new MockHttpServletRequest(); - Challenge loadedChallenge = target.loadOrGenerateChallenge(request); + WebAuthnChallenge loadedChallenge = target.loadOrGenerateChallenge(request); assertThat(loadedChallenge).isNotNull(); } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java index f2725535f6d..3dd78248666 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java @@ -16,7 +16,6 @@ package org.springframework.security.webauthn.config.configurers; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -28,14 +27,15 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.webauthn.WebAuthn4JWebAuthnManager; import org.springframework.security.webauthn.WebAuthnAuthenticationProvider; +import org.springframework.security.webauthn.WebAuthnManager; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.challenge.HttpSessionChallengeRepository; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.security.webauthn.options.OptionsProviderImpl; -import org.springframework.security.webauthn.server.ServerPropertyProvider; -import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.challenge.HttpSessionWebAuthnChallengeRepository; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.security.webauthn.server.EffectiveRpIdProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProviderImpl; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; import org.springframework.test.context.junit4.SpringRunner; @@ -56,7 +56,7 @@ public void test() { @EnableWebSecurity - static class Config extends WebSecurityConfigurerAdapter { + static class Config extends WebSecurityConfigurerAdapter { @Autowired private WebAuthnUserDetailsService webAuthnUserDetailsService; @@ -64,37 +64,8 @@ static class Config extends WebSecurityConfigurerAdapter { @Autowired private WebAuthnAuthenticatorService webAuthnAuthenticatorService; - @Configuration - static class BeanConfig { - @Bean - public WebAuthnUserDetailsService webAuthnUserDetailsService(){ - return mock(WebAuthnUserDetailsService.class); - } - - @Bean - public WebAuthnAuthenticatorService webAuthnAuthenticatorService(){ - return mock(WebAuthnAuthenticatorService.class); - } - - @Bean - public ChallengeRepository challengeRepository() { - return new HttpSessionChallengeRepository(); - } - - @Bean - public OptionsProvider optionsProvider(WebAuthnUserDetailsService webAuthnUserDetailsService, ChallengeRepository challengeRepository) { - OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(webAuthnUserDetailsService, challengeRepository); - optionsProviderImpl.setRpId("example.com"); - return optionsProviderImpl; - } - - @Bean - public ServerPropertyProvider serverPropertyProvider(OptionsProvider optionsProvider, ChallengeRepository challengeRepository) { - return new ServerPropertyProviderImpl(optionsProvider, challengeRepository); - } - - - } + @Autowired + private WebAuthnManager webAuthnManager; @Bean @Override @@ -116,7 +87,38 @@ protected void configure(HttpSecurity http) throws Exception { @Override public void configure(AuthenticationManagerBuilder builder) throws Exception { - builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(webAuthnUserDetailsService, webAuthnAuthenticatorService, new WebAuthnAuthenticationContextValidator())); + builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(webAuthnUserDetailsService, webAuthnAuthenticatorService, webAuthnManager)); + } + + @Configuration + static class BeanConfig { + + @Bean + public WebAuthn4JWebAuthnManager webAuthn4JWebAuthnAuthenticationManager() { + return mock(WebAuthn4JWebAuthnManager.class); + } + + @Bean + public WebAuthnUserDetailsService webAuthnUserDetailsService() { + return mock(WebAuthnUserDetailsService.class); + } + + @Bean + public WebAuthnAuthenticatorService webAuthnAuthenticatorService() { + return mock(WebAuthnAuthenticatorService.class); + } + + @Bean + public WebAuthnChallengeRepository challengeRepository() { + return new HttpSessionWebAuthnChallengeRepository(); + } + + @Bean + public WebAuthnServerPropertyProvider serverPropertyProvider(EffectiveRpIdProvider effectiveRpIdProvider, WebAuthnChallengeRepository webAuthnChallengeRepository) { + return new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, webAuthnChallengeRepository); + } + + } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java index 5e619e3e739..89a6f1e081a 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java @@ -17,8 +17,6 @@ package org.springframework.security.webauthn.config.configurers; -import com.webauthn4j.converter.util.JsonConverter; -import com.webauthn4j.data.client.challenge.DefaultChallenge; import com.webauthn4j.test.TestDataUtil; import org.junit.Before; import org.junit.Test; @@ -31,12 +29,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.webauthn.WebAuthnDataConverter; import org.springframework.security.webauthn.WebAuthnProcessingFilter; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.options.OptionsProvider; -import org.springframework.security.webauthn.options.OptionsProviderImpl; -import org.springframework.security.webauthn.server.ServerPropertyProvider; -import org.springframework.security.webauthn.server.ServerPropertyProviderImpl; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; +import org.springframework.security.webauthn.server.EffectiveRpIdProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; +import org.springframework.security.webauthn.server.WebAuthnServerPropertyProviderImpl; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; import org.springframework.test.context.junit4.SpringRunner; @@ -63,7 +62,7 @@ public class WebAuthnLoginConfigurerSetterSpringTest { private WebAuthnUserDetailsService webAuthnUserDetailsService; @Autowired - private ServerPropertyProvider serverPropertyProvider; + private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; @SuppressWarnings("unchecked") @Before @@ -72,14 +71,14 @@ public void setup() { Collection authenticators = Collections.singletonList(TestDataUtil.createAuthenticator()); when(mockUserDetails.getAuthenticators()).thenReturn(authenticators); when(mockUserDetails.getUserHandle()).thenReturn(new byte[32]); - doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadUserByUsername(null); - when(webAuthnUserDetailsService.loadUserByUsername(anyString())).thenReturn(mockUserDetails); + doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadWebAuthnUserByUsername(null); + when(webAuthnUserDetailsService.loadWebAuthnUserByUsername(anyString())).thenReturn(mockUserDetails); } @Test public void configured_filter_test() { WebAuthnProcessingFilter webAuthnProcessingFilter = (WebAuthnProcessingFilter) springSecurityFilterChain.getFilterChains().get(0).getFilters().stream().filter(item -> item instanceof WebAuthnProcessingFilter).findFirst().orElse(null); - assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(serverPropertyProvider); + assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(webAuthnServerPropertyProvider); } @EnableWebSecurity @@ -101,32 +100,30 @@ protected void configure(HttpSecurity http) throws Exception { static class BeanConfig { @Bean - public WebAuthnUserDetailsService webAuthnUserDetailsService(){ + public WebAuthnUserDetailsService webAuthnUserDetailsService() { return mock(WebAuthnUserDetailsService.class); } @Bean - public JsonConverter jsonConverter() { - return new JsonConverter(); + public WebAuthnDataConverter webAuthnDataConverter() { + return new WebAuthnDataConverter(); } @Bean - public ChallengeRepository challengeRepository() { - ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - when(challengeRepository.loadOrGenerateChallenge(any())).thenReturn(new DefaultChallenge("aFglXMZdQTKD4krvNzJBzA")); - return challengeRepository; + public EffectiveRpIdProvider effectiveRpIdProvider() { + return mock(EffectiveRpIdProvider.class); } @Bean - public OptionsProvider optionsProvider(WebAuthnUserDetailsService webAuthnUserDetailsService, ChallengeRepository challengeRepository) { - OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(webAuthnUserDetailsService, challengeRepository); - optionsProviderImpl.setRpId("example.com"); - return optionsProviderImpl; + public WebAuthnChallengeRepository challengeRepository() { + WebAuthnChallengeRepository webAuthnChallengeRepository = mock(WebAuthnChallengeRepository.class); + when(webAuthnChallengeRepository.loadOrGenerateChallenge(any())).thenReturn(new WebAuthnChallengeImpl("aFglXMZdQTKD4krvNzJBzA")); + return webAuthnChallengeRepository; } @Bean - public ServerPropertyProvider serverPropertyProvider(OptionsProvider optionsProvider, ChallengeRepository challengeRepository) { - return new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + public WebAuthnServerPropertyProvider serverPropertyProvider(EffectiveRpIdProvider effectiveRpIdProvider, WebAuthnChallengeRepository webAuthnChallengeRepository) { + return new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, webAuthnChallengeRepository); } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java index 3155c12de79..6ac358ff0bf 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java @@ -17,12 +17,6 @@ package org.springframework.security.webauthn.config.configurers; -import com.webauthn4j.converter.util.JsonConverter; -import com.webauthn4j.data.PublicKeyCredentialType; -import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.data.extension.client.FIDOAppIDExtensionClientInput; -import com.webauthn4j.data.extension.client.SupportedExtensionsExtensionClientInput; import com.webauthn4j.test.TestDataUtil; import org.junit.Before; import org.junit.Test; @@ -35,8 +29,10 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.webauthn.WebAuthnDataConverter; import org.springframework.security.webauthn.WebAuthnProcessingFilter; -import org.springframework.security.webauthn.challenge.ChallengeRepository; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; import org.springframework.test.context.junit4.SpringRunner; @@ -56,7 +52,6 @@ import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @@ -77,8 +72,8 @@ public void setup() { Collection authenticators = Collections.singletonList(TestDataUtil.createAuthenticator()); when(mockUserDetails.getAuthenticators()).thenReturn(authenticators); when(mockUserDetails.getUserHandle()).thenReturn(new byte[32]); - doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadUserByUsername(null); - when(webAuthnUserDetailsService.loadUserByUsername(anyString())).thenReturn(mockUserDetails); + doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadWebAuthnUserByUsername(null); + when(webAuthnUserDetailsService.loadWebAuthnUserByUsername(anyString())).thenReturn(mockUserDetails); } @Test @@ -100,32 +95,6 @@ public void rootPath_with_anonymous_user_test() throws Exception { .andExpect(status().is3xxRedirection()); } - @Test - public void attestationOptionsEndpointPath_with_anonymous_user_test() throws Exception { - mvc = MockMvcBuilders.standaloneSetup() - .addFilter(springSecurityFilterChain) - .build(); - - mvc - .perform(get("/webauthn/attestation/options").with(anonymous())) - .andExpect(unauthenticated()) - .andExpect(content().json("{\"rp\":{\"name\":\"example\",\"icon\":\"dummy\",\"id\":\"example.com\"},\"challenge\":\"aFglXMZdQTKD4krvNzJBzA\",\"pubKeyCredParams\":[{\"type\":\"public-key\",\"alg\":-7},{\"type\":\"public-key\",\"alg\":-65535}],\"timeout\":10000,\"excludeCredentials\":[],\"authenticatorSelection\":{\"requireResidentKey\":false,\"userVerification\":\"preferred\"},\"extensions\":{\"exts\":true}}")) - .andExpect(status().isOk()); - } - - @Test - public void assertionOptionsEndpointPath_with_anonymous_user_test() throws Exception { - mvc = MockMvcBuilders.standaloneSetup() - .addFilter(springSecurityFilterChain) - .build(); - - mvc - .perform(get("/webauthn/assertion/options").with(anonymous())) - .andExpect(unauthenticated()) - .andExpect(content().json("{\"challenge\":\"aFglXMZdQTKD4krvNzJBzA\",\"timeout\":20000,\"rpId\":\"example.com\",\"allowCredentials\":[],\"extensions\":{\"appid\":\"\"}}")) - .andExpect(status().isOk()); - } - @Test public void rootPath_with_authenticated_user_test() throws Exception { mvc = MockMvcBuilders.standaloneSetup() @@ -140,45 +109,14 @@ public void rootPath_with_authenticated_user_test() throws Exception { } - @Test - public void assertionOptionsEndpointPath_with_authenticated_user_test() throws Exception { - mvc = MockMvcBuilders.standaloneSetup() - .addFilter(springSecurityFilterChain) - .build(); - - mvc - .perform(get("/webauthn/assertion/options").with(user("john"))) - .andExpect(authenticated()) - .andExpect(content().json("{\"challenge\":\"aFglXMZdQTKD4krvNzJBzA\",\"timeout\":20000,\"rpId\":\"example.com\",\"allowCredentials\":[{\"type\":\"public-key\",\"id\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}],\"extensions\":{\"appid\":\"\"}}")) - .andExpect(status().isOk()); - } - @EnableWebSecurity static class Config extends WebSecurityConfigurerAdapter { - @Autowired - private JsonConverter jsonConverter; - @Override protected void configure(HttpSecurity http) throws Exception { // Authentication http.apply(webAuthnLogin()) - .rpId("example.com") - .rpIcon("dummy") - .rpName("example") - .publicKeyCredParams() - .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256) - .addPublicKeyCredParams(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.RS1) - .and() - .registrationTimeout(10000L) - .authenticationTimeout(20000L) - .registrationExtensions() - .put(new SupportedExtensionsExtensionClientInput(true)) - .and() - .authenticationExtensions() - .put(new FIDOAppIDExtensionClientInput("")) - .and() .usernameParameter("username") .passwordParameter("password") .credentialIdParameter("credentialId") @@ -188,14 +126,7 @@ protected void configure(HttpSecurity http) throws Exception { .clientExtensionsJSONParameter("clientExtensionsJSON") .successForwardUrl("/") .failureForwardUrl("/login") - .loginPage("/login") - .attestationOptionsEndpoint() - .processingUrl("/webauthn/attestation/options") - .and() - .assertionOptionsEndpoint() - .processingUrl("/webauthn/assertion/options") - .and() - .jsonConverter(jsonConverter); + .loginPage("/login"); // Authorization http.authorizeRequests() @@ -207,20 +138,20 @@ protected void configure(HttpSecurity http) throws Exception { static class BeanConfig { @Bean - public WebAuthnUserDetailsService webAuthnUserDetailsService(){ + public WebAuthnUserDetailsService webAuthnUserDetailsService() { return mock(WebAuthnUserDetailsService.class); } @Bean - public JsonConverter jsonConverter() { - return new JsonConverter(); + public WebAuthnDataConverter webAuthnDataConverter() { + return new WebAuthnDataConverter(); } @Bean - public ChallengeRepository challengeRepository() { - ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - when(challengeRepository.loadOrGenerateChallenge(any())).thenReturn(new DefaultChallenge("aFglXMZdQTKD4krvNzJBzA")); - return challengeRepository; + public WebAuthnChallengeRepository challengeRepository() { + WebAuthnChallengeRepository webAuthnChallengeRepository = mock(WebAuthnChallengeRepository.class); + when(webAuthnChallengeRepository.loadOrGenerateChallenge(any())).thenReturn(new WebAuthnChallengeImpl("aFglXMZdQTKD4krvNzJBzA")); + return webAuthnChallengeRepository; } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java deleted file mode 100644 index 8938dc64d89..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/AttestationOptionsEndpointFilterTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import com.webauthn4j.converter.util.JsonConverter; -import org.junit.Test; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.*; -import org.springframework.security.webauthn.options.AttestationOptions; -import org.springframework.security.webauthn.options.OptionsProvider; - -import javax.servlet.ServletException; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import static org.springframework.security.webauthn.endpoint.AttestationOptionsEndpointFilter.FILTER_URL; - -public class AttestationOptionsEndpointFilterTest { - - private JsonConverter jsonConverter = new JsonConverter(); - - @Test - public void getter_setter_test() { - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(mock(OptionsProvider.class), jsonConverter); - MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - attestationOptionsEndpointFilter.setMFATokenEvaluator(mfaTokenEvaluator); - attestationOptionsEndpointFilter.setTrustResolver(trustResolver); - assertThat(attestationOptionsEndpointFilter.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator); - assertThat(attestationOptionsEndpointFilter.getTrustResolver()).isEqualTo(trustResolver); - } - - @Test - public void afterPropertiesSet_test() { - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(mock(OptionsProvider.class), jsonConverter); - assertThatCode(attestationOptionsEndpointFilter::afterPropertiesSet).doesNotThrowAnyException(); - } - - @Test - public void doFilter_test() throws IOException, ServletException { - OptionsProvider optionsProvider = mock(OptionsProvider.class); - AttestationOptions attestationOptions = - new AttestationOptions(null, null, null, null, null, null, null, null, null); - when(optionsProvider.getAttestationOptions(any(), any(), any())).thenReturn(attestationOptions); - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); - MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - attestationOptionsEndpointFilter.setMFATokenEvaluator(mfaTokenEvaluator); - attestationOptionsEndpointFilter.setTrustResolver(trustResolver); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI(FILTER_URL); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - - attestationOptionsEndpointFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - } - - @Test - public void doFilter_with_error_test() throws IOException, ServletException { - OptionsProvider optionsProvider = mock(OptionsProvider.class); - doThrow(new RuntimeException()).when(optionsProvider).getAttestationOptions(any(), any(), any()); - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); - MFATokenEvaluator mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - attestationOptionsEndpointFilter.setMFATokenEvaluator(mfaTokenEvaluator); - attestationOptionsEndpointFilter.setTrustResolver(trustResolver); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI(FILTER_URL); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockFilterChain filterChain = new MockFilterChain(); - - attestationOptionsEndpointFilter.doFilter(request, response, filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value()); - } - - @Test - public void writeErrorResponse_with_RuntimeException_test() throws IOException { - OptionsProvider optionsProvider = mock(OptionsProvider.class); - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); - - MockHttpServletResponse response = new MockHttpServletResponse(); - RuntimeException exception = new RuntimeException(); - attestationOptionsEndpointFilter.writeErrorResponse(response, exception); - assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"The server encountered an internal error\"}"); - } - - @Test - public void writeErrorResponse_with_InsufficientAuthenticationException_test() throws IOException { - OptionsProvider optionsProvider = mock(OptionsProvider.class); - AttestationOptionsEndpointFilter attestationOptionsEndpointFilter = new AttestationOptionsEndpointFilter(optionsProvider, jsonConverter); - - MockHttpServletResponse response = new MockHttpServletResponse(); - InsufficientAuthenticationException exception = new InsufficientAuthenticationException(null); - attestationOptionsEndpointFilter.writeErrorResponse(response, exception); - assertThat(response.getContentAsString()).isEqualTo("{\"errorMessage\":\"Anonymous access is prohibited\"}"); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java deleted file mode 100644 index c5db3668a92..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/ErrorResponseTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ErrorResponseTest { - - @Test - public void constructor_test() { - ErrorResponse errorResponse = new ErrorResponse("message"); - assertThat(errorResponse.getErrorMessage()).isEqualTo("message"); - } - - @Test - public void equals_hashCode_test() { - ErrorResponse instanceA = new ErrorResponse("message"); - ErrorResponse instanceB = new ErrorResponse("message"); - - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceA).hasSameHashCodeAs(instanceB); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java deleted file mode 100644 index 8c2cabbb49f..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialDescriptorTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - - -import com.webauthn4j.data.PublicKeyCredentialType; -import org.junit.Test; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -public class WebAuthnPublicKeyCredentialDescriptorTest { - - @Test - public void constructor_test() { - WebAuthnPublicKeyCredentialDescriptor descriptor = new WebAuthnPublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, ""); - assertThat(descriptor.getType()).isEqualTo(PublicKeyCredentialType.PUBLIC_KEY); - assertThat(descriptor.getId()).isEqualTo(""); - } - - @Test - public void equals_hashCode_test() { - WebAuthnPublicKeyCredentialDescriptor instanceA = new WebAuthnPublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, ""); - WebAuthnPublicKeyCredentialDescriptor instanceB = new WebAuthnPublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, ""); - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceA).hasSameHashCodeAs(instanceB); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java deleted file mode 100644 index 223f675db5d..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/endpoint/WebAuthnPublicKeyCredentialUserEntityTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.endpoint; - - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class WebAuthnPublicKeyCredentialUserEntityTest { - - @Test - public void equals_hashCode_test() { - WebAuthnPublicKeyCredentialUserEntity instanceA = new WebAuthnPublicKeyCredentialUserEntity("", "john", "dummy", "dummy"); - WebAuthnPublicKeyCredentialUserEntity instanceB = new WebAuthnPublicKeyCredentialUserEntity("", "john", "dummy", "dummy"); - - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceA).hasSameHashCodeAs(instanceB); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java deleted file mode 100644 index a0610a5b1fa..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/options/AssertionOptionsTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - -import com.webauthn4j.data.PublicKeyCredentialDescriptor; -import com.webauthn4j.data.PublicKeyCredentialType; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import org.junit.Test; - -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AssertionOptionsTest { - - @Test - public void equals_hashCode_test() { - String rpId = "rpId"; - Challenge challenge = new DefaultChallenge(); - Long authenticationTimeout = 1000L; - List credentialIds = Collections.singletonList(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, new byte[32], null)); - AuthenticationExtensionsClientInputs authenticationExtensionsClientInputs = new AuthenticationExtensionsClientInputs<>(); - - AssertionOptions instanceA = - new AssertionOptions(challenge, authenticationTimeout, rpId, credentialIds, authenticationExtensionsClientInputs); - AssertionOptions instanceB = - new AssertionOptions(challenge, authenticationTimeout, rpId, credentialIds, authenticationExtensionsClientInputs); - - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceA).hasSameHashCodeAs(instanceB); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java deleted file mode 100644 index 85914b79250..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/options/AttestationOptionsTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - -import com.webauthn4j.data.*; -import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.data.extension.client.AuthenticationExtensionClientInput; -import com.webauthn4j.data.extension.client.AuthenticationExtensionsClientInputs; -import com.webauthn4j.data.extension.client.RegistrationExtensionClientInput; -import org.junit.Test; - -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -public class AttestationOptionsTest { - - @Test - public void equals_hashCode_test() { - PublicKeyCredentialRpEntity rpEntity = new PublicKeyCredentialRpEntity("rpId", "rpName", "rpIcon"); - PublicKeyCredentialUserEntity userEntity = new PublicKeyCredentialUserEntity("userHandle".getBytes(), "username", null); - Challenge challenge = new DefaultChallenge(); - List pubKeyCredParams = Collections.singletonList(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256)); - Long registrationTimeout = 1000L; - Long authenticationTimeout = 1000L; - List credentialIds = Collections.singletonList(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, new byte[32], null)); - AuthenticatorSelectionCriteria authenticatorSelection = new AuthenticatorSelectionCriteria(null, false, UserVerificationRequirement.PREFERRED); - AttestationConveyancePreference attestation = AttestationConveyancePreference.NONE; - AuthenticationExtensionsClientInputs registrationExtensionsClientInputs = new AuthenticationExtensionsClientInputs<>(); - AuthenticationExtensionsClientInputs authenticationExtensionsClientInputs = new AuthenticationExtensionsClientInputs<>(); - AttestationOptions instanceA = - new AttestationOptions(rpEntity, userEntity, challenge, pubKeyCredParams, registrationTimeout, - credentialIds, authenticatorSelection, attestation, registrationExtensionsClientInputs); - AttestationOptions instanceB = - new AttestationOptions(rpEntity, userEntity, challenge, pubKeyCredParams, registrationTimeout, - credentialIds, authenticatorSelection, attestation, registrationExtensionsClientInputs); - - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceA).hasSameHashCodeAs(instanceB); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java deleted file mode 100644 index 75c964be40f..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/options/OptionsProviderImplTest.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.options; - -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.data.PublicKeyCredentialDescriptor; -import com.webauthn4j.data.PublicKeyCredentialParameters; -import com.webauthn4j.data.PublicKeyCredentialType; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import org.assertj.core.util.Lists; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; - -import java.util.Collections; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -public class OptionsProviderImplTest { - - @Test - public void getAttestationOptions_test() { - Challenge challenge = new DefaultChallenge(); - byte[] credentialId = new byte[]{0x01, 0x23, 0x45}; - WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - WebAuthnUserDetails userDetails = mock(WebAuthnUserDetails.class); - Authenticator authenticator = mock(Authenticator.class, RETURNS_DEEP_STUBS); - List authenticators = Collections.singletonList(authenticator); - ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - - when(userDetailsService.loadUserByUsername(any())).thenReturn(userDetails); - doReturn(new byte[0]).when(userDetails).getUserHandle(); - doReturn(authenticators).when(userDetails).getAuthenticators(); - when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); - when(challengeRepository.loadOrGenerateChallenge(mockRequest)).thenReturn(challenge); - - OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(userDetailsService, challengeRepository); - optionsProviderImpl.setRpId("example.com"); - optionsProviderImpl.setRpName("rpName"); - optionsProviderImpl.setRpIcon("data://dummy"); - - AttestationOptions attestationOptions = optionsProviderImpl.getAttestationOptions(mockRequest, "dummy", null); - assertThat(attestationOptions.getRp().getId()).isEqualTo("example.com"); - assertThat(attestationOptions.getRp().getName()).isEqualTo("rpName"); - assertThat(attestationOptions.getRp().getIcon()).isEqualTo("data://dummy"); - assertThat(attestationOptions.getChallenge()).isEqualTo(challenge); - assertThat(attestationOptions.getExcludeCredentials()).containsExactly(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, credentialId, null)); - - } - - @Test - public void getAttestationOptions_with_challenge_test() { - Challenge challenge = new DefaultChallenge(); - byte[] credentialId = new byte[]{0x01, 0x23, 0x45}; - WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - WebAuthnUserDetails userDetails = mock(WebAuthnUserDetails.class); - Authenticator authenticator = mock(Authenticator.class, RETURNS_DEEP_STUBS); - List authenticators = Collections.singletonList(authenticator); - ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - - MockHttpServletRequest mockRequest = new MockHttpServletRequest(); - - when(userDetailsService.loadUserByUsername(any())).thenReturn(userDetails); - doReturn(new byte[0]).when(userDetails).getUserHandle(); - doReturn(authenticators).when(userDetails).getAuthenticators(); - when(authenticator.getAttestedCredentialData().getCredentialId()).thenReturn(credentialId); - - OptionsProviderImpl optionsProviderImpl = new OptionsProviderImpl(userDetailsService, challengeRepository); - optionsProviderImpl.setRpId("example.com"); - optionsProviderImpl.setRpName("rpName"); - optionsProviderImpl.setRpIcon("data://dummy"); - - AttestationOptions attestationOptions = optionsProviderImpl.getAttestationOptions(mockRequest, "dummy", challenge); - assertThat(attestationOptions.getRp().getId()).isEqualTo("example.com"); - assertThat(attestationOptions.getRp().getName()).isEqualTo("rpName"); - assertThat(attestationOptions.getRp().getIcon()).isEqualTo("data://dummy"); - assertThat(attestationOptions.getChallenge()).isEqualTo(challenge); - assertThat(attestationOptions.getExcludeCredentials()).containsExactly(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, credentialId, null)); - - } - - @Test - public void getEffectiveRpId() { - WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - OptionsProviderImpl optionsProvider = new OptionsProviderImpl(userDetailsService, challengeRepository); - optionsProvider.setRpId(null); - MockHttpServletRequest httpServletRequest = new MockHttpServletRequest(); - httpServletRequest.setScheme("https"); - httpServletRequest.setServerName("example.com"); - httpServletRequest.setServerPort(8080); - assertThat(optionsProvider.getEffectiveRpId(httpServletRequest)).isEqualTo("example.com"); - - } - - @Test - public void getter_setter_test() { - WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - OptionsProviderImpl optionsProvider = new OptionsProviderImpl(userDetailsService, challengeRepository); - - optionsProvider.setRpId("example.com"); - assertThat(optionsProvider.getRpId()).isEqualTo("example.com"); - optionsProvider.setRpName("example"); - assertThat(optionsProvider.getRpName()).isEqualTo("example"); - optionsProvider.setRpIcon("data://dummy"); - assertThat(optionsProvider.getRpIcon()).isEqualTo("data://dummy"); - List publicKeyCredParams = Lists.emptyList(); - optionsProvider.setPubKeyCredParams(publicKeyCredParams); - assertThat(optionsProvider.getPubKeyCredParams()).isEqualTo(publicKeyCredParams); - optionsProvider.setRegistrationTimeout(10000L); - assertThat(optionsProvider.getRegistrationTimeout()).isEqualTo(10000L); - optionsProvider.setAuthenticationTimeout(20000L); - assertThat(optionsProvider.getAuthenticationTimeout()).isEqualTo(20000L); - assertThat(optionsProvider.getRegistrationExtensions()).isEqualTo(new RegistrationExtensionsOptionProvider()); - assertThat(optionsProvider.getAuthenticationExtensions()).isEqualTo(new AuthenticationExtensionsOptionProvider()); - - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java similarity index 54% rename from webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java rename to webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java index 83e47c5ddb2..64b47be838b 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/server/ServerPropertyProviderImplTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java @@ -16,24 +16,21 @@ package org.springframework.security.webauthn.server; -import com.webauthn4j.data.client.Origin; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.server.ServerProperty; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.challenge.ChallengeRepository; -import org.springframework.security.webauthn.options.OptionsProvider; +import org.springframework.security.webauthn.challenge.WebAuthnChallenge; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; +import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ServerPropertyProviderImplTest { +public class WebAuthnServerPropertyProviderImplTest { - private ChallengeRepository challengeRepository = mock(ChallengeRepository.class); - private OptionsProvider optionsProvider = mock(OptionsProvider.class); - private ServerPropertyProviderImpl target = new ServerPropertyProviderImpl(optionsProvider, challengeRepository); + private WebAuthnChallengeRepository webAuthnChallengeRepository = mock(WebAuthnChallengeRepository.class); + private EffectiveRpIdProvider effectiveRpIdProvider = mock(EffectiveRpIdProvider.class); + private WebAuthnServerPropertyProviderImpl target = new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, webAuthnChallengeRepository); @Test public void provide_test() { @@ -41,14 +38,14 @@ public void provide_test() { request.setScheme("https"); request.setServerName("origin.example.com"); request.setServerPort(443); - Challenge mockChallenge = new DefaultChallenge(); - when(challengeRepository.loadOrGenerateChallenge(request)).thenReturn(mockChallenge); - when(optionsProvider.getEffectiveRpId(request)).thenReturn("rpid.example.com"); + WebAuthnChallenge mockChallenge = new WebAuthnChallengeImpl(); + when(webAuthnChallengeRepository.loadOrGenerateChallenge(request)).thenReturn(mockChallenge); + when(effectiveRpIdProvider.getEffectiveRpId(request)).thenReturn("rpid.example.com"); - ServerProperty serverProperty = target.provide(request); + WebAuthnServerProperty serverProperty = target.provide(request); assertThat(serverProperty.getRpId()).isEqualTo("rpid.example.com"); - assertThat(serverProperty.getOrigin()).isEqualTo(new Origin("https://origin.example.com")); + assertThat(serverProperty.getOrigin()).isEqualTo(new WebAuthnOrigin("https://origin.example.com")); assertThat(serverProperty.getChallenge()).isEqualTo(mockChallenge); } } diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java similarity index 78% rename from webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java rename to webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java index e558f93000a..c234f66af99 100644 --- a/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnUserDetailsImplTest.java +++ b/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java @@ -17,23 +17,23 @@ package org.springframework.security.webauthn.userdetails; -import com.webauthn4j.authenticator.Authenticator; -import com.webauthn4j.authenticator.AuthenticatorImpl; import org.junit.Test; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; +import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; -public class WebAuthnUserDetailsImplTest { +public class WebAuthnAndPasswordUserTest { @Test public void getter_setter_test() { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - Authenticator authenticator = new AuthenticatorImpl(null, null, 0); - WebAuthnUserDetailsImpl userDetails = new WebAuthnUserDetailsImpl( + WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); + WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( new byte[32], "dummy", "dummy", From be922536a38d00262e2e96266367cdad31976108 Mon Sep 17 00:00:00 2001 From: ynojima Date: Fri, 18 Oct 2019 23:42:47 +0900 Subject: [PATCH 5/9] Just for review: Remove test classes to reduce file counts --- .../component/RegistrationValidationTest.java | 105 ----- .../WebAuthn4JWebAuthnManagerTest.java | 109 ------ ...AuthnAssertionAuthenticationTokenTest.java | 59 --- .../WebAuthnAuthenticationDataTest.java | 109 ------ .../WebAuthnAuthenticationProviderTest.java | 359 ------------------ .../WebAuthnAuthenticationTokenTest.java | 60 --- .../WebAuthnProcessingFilterTest.java | 259 ------------- ...AuthnRegistrationRequestValidatorTest.java | 126 ------ .../WebAuthnAuthenticatorImplTest.java | 41 -- ...essionWebAuthnChallengeRepositoryTest.java | 117 ------ ...nticationProviderConfigurerSpringTest.java | 127 ------- ...bAuthnLoginConfigurerSetterSpringTest.java | 132 ------- .../WebAuthnLoginConfigurerSpringTest.java | 160 -------- .../exception/BadAaguidExceptionTest.java | 36 -- .../exception/BadAlgorithmExceptionTest.java | 36 -- .../BadAttestationStatementExceptionTest.java | 35 -- .../exception/BadChallengeExceptionTest.java | 36 -- .../BadCredentialIdExceptionTest.java | 37 -- .../exception/BadOriginExceptionTest.java | 35 -- .../exception/BadRpIdExceptionTest.java | 36 -- .../exception/BadSignatureExceptionTest.java | 35 -- .../exception/CertificateExceptionTest.java | 36 -- .../ConstraintViolationExceptionTest.java | 36 -- .../CredentialIdNotFoundExceptionTest.java | 35 -- .../DataConversionExceptionTest.java | 38 -- ...KeyDescriptionValidationExceptionTest.java | 36 -- .../MaliciousCounterValueExceptionTest.java | 36 -- .../exception/MaliciousDataExceptionTest.java | 36 -- .../MissingChallengeExceptionTest.java | 36 -- .../exception/OptionsExceptionTest.java | 36 -- .../PublicKeyMismatchExceptionTest.java | 35 -- ...elfAttestationProhibitedExceptionTest.java | 35 -- .../exception/TokenBindingExceptionTest.java | 36 -- .../TrustAnchorNotFoundExceptionTest.java | 36 -- .../UnexpectedExtensionExceptionTest.java | 36 -- .../UserNotPresentExceptionTest.java | 36 -- .../UserNotVerifiedExceptionTest.java | 36 -- .../WebAuthnAuthenticationExceptionTest.java | 37 -- ...ebAuthnServerPropertyProviderImplTest.java | 51 --- .../WebAuthnAndPasswordUserTest.java | 49 --- .../resources/certs/3tier-test-root-CA.der | Bin 389 -> 0 bytes .../resources/certs/3tier-test-root-CA.pem | 38 -- 42 files changed, 2799 deletions(-) delete mode 100644 webauthn/src/test/java/integration/component/RegistrationValidationTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java delete mode 100644 webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java delete mode 100644 webauthn/src/test/resources/certs/3tier-test-root-CA.der delete mode 100644 webauthn/src/test/resources/certs/3tier-test-root-CA.pem diff --git a/webauthn/src/test/java/integration/component/RegistrationValidationTest.java b/webauthn/src/test/java/integration/component/RegistrationValidationTest.java deleted file mode 100644 index 5347abd247c..00000000000 --- a/webauthn/src/test/java/integration/component/RegistrationValidationTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package integration.component; - -import com.webauthn4j.data.*; -import com.webauthn4j.data.attestation.statement.COSEAlgorithmIdentifier; -import com.webauthn4j.data.client.Origin; -import com.webauthn4j.data.client.challenge.Challenge; -import com.webauthn4j.data.client.challenge.DefaultChallenge; -import com.webauthn4j.test.authenticator.webauthn.PackedAuthenticator; -import com.webauthn4j.test.authenticator.webauthn.WebAuthnAuthenticatorAdaptor; -import com.webauthn4j.test.client.ClientPlatform; -import com.webauthn4j.util.Base64UrlUtil; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.*; -import org.springframework.security.webauthn.challenge.WebAuthnChallenge; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; -import org.springframework.security.webauthn.server.WebAuthnOrigin; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; - -import java.util.Collections; -import java.util.Set; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Test for WebAuthnRegistrationContextValidator - */ -public class RegistrationValidationTest { - - String rpId = "example.com"; - Challenge challenge = new DefaultChallenge(); - WebAuthnChallenge webAuthnChallenge = new WebAuthnChallengeImpl(challenge.getValue()); - private Origin origin = new Origin("http://localhost"); - private WebAuthnOrigin webAuthnOrigin = new WebAuthnOrigin(origin.toString()); - private WebAuthnAuthenticatorAdaptor webAuthnModelAuthenticatorAdaptor = new WebAuthnAuthenticatorAdaptor(new PackedAuthenticator()); - private ClientPlatform clientPlatform = new ClientPlatform(origin, webAuthnModelAuthenticatorAdaptor); - private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider = mock(WebAuthnServerPropertyProvider.class); - private WebAuthnRegistrationContextValidator webAuthnRegistrationContextValidator = WebAuthnRegistrationContextValidator.createNonStrictRegistrationContextValidator(); - private WebAuthnAuthenticationContextValidator webAuthnAuthenticationContextValidator = new WebAuthnAuthenticationContextValidator(); - private WebAuthnDataConverter webAuthnDataConverter = new WebAuthnDataConverter(); - private WebAuthnManager webAuthnManager = new WebAuthn4JWebAuthnManager(webAuthnRegistrationContextValidator, webAuthnAuthenticationContextValidator, webAuthnDataConverter); - private WebAuthnRegistrationRequestValidator target = new WebAuthnRegistrationRequestValidator(webAuthnManager, webAuthnServerPropertyProvider); - - @Test - public void validate_test() { - WebAuthnServerProperty serverProperty = new WebAuthnServerProperty(webAuthnOrigin, rpId, webAuthnChallenge, null); - when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - - - AuthenticatorSelectionCriteria authenticatorSelectionCriteria = - new AuthenticatorSelectionCriteria(AuthenticatorAttachment.CROSS_PLATFORM, true, UserVerificationRequirement.REQUIRED); - - PublicKeyCredentialParameters publicKeyCredentialParameters = new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256); - - PublicKeyCredentialUserEntity publicKeyCredentialUserEntity = new PublicKeyCredentialUserEntity(); - - PublicKeyCredentialCreationOptions credentialCreationOptions = new PublicKeyCredentialCreationOptions( - new PublicKeyCredentialRpEntity(rpId, "example.com"), - publicKeyCredentialUserEntity, - challenge, - Collections.singletonList(publicKeyCredentialParameters), - null, - null, - authenticatorSelectionCriteria, - AttestationConveyancePreference.NONE, - null - ); - - AuthenticatorAttestationResponse registrationRequest = clientPlatform.create(credentialCreationOptions).getAuthenticatorResponse(); - - MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); - mockHttpServletRequest.setScheme("https"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setServerPort(443); - - String clientDataBase64 = Base64UrlUtil.encodeToString(registrationRequest.getClientDataJSON()); - String attestationObjectBase64 = Base64UrlUtil.encodeToString(registrationRequest.getAttestationObject()); - Set transports = Collections.emptySet(); - String clientExtensionsJSON = null; - - target.validate(new WebAuthnRegistrationRequest(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON)); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java deleted file mode 100644 index b1d2c066308..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManagerTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import com.webauthn4j.util.exception.WebAuthnException; -import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; -import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; -import org.springframework.security.webauthn.exception.*; -import org.springframework.security.webauthn.server.WebAuthnOrigin; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; - -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; - -public class WebAuthn4JWebAuthnManagerTest { - - private WebAuthnRegistrationContextValidator registrationContextValidator = mock(WebAuthnRegistrationContextValidator.class); - private WebAuthnAuthenticationContextValidator authenticationContextValidator = mock(WebAuthnAuthenticationContextValidator.class); - private WebAuthnDataConverter webAuthnDataConverter = new WebAuthnDataConverter(); - private WebAuthnManager target = new WebAuthn4JWebAuthnManager(registrationContextValidator, authenticationContextValidator, webAuthnDataConverter); - - @Test(expected = BadAttestationStatementException.class) - public void verifyRegistrationData_caught_exception_test() { - - doThrow(new com.webauthn4j.validator.exception.BadAttestationStatementException("dummy")) - .when(registrationContextValidator).validate(any()); - - MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); - mockHttpServletRequest.setScheme("https"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setServerPort(443); - - byte[] clientDataJSON = new byte[0]; //dummy - byte[] attestationObject = new byte[0]; //dummy - WebAuthnServerProperty serverProperty = new WebAuthnServerProperty( - new WebAuthnOrigin("https://example.com"), - "example.com", - new WebAuthnChallengeImpl(), - new byte[]{0x43, 0x21} - ); - - target.verifyRegistrationData(new WebAuthnRegistrationData(clientDataJSON, attestationObject, null, null, serverProperty, null)); - - } - - @Test - public void wrapWithAuthenticationException_test() { - - Map map = new HashMap<>(); - map.put(new com.webauthn4j.validator.exception.BadAaguidException("dummy"), BadAaguidException.class); - map.put(new com.webauthn4j.validator.exception.BadAlgorithmException("dummy"), BadAlgorithmException.class); - map.put(new com.webauthn4j.validator.exception.BadAttestationStatementException("dummy"), BadAttestationStatementException.class); - map.put(new com.webauthn4j.validator.exception.KeyDescriptionValidationException("dummy"), KeyDescriptionValidationException.class); - map.put(new com.webauthn4j.validator.exception.BadChallengeException("dummy"), BadChallengeException.class); - map.put(new com.webauthn4j.validator.exception.BadOriginException("dummy"), BadOriginException.class); - map.put(new com.webauthn4j.validator.exception.BadRpIdException("dummy"), BadRpIdException.class); - map.put(new com.webauthn4j.validator.exception.BadSignatureException("dummy"), BadSignatureException.class); - map.put(new com.webauthn4j.validator.exception.CertificateException("dummy"), CertificateException.class); - map.put(new com.webauthn4j.validator.exception.ConstraintViolationException("dummy"), ConstraintViolationException.class); - map.put(new com.webauthn4j.validator.exception.MaliciousCounterValueException("dummy"), MaliciousCounterValueException.class); - map.put(new com.webauthn4j.validator.exception.MaliciousDataException("dummy"), MaliciousDataException.class); - map.put(new com.webauthn4j.validator.exception.MissingChallengeException("dummy"), MissingChallengeException.class); - map.put(new com.webauthn4j.validator.exception.PublicKeyMismatchException("dummy"), PublicKeyMismatchException.class); - map.put(new com.webauthn4j.validator.exception.SelfAttestationProhibitedException("dummy"), SelfAttestationProhibitedException.class); - map.put(new com.webauthn4j.validator.exception.TokenBindingException("dummy"), TokenBindingException.class); - map.put(new com.webauthn4j.validator.exception.TrustAnchorNotFoundException("dummy"), TrustAnchorNotFoundException.class); - map.put(new com.webauthn4j.validator.exception.UnexpectedExtensionException("dummy"), UnexpectedExtensionException.class); - map.put(new com.webauthn4j.validator.exception.UserNotPresentException("dummy"), UserNotPresentException.class); - map.put(new com.webauthn4j.validator.exception.UserNotVerifiedException("dummy"), UserNotVerifiedException.class); - map.put(new UnknownValidationException("dummy"), ValidationException.class); - map.put(new com.webauthn4j.converter.exception.DataConversionException("dummy"), DataConversionException.class); - map.put(new WebAuthnException("dummy"), AuthenticationServiceException.class); - - for (Map.Entry entry : map.entrySet()) { - assertThat(WebAuthn4JWebAuthnManager.wrapWithAuthenticationException(entry.getKey())).isInstanceOf(entry.getValue()); - } - } - - static class UnknownValidationException extends com.webauthn4j.validator.exception.ValidationException { - - UnknownValidationException(String message) { - super(message); - } - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java deleted file mode 100644 index 33c393b6cde..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAssertionAuthenticationTokenTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class WebAuthnAssertionAuthenticationTokenTest { - - @Test(expected = IllegalArgumentException.class) - public void setAuthenticated_with_true_test() { - WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); - WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); - token.setAuthenticated(true); - assertThat(token.isAuthenticated()).isTrue(); - } - - @Test - public void setAuthenticated_with_false_test() { - WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); - WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); - token.setAuthenticated(false); - assertThat(token.isAuthenticated()).isFalse(); - } - - @Test - public void eraseCredentials_test() { - WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); - WebAuthnAssertionAuthenticationToken token = new WebAuthnAssertionAuthenticationToken(request); - token.eraseCredentials(); - assertThat(token.getCredentials()).isNull(); - } - - @Test - public void equals_hashCode_test() { - WebAuthnAuthenticationData request = mock(WebAuthnAuthenticationData.class); - WebAuthnAssertionAuthenticationToken tokenA = new WebAuthnAssertionAuthenticationToken(request); - WebAuthnAssertionAuthenticationToken tokenB = new WebAuthnAssertionAuthenticationToken(request); - - assertThat(tokenA).isEqualTo(tokenB); - assertThat(tokenA).hasSameHashCodeAs(tokenB); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java deleted file mode 100644 index 42c043667b3..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationDataTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import com.webauthn4j.converter.AuthenticatorDataConverter; -import com.webauthn4j.converter.util.CborConverter; -import com.webauthn4j.data.client.ClientDataType; -import com.webauthn4j.test.TestDataUtil; -import org.junit.Test; -import org.springframework.security.webauthn.challenge.WebAuthnChallenge; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; -import org.springframework.security.webauthn.server.WebAuthnOrigin; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; - -public class WebAuthnAuthenticationDataTest { - - private CborConverter cborConverter = new CborConverter(); - - @Test - public void getter_test() { - WebAuthnChallenge challenge = new WebAuthnChallengeImpl(); - byte[] clientDataJSON = TestDataUtil.createClientDataJSON(ClientDataType.GET); - byte[] authenticatorData = new AuthenticatorDataConverter(cborConverter).convert(TestDataUtil.createAuthenticatorData()); - WebAuthnServerProperty serverProperty = new WebAuthnServerProperty( - new WebAuthnOrigin("https://example.com"), - "example.com", - challenge, - new byte[]{0x43, 0x21} - ); - WebAuthnAuthenticationData authenticationData = new WebAuthnAuthenticationData( - new byte[]{0x01, 0x23}, - clientDataJSON, - authenticatorData, - new byte[]{0x45, 0x56}, - "", - serverProperty, - true, - true, - Collections.singletonList("uvi") - ); - assertThat(authenticationData.getCredentialId()).isEqualTo(new byte[]{0x01, 0x23}); - assertThat(authenticationData.getClientDataJSON()).isEqualTo(clientDataJSON); - assertThat(authenticationData.getAuthenticatorData()).isEqualTo(authenticatorData); - assertThat(authenticationData.getSignature()).isEqualTo(new byte[]{0x45, 0x56}); - assertThat(authenticationData.getClientExtensionsJSON()).isEqualTo(""); - assertThat(authenticationData.getServerProperty()).isEqualTo(serverProperty); - assertThat(authenticationData.isUserVerificationRequired()).isEqualTo(true); - assertThat(authenticationData.isUserPresenceRequired()).isEqualTo(true); - assertThat(authenticationData.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList("uvi")); - } - - @Test - public void equals_hashCode_test() { - WebAuthnChallenge challenge = new WebAuthnChallengeImpl(); - byte[] clientDataJSON = TestDataUtil.createClientDataJSON(ClientDataType.GET); - byte[] authenticatorData = new AuthenticatorDataConverter(cborConverter).convert(TestDataUtil.createAuthenticatorData()); - WebAuthnAuthenticationData requestA = new WebAuthnAuthenticationData( - new byte[]{0x01, 0x23}, - clientDataJSON, - authenticatorData, - new byte[]{0x45, 0x56}, - "", - new WebAuthnServerProperty( - new WebAuthnOrigin("https://example.com"), - "example.com", - challenge, - new byte[]{0x43, 0x21} - ), - true, - Collections.singletonList("uvi") - ); - WebAuthnAuthenticationData requestB = new WebAuthnAuthenticationData( - new byte[]{0x01, 0x23}, - clientDataJSON, - authenticatorData, - new byte[]{0x45, 0x56}, - "", - new WebAuthnServerProperty( - new WebAuthnOrigin("https://example.com"), - "example.com", - challenge, - new byte[]{0x43, 0x21} - ), - true, - Collections.singletonList("uvi") - ); - - assertThat(requestA).isEqualTo(requestB); - assertThat(requestA).hasSameHashCodeAs(requestB); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java deleted file mode 100644 index 181355911fe..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationProviderTest.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.security.authentication.*; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; -import org.springframework.security.webauthn.exception.BadChallengeException; -import org.springframework.security.webauthn.exception.CredentialIdNotFoundException; -import org.springframework.security.webauthn.userdetails.WebAuthnAndPasswordUser; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsChecker; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -/** - * Test for WebAuthnAuthenticationProvider - */ -public class WebAuthnAuthenticationProviderTest { - - private WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - - private WebAuthnAuthenticatorService authenticatorService = mock(WebAuthnAuthenticatorService.class); - - private WebAuthnManager webAuthnManager = mock(WebAuthnManager.class); - - private WebAuthnAuthenticationProvider authenticationProvider - = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, webAuthnManager); - - @Before - public void setup() { - authenticationProvider = new WebAuthnAuthenticationProvider(userDetailsService, authenticatorService, webAuthnManager); - } - - /** - * Verifies that an unsupported authentication token will be rejected. - */ - @Test(expected = IllegalArgumentException.class) - public void authenticate_with_invalid_authenticationToken() { - Authentication token = new UsernamePasswordAuthenticationToken("username", "password"); - authenticationProvider.authenticate(token); - } - - /** - * Verifies that the authentication token without credentials will be rejected. - */ - @Test(expected = BadCredentialsException.class) - public void authenticate_with_authenticationToken_without_credentials() { - Authentication token = new WebAuthnAssertionAuthenticationToken(null); - authenticationProvider.authenticate(token); - } - - - /** - * Verifies that authentication process passes successfully if input is correct. - */ - @Test - public void authenticate_test() { - //Given - byte[] credentialId = new byte[32]; - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); - WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - Collections.singletonList(grantedAuthority)); - when(authenticator.getCredentialId()).thenReturn(credentialId); - - //When - WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); - when(credential.getCredentialId()).thenReturn(credentialId); - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(user); - Authentication token = new WebAuthnAssertionAuthenticationToken(credential); - Authentication authenticatedToken = authenticationProvider.authenticate(token); - - ArgumentCaptor captor = ArgumentCaptor.forClass(WebAuthnAuthenticationData.class); - verify(webAuthnManager).verifyAuthenticationData(captor.capture(), any()); - WebAuthnAuthenticationData authenticationData = captor.getValue(); - - assertThat(authenticationData.getExpectedAuthenticationExtensionIds()).isEqualTo(credential.getExpectedAuthenticationExtensionIds()); - - assertThat(authenticatedToken.getPrincipal()).isInstanceOf(WebAuthnAndPasswordUser.class); - assertThat(authenticatedToken.getCredentials()).isEqualTo(credential); - assertThat(authenticatedToken.getAuthorities().toArray()).containsExactly(grantedAuthority); - } - - /** - * Verifies that authentication process passes successfully if input is correct. - */ - @Test - public void authenticate_with_forcePrincipalAsString_option_test() { - //Given - byte[] credentialId = new byte[32]; - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); - WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - Collections.singletonList(grantedAuthority)); - when(authenticator.getCredentialId()).thenReturn(credentialId); - - //When - WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); - when(credential.getCredentialId()).thenReturn(credentialId); - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(user); - Authentication token = new WebAuthnAssertionAuthenticationToken(credential); - authenticationProvider.setForcePrincipalAsString(true); - Authentication authenticatedToken = authenticationProvider.authenticate(token); - - assertThat(authenticatedToken.getPrincipal()).isInstanceOf(String.class); - assertThat(authenticatedToken.getCredentials()).isEqualTo(credential); - assertThat(authenticatedToken.getAuthorities().toArray()).containsExactly(grantedAuthority); - } - - /** - * Verifies that validation fails if ValidationException is thrown from authenticationContextValidator - */ - @Test(expected = BadChallengeException.class) - public void authenticate_with_BadChallengeException_from_authenticationContextValidator_test() { - //Given - byte[] credentialId = new byte[32]; - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = mock(WebAuthnAuthenticator.class, RETURNS_DEEP_STUBS); - WebAuthnAndPasswordUser user = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - Collections.singletonList(grantedAuthority)); - when(authenticator.getCredentialId()).thenReturn(credentialId); - - doThrow(BadChallengeException.class).when(webAuthnManager).verifyAuthenticationData(any(), any()); - - //When - WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); - when(credential.getCredentialId()).thenReturn(credentialId); - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(user); - Authentication token = new WebAuthnAssertionAuthenticationToken(credential); - authenticationProvider.authenticate(token); - } - - - @Test - public void retrieveWebAuthnUserDetails_test() { - byte[] credentialId = new byte[0]; - WebAuthnUserDetails expectedUser = mock(WebAuthnUserDetails.class); - - //Given - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(expectedUser); - - //When - WebAuthnUserDetails userDetails = authenticationProvider.retrieveWebAuthnUserDetails(credentialId); - - //Then - assertThat(userDetails).isEqualTo(expectedUser); - - } - - @Test(expected = BadCredentialsException.class) - public void retrieveWebAuthnUserDetails_test_with_CredentialIdNotFoundException() { - byte[] credentialId = new byte[0]; - - //Given - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); - - //When - authenticationProvider.retrieveWebAuthnUserDetails(credentialId); - } - - @Test(expected = CredentialIdNotFoundException.class) - public void retrieveWebAuthnUserDetails_test_with_CredentialIdNotFoundException_and_hideCredentialIdNotFoundExceptions_option_false() { - byte[] credentialId = new byte[0]; - - //Given - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenThrow(CredentialIdNotFoundException.class); - - //When - authenticationProvider.setHideCredentialIdNotFoundExceptions(false); - authenticationProvider.retrieveWebAuthnUserDetails(credentialId); - } - - @Test(expected = InternalAuthenticationServiceException.class) - public void retrieveWebAuthnUserDetails_test_with_RuntimeException_from_webAuthnAuthenticatorService() { - byte[] credentialId = new byte[0]; - - //Given - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenThrow(RuntimeException.class); - - //When - authenticationProvider.setHideCredentialIdNotFoundExceptions(false); - authenticationProvider.retrieveWebAuthnUserDetails(credentialId); - } - - @Test(expected = InternalAuthenticationServiceException.class) - public void retrieveWebAuthnUserDetails_test_with_null_from_webAuthnAuthenticatorService() { - byte[] credentialId = new byte[0]; - - //Given - when(userDetailsService.loadWebAuthnUserByCredentialId(credentialId)).thenReturn(null); - - //When - authenticationProvider.setHideCredentialIdNotFoundExceptions(false); - authenticationProvider.retrieveWebAuthnUserDetails(credentialId); - } - - @Test - public void getter_setter_test() { - WebAuthnUserDetailsService userDetailsService = mock(WebAuthnUserDetailsService.class); - WebAuthnUserDetailsChecker preAuthenticationChecker = mock(WebAuthnUserDetailsChecker.class); - WebAuthnUserDetailsChecker postAuthenticationChecker = mock(WebAuthnUserDetailsChecker.class); - - authenticationProvider.setForcePrincipalAsString(true); - assertThat(authenticationProvider.isForcePrincipalAsString()).isTrue(); - authenticationProvider.setHideCredentialIdNotFoundExceptions(true); - assertThat(authenticationProvider.isHideCredentialIdNotFoundExceptions()).isTrue(); - - authenticationProvider.setUserDetailsService(userDetailsService); - assertThat(authenticationProvider.getUserDetailsService()).isEqualTo(userDetailsService); - - authenticationProvider.setPreAuthenticationChecks(preAuthenticationChecker); - assertThat(authenticationProvider.getPreAuthenticationChecks()).isEqualTo(preAuthenticationChecker); - authenticationProvider.setPostAuthenticationChecks(postAuthenticationChecker); - assertThat(authenticationProvider.getPostAuthenticationChecks()).isEqualTo(postAuthenticationChecker); - - } - - @Test - public void userDetailsChecker_check_test() { - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - Collections.singletonList(grantedAuthority)); - authenticationProvider.getPreAuthenticationChecks().check(userDetails); - } - - @Test(expected = DisabledException.class) - public void userDetailsChecker_check_with_disabled_userDetails_test() { - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - true, - false, - true, - true, - true, - Collections.singletonList(grantedAuthority)); - authenticationProvider.getPreAuthenticationChecks().check(userDetails); - } - - @Test(expected = AccountExpiredException.class) - public void userDetailsChecker_check_with_expired_userDetails_test() { - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - true, - true, - false, - true, - true, - Collections.singletonList(grantedAuthority)); - authenticationProvider.getPreAuthenticationChecks().check(userDetails); - } - - @Test(expected = CredentialsExpiredException.class) - public void userDetailsChecker_check_with_credentials_expired_userDetails_test() { - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - true, - true, - true, - false, - true, - Collections.singletonList(grantedAuthority)); - authenticationProvider.getPostAuthenticationChecks().check(userDetails); - } - - @Test(expected = LockedException.class) - public void userDetailsChecker_check_with_locked_userDetails_test() { - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( - new byte[0], - "dummy", - "dummy", - Collections.singletonList(authenticator), - true, - true, - true, - true, - false, - Collections.singletonList(grantedAuthority)); - authenticationProvider.getPreAuthenticationChecks().check(userDetails); - } - - @Test - public void isUserVerificationRequired_test() { - WebAuthnUserDetails webAuthnUserDetails = mock(WebAuthnUserDetails.class); - when(webAuthnUserDetails.getUsername()).thenReturn("john.doe"); - WebAuthnAuthenticationData credentials = mock(WebAuthnAuthenticationData.class); - when(credentials.isUserVerificationRequired()).thenReturn(true); - SecurityContext securityContext = mock(SecurityContext.class); - Authentication authentication = mock(Authentication.class); - when(authentication.isAuthenticated()).thenReturn(true); - when(authentication.getName()).thenReturn("john.doe"); - when(securityContext.getAuthentication()).thenReturn(authentication); - SecurityContextHolder.setContext(securityContext); - assertThat(authenticationProvider.isUserVerificationRequired(webAuthnUserDetails, credentials)).isFalse(); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java deleted file mode 100644 index 4bf68dc1b91..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnAuthenticationTokenTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Test for WebAuthnAuthenticationToken - */ -public class WebAuthnAuthenticationTokenTest { - - /** - * Verifies that constructor with 3 args yields authenticated authenticator. - */ - @Test - public void webAuthnAuthenticationToken() { - WebAuthnAuthenticationToken webAuthnAuthenticationToken = new WebAuthnAuthenticationToken(null, null, null); - assertThat(webAuthnAuthenticationToken.isAuthenticated()).isTrue(); - } - - /** - * Verifies that getter returns constructor parameters - */ - @Test - public void test_methods() { - WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); - WebAuthnAuthenticationToken webAuthnAuthenticationToken = new WebAuthnAuthenticationToken("username", credential, null); - - assertThat(webAuthnAuthenticationToken.getPrincipal()).isEqualTo("username"); - assertThat(webAuthnAuthenticationToken.getCredentials()).isEqualTo(credential); - } - - @Test - public void equals_hashCode_test() { - WebAuthnAuthenticationData credential = mock(WebAuthnAuthenticationData.class); - WebAuthnAuthenticationToken tokenA = new WebAuthnAuthenticationToken("username", credential, null); - WebAuthnAuthenticationToken tokenB = new WebAuthnAuthenticationToken("username", credential, null); - assertThat(tokenA).isEqualTo(tokenB); - assertThat(tokenA).hasSameHashCodeAs(tokenB); - } - - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java deleted file mode 100644 index d79e5e53485..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnProcessingFilterTest.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import com.webauthn4j.data.extension.client.FIDOAppIDExtensionClientInput; -import com.webauthn4j.util.Base64UrlUtil; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -/** - * Test for WebAuthnProcessingFilter - */ -public class WebAuthnProcessingFilterTest { - - @Rule - public MockitoRule mockito = MockitoJUnit.rule(); - - private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; - private AuthenticationManager authenticationManager; - private MockHttpServletRequest mockHttpServletRequest; - private MockHttpServletResponse mockHttpServletResponse; - - @Spy - private WebAuthnProcessingFilter target; - - private ArgumentCaptor captor = ArgumentCaptor.forClass(Authentication.class); - - @Before - public void setup() { - webAuthnServerPropertyProvider = mock(WebAuthnServerPropertyProvider.class); - authenticationManager = mock(AuthenticationManager.class); - mockHttpServletRequest = new MockHttpServletRequest(); - mockHttpServletResponse = new MockHttpServletResponse(); - - target.setAuthenticationManager(authenticationManager); - target.setServerPropertyProvider(webAuthnServerPropertyProvider); - } - - @Test - public void attemptAuthentication_test_with_username_password() { - - mockHttpServletRequest.setMethod("POST"); - mockHttpServletRequest.setParameter("username", "username"); - mockHttpServletRequest.setParameter("password", "password"); - - when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); - - Authentication authenticationToken = captor.getValue(); - assertThat(authenticationToken.getPrincipal()).isEqualTo("username"); - assertThat(authenticationToken.getCredentials()).isEqualTo("password"); - - } - - @Test - public void attemptAuthentication_test_with_credential() { - - String credentialId = "AAhdofeLeQWG6Y6gwwytZKNCDFB1WaIgqDsOwVYR5UavKQhAti4ic9_Dz-_CQEPpN0To6hiDRSCvmFHXaG6HK5yvvhm4DJRVJXzSvZiq5NefbXSYIr2uUaKbsoBe1lulhNdL9dRt6Dkkp38uq02YIR5CDaoxD-HQgMsS667aWlhHVKE884Sq0d1VVgGTDb1ds-Py_H7CDqk9SDErb8-XtQ9L"; - String clientDataJSON = "eyJjaGFsbGVuZ2UiOiJGT3JHWklmSFJfeURaSklydTVPdXBBIiwiaGFzaEFsZyI6IlMyNTYiLCJvcmlnaW4iOiJsb2NhbGhvc3QifQ"; - String authenticatorData = "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABaQ"; - String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; - String clientExtensionsJSON = ""; - - WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); - - - //Given - mockHttpServletRequest.setMethod("POST"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setParameter("credentialId", credentialId); - mockHttpServletRequest.setParameter("clientDataJSON", clientDataJSON); - mockHttpServletRequest.setParameter("authenticatorData", authenticatorData); - mockHttpServletRequest.setParameter("signature", signature); - mockHttpServletRequest.setParameter("clientExtensionsJSON", clientExtensionsJSON); - - when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - - //When - target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); - - //Then - WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); - verify(webAuthnServerPropertyProvider).provide(mockHttpServletRequest); - assertThat(authenticationToken.getPrincipal()).isNull(); - assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationData.class); - assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); - assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); - assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); - assertThat(authenticationToken.getCredentials().getSignature()).isEqualTo(Base64UrlUtil.decode(signature)); - assertThat(authenticationToken.getCredentials().getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(authenticationToken.getCredentials().getServerProperty()).isEqualTo(serverProperty); - - } - - @Test - public void attemptAuthentication_test_with_get_method() { - - String credentialId = "AAhdofeLeQWG6Y6gwwytZKNCDFB1WaIgqDsOwVYR5UavKQhAti4ic9_Dz-_CQEPpN0To6hiDRSCvmFHXaG6HK5yvvhm4DJRVJXzSvZiq5NefbXSYIr2uUaKbsoBe1lulhNdL9dRt6Dkkp38uq02YIR5CDaoxD-HQgMsS667aWlhHVKE884Sq0d1VVgGTDb1ds-Py_H7CDqk9SDErb8-XtQ9L"; - String clientDataJSON = "eyJjaGFsbGVuZ2UiOiJGT3JHWklmSFJfeURaSklydTVPdXBBIiwiaGFzaEFsZyI6IlMyNTYiLCJvcmlnaW4iOiJsb2NhbGhvc3QifQ"; - String authenticatorData = "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABaQ"; - String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; - String clientExtensionsJSON = ""; - - WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); - - //Given - target.setPostOnly(false); - mockHttpServletRequest.setMethod("GET"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setParameter("credentialId", credentialId); - mockHttpServletRequest.setParameter("clientDataJSON", clientDataJSON); - mockHttpServletRequest.setParameter("authenticatorData", authenticatorData); - mockHttpServletRequest.setParameter("signature", signature); - mockHttpServletRequest.setParameter("clientExtensionsJSON", clientExtensionsJSON); - - when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - - //When - target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); - - //Then - WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); - verify(webAuthnServerPropertyProvider).provide(mockHttpServletRequest); - assertThat(authenticationToken.getPrincipal()).isNull(); - assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationData.class); - assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); - assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); - assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); - assertThat(authenticationToken.getCredentials().getSignature()).isEqualTo(Base64UrlUtil.decode(signature)); - assertThat(authenticationToken.getCredentials().getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(authenticationToken.getCredentials().getServerProperty()).isEqualTo(serverProperty); - - } - - - @Test - public void attemptAuthentication_test_with_customized_parameter() { - - String usernameParameter = "param_username"; - String passwordParameter = "param_password"; - String credentialIdParameter = "param_credentialId"; - String clientDataJSONParameter = "param_clientDataJSON"; - String authenticatorDataParameter = "param_authenticatorData"; - String signatureParameter = "param_signature"; - String clientExtensionsJSONParameter = "param_clientExtensionsJSON"; - - String credentialId = "AAhdofeLeQWG6Y6gwwytZKNCDFB1WaIgqDsOwVYR5UavKQhAti4ic9_Dz-_CQEPpN0To6hiDRSCvmFHXaG6HK5yvvhm4DJRVJXzSvZiq5NefbXSYIr2uUaKbsoBe1lulhNdL9dRt6Dkkp38uq02YIR5CDaoxD-HQgMsS667aWlhHVKE884Sq0d1VVgGTDb1ds-Py_H7CDqk9SDErb8-XtQ9L"; - String clientDataJSON = "eyJjaGFsbGVuZ2UiOiJGT3JHWklmSFJfeURaSklydTVPdXBBIiwiaGFzaEFsZyI6IlMyNTYiLCJvcmlnaW4iOiJsb2NhbGhvc3QifQ"; - String authenticatorData = "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAABaQ"; - String signature = "MEUCIGBYMUVg2KkMG7V7UEsGxUeKVaO8x587JyVoZkk6FmsgAiEA5XRKxlYe2Vpwn-JYEJhcEVJ3-0nYFG-JfheOk4rA3dc"; - String clientExtensionsJSON = ""; - - WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); - - //Given - target.setUsernameParameter(usernameParameter); - target.setPasswordParameter(passwordParameter); - target.setCredentialIdParameter(credentialIdParameter); - target.setClientDataJSONParameter(clientDataJSONParameter); - target.setAuthenticatorDataParameter(authenticatorDataParameter); - target.setSignatureParameter(signatureParameter); - target.setClientExtensionsJSONParameter(clientExtensionsJSONParameter); - target.setExpectedAuthenticationExtensionIds(Collections.singletonList(FIDOAppIDExtensionClientInput.ID)); - - mockHttpServletRequest.setMethod("POST"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setParameter(credentialIdParameter, credentialId); - mockHttpServletRequest.setParameter(clientDataJSONParameter, clientDataJSON); - mockHttpServletRequest.setParameter(authenticatorDataParameter, authenticatorData); - mockHttpServletRequest.setParameter(signatureParameter, signature); - mockHttpServletRequest.setParameter(clientExtensionsJSONParameter, clientExtensionsJSON); - - when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - - //When - target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); - - //Then - assertThat(target.getUsernameParameter()).isEqualTo(usernameParameter); - assertThat(target.getPasswordParameter()).isEqualTo(passwordParameter); - assertThat(target.getCredentialIdParameter()).isEqualTo(credentialIdParameter); - assertThat(target.getClientDataJSONParameter()).isEqualTo(clientDataJSONParameter); - assertThat(target.getAuthenticatorDataParameter()).isEqualTo(authenticatorDataParameter); - assertThat(target.getSignatureParameter()).isEqualTo(signatureParameter); - assertThat(target.getClientExtensionsJSONParameter()).isEqualTo(clientExtensionsJSONParameter); - assertThat(target.getExpectedAuthenticationExtensionIds()).isEqualTo(Collections.singletonList(FIDOAppIDExtensionClientInput.ID)); - assertThat(target.getServerPropertyProvider()).isEqualTo(webAuthnServerPropertyProvider); - - - WebAuthnAssertionAuthenticationToken authenticationToken = (WebAuthnAssertionAuthenticationToken) captor.getValue(); - verify(webAuthnServerPropertyProvider).provide(mockHttpServletRequest); - assertThat(authenticationToken.getPrincipal()).isNull(); - assertThat(authenticationToken.getCredentials()).isInstanceOf(WebAuthnAuthenticationData.class); - assertThat(authenticationToken.getCredentials().getCredentialId()).isEqualTo(Base64UrlUtil.decode(credentialId)); - assertThat(authenticationToken.getCredentials().getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataJSON)); - assertThat(authenticationToken.getCredentials().getAuthenticatorData()).isEqualTo(Base64UrlUtil.decode(authenticatorData)); - assertThat(authenticationToken.getCredentials().getSignature()).isEqualTo(Base64UrlUtil.decode(signature)); - assertThat(authenticationToken.getCredentials().getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(authenticationToken.getCredentials().getServerProperty()).isEqualTo(serverProperty); - - - } - - - @Test(expected = AuthenticationServiceException.class) - public void attemptAuthentication_test_with_wrong_port() { - - //Given - mockHttpServletRequest.setMethod("GET"); - when(authenticationManager.authenticate(captor.capture())).thenReturn(null); - - //When - target.attemptAuthentication(mockHttpServletRequest, mockHttpServletResponse); - } - - @Test - public void constructor_test() { - WebAuthnServerPropertyProvider webAuthnServerPropertyProvider = mock(WebAuthnServerPropertyProvider.class); - WebAuthnProcessingFilter webAuthnProcessingFilter = new WebAuthnProcessingFilter(AuthorityUtils.NO_AUTHORITIES, webAuthnServerPropertyProvider); - assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(webAuthnServerPropertyProvider); - } - - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java deleted file mode 100644 index 9a217f8512c..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidatorTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn; - -import com.webauthn4j.util.Base64UrlUtil; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; - -import java.util.Collections; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -/** - * Test for {@link WebAuthnRegistrationRequestValidator} - */ -public class WebAuthnRegistrationRequestValidatorTest { - - @Rule - public MockitoRule mockito = MockitoJUnit.rule(); - - @Mock - private WebAuthnManager webAuthnManager; - - @Mock - private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; - - private WebAuthnRegistrationRequestValidator target; - - @Before - public void setup() { - target = new WebAuthnRegistrationRequestValidator(webAuthnManager, webAuthnServerPropertyProvider); - } - - @Test - public void validate_test() { - - WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); - when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - - doNothing().when(webAuthnManager).verifyRegistrationData(any()); - - MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); - mockHttpServletRequest.setScheme("https"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setServerPort(443); - String clientDataBase64 = "clientDataBase64"; - String attestationObjectBase64 = "attestationObjectBase64"; - Set transports = Collections.emptySet(); - String clientExtensionsJSON = "clientExtensionsJSON"; - - target.validate(new WebAuthnRegistrationRequest(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, transports, clientExtensionsJSON)); - - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationData.class); - verify(webAuthnManager).verifyRegistrationData(argumentCaptor.capture()); - WebAuthnRegistrationData registrationData = argumentCaptor.getValue(); - - assertThat(registrationData.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); - assertThat(registrationData.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); - assertThat(registrationData.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(registrationData.getServerProperty()).isEqualTo(serverProperty); - assertThat(registrationData.getExpectedRegistrationExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); - } - - @Test - public void validate_with_transports_null_test() { - - WebAuthnServerProperty serverProperty = mock(WebAuthnServerProperty.class); - when(webAuthnServerPropertyProvider.provide(any())).thenReturn(serverProperty); - - doNothing().when(webAuthnManager).verifyRegistrationData(any()); - - MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest(); - mockHttpServletRequest.setScheme("https"); - mockHttpServletRequest.setServerName("example.com"); - mockHttpServletRequest.setServerPort(443); - String clientDataBase64 = "clientDataBase64"; - String attestationObjectBase64 = "attestationObjectBase64"; - String clientExtensionsJSON = "clientExtensionsJSON"; - - target.validate(new WebAuthnRegistrationRequest(mockHttpServletRequest, clientDataBase64, attestationObjectBase64, null, clientExtensionsJSON)); - - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(WebAuthnRegistrationData.class); - verify(webAuthnManager).verifyRegistrationData(argumentCaptor.capture()); - WebAuthnRegistrationData registrationData = argumentCaptor.getValue(); - - assertThat(registrationData.getClientDataJSON()).isEqualTo(Base64UrlUtil.decode(clientDataBase64)); - assertThat(registrationData.getAttestationObject()).isEqualTo(Base64UrlUtil.decode(attestationObjectBase64)); - assertThat(registrationData.getClientExtensionsJSON()).isEqualTo(clientExtensionsJSON); - assertThat(registrationData.getServerProperty()).isEqualTo(serverProperty); - assertThat(registrationData.getExpectedRegistrationExtensionIds()).isEqualTo(target.getExpectedRegistrationExtensionIds()); - } - - - @Test - public void getter_setter_test() { - - target.setExpectedRegistrationExtensionIds(Collections.singletonList("appId")); - assertThat(target.getExpectedRegistrationExtensionIds()).containsExactly("appId"); - - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java deleted file mode 100644 index ca87b543f1d..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImplTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.authenticator; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test for {@link WebAuthnAuthenticatorImpl} - */ -public class WebAuthnAuthenticatorImplTest { - - @Test - public void equals_hashCode_test() { - WebAuthnAuthenticator instanceA = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAuthenticator instanceB = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - assertThat(instanceA).isEqualTo(instanceB); - assertThat(instanceA).hasSameHashCodeAs(instanceB); - } - - @Test - public void get_name_test() { - WebAuthnAuthenticatorImpl instance = new WebAuthnAuthenticatorImpl(new byte[0], "authenticator", new byte[0], 0, null, null); - assertThat(instance.getName()).isEqualTo("authenticator"); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java deleted file mode 100644 index eb87e42efce..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepositoryTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.challenge; - -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpSession; - -import javax.servlet.http.HttpSession; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test for {@link HttpSessionWebAuthnChallengeRepository} - */ -public class HttpSessionWebAuthnChallengeRepositoryTest { - - private HttpSessionWebAuthnChallengeRepository target = new HttpSessionWebAuthnChallengeRepository(); - - @Test - public void generateChallenge_test() { - WebAuthnChallenge challenge = target.generateChallenge(); - assertThat(challenge).isNotNull(); - assertThat(challenge.getValue()).hasSize(16); - } - - @Test - public void saveChallenge_test() { - MockHttpServletRequest request = new MockHttpServletRequest(); - String attrName = ".test-challenge"; - - target.setSessionAttributeName(attrName); - WebAuthnChallenge challenge = target.generateChallenge(); - target.saveChallenge(challenge, request); - - HttpSession session = request.getSession(); - assertThat((WebAuthnChallenge) session.getAttribute(attrName)).isEqualTo(challenge); - } - - @Test - public void saveChallenge_test_with_null() { - MockHttpSession session = new MockHttpSession(); - MockHttpServletRequest prevRequest = new MockHttpServletRequest(); - prevRequest.setSession(session); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(session); - - WebAuthnChallenge challenge = target.generateChallenge(); - target.saveChallenge(challenge, prevRequest); - target.saveChallenge(null, request); - WebAuthnChallenge loadedChallenge = target.loadChallenge(request); - - assertThat(loadedChallenge).isNull(); - } - - @Test - public void saveChallenge_test_without_prev_request() { - MockHttpServletRequest request = new MockHttpServletRequest(); - - target.saveChallenge(null, request); - WebAuthnChallenge loadedChallenge = target.loadChallenge(request); - - assertThat(loadedChallenge).isNull(); - } - - - @Test - public void loadChallenge_test() { - MockHttpSession session = new MockHttpSession(); - MockHttpServletRequest prevRequest = new MockHttpServletRequest(); - prevRequest.setSession(session); - - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setSession(session); - String attrName = ".test-challenge"; - - target.setSessionAttributeName(attrName); - WebAuthnChallenge challenge = target.generateChallenge(); - target.saveChallenge(challenge, prevRequest); - WebAuthnChallenge loadedChallenge = target.loadChallenge(request); - - assertThat(loadedChallenge).isEqualTo(challenge); - } - - @Test - public void loadChallenge_test_without_previous_request() { - MockHttpServletRequest request = new MockHttpServletRequest(); - - WebAuthnChallenge loadedChallenge = target.loadChallenge(request); - - assertThat(loadedChallenge).isNull(); - } - - @Test - public void loadOrGenerateChallenge_test_without_previous_request() { - MockHttpServletRequest request = new MockHttpServletRequest(); - - WebAuthnChallenge loadedChallenge = target.loadOrGenerateChallenge(request); - - assertThat(loadedChallenge).isNotNull(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java deleted file mode 100644 index 3dd78248666..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnAuthenticationProviderConfigurerSpringTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.config.configurers; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.webauthn.WebAuthn4JWebAuthnManager; -import org.springframework.security.webauthn.WebAuthnAuthenticationProvider; -import org.springframework.security.webauthn.WebAuthnManager; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorService; -import org.springframework.security.webauthn.challenge.HttpSessionWebAuthnChallengeRepository; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; -import org.springframework.security.webauthn.server.EffectiveRpIdProvider; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProviderImpl; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import org.springframework.test.context.junit4.SpringRunner; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; - -@RunWith(SpringRunner.class) -public class WebAuthnAuthenticationProviderConfigurerSpringTest { - - @Autowired - ProviderManager providerManager; - - @Test - public void test() { - assertThat(providerManager.getProviders()).extracting("class").contains(WebAuthnAuthenticationProvider.class); - } - - - @EnableWebSecurity - static class Config extends WebSecurityConfigurerAdapter { - - @Autowired - private WebAuthnUserDetailsService webAuthnUserDetailsService; - - @Autowired - private WebAuthnAuthenticatorService webAuthnAuthenticatorService; - - @Autowired - private WebAuthnManager webAuthnManager; - - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManager(); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - - // Authentication - http.apply(webAuthnLogin()); - - // Authorization - http.authorizeRequests() - .antMatchers("/login").permitAll() - .anyRequest().authenticated(); - } - - @Override - public void configure(AuthenticationManagerBuilder builder) throws Exception { - builder.apply(new WebAuthnAuthenticationProviderConfigurer<>(webAuthnUserDetailsService, webAuthnAuthenticatorService, webAuthnManager)); - } - - @Configuration - static class BeanConfig { - - @Bean - public WebAuthn4JWebAuthnManager webAuthn4JWebAuthnAuthenticationManager() { - return mock(WebAuthn4JWebAuthnManager.class); - } - - @Bean - public WebAuthnUserDetailsService webAuthnUserDetailsService() { - return mock(WebAuthnUserDetailsService.class); - } - - @Bean - public WebAuthnAuthenticatorService webAuthnAuthenticatorService() { - return mock(WebAuthnAuthenticatorService.class); - } - - @Bean - public WebAuthnChallengeRepository challengeRepository() { - return new HttpSessionWebAuthnChallengeRepository(); - } - - @Bean - public WebAuthnServerPropertyProvider serverPropertyProvider(EffectiveRpIdProvider effectiveRpIdProvider, WebAuthnChallengeRepository webAuthnChallengeRepository) { - return new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, webAuthnChallengeRepository); - } - - - } - - } - - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java deleted file mode 100644 index 89a6f1e081a..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSetterSpringTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.config.configurers; - - -import com.webauthn4j.test.TestDataUtil; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.webauthn.WebAuthnDataConverter; -import org.springframework.security.webauthn.WebAuthnProcessingFilter; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; -import org.springframework.security.webauthn.server.EffectiveRpIdProvider; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; -import org.springframework.security.webauthn.server.WebAuthnServerPropertyProviderImpl; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; - -import java.util.Collection; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; - -@RunWith(SpringRunner.class) -public class WebAuthnLoginConfigurerSetterSpringTest { - - @Autowired - FilterChainProxy springSecurityFilterChain; - - private MockMvc mvc; - - @Autowired - private WebAuthnUserDetailsService webAuthnUserDetailsService; - - @Autowired - private WebAuthnServerPropertyProvider webAuthnServerPropertyProvider; - - @SuppressWarnings("unchecked") - @Before - public void setup() { - WebAuthnUserDetails mockUserDetails = mock(WebAuthnUserDetails.class); - Collection authenticators = Collections.singletonList(TestDataUtil.createAuthenticator()); - when(mockUserDetails.getAuthenticators()).thenReturn(authenticators); - when(mockUserDetails.getUserHandle()).thenReturn(new byte[32]); - doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadWebAuthnUserByUsername(null); - when(webAuthnUserDetailsService.loadWebAuthnUserByUsername(anyString())).thenReturn(mockUserDetails); - } - - @Test - public void configured_filter_test() { - WebAuthnProcessingFilter webAuthnProcessingFilter = (WebAuthnProcessingFilter) springSecurityFilterChain.getFilterChains().get(0).getFilters().stream().filter(item -> item instanceof WebAuthnProcessingFilter).findFirst().orElse(null); - assertThat(webAuthnProcessingFilter.getServerPropertyProvider()).isEqualTo(webAuthnServerPropertyProvider); - } - - @EnableWebSecurity - static class Config extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - - // Authentication - http.apply(webAuthnLogin()); - - // Authorization - http.authorizeRequests() - .antMatchers("/login").permitAll() - .anyRequest().authenticated(); - } - - @Configuration - static class BeanConfig { - - @Bean - public WebAuthnUserDetailsService webAuthnUserDetailsService() { - return mock(WebAuthnUserDetailsService.class); - } - - @Bean - public WebAuthnDataConverter webAuthnDataConverter() { - return new WebAuthnDataConverter(); - } - - @Bean - public EffectiveRpIdProvider effectiveRpIdProvider() { - return mock(EffectiveRpIdProvider.class); - } - - @Bean - public WebAuthnChallengeRepository challengeRepository() { - WebAuthnChallengeRepository webAuthnChallengeRepository = mock(WebAuthnChallengeRepository.class); - when(webAuthnChallengeRepository.loadOrGenerateChallenge(any())).thenReturn(new WebAuthnChallengeImpl("aFglXMZdQTKD4krvNzJBzA")); - return webAuthnChallengeRepository; - } - - @Bean - public WebAuthnServerPropertyProvider serverPropertyProvider(EffectiveRpIdProvider effectiveRpIdProvider, WebAuthnChallengeRepository webAuthnChallengeRepository) { - return new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, webAuthnChallengeRepository); - } - - } - - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java deleted file mode 100644 index 6ac358ff0bf..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/config/configurers/WebAuthnLoginConfigurerSpringTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.config.configurers; - - -import com.webauthn4j.test.TestDataUtil; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.webauthn.WebAuthnDataConverter; -import org.springframework.security.webauthn.WebAuthnProcessingFilter; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetails; -import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; - -import java.util.Collection; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; -import static org.springframework.security.webauthn.config.configurers.WebAuthnLoginConfigurer.webAuthnLogin; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@RunWith(SpringRunner.class) -public class WebAuthnLoginConfigurerSpringTest { - - @Autowired - FilterChainProxy springSecurityFilterChain; - - private MockMvc mvc; - - @Autowired - private WebAuthnUserDetailsService webAuthnUserDetailsService; - - @SuppressWarnings("unchecked") - @Before - public void setup() { - WebAuthnUserDetails mockUserDetails = mock(WebAuthnUserDetails.class); - Collection authenticators = Collections.singletonList(TestDataUtil.createAuthenticator()); - when(mockUserDetails.getAuthenticators()).thenReturn(authenticators); - when(mockUserDetails.getUserHandle()).thenReturn(new byte[32]); - doThrow(new UsernameNotFoundException(null)).when(webAuthnUserDetailsService).loadWebAuthnUserByUsername(null); - when(webAuthnUserDetailsService.loadWebAuthnUserByUsername(anyString())).thenReturn(mockUserDetails); - } - - @Test - public void configured_filter_test() { - WebAuthnProcessingFilter webAuthnProcessingFilter = (WebAuthnProcessingFilter) springSecurityFilterChain.getFilterChains().get(0).getFilters().stream().filter(item -> item instanceof WebAuthnProcessingFilter).findFirst().orElse(null); - assertThat(webAuthnProcessingFilter).isNotNull(); - } - - - @Test - public void rootPath_with_anonymous_user_test() throws Exception { - mvc = MockMvcBuilders.standaloneSetup() - .addFilter(springSecurityFilterChain) - .build(); - - mvc - .perform(get("/").with(anonymous())) - .andExpect(unauthenticated()) - .andExpect(status().is3xxRedirection()); - } - - @Test - public void rootPath_with_authenticated_user_test() throws Exception { - mvc = MockMvcBuilders.standaloneSetup() - .defaultRequest(get("/").with(user("john"))) - .addFilter(springSecurityFilterChain) - .build(); - - mvc - .perform(get("/")) - .andExpect(authenticated()) - .andExpect(status().isNotFound()); - - } - - @EnableWebSecurity - static class Config extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - - // Authentication - http.apply(webAuthnLogin()) - .usernameParameter("username") - .passwordParameter("password") - .credentialIdParameter("credentialId") - .clientDataJSONParameter("clientDataJSON") - .authenticatorDataParameter("authenticatorData") - .signatureParameter("signature") - .clientExtensionsJSONParameter("clientExtensionsJSON") - .successForwardUrl("/") - .failureForwardUrl("/login") - .loginPage("/login"); - - // Authorization - http.authorizeRequests() - .antMatchers("/login").permitAll() - .anyRequest().authenticated(); - } - - @Configuration - static class BeanConfig { - - @Bean - public WebAuthnUserDetailsService webAuthnUserDetailsService() { - return mock(WebAuthnUserDetailsService.class); - } - - @Bean - public WebAuthnDataConverter webAuthnDataConverter() { - return new WebAuthnDataConverter(); - } - - @Bean - public WebAuthnChallengeRepository challengeRepository() { - WebAuthnChallengeRepository webAuthnChallengeRepository = mock(WebAuthnChallengeRepository.class); - when(webAuthnChallengeRepository.loadOrGenerateChallenge(any())).thenReturn(new WebAuthnChallengeImpl("aFglXMZdQTKD4krvNzJBzA")); - return webAuthnChallengeRepository; - } - - } - - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java deleted file mode 100644 index 429ddd5a28f..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAaguidExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadAaguidExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new BadAaguidException("dummy", cause); - new BadAaguidException("dummy"); - }).doesNotThrowAnyException(); - - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java deleted file mode 100644 index a1ef55b27f5..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAlgorithmExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadAlgorithmExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new BadAlgorithmException("dummy", cause); - new BadAlgorithmException("dummy"); - }).doesNotThrowAnyException(); - - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java deleted file mode 100644 index 635cea4f7b9..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadAttestationStatementExceptionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadAttestationStatementExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new BadAttestationStatementException("dummy", cause); - new BadAttestationStatementException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java deleted file mode 100644 index 43a1584f824..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadChallengeExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadChallengeExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new BadChallengeException("dummy", cause); - new BadChallengeException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java deleted file mode 100644 index ee69e1101a0..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadCredentialIdExceptionTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadCredentialIdExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new BadCredentialIdException("dummy", cause); - new BadCredentialIdException("dummy"); - }).doesNotThrowAnyException(); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java deleted file mode 100644 index f0686fe8025..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadOriginExceptionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadOriginExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new BadOriginException("dummy", cause); - new BadOriginException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java deleted file mode 100644 index c2f28eea882..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadRpIdExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadRpIdExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new BadRpIdException("dummy", cause); - new BadRpIdException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java deleted file mode 100644 index 4ba32976523..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/BadSignatureExceptionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class BadSignatureExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new BadSignatureException("dummy", cause); - new BadSignatureException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java deleted file mode 100644 index 60746e19302..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/CertificateExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class CertificateExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new CertificateException("dummy", cause); - new CertificateException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java deleted file mode 100644 index ecc7f744d36..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/ConstraintViolationExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class ConstraintViolationExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new ConstraintViolationException("dummy", cause); - new ConstraintViolationException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java deleted file mode 100644 index 6299c67c0e7..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundExceptionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class CredentialIdNotFoundExceptionTest { - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new CredentialIdNotFoundException("dummy", cause); - new CredentialIdNotFoundException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java deleted file mode 100644 index 29d739ac676..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/DataConversionExceptionTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class DataConversionExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new DataConversionException("dummy", cause); - new DataConversionException("dummy"); - }).doesNotThrowAnyException(); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java deleted file mode 100644 index 3cc91492c95..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class KeyDescriptionValidationExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new KeyDescriptionValidationException("dummy", cause); - new KeyDescriptionValidationException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java deleted file mode 100644 index e621e03ed0b..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousCounterValueExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class MaliciousCounterValueExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new MaliciousCounterValueException("dummy", cause); - new MaliciousCounterValueException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java deleted file mode 100644 index b2cbbf7ff0d..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MaliciousDataExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class MaliciousDataExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new MaliciousDataException("dummy", cause); - new MaliciousDataException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java deleted file mode 100644 index b2774ef2702..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/MissingChallengeExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class MissingChallengeExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new MissingChallengeException("dummy", cause); - new MissingChallengeException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java deleted file mode 100644 index 916f2f919c5..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/OptionsExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class OptionsExceptionTest { - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new MetadataException("dummy", cause); - new MetadataException("dummy"); - new MetadataException(cause); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java deleted file mode 100644 index b219de53d9f..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/PublicKeyMismatchExceptionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class PublicKeyMismatchExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new PublicKeyMismatchException("dummy", cause); - new PublicKeyMismatchException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java deleted file mode 100644 index 8873a044aea..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedExceptionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class SelfAttestationProhibitedExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - assertThatCode(() -> { - new SelfAttestationProhibitedException("dummy", cause); - new SelfAttestationProhibitedException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java deleted file mode 100644 index b8b7571cd98..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/TokenBindingExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class TokenBindingExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new TokenBindingException("dummy", cause); - new TokenBindingException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java deleted file mode 100644 index 285e70747ed..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class TrustAnchorNotFoundExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new TrustAnchorNotFoundException("dummy", cause); - new TrustAnchorNotFoundException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java deleted file mode 100644 index 944638ff0e5..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UnexpectedExtensionExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class UnexpectedExtensionExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new UnexpectedExtensionException("dummy", cause); - new UnexpectedExtensionException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java deleted file mode 100644 index 1bb5d55a91d..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotPresentExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class UserNotPresentExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new UserNotPresentException("dummy", cause); - new UserNotPresentException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java deleted file mode 100644 index 9ecd822ba9e..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/UserNotVerifiedExceptionTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class UserNotVerifiedExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new UserNotVerifiedException("dummy", cause); - new UserNotVerifiedException("dummy"); - }).doesNotThrowAnyException(); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java deleted file mode 100644 index 1815566d458..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/exception/WebAuthnAuthenticationExceptionTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThatCode; - -@SuppressWarnings("ThrowableNotThrown") -public class WebAuthnAuthenticationExceptionTest { - - private RuntimeException cause = new RuntimeException(); - - @Test - public void test() { - - assertThatCode(() -> { - new WebAuthnAuthenticationException("dummy", cause); - new WebAuthnAuthenticationException("dummy"); - }).doesNotThrowAnyException(); - } - -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java deleted file mode 100644 index 64b47be838b..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImplTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.server; - -import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.webauthn.challenge.WebAuthnChallenge; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeImpl; -import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class WebAuthnServerPropertyProviderImplTest { - - private WebAuthnChallengeRepository webAuthnChallengeRepository = mock(WebAuthnChallengeRepository.class); - private EffectiveRpIdProvider effectiveRpIdProvider = mock(EffectiveRpIdProvider.class); - private WebAuthnServerPropertyProviderImpl target = new WebAuthnServerPropertyProviderImpl(effectiveRpIdProvider, webAuthnChallengeRepository); - - @Test - public void provide_test() { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setScheme("https"); - request.setServerName("origin.example.com"); - request.setServerPort(443); - WebAuthnChallenge mockChallenge = new WebAuthnChallengeImpl(); - when(webAuthnChallengeRepository.loadOrGenerateChallenge(request)).thenReturn(mockChallenge); - when(effectiveRpIdProvider.getEffectiveRpId(request)).thenReturn("rpid.example.com"); - - WebAuthnServerProperty serverProperty = target.provide(request); - - assertThat(serverProperty.getRpId()).isEqualTo("rpid.example.com"); - assertThat(serverProperty.getOrigin()).isEqualTo(new WebAuthnOrigin("https://origin.example.com")); - assertThat(serverProperty.getChallenge()).isEqualTo(mockChallenge); - } -} diff --git a/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java b/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java deleted file mode 100644 index c234f66af99..00000000000 --- a/webauthn/src/test/java/org/springframework/security/webauthn/userdetails/WebAuthnAndPasswordUserTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.userdetails; - - -import org.junit.Test; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; - -public class WebAuthnAndPasswordUserTest { - - @Test - public void getter_setter_test() { - GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_ADMIN"); - WebAuthnAuthenticator authenticator = new WebAuthnAuthenticatorImpl(new byte[0], "dummy", new byte[0], 0, null, null); - WebAuthnAndPasswordUser userDetails = new WebAuthnAndPasswordUser( - new byte[32], - "dummy", - "dummy", - Collections.singletonList(authenticator), - Collections.singletonList(grantedAuthority)); - - userDetails.setSingleFactorAuthenticationAllowed(true); - assertThat(userDetails.getUserHandle()).isEqualTo(new byte[32]); - assertThat(userDetails.isSingleFactorAuthenticationAllowed()).isTrue(); - assertThat(userDetails.getAuthenticators()).isEqualTo(Collections.singletonList(authenticator)); - } - -} diff --git a/webauthn/src/test/resources/certs/3tier-test-root-CA.der b/webauthn/src/test/resources/certs/3tier-test-root-CA.der deleted file mode 100644 index cad0759b845378370e47421d1d83dc9f42ac0902..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmXqLVr(>MVpL}m;FYLbIa^c5u3=SZ!<9K5-3DB299nH2=WJP+nG9SEg$xAPm_u2( zcsPSI5{n9a5|i`{^$c`CV$3|M#RWx~dFi^vsmY~9nI)CF<*7-Dr6n183MHwQ?Fo4K%(o(OMZ0R4+s553x%{uT|M}c^uWQjG0IsQmJpcdz diff --git a/webauthn/src/test/resources/certs/3tier-test-root-CA.pem b/webauthn/src/test/resources/certs/3tier-test-root-CA.pem deleted file mode 100644 index 0972044392b..00000000000 --- a/webauthn/src/test/resources/certs/3tier-test-root-CA.pem +++ /dev/null @@ -1,38 +0,0 @@ -Certificate: - Data: - Version: 1 (0x0) - Serial Number: - 0d:18:7e:a9:9b:29:2c:3e:80:aa:55:80:d4:9c:88:8b - Signature Algorithm: ecdsa-with-SHA256 - Issuer: O=SharpLab., CN=spring-security-webauthn test root CA - Validity - Not Before: Sep 22 03:18:29 2017 GMT - Not After : Aug 29 03:18:29 2117 GMT - Subject: O=SharpLab., CN=spring-security-webauthn test root CA - Subject Public Key Info: - Public Key Algorithm: id-ecPublicKey - Public-Key: (256 bit) - pub: - 04:cd:77:2e:b9:c5:ba:54:35:e1:33:41:ca:b5:0a: - 01:7a:c8:6a:46:c3:c7:94:0f:7f:94:76:5b:f9:ca: - 38:eb:68:2d:ba:0f:e6:55:89:4a:98:28:fa:c1:3a: - 75:8d:17:19:39:17:bc:e9:1e:56:65:46:8c:33:41: - 40:84:dd:ff:22 - ASN1 OID: prime256v1 - NIST CURVE: P-256 - Signature Algorithm: ecdsa-with-SHA256 - 30:45:02:20:36:9e:79:a0:04:ea:80:df:32:86:4d:c2:01:4b: - 01:cb:09:7a:99:1a:a4:22:2b:79:9a:c7:2d:59:dc:f2:35:9f: - 02:21:00:a5:96:b3:76:e2:2e:bc:50:e9:e6:4d:78:61:a8:87: - 25:56:46:b1:bb:84:6d:0f:ea:b7:fc:f3:8e:de:8a:a2:e2 ------BEGIN CERTIFICATE----- -MIIBgTCCAScCEA0YfqmbKSw+gKpVgNSciIswCgYIKoZIzj0EAwIwRDESMBAGA1UE -CgwJU2hhcnBMYWIuMS4wLAYDVQQDDCVzcHJpbmctc2VjdXJpdHktd2ViYXV0aG4g -dGVzdCByb290IENBMCAXDTE3MDkyMjAzMTgyOVoYDzIxMTcwODI5MDMxODI5WjBE -MRIwEAYDVQQKDAlTaGFycExhYi4xLjAsBgNVBAMMJXNwcmluZy1zZWN1cml0eS13 -ZWJhdXRobiB0ZXN0IHJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATN -dy65xbpUNeEzQcq1CgF6yGpGw8eUD3+Udlv5yjjraC26D+ZViUqYKPrBOnWNFxk5 -F7zpHlZlRowzQUCE3f8iMAoGCCqGSM49BAMCA0gAMEUCIDaeeaAE6oDfMoZNwgFL -AcsJepkapCIreZrHLVnc8jWfAiEApZazduIuvFDp5k14YaiHJVZGsbuEbQ/qt/zz -jt6KouI= ------END CERTIFICATE----- From b430a7166b5358266d4fe66efdffc8fbb2e89947 Mon Sep 17 00:00:00 2001 From: ynojima Date: Fri, 18 Oct 2019 23:59:34 +0900 Subject: [PATCH 6/9] Just for review: Remove specialized wrapper exception class to reduce number of classes --- .../app/web/WebAuthnSampleController.java | 10 +-- .../webauthn/WebAuthn4JWebAuthnManager.java | 73 +------------------ .../exception/BadAaguidException.java | 31 -------- .../exception/BadAlgorithmException.java | 33 --------- .../BadAttestationStatementException.java | 31 -------- .../exception/BadChallengeException.java | 31 -------- .../exception/BadCredentialIdException.java | 27 ------- .../exception/BadOriginException.java | 30 -------- .../webauthn/exception/BadRpIdException.java | 30 -------- .../exception/BadSignatureException.java | 31 -------- .../exception/CertificateException.java | 30 -------- .../ConstraintViolationException.java | 32 -------- .../CredentialIdNotFoundException.java | 2 +- .../exception/DataConversionException.java | 32 -------- .../KeyDescriptionValidationException.java | 31 -------- .../MaliciousCounterValueException.java | 32 -------- .../exception/MaliciousDataException.java | 30 -------- .../webauthn/exception/MetadataException.java | 35 --------- .../exception/MissingChallengeException.java | 31 -------- .../exception/PublicKeyMismatchException.java | 32 -------- .../SelfAttestationProhibitedException.java | 32 -------- .../exception/TokenBindingException.java | 32 -------- .../TrustAnchorNotFoundException.java | 33 --------- .../UnexpectedExtensionException.java | 32 -------- .../exception/UserNotPresentException.java | 33 --------- .../exception/UserNotVerifiedException.java | 33 --------- .../exception/ValidationException.java | 32 -------- .../webauthn/exception/package-info.java | 20 ----- 28 files changed, 6 insertions(+), 855 deletions(-) delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java index 7e6795eb811..02f55174812 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java @@ -18,6 +18,7 @@ import com.webauthn4j.util.Base64UrlUtil; import com.webauthn4j.util.UUIDUtil; +import com.webauthn4j.util.exception.WebAuthnException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -33,8 +34,7 @@ import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorTransport; -import org.springframework.security.webauthn.exception.DataConversionException; -import org.springframework.security.webauthn.exception.ValidationException; +import org.springframework.security.webauthn.exception.WebAuthnAuthenticationException; import org.springframework.security.webauthn.userdetails.InMemoryWebAuthnAndPasswordUserDetailsManager; import org.springframework.security.webauthn.userdetails.WebAuthnAndPasswordUser; import org.springframework.stereotype.Controller; @@ -126,14 +126,10 @@ public String create(HttpServletRequest request, @Valid @ModelAttribute("userFor try { registrationRequestValidator.validate(webAuthnRegistrationRequest); } - catch (ValidationException e){ + catch (WebAuthnException | WebAuthnAuthenticationException e){ logger.debug("WebAuthn registration request validation failed.", e); return VIEW_SIGNUP_SIGNUP; } - catch (DataConversionException e){ - logger.debug("WebAuthn registration request data conversion failed.", e); - return VIEW_SIGNUP_SIGNUP; - } AuthenticatorCreateForm sourceAuthenticator = userCreateForm.getAuthenticator(); diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java index b931827a183..a8db0aeb74b 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java @@ -77,67 +77,6 @@ public WebAuthn4JWebAuthnManager() { this(new WebAuthnDataConverter()); } - /** - * Wraps WebAuthnAuthentication to proper {@link RuntimeException} (mainly {@link AuthenticationException} subclass. - * - * @param e exception to be wrapped - * @return wrapping exception - */ - @SuppressWarnings("squid:S3776") - static RuntimeException wrapWithAuthenticationException(WebAuthnException e) { - // ValidationExceptions - if (e instanceof com.webauthn4j.validator.exception.BadAaguidException) { - return new BadAaguidException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadAlgorithmException) { - return new BadAlgorithmException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadAttestationStatementException) { - if (e instanceof com.webauthn4j.validator.exception.KeyDescriptionValidationException) { - return new KeyDescriptionValidationException(e.getMessage(), e); - } else { - return new BadAttestationStatementException(e.getMessage(), e); - } - } else if (e instanceof com.webauthn4j.validator.exception.BadChallengeException) { - return new BadChallengeException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadOriginException) { - return new BadOriginException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadRpIdException) { - return new BadRpIdException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.BadSignatureException) { - return new BadSignatureException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.CertificateException) { - return new CertificateException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.ConstraintViolationException) { - return new ConstraintViolationException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.MaliciousCounterValueException) { - return new MaliciousCounterValueException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.MaliciousDataException) { - return new MaliciousDataException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.MissingChallengeException) { - return new MissingChallengeException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.PublicKeyMismatchException) { - return new PublicKeyMismatchException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.SelfAttestationProhibitedException) { - return new SelfAttestationProhibitedException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.TokenBindingException) { - return new TokenBindingException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.TrustAnchorNotFoundException) { - return new TrustAnchorNotFoundException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.UnexpectedExtensionException) { - return new UnexpectedExtensionException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.UserNotPresentException) { - return new UserNotPresentException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.UserNotVerifiedException) { - return new UserNotVerifiedException(e.getMessage(), e); - } else if (e instanceof com.webauthn4j.validator.exception.ValidationException) { - return new ValidationException("WebAuthn validation error", e); - } - // DataConversionException - else if (e instanceof com.webauthn4j.converter.exception.DataConversionException) { - return new DataConversionException("WebAuthn data conversion error", e); - } else { - return new AuthenticationServiceException(null, e); - } - } public void verifyRegistrationData( WebAuthnRegistrationData registrationData @@ -152,11 +91,7 @@ public void verifyRegistrationData( WebAuthnRegistrationContext registrationContext = createRegistrationContext(registrationData); - try { - registrationContextValidator.validate(registrationContext); - } catch (WebAuthnException e) { - throw wrapWithAuthenticationException(e); - } + registrationContextValidator.validate(registrationContext); } @Override @@ -184,11 +119,7 @@ public void verifyAuthenticationData(WebAuthnAuthenticationData authenticationDa transports ); - try { - authenticationContextValidator.validate(authenticationContext, authenticator); - } catch (WebAuthnException e) { - throw wrapWithAuthenticationException(e); - } + authenticationContextValidator.validate(authenticationContext, authenticator); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java deleted file mode 100644 index e4cd4318142..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAaguidException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad aaguid is specified - */ -public class BadAaguidException extends ValidationException { - - public BadAaguidException(String message, Throwable cause) { - super(message, cause); - } - - public BadAaguidException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java deleted file mode 100644 index 89f792d5e51..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAlgorithmException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad algorithm is specified - * - * @author Yoshikazu Nojima - */ -public class BadAlgorithmException extends ValidationException { - - public BadAlgorithmException(String message, Throwable cause) { - super(message, cause); - } - - public BadAlgorithmException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java deleted file mode 100644 index e30f6c76e5f..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadAttestationStatementException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad attestation statement is specified - */ -public class BadAttestationStatementException extends ValidationException { - - public BadAttestationStatementException(String message, Throwable cause) { - super(message, cause); - } - - public BadAttestationStatementException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java deleted file mode 100644 index c41c0933192..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadChallengeException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad challenge is detected - */ -public class BadChallengeException extends ValidationException { - - public BadChallengeException(String message, Throwable cause) { - super(message, cause); - } - - public BadChallengeException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java deleted file mode 100644 index 45a91f5679a..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadCredentialIdException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -public class BadCredentialIdException extends ValidationException { - public BadCredentialIdException(String message, Throwable cause) { - super(message, cause); - } - - public BadCredentialIdException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java deleted file mode 100644 index 61965da944e..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadOriginException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad origin is specified - */ -public class BadOriginException extends ValidationException { - public BadOriginException(String message, Throwable cause) { - super(message, cause); - } - - public BadOriginException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java deleted file mode 100644 index 93ca3be5349..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadRpIdException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad rpId is specified - */ -public class BadRpIdException extends ValidationException { - public BadRpIdException(String message, Throwable cause) { - super(message, cause); - } - - public BadRpIdException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java deleted file mode 100644 index d1ef7f03da1..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/BadSignatureException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - - -/** - * Thrown if bad signature is specified - */ -public class BadSignatureException extends ValidationException { - public BadSignatureException(String message, Throwable cause) { - super(message, cause); - } - - public BadSignatureException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java deleted file mode 100644 index 29523b25803..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/CertificateException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if certificate problems happen - */ -public class CertificateException extends ValidationException { - public CertificateException(String message, Throwable cause) { - super(message, cause); - } - - public CertificateException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java deleted file mode 100644 index 0bde4fe1646..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/ConstraintViolationException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - - -/** - * Thrown if the value violates constraints - */ -public class ConstraintViolationException extends ValidationException { - - public ConstraintViolationException(String message, Throwable cause) { - super(message, cause); - } - - public ConstraintViolationException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java index a917febf052..babb04630ec 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/exception/CredentialIdNotFoundException.java @@ -19,7 +19,7 @@ /** * Thrown if an authentication request is rejected because credentialId is not found. */ -public class CredentialIdNotFoundException extends ValidationException { +public class CredentialIdNotFoundException extends WebAuthnAuthenticationException { public CredentialIdNotFoundException(String message) { super(message); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java deleted file mode 100644 index bd050031a0f..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/DataConversionException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -import org.springframework.security.core.AuthenticationException; - -/** - * Thrown if data conversion failed - */ -public class DataConversionException extends AuthenticationException { - public DataConversionException(String message) { - super(message); - } - - public DataConversionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java deleted file mode 100644 index 29a516160d2..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/KeyDescriptionValidationException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if bad key description is specified - */ -public class KeyDescriptionValidationException extends ValidationException { - - public KeyDescriptionValidationException(String message, Throwable cause) { - super(message, cause); - } - - public KeyDescriptionValidationException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java deleted file mode 100644 index b9c2b5cbae5..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousCounterValueException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if the counter value is lower than expected value - */ -public class MaliciousCounterValueException extends ValidationException { - - public MaliciousCounterValueException(String message, Throwable cause) { - super(message, cause); - } - - public MaliciousCounterValueException(String message) { - super(message); - } - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java deleted file mode 100644 index 4075ae76297..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MaliciousDataException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if malicious data is specified - */ -public class MaliciousDataException extends ValidationException { - public MaliciousDataException(String message, Throwable cause) { - super(message, cause); - } - - public MaliciousDataException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java deleted file mode 100644 index 4f12962e151..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MetadataException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if an error happen while processing metadata - */ -public class MetadataException extends RuntimeException { - public MetadataException(String message, Throwable cause) { - super(message, cause); - } - - public MetadataException(String message) { - super(message); - } - - public MetadataException(Throwable cause) { - super(cause); - } - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java deleted file mode 100644 index d3290663e9c..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/MissingChallengeException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - - -/** - * Thrown if the challenge doesn't exist in the session - */ -public class MissingChallengeException extends ValidationException { - public MissingChallengeException(String message, Throwable cause) { - super(message, cause); - } - - public MissingChallengeException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java deleted file mode 100644 index b519ecb14e3..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/PublicKeyMismatchException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if the public key in the first certificate in x5c doesn't matches the credentialPublicKey in the attestedCredentialData - * - * @author Yoshikazu Nojima - */ -public class PublicKeyMismatchException extends ValidationException { - public PublicKeyMismatchException(String message, Throwable cause) { - super(message, cause); - } - - public PublicKeyMismatchException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java deleted file mode 100644 index a656daa8a36..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/SelfAttestationProhibitedException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if self attestation is specified while prohibited - * - * @author Yoshikazu Nojima - */ -public class SelfAttestationProhibitedException extends ValidationException { - public SelfAttestationProhibitedException(String message, Throwable cause) { - super(message, cause); - } - - public SelfAttestationProhibitedException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java deleted file mode 100644 index e99ec1c1954..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/TokenBindingException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if tokenBinding problems happen - * - * @author Yoshikazu Nojima - */ -public class TokenBindingException extends ValidationException { - public TokenBindingException(String message, Throwable cause) { - super(message, cause); - } - - public TokenBindingException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java deleted file mode 100644 index 94b8d5ad13c..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/TrustAnchorNotFoundException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if no trust anchor chained to the attestation certificate is found - * - * @author Yoshikazu Nojima - */ -public class TrustAnchorNotFoundException extends ValidationException { - - public TrustAnchorNotFoundException(String message, Throwable cause) { - super(message, cause); - } - - public TrustAnchorNotFoundException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java deleted file mode 100644 index 587f8c4c97f..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UnexpectedExtensionException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if unexpected extension is contained - * - * @author Yoshikazu Nojima - */ -public class UnexpectedExtensionException extends ValidationException { - public UnexpectedExtensionException(String msg, Throwable cause) { - super(msg, cause); - } - - public UnexpectedExtensionException(String msg) { - super(msg); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java deleted file mode 100644 index effa1a2151e..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotPresentException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if user is to be present but not present - * - * @author Yoshikazu Nojima - */ -public class UserNotPresentException extends ValidationException { - - public UserNotPresentException(String message, Throwable cause) { - super(message, cause); - } - - public UserNotPresentException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java deleted file mode 100644 index 88d4c451e06..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/UserNotVerifiedException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if user is to be verified but not verified - * - * @author Yoshikazu Nojima - */ -public class UserNotVerifiedException extends ValidationException { - - public UserNotVerifiedException(String message, Throwable cause) { - super(message, cause); - } - - public UserNotVerifiedException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java deleted file mode 100644 index 883ab6458f3..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/ValidationException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.exception; - -/** - * Thrown if WebAuthn request validation error happen - * - * @author Yoshikazu Nojima - */ -public class ValidationException extends WebAuthnAuthenticationException { - public ValidationException(String message, Throwable cause) { - super(message, cause); - } - - public ValidationException(String message) { - super(message); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java b/webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java deleted file mode 100644 index 324fa5d98e3..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/exception/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Exception classes for WebAuthn - */ -package org.springframework.security.webauthn.exception; From ca075724f4676900746220793d547083a3daa136 Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 19 Oct 2019 10:41:10 +0900 Subject: [PATCH 7/9] Just for review: Remove WebAuthn4J wrapper classes to reduce number of classes --- .../app/web/WebAuthnSampleController.java | 6 +- .../webauthn/WebAuthn4JWebAuthnManager.java | 17 +- .../webauthn/WebAuthnAuthenticationData.java | 10 +- .../webauthn/WebAuthnProcessingFilter.java | 4 +- .../webauthn/WebAuthnRegistrationData.java | 8 +- .../WebAuthnRegistrationRequestValidator.java | 4 +- .../authenticator/WebAuthnAuthenticator.java | 3 +- .../WebAuthnAuthenticatorImpl.java | 7 +- .../WebAuthnAuthenticatorTransport.java | 86 ---------- ...ttpSessionWebAuthnChallengeRepository.java | 14 +- .../webauthn/challenge/WebAuthnChallenge.java | 27 ---- .../challenge/WebAuthnChallengeImpl.java | 81 ---------- .../WebAuthnChallengeRepository.java | 32 ++-- .../webauthn/server/WebAuthnOrigin.java | 152 ------------------ .../server/WebAuthnServerProperty.java | 102 ------------ .../WebAuthnServerPropertyProvider.java | 10 +- .../WebAuthnServerPropertyProviderImpl.java | 17 +- 17 files changed, 73 insertions(+), 507 deletions(-) delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java diff --git a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java index 02f55174812..f8c7afacd02 100644 --- a/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java +++ b/samples/javaconfig/webauthn/src/main/java/org/springframework/security/webauthn/sample/app/web/WebAuthnSampleController.java @@ -16,6 +16,7 @@ package org.springframework.security.webauthn.sample.app.web; +import com.webauthn4j.data.AuthenticatorTransport; import com.webauthn4j.util.Base64UrlUtil; import com.webauthn4j.util.UUIDUtil; import com.webauthn4j.util.exception.WebAuthnException; @@ -33,7 +34,6 @@ import org.springframework.security.webauthn.WebAuthnRegistrationRequestValidator; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorImpl; -import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticatorTransport; import org.springframework.security.webauthn.exception.WebAuthnAuthenticationException; import org.springframework.security.webauthn.userdetails.InMemoryWebAuthnAndPasswordUserDetailsManager; import org.springframework.security.webauthn.userdetails.WebAuthnAndPasswordUser; @@ -138,13 +138,13 @@ public String create(HttpServletRequest request, @Valid @ModelAttribute("userFor byte[] attestedCredentialData = webAuthnDataConverter.extractAttestedCredentialData(authenticatorData); byte[] credentialId = webAuthnDataConverter.extractCredentialId(attestedCredentialData); long signCount = webAuthnDataConverter.extractSignCount(authenticatorData); - Set transports; + Set transports; if (sourceAuthenticator.getTransports() == null) { transports = null; } else { transports = sourceAuthenticator.getTransports().stream() - .map(WebAuthnAuthenticatorTransport::create) + .map(AuthenticatorTransport::create) .collect(Collectors.toSet()); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java index a8db0aeb74b..906b4e76cf8 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthn4JWebAuthnManager.java @@ -26,17 +26,12 @@ import com.webauthn4j.data.client.Origin; import com.webauthn4j.data.client.challenge.DefaultChallenge; import com.webauthn4j.server.ServerProperty; -import com.webauthn4j.util.exception.WebAuthnException; import com.webauthn4j.validator.WebAuthnAuthenticationContextValidator; import com.webauthn4j.validator.WebAuthnRegistrationContextValidator; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.webauthn.authenticator.WebAuthnAuthenticator; -import org.springframework.security.webauthn.exception.*; -import org.springframework.security.webauthn.server.WebAuthnOrigin; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; import org.springframework.util.Assert; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.Set; @@ -129,7 +124,7 @@ public String getEffectiveRpId(HttpServletRequest request) { if (this.rpId != null) { effectiveRpId = this.rpId; } else { - WebAuthnOrigin origin = WebAuthnOrigin.create(request); + Origin origin = createOrigin(request); effectiveRpId = origin.getHost(); } return effectiveRpId; @@ -179,11 +174,11 @@ private WebAuthnAuthenticationContext createWebAuthnAuthenticationContext(WebAut ); } - private Origin convertToOrigin(WebAuthnOrigin webAuthnOrigin) { + private Origin convertToOrigin(Origin webAuthnOrigin) { return new Origin(webAuthnOrigin.getScheme(), webAuthnOrigin.getHost(), webAuthnOrigin.getPort()); } - private ServerProperty convertToServerProperty(WebAuthnServerProperty webAuthnServerProperty) { + private ServerProperty convertToServerProperty(ServerProperty webAuthnServerProperty) { return new ServerProperty( convertToOrigin(webAuthnServerProperty.getOrigin()), webAuthnServerProperty.getRpId(), @@ -191,4 +186,8 @@ private ServerProperty convertToServerProperty(WebAuthnServerProperty webAuthnSe webAuthnServerProperty.getTokenBindingId()); } + private static Origin createOrigin(ServletRequest request) { + return new Origin(request.getScheme(), request.getServerName(), request.getServerPort()); + } + } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java index baf1f90614b..dff75d5fe67 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnAuthenticationData.java @@ -16,8 +16,8 @@ package org.springframework.security.webauthn; +import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.ArrayUtil; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; import java.io.Serializable; import java.util.Arrays; @@ -42,7 +42,7 @@ public class WebAuthnAuthenticationData implements Serializable { private final byte[] signature; private final String clientExtensionsJSON; - private final WebAuthnServerProperty serverProperty; + private final ServerProperty serverProperty; private final boolean userVerificationRequired; private final boolean userPresenceRequired; private final List expectedAuthenticationExtensionIds; @@ -54,7 +54,7 @@ public WebAuthnAuthenticationData( byte[] authenticatorData, byte[] signature, String clientExtensionsJSON, - WebAuthnServerProperty serverProperty, + ServerProperty serverProperty, boolean userVerificationRequired, boolean userPresenceRequired, List expectedAuthenticationExtensionIds) { @@ -77,7 +77,7 @@ public WebAuthnAuthenticationData( byte[] authenticatorData, byte[] signature, String clientExtensionsJSON, - WebAuthnServerProperty serverProperty, + ServerProperty serverProperty, boolean userVerificationRequired, List expectedAuthenticationExtensionIds) { @@ -114,7 +114,7 @@ public String getClientExtensionsJSON() { return clientExtensionsJSON; } - public WebAuthnServerProperty getServerProperty() { + public ServerProperty getServerProperty() { return serverProperty; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java index fc6b3ae9cd8..8bdd3e762b2 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnProcessingFilter.java @@ -16,6 +16,7 @@ package org.springframework.security.webauthn; +import com.webauthn4j.server.ServerProperty; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationServiceException; @@ -24,7 +25,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import org.springframework.util.Assert; import org.springframework.util.Base64Utils; @@ -135,7 +135,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ byte[] rawAuthenticatorData = authenticatorData == null ? null : Base64Utils.decodeFromUrlSafeString(authenticatorData); byte[] signatureBytes = signature == null ? null : Base64Utils.decodeFromUrlSafeString(signature); - WebAuthnServerProperty webAuthnServerProperty = serverPropertyProvider.provide(request); + ServerProperty webAuthnServerProperty = serverPropertyProvider.provide(request); WebAuthnAuthenticationData webAuthnAuthenticationData = new WebAuthnAuthenticationData( rawId, diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java index 2900b8e2ebd..ce3bb9d03bb 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationData.java @@ -16,8 +16,8 @@ package org.springframework.security.webauthn; +import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.ArrayUtil; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; import java.util.Collections; import java.util.List; @@ -30,11 +30,11 @@ public class WebAuthnRegistrationData { private final Set transports; private final String clientExtensionsJSON; - private final WebAuthnServerProperty serverProperty; + private final ServerProperty serverProperty; private final List expectedRegistrationExtensionIds; public WebAuthnRegistrationData(byte[] clientDataJSON, byte[] attestationObject, Set transports, String clientExtensionsJSON, - WebAuthnServerProperty serverProperty, + ServerProperty serverProperty, List expectedRegistrationExtensionIds) { this.clientDataJSON = ArrayUtil.clone(clientDataJSON); this.attestationObject = ArrayUtil.clone(attestationObject); @@ -60,7 +60,7 @@ public String getClientExtensionsJSON() { return clientExtensionsJSON; } - public WebAuthnServerProperty getServerProperty() { + public ServerProperty getServerProperty() { return serverProperty; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java index 0cd18c0ca6d..82ac1aac663 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/WebAuthnRegistrationRequestValidator.java @@ -16,8 +16,8 @@ package org.springframework.security.webauthn; +import com.webauthn4j.server.ServerProperty; import com.webauthn4j.util.Base64UrlUtil; -import org.springframework.security.webauthn.server.WebAuthnServerProperty; import org.springframework.security.webauthn.server.WebAuthnServerPropertyProvider; import org.springframework.util.Assert; @@ -43,7 +43,7 @@ public void validate(WebAuthnRegistrationRequest registrationRequest) { Assert.notNull(registrationRequest, "target must not be null"); Assert.notNull(registrationRequest.getHttpServletRequest(), "httpServletRequest must not be null"); - WebAuthnServerProperty webAuthnServerProperty = webAuthnServerPropertyProvider.provide(registrationRequest.getHttpServletRequest()); + ServerProperty webAuthnServerProperty = webAuthnServerPropertyProvider.provide(registrationRequest.getHttpServletRequest()); WebAuthnRegistrationData webAuthnRegistrationData = new WebAuthnRegistrationData( Base64UrlUtil.decode(registrationRequest.getClientDataBase64Url()), diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java index 8218f843195..86958e18a42 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticator.java @@ -16,6 +16,7 @@ package org.springframework.security.webauthn.authenticator; +import com.webauthn4j.data.AuthenticatorTransport; import org.springframework.security.webauthn.userdetails.WebAuthnUserDetailsService; import java.util.Set; @@ -36,7 +37,7 @@ public interface WebAuthnAuthenticator { void setCounter(long counter); - Set getTransports(); + Set getTransports(); String getClientExtensions(); diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java index 2f78f3ef9c4..092944e7a7c 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorImpl.java @@ -17,6 +17,7 @@ package org.springframework.security.webauthn.authenticator; +import com.webauthn4j.data.AuthenticatorTransport; import com.webauthn4j.util.ArrayUtil; import java.util.Arrays; @@ -31,7 +32,7 @@ public class WebAuthnAuthenticatorImpl implements WebAuthnAuthenticator { private String name; private byte[] attestationObject; private long counter; - private Set transports; + private Set transports; private String clientExtensions; // ~ Constructor @@ -51,7 +52,7 @@ public WebAuthnAuthenticatorImpl( String name, byte[] attestationObject, long counter, - Set transports, + Set transports, String clientExtensions) { this.credentialId = credentialId; this.name = name; @@ -88,7 +89,7 @@ public void setCounter(long counter) { } @Override - public Set getTransports() { + public Set getTransports() { return transports; } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java b/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java deleted file mode 100644 index 6591f64ec06..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/authenticator/WebAuthnAuthenticatorTransport.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.authenticator; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; - -/** - * Authenticators may implement various transports for communicating with clients. - * This enumeration defines hints as to how clients might communicate with a particular authenticator in order to - * obtain an assertion for a specific credential. Note that these hints represent the WebAuthn Relying Party's - * best belief as to how an authenticator may be reached. - * - * @see - * 5.10.4. Authenticator Transport Enumeration (enum WebAuthnAuthenticatorTransport) - */ -public enum WebAuthnAuthenticatorTransport { - - /** - * Indicates the respective authenticator can be contacted over removable USB. - */ - USB("usb"), - - /** - * Indicates the respective authenticator can be contacted over Near Field Communication (NFC). - */ - NFC("nfc"), - - /** - * Indicates the respective authenticator can be contacted over Bluetooth Smart - * (Bluetooth Low Energy / BLE). - */ - BLE("ble"); - - private String value; - - WebAuthnAuthenticatorTransport(String value) { - this.value = value; - } - - public static WebAuthnAuthenticatorTransport create(String value) { - if (value == null) { - return null; - } - switch (value) { - case "usb": - return USB; - case "nfc": - return NFC; - case "ble": - return BLE; - default: - throw new IllegalArgumentException("value '" + value + "' is out of range"); - } - } - - @JsonCreator - private static WebAuthnAuthenticatorTransport deserialize(String value) throws InvalidFormatException { - try { - return create(value); - } catch (IllegalArgumentException e) { - throw new InvalidFormatException(null, "value is out of range", value, WebAuthnAuthenticatorTransport.class); - } - } - - @JsonValue - public String getValue() { - return value; - } - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java index 0da1a46c7d1..aeebce4859d 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/HttpSessionWebAuthnChallengeRepository.java @@ -16,6 +16,8 @@ package org.springframework.security.webauthn.challenge; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.data.client.challenge.DefaultChallenge; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.util.Assert; @@ -45,12 +47,12 @@ public class HttpSessionWebAuthnChallengeRepository implements WebAuthnChallenge // ======================================================================================================== @Override - public WebAuthnChallenge generateChallenge() { - return new WebAuthnChallengeImpl(); + public Challenge generateChallenge() { + return new DefaultChallenge(); } @Override - public void saveChallenge(WebAuthnChallenge challenge, HttpServletRequest request) { + public void saveChallenge(Challenge challenge, HttpServletRequest request) { if (challenge == null) { HttpSession session = request.getSession(false); if (session != null) { @@ -63,16 +65,16 @@ public void saveChallenge(WebAuthnChallenge challenge, HttpServletRequest reques } @Override - public WebAuthnChallenge loadChallenge(HttpServletRequest request) { + public Challenge loadChallenge(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } - return (WebAuthnChallenge) session.getAttribute(this.sessionAttributeName); + return (Challenge) session.getAttribute(this.sessionAttributeName); } /** - * Sets the {@link HttpSession} attribute name that the {@link WebAuthnChallenge} is stored in + * Sets the {@link HttpSession} attribute name that the {@link Challenge} is stored in * * @param sessionAttributeName the new attribute name to use */ diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java deleted file mode 100644 index a7b58fdabe1..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallenge.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.challenge; - -public interface WebAuthnChallenge { - - /** - * Gets the challenge value. Cannot be null. - * - * @return the challenge value - */ - byte[] getValue(); -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java deleted file mode 100644 index ffe11305479..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeImpl.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.challenge; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.webauthn4j.util.ArrayUtil; -import com.webauthn4j.util.Base64UrlUtil; -import org.springframework.util.Assert; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.UUID; - -public class WebAuthnChallengeImpl implements WebAuthnChallenge { - private final byte[] value; - - /** - * Creates a new instance - * - * @param value the value of the challenge - */ - public WebAuthnChallengeImpl(byte[] value) { - Assert.notNull(value, "value cannot be null"); - this.value = ArrayUtil.clone(value); - } - - public WebAuthnChallengeImpl(String base64urlString) { - Assert.notNull(base64urlString, "base64urlString cannot be null"); - this.value = Base64UrlUtil.decode(base64urlString); - } - - public WebAuthnChallengeImpl() { - UUID uuid = UUID.randomUUID(); - long hi = uuid.getMostSignificantBits(); - long lo = uuid.getLeastSignificantBits(); - this.value = ByteBuffer.allocate(16).putLong(hi).putLong(lo).array(); - } - - @JsonCreator - public WebAuthnChallengeImpl create(String base64url) { - return new WebAuthnChallengeImpl(Base64UrlUtil.decode(base64url)); - } - - @JsonValue - public String toBase64UrlString() { - return Base64UrlUtil.encodeToString(getValue()); - } - - @Override - public byte[] getValue() { - return ArrayUtil.clone(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WebAuthnChallengeImpl that = (WebAuthnChallengeImpl) o; - return Arrays.equals(value, that.value); - } - - @Override - public int hashCode() { - return Arrays.hashCode(value); - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java index b6cf462b257..76b59ddc1e6 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/challenge/WebAuthnChallengeRepository.java @@ -17,12 +17,14 @@ package org.springframework.security.webauthn.challenge; +import com.webauthn4j.data.client.challenge.Challenge; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** - * An API to allow changing the method in which the expected {@link WebAuthnChallenge} is + * An API to allow changing the method in which the expected {@link Challenge} is * associated to the {@link HttpServletRequest}. For example, it may be stored in * {@link HttpSession}. * @@ -31,38 +33,38 @@ public interface WebAuthnChallengeRepository { /** - * Generates a {@link WebAuthnChallenge} + * Generates a {@link Challenge} * - * @return the {@link WebAuthnChallenge} that was generated. Cannot be null. + * @return the {@link Challenge} that was generated. Cannot be null. */ - WebAuthnChallenge generateChallenge(); + Challenge generateChallenge(); /** - * Saves the {@link WebAuthnChallenge} using the {@link HttpServletRequest} and - * {@link HttpServletResponse}. If the {@link WebAuthnChallenge} is null, it is the same as + * Saves the {@link Challenge} using the {@link HttpServletRequest} and + * {@link HttpServletResponse}. If the {@link Challenge} is null, it is the same as * deleting it. * - * @param challenge the {@link WebAuthnChallenge} to save or null to delete + * @param challenge the {@link Challenge} to save or null to delete * @param request the {@link HttpServletRequest} to use */ - void saveChallenge(WebAuthnChallenge challenge, HttpServletRequest request); + void saveChallenge(Challenge challenge, HttpServletRequest request); /** - * Loads the expected {@link WebAuthnChallenge} from the {@link HttpServletRequest} + * Loads the expected {@link Challenge} from the {@link HttpServletRequest} * * @param request the {@link HttpServletRequest} to use - * @return the {@link WebAuthnChallenge} or null if none exists + * @return the {@link Challenge} or null if none exists */ - WebAuthnChallenge loadChallenge(HttpServletRequest request); + Challenge loadChallenge(HttpServletRequest request); /** - * Loads or generates {@link WebAuthnChallenge} from the {@link HttpServletRequest} + * Loads or generates {@link Challenge} from the {@link HttpServletRequest} * * @param request the {@link HttpServletRequest} to use - * @return the {@link WebAuthnChallenge} or null if none exists + * @return the {@link Challenge} or null if none exists */ - default WebAuthnChallenge loadOrGenerateChallenge(HttpServletRequest request) { - WebAuthnChallenge challenge = this.loadChallenge(request); + default Challenge loadOrGenerateChallenge(HttpServletRequest request) { + Challenge challenge = this.loadChallenge(request); if (challenge == null) { challenge = this.generateChallenge(); this.saveChallenge(challenge, request); diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java deleted file mode 100644 index 45d74a0e1d1..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnOrigin.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.server; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; - -import javax.servlet.ServletRequest; -import java.io.Serializable; -import java.net.URI; -import java.util.Objects; - -/** - * {@link WebAuthnOrigin} contains the fully qualified origin of the requester, as provided to the authenticator - * by the client. - * - * @see 5.10.1. Client Data Used in WebAuthn Signatures - origin - */ -public class WebAuthnOrigin implements Serializable { - - private static final String SCHEME_HTTPS = "https"; - private static final String SCHEME_HTTP = "http"; - private static final String SCHEME_ERROR_MESSAGE = "scheme must be 'http' or 'https'"; - - private String scheme; - private String host; - private int port; - - public WebAuthnOrigin(String scheme, String host, int port) { - if (!Objects.equals(SCHEME_HTTPS, scheme) && !Objects.equals(SCHEME_HTTP, scheme)) { - throw new IllegalArgumentException(SCHEME_ERROR_MESSAGE); - } - - this.scheme = scheme; - this.host = host; - this.port = port; - } - - public WebAuthnOrigin(String originUrl) { - URI uri = URI.create(originUrl); - this.scheme = uri.getScheme(); - this.host = uri.getHost(); - int originPort = uri.getPort(); - - if (originPort == -1) { - if (this.scheme == null) { - throw new IllegalArgumentException(SCHEME_ERROR_MESSAGE); - } - switch (this.scheme) { - case SCHEME_HTTPS: - originPort = 443; - break; - case SCHEME_HTTP: - originPort = 80; - break; - default: - throw new IllegalArgumentException(SCHEME_ERROR_MESSAGE); - } - } - this.port = originPort; - } - - public static WebAuthnOrigin create(ServletRequest request) { - return new WebAuthnOrigin(request.getScheme(), request.getServerName(), request.getServerPort()); - } - - public static WebAuthnOrigin create(String value) { - try { - return new WebAuthnOrigin(value); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("value is out of range: " + e.getMessage()); - } - } - - @JsonCreator - private static WebAuthnOrigin deserialize(String value) throws InvalidFormatException { - try { - return create(value); - } catch (IllegalArgumentException e) { - throw new InvalidFormatException(null, "value is out of range", value, WebAuthnOrigin.class); - } - } - - public String getScheme() { - return scheme; - } - - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - - @JsonValue - @Override - public String toString() { - String result = this.scheme + "://" + this.host; - switch (this.scheme) { - case SCHEME_HTTPS: - if (this.port != 443) { - result += ":" + this.port; - } - break; - case SCHEME_HTTP: - if (this.port != 80) { - result += ":" + this.port; - } - break; - default: - throw new IllegalStateException(); - } - return result; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof WebAuthnOrigin)) return false; - - WebAuthnOrigin origin = (WebAuthnOrigin) o; - - if (port != origin.port) return false; - //noinspection SimplifiableIfStatement - if (!scheme.equals(origin.scheme)) return false; - return host.equals(origin.host); - } - - @Override - public int hashCode() { - int result = scheme.hashCode(); - result = 31 * result + host.hashCode(); - result = 31 * result + port; - return result; - } -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java deleted file mode 100644 index 214f3dd6740..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerProperty.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.webauthn.server; - -import com.webauthn4j.util.ArrayUtil; -import org.springframework.security.webauthn.challenge.WebAuthnChallenge; - -import java.util.Arrays; -import java.util.Objects; - -public class WebAuthnServerProperty { - // ~ Instance fields - // ================================================================================================ - - private final WebAuthnOrigin origin; - private final String rpId; - private final WebAuthnChallenge challenge; - private final byte[] tokenBindingId; - - // ~ Constructor - // ======================================================================================================== - - public WebAuthnServerProperty(WebAuthnOrigin origin, String rpId, WebAuthnChallenge challenge, byte[] tokenBindingId) { - this.origin = origin; - this.rpId = rpId; - this.challenge = challenge; - this.tokenBindingId = ArrayUtil.clone(tokenBindingId); - } - - // ~ Methods - // ======================================================================================================== - - /** - * Returns the {@link WebAuthnOrigin} - * - * @return the {@link WebAuthnOrigin} - */ - public WebAuthnOrigin getOrigin() { - return origin; - } - - /** - * Returns the rpId - * - * @return the rpId - */ - public String getRpId() { - return rpId; - } - - /** - * Returns the {@link WebAuthnChallenge} - * - * @return the {@link WebAuthnChallenge} - */ - public WebAuthnChallenge getChallenge() { - return challenge; - } - - /** - * Returns the tokenBindingId - * - * @return the tokenBindingId - */ - public byte[] getTokenBindingId() { - return ArrayUtil.clone(tokenBindingId); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WebAuthnServerProperty that = (WebAuthnServerProperty) o; - return Objects.equals(origin, that.origin) && - Objects.equals(rpId, that.rpId) && - Objects.equals(challenge, that.challenge) && - Arrays.equals(tokenBindingId, that.tokenBindingId); - } - - @Override - public int hashCode() { - - int result = Objects.hash(origin, rpId, challenge); - result = 31 * result + Arrays.hashCode(tokenBindingId); - return result; - } - -} diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java index 9291e46b7e3..51510e82a9f 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProvider.java @@ -17,21 +17,23 @@ package org.springframework.security.webauthn.server; +import com.webauthn4j.server.ServerProperty; + import javax.servlet.http.HttpServletRequest; /** - * Provides {@link WebAuthnServerProperty} instance associated with {@link HttpServletRequest} + * Provides {@link ServerProperty} instance associated with {@link HttpServletRequest} * * @author Yoshikazu Nojima */ public interface WebAuthnServerPropertyProvider { /** - * Provides {@link WebAuthnServerProperty} + * Provides {@link ServerProperty} * * @param request http servlet request - * @return the {@link WebAuthnServerProperty} + * @return the {@link ServerProperty} */ - WebAuthnServerProperty provide(HttpServletRequest request); + ServerProperty provide(HttpServletRequest request); } diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java index 98f702ad5e0..2cd03fc9948 100644 --- a/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java +++ b/webauthn/src/main/java/org/springframework/security/webauthn/server/WebAuthnServerPropertyProviderImpl.java @@ -16,10 +16,13 @@ package org.springframework.security.webauthn.server; -import org.springframework.security.webauthn.challenge.WebAuthnChallenge; +import com.webauthn4j.data.client.Origin; +import com.webauthn4j.data.client.challenge.Challenge; +import com.webauthn4j.server.ServerProperty; import org.springframework.security.webauthn.challenge.WebAuthnChallengeRepository; import org.springframework.util.Assert; +import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; /** @@ -41,12 +44,16 @@ public WebAuthnServerPropertyProviderImpl(EffectiveRpIdProvider effectiveRpIdPro this.webAuthnChallengeRepository = webAuthnChallengeRepository; } - public WebAuthnServerProperty provide(HttpServletRequest request) { + public ServerProperty provide(HttpServletRequest request) { - WebAuthnOrigin origin = WebAuthnOrigin.create(request); + Origin origin = createOrigin(request); String effectiveRpId = effectiveRpIdProvider.getEffectiveRpId(request); - WebAuthnChallenge challenge = webAuthnChallengeRepository.loadOrGenerateChallenge(request); + Challenge challenge = webAuthnChallengeRepository.loadOrGenerateChallenge(request); - return new WebAuthnServerProperty(origin, effectiveRpId, challenge, null); // tokenBinding is not supported by Servlet API as of 4.0 + return new ServerProperty(origin, effectiveRpId, challenge, null); // tokenBinding is not supported by Servlet API as of 4.0 + } + + private static Origin createOrigin(ServletRequest request) { + return new Origin(request.getScheme(), request.getServerName(), request.getServerPort()); } } From c9411eb6a88c5335e0803def6a6fc559c7537089 Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 19 Oct 2019 10:42:20 +0900 Subject: [PATCH 8/9] Just for review: Remove unnecessary classes (package-info.java) --- .../security/webauthn/package-info.java | 20 ------------------- .../webauthn/userdetails/package-info.java | 20 ------------------- 2 files changed, 40 deletions(-) delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/package-info.java delete mode 100644 webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/package-info.java b/webauthn/src/main/java/org/springframework/security/webauthn/package-info.java deleted file mode 100644 index 7550d9bfccd..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Spring Security WebAuthn classes - */ -package org.springframework.security.webauthn; diff --git a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java b/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java deleted file mode 100644 index 7def00d0f80..00000000000 --- a/webauthn/src/main/java/org/springframework/security/webauthn/userdetails/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2002-2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * WebAuthn specific UserDetails classes - */ -package org.springframework.security.webauthn.userdetails; From ba25a7e79792e20286c1126b0dfd5be761292d14 Mon Sep 17 00:00:00 2001 From: ynojima Date: Sat, 19 Oct 2019 11:27:55 +0900 Subject: [PATCH 9/9] Just for review: Remove MFA test classes --- ...AuthenticationProviderConfigurerTests.java | 49 ----------- .../MFATokenEvaluatorImplTests.java | 72 ---------------- ...ultiFactorAuthenticationProviderTests.java | 86 ------------------- .../MultiFactorAuthenticationTokenTests.java | 61 ------------- 4 files changed, 268 deletions(-) delete mode 100644 config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java delete mode 100644 core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java delete mode 100644 core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java delete mode 100644 core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java diff --git a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java deleted file mode 100644 index 5acafecc0ea..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/authentication/configurers/mfa/MultiFactorAuthenticationProviderConfigurerTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.config.annotation.authentication.configurers.mfa; - -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.MFATokenEvaluator; -import org.springframework.security.authentication.MultiFactorAuthenticationProvider; -import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.annotation.authentication.configurers.mfa.MultiFactorAuthenticationProviderConfigurer.multiFactorAuthenticationProvider; - -public class MultiFactorAuthenticationProviderConfigurerTests { - - @Test - public void test(){ - AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); - MFATokenEvaluator mfaTokenEvaluator = mock(MFATokenEvaluator.class); - MultiFactorAuthenticationProviderConfigurer configurer - = multiFactorAuthenticationProvider(delegatedAuthenticationProvider); - configurer.mfaTokenEvaluator(mfaTokenEvaluator); - ProviderManagerBuilder providerManagerBuilder = mock(ProviderManagerBuilder.class); - configurer.configure(providerManagerBuilder); - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AuthenticationProvider.class); - verify(providerManagerBuilder).authenticationProvider(argumentCaptor.capture()); - MultiFactorAuthenticationProvider authenticationProvider = (MultiFactorAuthenticationProvider) argumentCaptor.getValue(); - - assertThat(authenticationProvider.getAuthenticationProvider()).isEqualTo(delegatedAuthenticationProvider); - assertThat(authenticationProvider.getMFATokenEvaluator()).isEqualTo(mfaTokenEvaluator); - } -} diff --git a/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java b/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java deleted file mode 100644 index 0e07b4193b1..00000000000 --- a/core/src/test/java/org/springframework/security/authentication/MFATokenEvaluatorImplTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication; - -import org.junit.Test; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.MFAUserDetails; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MFATokenEvaluatorImplTests { - - // ~ Methods - // ======================================================================================================== - @Test - public void testCorrectOperationIsAnonymous() { - MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new MultiFactorAuthenticationToken("ignored", - "ignored", AuthorityUtils.createAuthorityList("ignored")))).isTrue(); - assertThat(mfaTokenEvaluator.isMultiFactorAuthentication(new TestingAuthenticationToken("ignored", - "ignored", AuthorityUtils.createAuthorityList("ignored")))).isFalse(); - } - - @Test - public void testIsSingleFactorAuthenticationAllowedWithNonMFAUserDetailsPrincipal(){ - MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken("", "")); - assertThat(result).isFalse(); - } - - @Test - public void testIsSingleFactorAuthenticationAllowedWithMFAUserDetailsPrincipal(){ - MFAUserDetails mfaUserDetails = mock(MFAUserDetails.class); - when(mfaUserDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); - MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - boolean result = mfaTokenEvaluator.isSingleFactorAuthenticationAllowed(new TestingAuthenticationToken(mfaUserDetails, "")); - assertThat(result).isTrue(); - } - - @Test - public void testGettersSetters() { - MFATokenEvaluatorImpl mfaTokenEvaluator = new MFATokenEvaluatorImpl(); - - assertThat(MultiFactorAuthenticationToken.class).isEqualTo( - mfaTokenEvaluator.getMultiFactorClass()); - mfaTokenEvaluator.setMultiFactorClass(TestingAuthenticationToken.class); - assertThat(mfaTokenEvaluator.getMultiFactorClass()).isEqualTo( - TestingAuthenticationToken.class); - - assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isTrue(); - mfaTokenEvaluator.setSingleFactorAuthenticationAllowed(false); - assertThat(mfaTokenEvaluator.isSingleFactorAuthenticationAllowed()).isFalse(); - - } - -} diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java deleted file mode 100644 index 86aadeff2d9..00000000000 --- a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationProviderTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication; - - -import org.junit.Test; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.MFAUserDetails; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MultiFactorAuthenticationProviderTests { - - @Test - public void authenticate_with_singleFactorAuthenticationAllowedOption_false_test(){ - AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); - MFAUserDetails userDetails = mock(MFAUserDetails.class); - when(userDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList()); - authenticationToken.setDetails(userDetails); - when(delegatedAuthenticationProvider.supports(any())).thenReturn(true); - when(delegatedAuthenticationProvider.authenticate(any())) - .thenReturn(new UsernamePasswordAuthenticationToken( - "principal", - "credentials", - Collections.singletonList(new SimpleGrantedAuthority("ROLE_DUMMY")) - )); - - MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); - Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("dummy", "dummy")); - - assertThat(result).isInstanceOf(MultiFactorAuthenticationToken.class); - assertThat(result.getPrincipal()).isEqualTo("principal"); - assertThat(result.getCredentials()).isEqualTo("credentials"); - assertThat(result.getAuthorities()).isEmpty(); - - } - - @Test - public void authenticate_with_singleFactorAuthenticationAllowedOption_true_test(){ - AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); - MFAUserDetails userDetails = mock(MFAUserDetails.class); - when(userDetails.isSingleFactorAuthenticationAllowed()).thenReturn(true); - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, Collections.emptyList()); - authenticationToken.setDetails(userDetails); - when(delegatedAuthenticationProvider.supports(any())).thenReturn(true); - when(delegatedAuthenticationProvider.authenticate(any())) - .thenReturn(authenticationToken); - - MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); - Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken("dummy", "dummy")); - - assertThat(result).isInstanceOf(UsernamePasswordAuthenticationToken.class); - assertThat(result).isEqualTo(result); - } - - @Test(expected = IllegalArgumentException.class) - public void authenticate_with_invalid_AuthenticationToken_test(){ - AuthenticationProvider delegatedAuthenticationProvider = mock(AuthenticationProvider.class); - when(delegatedAuthenticationProvider.supports(any())).thenReturn(false); - - MultiFactorAuthenticationProvider provider = new MultiFactorAuthenticationProvider(delegatedAuthenticationProvider, new MFATokenEvaluatorImpl()); - provider.authenticate(new TestingAuthenticationToken("dummy", "dummy")); - } - -} diff --git a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java b/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java deleted file mode 100644 index 99a9e42cb4c..00000000000 --- a/core/src/test/java/org/springframework/security/authentication/MultiFactorAuthenticationTokenTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authentication; - -import org.junit.Test; -import org.springframework.security.core.authority.AuthorityUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -public class MultiFactorAuthenticationTokenTests { - // ~ Methods - // ======================================================================================================== - - @Test - public void authenticatedPropertyContractIsSatisfied() { - MultiFactorAuthenticationToken token = new MultiFactorAuthenticationToken( - "Test", "Password", AuthorityUtils.NO_AUTHORITIES); - - // check default given we passed some GrantedAuthority[]s (well, we passed empty - // list) - assertThat(token.isAuthenticated()).isTrue(); - - // check explicit set to untrusted (we can safely go from trusted to untrusted, - // but not the reverse) - token.setAuthenticated(false); - assertThat(token.isAuthenticated()).isFalse(); - - } - - @Test - public void gettersReturnCorrectData() { - MultiFactorAuthenticationToken token = new MultiFactorAuthenticationToken( - "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); - assertThat(token.getPrincipal()).isEqualTo("Test"); - assertThat(token.getCredentials()).isEqualTo("Password"); - assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_ONE"); - assertThat(AuthorityUtils.authorityListToSet(token.getAuthorities())).contains("ROLE_TWO"); - } - - @Test(expected = NoSuchMethodException.class) - public void testNoArgConstructorDoesntExist() throws Exception { - Class clazz = UsernamePasswordAuthenticationToken.class; - clazz.getDeclaredConstructor((Class[]) null); - } - -}