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