
org.sonar.java.checks.CounterModeIVShouldNotBeReusedCheck Maven / Gradle / Ivy
The newest version!
/*
* SonarQube Java
* Copyright (C) 2012-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import javax.crypto.Cipher;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.ExpressionsHelper;
import org.sonar.java.checks.helpers.HardcodedStringExpressionChecker;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.expression.MemberSelectExpressionTreeImpl;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
@Rule(key = "S6432")
public class CounterModeIVShouldNotBeReusedCheck extends IssuableSubscriptionVisitor {
private static final String PRIMARY_LOCATION_ISSUE_MESSAGE = "Use a dynamically-generated initialization vector (IV) to avoid IV-key pair reuse.";
private static final String SECONDARY_LOCATION_ISSUE_MESSAGE = "The static value is defined here.";
private static final MethodMatchers JCA_CHIPER_INIT_METHODS = MethodMatchers.create()
.ofTypes("javax.crypto.Cipher")
.names("init")
.addParametersMatcher("int", MethodMatchers.ANY, "java.security.spec.AlgorithmParameterSpec")
.build();
private static final MethodMatchers BC_CHIPER_INIT_METHODS = MethodMatchers.create()
.ofTypes("org.bouncycastle.crypto.modes.GCMBlockCipher", "org.bouncycastle.crypto.modes.CCMBlockCipher")
.names("init")
.addParametersMatcher("boolean", "org.bouncycastle.crypto.CipherParameters")
.build();
private static final MethodMatchers GCM_CONSTRUCTOR = MethodMatchers.create()
.ofTypes("javax.crypto.spec.GCMParameterSpec")
.constructor()
.addParametersMatcher(parameters -> !parameters.isEmpty())
.build();
private static final MethodMatchers AEAD_CONSTRUCTOR = MethodMatchers.create()
.ofTypes("org.bouncycastle.crypto.params.AEADParameters")
.constructor()
.addParametersMatcher(parameters -> !parameters.isEmpty())
.build();
@Override
public List nodesToVisit() {
return Collections.singletonList(Tree.Kind.METHOD_INVOCATION);
}
@Override
public void visitNode(Tree tree) {
MethodInvocationTree method = (MethodInvocationTree) tree;
var secondaryLocations = new ArrayList();
if (isJCAOperationModeEncrypt(method)) {
checkForHardcodedIVInitialization(method, 2, secondaryLocations);
} else if (isBCCipherForEncryption(method)) {
checkForHardcodedIVInitialization(method, 1, secondaryLocations);
}
}
private void checkForHardcodedIVInitialization(MethodInvocationTree method, int constructorParamIndex, List secondaryLocations) {
if (checkForJCAHardcodedIVInitialization(method.arguments().get(constructorParamIndex), secondaryLocations)) {
MemberSelectExpressionTree methodSelect = (MemberSelectExpressionTreeImpl) method.methodSelect();
reportIssue(methodSelect.identifier(), PRIMARY_LOCATION_ISSUE_MESSAGE, secondaryLocations, null);
}
}
private static boolean isJCAOperationModeEncrypt(MethodInvocationTree method) {
if (JCA_CHIPER_INIT_METHODS.matches(method)) {
Optional value = method.arguments().get(0).asConstant(Integer.class);
return value.isPresent() && value.get() == Cipher.ENCRYPT_MODE;
}
return false;
}
private static boolean isBCCipherForEncryption(MethodInvocationTree method) {
if (BC_CHIPER_INIT_METHODS.matches(method)) {
Optional value = method.arguments().get(0).asConstant(Boolean.class);
return value.isPresent() && value.get();
}
return false;
}
// argument here is going to be a GCMParameterSpec
private static boolean checkForJCAHardcodedIVInitialization(ExpressionTree expression, List secondaryLocations) {
ExpressionTree argument = ExpressionUtils.skipParentheses(expression);
switch (argument.kind()) {
case IDENTIFIER:
List assignments = ExpressionsHelper.getIdentifierAssignments((IdentifierTree) argument);
secondaryLocations.add(new JavaFileScannerContext.Location(SECONDARY_LOCATION_ISSUE_MESSAGE, argument));
return assignments.stream()
.allMatch(assignment -> checkForJCAHardcodedIVInitialization(assignment, secondaryLocations));
case NEW_CLASS:
NewClassTree constructor = (NewClassTree) argument;
if (GCM_CONSTRUCTOR.matches(constructor)) {
ExpressionTree arg = constructor.arguments().get(1);
secondaryLocations.add(new JavaFileScannerContext.Location(SECONDARY_LOCATION_ISSUE_MESSAGE, arg));
return HardcodedStringExpressionChecker.isExpressionDerivedFromPlainText(arg, secondaryLocations, new HashSet<>());
} else if (AEAD_CONSTRUCTOR.matches(constructor)) {
ExpressionTree arg = constructor.arguments().get(2);
secondaryLocations.add(new JavaFileScannerContext.Location(SECONDARY_LOCATION_ISSUE_MESSAGE, arg));
return HardcodedStringExpressionChecker.isExpressionDerivedFromPlainText(arg, secondaryLocations, new HashSet<>());
}
return false;
default:
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy