Skip to content

FIPS based enhancement in PasswordEncoderFactories  #14670

Description

@emWolf09

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)

Activity

  1. rwinch commented on Mar 1, 2024

    @rwinch
    Member

    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
    }
  2. emWolf09 commented on Mar 4, 2024

    @emWolf09
    Author

    @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();
    }

  3. rwinch commented on Mar 5, 2024

    @rwinch
    Member

    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 #14691 to track making that change

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions