diff --git a/crypto/spring-security-crypto.gradle b/crypto/spring-security-crypto.gradle index 2b010da16a..8370c1324c 100644 --- a/crypto/spring-security-crypto.gradle +++ b/crypto/spring-security-crypto.gradle @@ -1,3 +1,7 @@ +plugins { + id 'security-nullability' +} + apply plugin: 'io.spring.convention.spring-module' dependencies { diff --git a/crypto/src/main/java/org/springframework/security/crypto/argon2/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/argon2/package-info.java new file mode 100644 index 0000000000..5c8d50a8ac --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/argon2/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.argon2; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java index 4f19f52de0..910b2d5606 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java +++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCrypt.java @@ -210,9 +210,9 @@ public class BCrypt { static final int MAX_LOG_ROUNDS = 31; // Expanded Blowfish key - private int P[]; + private int P[] = new int[0]; - private int S[]; + private int S[] = new int[0]; /** * Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java index d17511b033..84dfb08cd3 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java @@ -22,6 +22,7 @@ import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.security.crypto.password.PasswordEncoder; @@ -43,7 +44,7 @@ public class BCryptPasswordEncoder implements PasswordEncoder { private final BCryptVersion version; - private final SecureRandom random; + private final @Nullable SecureRandom random; public BCryptPasswordEncoder() { this(-1); @@ -67,7 +68,7 @@ public class BCryptPasswordEncoder implements PasswordEncoder { * @param version the version of bcrypt, can be 2a,2b,2y * @param random the secure random instance to use */ - public BCryptPasswordEncoder(BCryptVersion version, SecureRandom random) { + public BCryptPasswordEncoder(BCryptVersion version, @Nullable SecureRandom random) { this(version, -1, random); } @@ -75,7 +76,7 @@ public class BCryptPasswordEncoder implements PasswordEncoder { * @param strength the log rounds to use, between 4 and 31 * @param random the secure random instance to use */ - public BCryptPasswordEncoder(int strength, SecureRandom random) { + public BCryptPasswordEncoder(int strength, @Nullable SecureRandom random) { this(BCryptVersion.$2A, strength, random); } @@ -92,7 +93,7 @@ public class BCryptPasswordEncoder implements PasswordEncoder { * @param strength the log rounds to use, between 4 and 31 * @param random the secure random instance to use */ - public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) { + public BCryptPasswordEncoder(BCryptVersion version, int strength, @Nullable SecureRandom random) { if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) { throw new IllegalArgumentException("Bad strength"); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/package-info.java new file mode 100644 index 0000000000..49fe1d1619 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.bcrypt; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java b/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java index 093de89fdd..a5d2e2f7e1 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java +++ b/crypto/src/main/java/org/springframework/security/crypto/codec/Utf8.java @@ -22,6 +22,8 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import org.jspecify.annotations.Nullable; + /** * UTF-8 Charset encoder/decoder. *

@@ -39,7 +41,7 @@ public final class Utf8 { /** * Get the bytes of the String in UTF-8 encoded form. */ - public static byte[] encode(CharSequence string) { + public static byte[] encode(@Nullable CharSequence string) { try { ByteBuffer bytes = CHARSET.newEncoder().encode(CharBuffer.wrap(string)); byte[] bytesCopy = new byte[bytes.limit()]; diff --git a/crypto/src/main/java/org/springframework/security/crypto/codec/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/codec/package-info.java index e560395216..ca4385bb84 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/codec/package-info.java +++ b/crypto/src/main/java/org/springframework/security/crypto/codec/package-info.java @@ -17,4 +17,7 @@ /** * Internal codec classes. Only intended for use within the framework. */ +@NullMarked package org.springframework.security.crypto.codec; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java index d0d92910d7..f12706c79c 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java @@ -25,6 +25,8 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; +import org.jspecify.annotations.Nullable; + import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -81,7 +83,7 @@ public final class AesBytesEncryptor implements BytesEncryptor { * @param salt the hex-encoded salt value * @param ivGenerator the generator used to generate the initialization vector */ - public AesBytesEncryptor(String password, CharSequence salt, BytesKeyGenerator ivGenerator) { + public AesBytesEncryptor(String password, CharSequence salt, @Nullable BytesKeyGenerator ivGenerator) { this(password, salt, ivGenerator, CipherAlgorithm.CBC); } @@ -95,7 +97,8 @@ public final class AesBytesEncryptor implements BytesEncryptor { * @param ivGenerator the generator used to generate the initialization vector * @param alg the {@link CipherAlgorithm} to be used */ - public AesBytesEncryptor(String password, CharSequence salt, BytesKeyGenerator ivGenerator, CipherAlgorithm alg) { + public AesBytesEncryptor(String password, CharSequence salt, @Nullable BytesKeyGenerator ivGenerator, + CipherAlgorithm alg) { this(CipherUtils.newSecretKey("PBKDF2WithHmacSHA1", new PBEKeySpec(password.toCharArray(), Hex.decode(salt), 1024, 256)), ivGenerator, alg); } @@ -108,7 +111,7 @@ public final class AesBytesEncryptor implements BytesEncryptor { * {@link CipherAlgorithm} * @param alg the {@link CipherAlgorithm} to be used */ - public AesBytesEncryptor(SecretKey secretKey, BytesKeyGenerator ivGenerator, CipherAlgorithm alg) { + public AesBytesEncryptor(SecretKey secretKey, @Nullable BytesKeyGenerator ivGenerator, CipherAlgorithm alg) { this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES"); this.alg = alg; this.encryptor = alg.createCipher(); diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java index 723208caff..3641f4656c 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java @@ -32,6 +32,8 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; +import org.jspecify.annotations.Nullable; + /** * Static helper for working with the Cipher API. * @@ -109,7 +111,8 @@ final class CipherUtils { /** * Initializes the Cipher for use. */ - static void initCipher(Cipher cipher, int mode, SecretKey secretKey, AlgorithmParameterSpec parameterSpec) { + static void initCipher(Cipher cipher, int mode, SecretKey secretKey, + @Nullable AlgorithmParameterSpec parameterSpec) { try { if (parameterSpec != null) { cipher.init(mode, secretKey, parameterSpec); diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java index 9c226042f2..bf0314bdf6 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/KeyStoreKeyFactory.java @@ -25,6 +25,8 @@ import java.security.cert.Certificate; import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.RSAPublicKeySpec; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; @@ -39,7 +41,7 @@ public class KeyStoreKeyFactory { private final char[] password; - private KeyStore store; + private @Nullable KeyStore store; private final Object lock = new Object(); diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java index fd3e058a11..0ac046fdfd 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaKeyHelper.java @@ -44,6 +44,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bouncycastle.asn1.ASN1Sequence; +import org.jspecify.annotations.Nullable; /** * Reads RSA key pairs using BC provider classes but without the need to specify a crypto @@ -164,7 +165,7 @@ final class RsaKeyHelper { private static final Pattern SSH_PUB_KEY = Pattern.compile("ssh-(rsa|dsa) ([A-Za-z0-9/+]+=*) (.*)"); - private static RSAPublicKey extractPublicKey(String key) { + private static @Nullable RSAPublicKey extractPublicKey(String key) { Matcher m = SSH_PUB_KEY.matcher(key); diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java index 655ea45b08..77d81f7b0c 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaRawEncryptor.java @@ -28,6 +28,8 @@ import java.util.Base64; import javax.crypto.Cipher; +import org.jspecify.annotations.Nullable; + /** * @author Dave Syer * @since 6.3 @@ -42,7 +44,7 @@ public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHol private RSAPublicKey publicKey; - private RSAPrivateKey privateKey; + private @Nullable RSAPrivateKey privateKey; private Charset defaultCharset; @@ -70,11 +72,12 @@ public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHol this(DEFAULT_ENCODING, publicKey, null); } - public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) { + public RsaRawEncryptor(String encoding, PublicKey publicKey, @Nullable PrivateKey privateKey) { this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT); } - public RsaRawEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) { + public RsaRawEncryptor(String encoding, PublicKey publicKey, @Nullable PrivateKey privateKey, + RsaAlgorithm algorithm) { this.charset = Charset.forName(encoding); this.publicKey = (RSAPublicKey) publicKey; this.privateKey = (RSAPrivateKey) privateKey; @@ -135,7 +138,7 @@ public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHol } } - private static byte[] decrypt(byte[] text, RSAPrivateKey key, RsaAlgorithm alg) { + private static byte[] decrypt(byte[] text, @Nullable RSAPrivateKey key, RsaAlgorithm alg) { ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); try { final Cipher cipher = Cipher.getInstance(alg.getJceName()); @@ -160,7 +163,10 @@ public class RsaRawEncryptor implements BytesEncryptor, TextEncryptor, RsaKeyHol } // copied from sun.security.rsa.RSACore.getByteLength(java.math.BigInteger) - public static int getByteLength(RSAKey key) { + public static int getByteLength(@Nullable RSAKey key) { + if (key == null) { + throw new IllegalArgumentException("key cannot be null"); + } int n = key.getModulus().bitLength(); return (n + 7) >> 3; } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java index ad8b76d4fb..f9d493e2f1 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java @@ -28,6 +28,8 @@ import java.util.Base64; import javax.crypto.Cipher; +import org.jspecify.annotations.Nullable; + import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -50,7 +52,7 @@ public class RsaSecretEncryptor implements BytesEncryptor, TextEncryptor, RsaKey private final PublicKey publicKey; - private final PrivateKey privateKey; + private final @Nullable PrivateKey privateKey; private final Charset defaultCharset; @@ -120,16 +122,17 @@ public class RsaSecretEncryptor implements BytesEncryptor, TextEncryptor, RsaKey this(DEFAULT_ENCODING, publicKey, null); } - public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey) { + public RsaSecretEncryptor(String encoding, PublicKey publicKey, @Nullable PrivateKey privateKey) { this(encoding, publicKey, privateKey, RsaAlgorithm.DEFAULT); } - public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm) { + public RsaSecretEncryptor(String encoding, PublicKey publicKey, @Nullable PrivateKey privateKey, + RsaAlgorithm algorithm) { this(encoding, publicKey, privateKey, algorithm, DEFAULT_SALT, false); } - public RsaSecretEncryptor(String encoding, PublicKey publicKey, PrivateKey privateKey, RsaAlgorithm algorithm, - String salt, boolean gcm) { + public RsaSecretEncryptor(String encoding, PublicKey publicKey, @Nullable PrivateKey privateKey, + RsaAlgorithm algorithm, String salt, boolean gcm) { this.charset = Charset.forName(encoding); this.publicKey = publicKey; this.privateKey = privateKey; @@ -206,7 +209,7 @@ public class RsaSecretEncryptor implements BytesEncryptor, TextEncryptor, RsaKey return ((b[0] & 0xFF) << 8) | (b[1] & 0xFF); } - private static byte[] decrypt(byte[] text, PrivateKey key, RsaAlgorithm alg, String salt, boolean gcm) { + private static byte[] decrypt(byte[] text, @Nullable PrivateKey key, RsaAlgorithm alg, String salt, boolean gcm) { ByteArrayInputStream input = new ByteArrayInputStream(text); ByteArrayOutputStream output = new ByteArrayOutputStream(text.length); try { diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java new file mode 100644 index 0000000000..b29482413a --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.encrypt; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/factory/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/factory/package-info.java new file mode 100644 index 0000000000..81e80f5c1a --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/factory/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.factory; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java new file mode 100644 index 0000000000..b5cd724826 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.keygen; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java index 8b0833d37c..5447aa557d 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/DelegatingPasswordEncoder.java @@ -19,6 +19,8 @@ package org.springframework.security.crypto.password; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + /** * A password encoder that delegates to another PasswordEncoder based upon a prefixed * identifier. @@ -146,7 +148,7 @@ public class DelegatingPasswordEncoder implements PasswordEncoder { private final PasswordEncoder passwordEncoderForEncode; - private final Map idToPasswordEncoder; + private final Map<@Nullable String, PasswordEncoder> idToPasswordEncoder; private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder(); @@ -232,6 +234,9 @@ public class DelegatingPasswordEncoder implements PasswordEncoder { @Override public String encode(CharSequence rawPassword) { + if (rawPassword == null) { + throw new IllegalArgumentException("rawPassword cannot be null"); + } return this.idPrefix + this.idForEncode + this.idSuffix + this.passwordEncoderForEncode.encode(rawPassword); } @@ -249,7 +254,7 @@ public class DelegatingPasswordEncoder implements PasswordEncoder { return delegate.matches(rawPassword, encodedPassword); } - private String extractId(String prefixEncodedPassword) { + private @Nullable String extractId(@Nullable String prefixEncodedPassword) { if (prefixEncodedPassword == null) { return null; } @@ -265,14 +270,17 @@ public class DelegatingPasswordEncoder implements PasswordEncoder { } @Override - public boolean upgradeEncoding(String prefixEncodedPassword) { + public boolean upgradeEncoding(@Nullable String prefixEncodedPassword) { + if (prefixEncodedPassword == null) { + return false; + } String id = extractId(prefixEncodedPassword); if (!this.idForEncode.equalsIgnoreCase(id)) { return true; } else { String encodedPassword = extractEncodedPassword(prefixEncodedPassword); - return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword); + return this.passwordEncoderForEncode.upgradeEncoding(encodedPassword); } } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java index 35f7224a7c..40d12ce743 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java @@ -20,6 +20,8 @@ import java.security.MessageDigest; import java.util.Base64; import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.security.crypto.codec.Utf8; import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; @@ -72,7 +74,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { this.saltGenerator = saltGenerator; } - private byte[] combineHashAndSalt(byte[] hash, byte[] salt) { + private byte[] combineHashAndSalt(byte[] hash, byte @Nullable [] salt) { if (salt == null) { return hash; } @@ -96,7 +98,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { return encode(rawPass, salt); } - private String encode(CharSequence rawPassword, byte[] salt) { + private String encode(@Nullable CharSequence rawPassword, byte @Nullable [] salt) { MessageDigest sha = getSha(rawPassword); if (salt != null) { sha.update(salt); @@ -106,7 +108,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { return prefix + Utf8.decode(Base64.getEncoder().encode(hash)); } - private MessageDigest getSha(CharSequence rawPassword) { + private MessageDigest getSha(@Nullable CharSequence rawPassword) { try { MessageDigest sha = MessageDigest.getInstance("SHA"); sha.update(Utf8.encode(rawPassword)); @@ -117,7 +119,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { } } - private String getPrefix(byte[] salt) { + private String getPrefix(byte @Nullable [] salt) { if (salt == null || salt.length == 0) { return this.forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX; } @@ -145,7 +147,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { return matches((rawPassword != null) ? rawPassword.toString() : null, encodedPassword); } - private boolean matches(String rawPassword, String encodedPassword) { + private boolean matches(@Nullable String rawPassword, String encodedPassword) { String prefix = extractPrefix(encodedPassword); if (prefix == null) { return PasswordEncoderUtils.equals(encodedPassword, rawPassword); @@ -156,7 +158,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { return PasswordEncoderUtils.equals(encodedRawPass, encodedPassword.substring(startOfHash)); } - private byte[] getSalt(String encodedPassword, String prefix) { + private byte @Nullable [] getSalt(String encodedPassword, String prefix) { if (prefix.equals(SSHA_PREFIX) || prefix.equals(SSHA_PREFIX_LC)) { return extractSalt(encodedPassword); } @@ -170,7 +172,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { /** * Returns the hash prefix or null if there isn't one. */ - private String extractPrefix(String encPass) { + private @Nullable String extractPrefix(String encPass) { if (!encPass.startsWith("{")) { return null; } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java b/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java index 4e1a69a949..f117b4554c 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoderUtils.java @@ -18,6 +18,8 @@ package org.springframework.security.crypto.password; import java.security.MessageDigest; +import org.jspecify.annotations.Nullable; + import org.springframework.security.crypto.codec.Utf8; /** @@ -36,13 +38,13 @@ final class PasswordEncoderUtils { * @param actual * @return */ - static boolean equals(String expected, String actual) { + static boolean equals(String expected, @Nullable String actual) { byte[] expectedBytes = bytesUtf8(expected); byte[] actualBytes = bytesUtf8(actual); return MessageDigest.isEqual(expectedBytes, actualBytes); } - private static byte[] bytesUtf8(String s) { + private static byte @Nullable [] bytesUtf8(@Nullable String s) { // need to check if Utf8.encode() runs in constant time (probably not). // This may leak length of string. return (s != null) ? Utf8.encode(s) : null; diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/password/package-info.java new file mode 100644 index 0000000000..c94e7a1310 --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/password/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.password; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/scrypt/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/scrypt/package-info.java new file mode 100644 index 0000000000..a23e39104d --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/scrypt/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.scrypt; + +import org.jspecify.annotations.NullMarked; diff --git a/crypto/src/main/java/org/springframework/security/crypto/util/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/util/package-info.java new file mode 100644 index 0000000000..062eaced1a --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/util/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2025 the original author 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. + */ + +@NullMarked +package org.springframework.security.crypto.util; + +import org.jspecify.annotations.NullMarked;