Expected Behavior
Currently PasswordEncoderFactories class has method createDelegatingPasswordEncoder() which creates MD5 based Password Encoder.
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
We are using spring-security dependency in our project. We are planning to replace our jdk17 with a FIPS compliant jdk17. FIPS compliant JDK is using BouncyCastle which does not allows MD5. We need some enhancement from spring-security so that when FIPS mode is enabled, it does not create encoder which are not allowed by BouncyCastle.
We do not want to re-write our security context implementation without spring-security. So hoping for community guidance.
Stacktrace when MD5 is missing in MessageDigests provided by FIPS compliant JDK.
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 13:52:20.750 [main][] ERROR o.s.b.SpringApplication - Application run failed org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'filterChain' defined in class path resource [com/salesforce/zos/web/security/ForceHttpsWebSecurityConfig.class]: Unsatisfied dependency expressed through method 'filterChain' parameter 0: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: No such hashing algorithm at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:797) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:541) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1330) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:558) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:518) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:949) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:615) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440) at org.springframework.boot.SpringApplication.run(SpringApplication.java:324) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) at com.salesforce.Application.main(Application.java:24) Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: No such hashing algorithm at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:650) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:484) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1330) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:558) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:518) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:343) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:906) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:784) ... 19 common frames omitted Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception with message: No such hashing algorithm at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171) at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:646) ... 31 common frames omitted Caused by: java.lang.IllegalStateException: No such hashing algorithm at org.springframework.security.crypto.password.Digester.createDigest(Digester.java:69) at org.springframework.security.crypto.password.Digester.
(Digester.java:44) at org.springframework.security.crypto.password.MessageDigestPasswordEncoder. (MessageDigestPasswordEncoder.java:104) at org.springframework.security.crypto.factory.PasswordEncoderFactories.createDelegatingPasswordEncoder(PasswordEncoderFactories.java:78) at org.springframework.security.authentication.dao.DaoAuthenticationProvider. (DaoAuthenticationProvider.java:64) at org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer.configure(InitializeUserDetailsBeanManagerConfigurer.java:68) at org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer$InitializeUserDetailsManagerConfigurer.configure(InitializeUserDetailsBeanManagerConfigurer.java:55) at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.configure(AbstractConfiguredSecurityBuilder.java:355) at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:309) at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38) at org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.getAuthenticationManager(AuthenticationConfiguration.java:121) at org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.authenticationManager(HttpSecurityConfiguration.java:132) at org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity(HttpSecurityConfiguration.java:108) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ... 32 common frames omitted Caused by: java.security.NoSuchAlgorithmException: MD5 MessageDigest not available at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:159) at java.base/java.security.MessageDigest.getInstance(MessageDigest.java:185) at org.springframework.security.crypto.password.Digester.createDigest(Digester.java:66)
Comment From: rwinch
You can change the PasswordEncoder by exposing it as a Bean. For FIPS you will likely want to use Pbkdf2.
@Bean
public static Pbkdf2PasswordEncoder passwordEncoder() {
return new Pbkdf2PasswordEncoder(....); // get the parameters from the FIPS spec
}
Comment From: emWolf09
@rwinch the issue of not having MD5 in jdk file with BouncyCastle will not be resolved even if we create a bean of password encode as you suggested. Reason is if you see InitializeUserDetailsBeanManagerConfigurer.configure(), it creates a object of DaoAuthenticationProvider first with createDelegatingPasswordEncoder(). And, when It will see that MD5 is not provided by jdk, code flow will fail and result into failed spring security context creation.
See line number 68 in InitializeUserDetailsBeanManagerConfigurer class for below code. (spring-security 6.2.2) where the issue is
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); if (passwordEncoder != null) { provider.setPasswordEncoder(passwordEncoder); }
Instead you should first see if there is an already created PasswordEncode bean provided by app context and pass it into constructor of DaoAuthenticationProvider() like below
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); DaoAuthenticationProvider provider; if (passwordEncoder != null) { // add provided encoder. provider = DaoAuthenticationProvider(passwordEncoder); } else { // add delegation provider. DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); }
Comment From: rwinch
Thanks for your additional feedback. It sounds like what you would like is to easily prevent PasswordEncoderFactories.createDelegatingPasswordEncoder() from being executed because it tries to use MD5 and this is not allowed in the FIPS JDK & is preventing you from being able to be FIPS certified.
You can prevent PasswordEncoderFactories.createDelegatingPasswordEncoder() from being executed by specifying the DaoAuthenticationProvider as a Bean. An outline is shown below:
@Bean
static UserDetailsService userDetailsService(PasswordEncoder encoder) {
// ...
}
@Bean
static PasswordEncoder passwordEncoder() {
// ...
}
@Bean
static DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(passwordEncoder);
authProvider.setUserDetailsService(userDetailsService);
// ...
return authProvider;
}
Spring Security's configuration could be more efficient by ensuring that InitializeUserDetailsBeanManagerConfigurer injects the PasswordEncoder into the constructor as you suggested above. I've created https://github.com/spring-projects/spring-security/issues/14691 to track making that change