From d5603a944d0d13bfac4ae0166027394dc016c168 Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Mon, 3 Apr 2023 14:37:42 -0300 Subject: [PATCH] Avoid exception if PBKDF2WithHmacSHA256 is not available Issue gh-12873 --- crypto/spring-security-crypto.gradle | 1 + .../factory/PasswordEncoderFactories.java | 30 +++++++++++++++++-- .../PasswordEncoderFactoriesTests.java | 18 ++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/crypto/spring-security-crypto.gradle b/crypto/spring-security-crypto.gradle index b45a20b0d8..030e01aa79 100644 --- a/crypto/spring-security-crypto.gradle +++ b/crypto/spring-security-crypto.gradle @@ -11,5 +11,6 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-engine" testImplementation "org.mockito:mockito-core" testImplementation "org.mockito:mockito-junit-jupiter" + testImplementation "org.mockito:mockito-inline" testImplementation "org.springframework:spring-test" } diff --git a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java index 67b1b9576f..c7fb5bf5fb 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java +++ b/crypto/src/main/java/org/springframework/security/crypto/factory/PasswordEncoderFactories.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -16,8 +16,13 @@ package org.springframework.security.crypto.factory; +import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -34,6 +39,8 @@ import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; */ public final class PasswordEncoderFactories { + private static final Log logger = LogFactory.getLog(PasswordEncoderFactories.class); + private PasswordEncoderFactories() { } @@ -78,7 +85,8 @@ public final class PasswordEncoderFactories { encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5")); encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5()); - encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()); + putIfAlgorithmSupported("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder::defaultsForSpringSecurity_v5_8, + encoders); encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1()); encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()); encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1")); @@ -90,4 +98,22 @@ public final class PasswordEncoderFactories { return new DelegatingPasswordEncoder(encodingId, encoders); } + private static void putIfAlgorithmSupported(String encodingId, Supplier encoderSupplier, + Map encoders) { + try { + PasswordEncoder passwordEncoder = encoderSupplier.get(); + encoders.put(encodingId, passwordEncoder); + } + catch (Exception ex) { + if (ex.getCause() instanceof NoSuchAlgorithmException) { + logger.warn(String.format( + "Cannot create PasswordEncoder with encodingId [%s] because the algorithm is not available", + encodingId), ex); + } + else { + throw ex; + } + } + } + } diff --git a/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java b/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java index 0b2dbd4a02..526e45c85d 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/factory/PasswordEncoderFactoriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -16,11 +16,17 @@ package org.springframework.security.crypto.factory; +import java.security.NoSuchAlgorithmException; + import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.mockStatic; /** * @author Rob Winch @@ -123,4 +129,14 @@ public class PasswordEncoderFactoriesTests { assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue(); } + @Test + void constructWhenAlgorithmNotAvailableThenSkip() { + try (MockedStatic pbkdf2PasswordEncoderMock = mockStatic(Pbkdf2PasswordEncoder.class)) { + pbkdf2PasswordEncoderMock.when(Pbkdf2PasswordEncoder::defaultsForSpringSecurity_v5_8) + .thenThrow(new IllegalArgumentException(new NoSuchAlgorithmException())); + + assertThatNoException().isThrownBy(PasswordEncoderFactories::createDelegatingPasswordEncoder); + } + } + }