From c4dc49f5045d6f37683fa77c15b8342b1f5abdc6 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Tue, 15 Sep 2026 11:08:12 +0200 Subject: [PATCH] SONARJAVA-6939 Make S4426 key sizes configurable via rule property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add minimumKeySizes @RuleProperty (format: "RSA:4096,AES:256") that patches the defaults — only listed algorithms are overridden, others keep their default values - Add EC:224 to defaults; EC via KeyPairGenerator.initialize(int) is now also checked alongside the existing ECGenParameterSpec path - Extract default key sizes, parsing, and EC curve pattern into CryptographicKeySizeConfiguration in sonar-analyzer-commons. Real version number will be bumped after analyzer-commons' release. Co-Authored-By: Claude Sonnet 4.6 --- .../security/CryptographicKeySizeCheck.java | 9 ++++ .../CryptographicKeySizeCheckCustom.java | 44 +++++++++++++++++++ .../security/CryptographicKeySizeCheck.java | 42 +++++++++++------- .../CryptographicKeySizeCheckTest.java | 10 +++++ 4 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheckCustom.java diff --git a/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheck.java b/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheck.java index 9090b5547c3..b09a9f143e7 100644 --- a/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheck.java +++ b/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheck.java @@ -162,6 +162,15 @@ public void key_variable_compliant() throws NoSuchAlgorithmException { } } +class CryptographicKeySizeCheckECInt { + public void key_variable() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(192); // Noncompliant {{Use a key length of at least 224 bits for EC cipher algorithm.}} +// ^^^^^^^^^^^^^^^^^^^^^^ + keyGen.initialize(224); // Compliant + } +} + class CryptographicKeySizeCheckEC { public void key_EC() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC"); diff --git a/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheckCustom.java b/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheckCustom.java new file mode 100644 index 00000000000..ffb11c165f3 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/security/CryptographicKeySizeCheckCustom.java @@ -0,0 +1,44 @@ +package checks.security; + +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import javax.crypto.KeyGenerator; + +class CryptographicKeySizeCheckCustom { + public void rsa() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); // Noncompliant {{Use a key length of at least 4096 bits for RSA cipher algorithm.}} + keyGen.initialize(4096); // Compliant + } + + public void aes() throws NoSuchAlgorithmException { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(64); // Compliant - threshold lowered to 64 + keyGen.init(128); // Compliant + } + + public void dh() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH"); + keyGen.initialize(1024); // Noncompliant {{Use a key length of at least 2048 bits for DH cipher algorithm.}} + keyGen.initialize(2048); // Compliant + } + + public void diffieHellman() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DiffieHellman"); + keyGen.initialize(1024); // Noncompliant {{Use a key length of at least 2048 bits for DiffieHellman cipher algorithm.}} + keyGen.initialize(2048); // Compliant + } + + public void dsa() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DSA"); + keyGen.initialize(1024, new SecureRandom()); // Noncompliant {{Use a key length of at least 2048 bits for DSA cipher algorithm.}} + keyGen.initialize(2048, new SecureRandom()); // Compliant + } + + public void ec() throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(192); // Noncompliant {{Use a key length of at least 224 bits for EC cipher algorithm.}} + keyGen.initialize(224); // Compliant + } +} \ No newline at end of file diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/CryptographicKeySizeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/CryptographicKeySizeCheck.java index aa73104dafc..d82293045fa 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/CryptographicKeySizeCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/CryptographicKeySizeCheck.java @@ -19,12 +19,10 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.annotation.Nullable; import org.sonar.check.Rule; +import org.sonar.check.RuleProperty; import org.sonar.java.checks.helpers.ExpressionsHelper; -import org.sonarsource.analyzer.commons.collections.MapBuilder; import org.sonar.java.checks.methods.AbstractMethodDetection; import org.sonar.java.model.ExpressionUtils; import org.sonar.java.model.LiteralUtils; @@ -34,6 +32,7 @@ import org.sonar.plugins.java.api.tree.MethodInvocationTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.NewClassTree; +import org.sonarsource.analyzer.commons.appsec.CryptographicKeySizeConfiguration; import static org.sonar.java.model.ExpressionUtils.getAssignedSymbol; import static org.sonar.java.model.ExpressionUtils.isInvocationOnVariable; @@ -47,16 +46,21 @@ public class CryptographicKeySizeCheck extends AbstractMethodDetection { private static final String GET_INSTANCE_METHOD = "getInstance"; private static final String STRING = "java.lang.String"; - private static final int EC_MIN_KEY = 224; - private static final Pattern EC_KEY_PATTERN = Pattern.compile("^(secp|prime|sect|c2tnb)(\\d+)"); + @RuleProperty( + key = "minimumKeySizes", + description = "Comma-separated list of algorithm:minKeySize pairs (e.g. \"RSA:4096,AES:256\"). " + + "Patches the default minimum key sizes — only the listed algorithms are overridden; others keep their defaults.", + defaultValue = CryptographicKeySizeConfiguration.DEFAULT_KEY_SIZES) + public String minimumKeySizes = CryptographicKeySizeConfiguration.DEFAULT_KEY_SIZES; - private static final Map ALGORITHM_KEY_SIZE_MAP = MapBuilder.newMap() - .put("RSA", 2048) - .put("DH", 2048) - .put("DIFFIEHELLMAN", 2048) - .put("DSA", 2048) - .put("AES", 128) - .build(); + private Map effectiveKeySizeMap; + + private Map getEffectiveKeySizeMap() { + if (effectiveKeySizeMap == null) { + effectiveKeySizeMap = CryptographicKeySizeConfiguration.effectiveKeySizes(minimumKeySizes); + } + return effectiveKeySizeMap; + } private static final MethodMatchers KEY_GEN = MethodMatchers.or( MethodMatchers.create() @@ -69,7 +73,7 @@ public class CryptographicKeySizeCheck extends AbstractMethodDetection { .names("initialize") .addParametersMatcher("int") .addParametersMatcher("int", "java.security.SecureRandom") - .build()) ; + .build()); @Override protected MethodMatchers getMethodInvocationMatchers() { @@ -101,9 +105,13 @@ protected void onMethodInvocationFound(MethodInvocationTree mit) { protected void onConstructorFound(NewClassTree newClassTree) { String firstArgument = ExpressionsHelper.getConstantValueAsString(newClassTree.arguments().get(0)).value(); if (firstArgument != null) { - Matcher matcher = EC_KEY_PATTERN.matcher(firstArgument); - if (matcher.find() && Integer.valueOf(matcher.group(2)) < EC_MIN_KEY) { - reportIssue(newClassTree, "Use a key length of at least " + EC_MIN_KEY + " bits for EC cipher algorithm."); + Integer ecMinKey = getEffectiveKeySizeMap().get("EC"); + if (ecMinKey != null) { + CryptographicKeySizeConfiguration.extractEcKeySize(firstArgument).ifPresent(keySize -> { + if (keySize < ecMinKey) { + reportIssue(newClassTree, "Use a key length of at least " + ecMinKey + " bits for EC cipher algorithm."); + } + }); } } } @@ -116,7 +124,7 @@ private class MethodVisitor extends BaseTreeVisitor { public MethodVisitor(String getInstanceArg, @Nullable Symbol variable) { this.algorithm = getInstanceArg; - this.minKeySize = ALGORITHM_KEY_SIZE_MAP.get(this.algorithm.toUpperCase(Locale.ENGLISH)); + this.minKeySize = getEffectiveKeySizeMap().get(this.algorithm.toUpperCase(Locale.ROOT)); this.variable = variable; } diff --git a/java-checks/src/test/java/org/sonar/java/checks/security/CryptographicKeySizeCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/security/CryptographicKeySizeCheckTest.java index f96a1b16b03..26089cbb64c 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/security/CryptographicKeySizeCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/security/CryptographicKeySizeCheckTest.java @@ -39,4 +39,14 @@ void test_without_semantic() { .withoutSemantic() .verifyIssues(); } + + @Test + void test_custom_key_sizes() { + CryptographicKeySizeCheck check = new CryptographicKeySizeCheck(); + check.minimumKeySizes = "RSA:4096,AES:64"; + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/security/CryptographicKeySizeCheckCustom.java")) + .withCheck(check) + .verifyIssues(); + } }