All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.AbstractHashAlgorithmChecker Maven / Gradle / Ivy

There is a newer version: 8.10.0.38194
Show newest version
/*
 * SonarQube Java
 * Copyright (C) 2012-2024 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.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import org.sonar.java.checks.helpers.JavaPropertiesHelper;
import org.sonar.java.checks.methods.AbstractMethodDetection;
import org.sonarsource.analyzer.commons.collections.MapBuilder;
import org.sonar.java.model.ExpressionUtils;
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.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.NewClassTree;

import static org.sonar.plugins.java.api.semantic.MethodMatchers.ANY;

public abstract class AbstractHashAlgorithmChecker extends AbstractMethodDetection {

  public static final String GET_INSTANCE = "getInstance";
  public static final String JAVA_LANG_STRING = "java.lang.String";

  protected static final Map ALGORITHM_BY_METHOD_NAME = MapBuilder.newMap()
    .put("getMd2Digest", InsecureAlgorithm.MD2)
    .put("getMd5Digest", InsecureAlgorithm.MD5)
    .put("getShaDigest", InsecureAlgorithm.SHA1)
    .put("getSha1Digest", InsecureAlgorithm.SHA1)
    .put("md2", InsecureAlgorithm.MD2)
    .put("md2Hex", InsecureAlgorithm.MD2)
    .put("md5", InsecureAlgorithm.MD5)
    .put("md5Hex", InsecureAlgorithm.MD5)
    .put("sha1", InsecureAlgorithm.SHA1)
    .put("sha1Hex", InsecureAlgorithm.SHA1)
    .put("sha", InsecureAlgorithm.SHA1)
    .put("shaHex", InsecureAlgorithm.SHA1)
    .put("md5Digest", InsecureAlgorithm.MD5)
    .put("md5DigestAsHex", InsecureAlgorithm.MD5)
    .put("appendMd5DigestAsHex", InsecureAlgorithm.MD5)
    .build();

  private static final String CONSTRUCTOR = "";

  /**
   * These APIs have static getInstance method to get an implementation of some crypto algorithm.
   * javax.crypto.Cipher is missing from this list, because it is covered by rule S5547 {@link StrongCipherAlgorithmCheck}
   * Details can be found here Security Standard Names
   */
  private static final String[] CRYPTO_APIS = {
    "java.security.AlgorithmParameters",
    "java.security.AlgorithmParameterGenerator",
    "java.security.MessageDigest",
    "java.security.KeyFactory",
    "java.security.KeyPairGenerator",
    "java.security.Signature",
    "javax.crypto.Mac",
    "javax.crypto.KeyGenerator"
  };

  public enum InsecureAlgorithm {
    MD2, MD4, MD5, MD6, RIPEMD,
    HAVAL128 {
      @Override
      public String toString() {
        return "HAVAL-128";
      }
    },
    SHA {
      @Override
      public boolean match(String algorithm) {
        // exact match required for SHA, so it doesn't match compliant SHA-512
        return "SHA".equals(algorithm);
      }
    },
    SHA0 {
      @Override
      public String toString() {
        return "SHA-0";
      }
    },
    SHA1 {
      @Override
      public String toString() {
        return "SHA-1";
      }
    },
    SHA224 {
      @Override
      public String toString() {
        return "SHA-224";
      }
    },
    DSA {
      @Override
      public boolean match(String algorithm) {
        // exact match required for DSA, so it doesn't match ECDSA
        return "DSA".equals(algorithm);
      }
    };

    public boolean match(String algorithm) {
      String normalizedName = algorithm.replace("-", "").toLowerCase(Locale.ENGLISH);
      return normalizedName.contains(name().toLowerCase(Locale.ENGLISH));
    }
  }

  public enum DeprecatedSpringPasswordEncoder {
    MD5("org.springframework.security.authentication.encoding.Md5PasswordEncoder", CONSTRUCTOR),
    SHA("org.springframework.security.authentication.encoding.ShaPasswordEncoder", CONSTRUCTOR),
    LDAP("org.springframework.security.crypto.password.LdapShaPasswordEncoder", CONSTRUCTOR),
    MD4("org.springframework.security.crypto.password.Md4PasswordEncoder", CONSTRUCTOR),
    MESSAGE_DIGEST("org.springframework.security.crypto.password.MessageDigestPasswordEncoder", CONSTRUCTOR),
    STANDARD("org.springframework.security.crypto.password.StandardPasswordEncoder", CONSTRUCTOR),
    NO_OP("org.springframework.security.crypto.password.NoOpPasswordEncoder", GET_INSTANCE);

    public final String classFqn;
    public final String methodName;
    public final String className;

    DeprecatedSpringPasswordEncoder(String fqn, String methodName) {
      this.classFqn = fqn;
      this.methodName = methodName;
      String[] fqnParts = fqn.split("\\.");
      this.className = fqnParts[fqnParts.length - 1];
    }
  }

  protected abstract Optional getMessageForClass(String className);
  protected abstract String getMessageForAlgorithm(String algorithmName);

  @Override
  protected MethodMatchers getMethodInvocationMatchers() {
    return getWeakHashMethodInvocationMatchers();
  }

  @Override
  protected void onMethodInvocationFound(MethodInvocationTree mit) {
    IdentifierTree methodName = ExpressionUtils.methodName(mit);
    Optional message = getMessageForClass(methodName.symbol().owner().type().fullyQualifiedName());
    if (message.isPresent()) {
      reportIssue(methodName, message.get());
      return;
    }
    InsecureAlgorithm algorithm = ALGORITHM_BY_METHOD_NAME.get(methodName.name());
    if (algorithm == null) {
      algorithm = algorithm(mit.arguments().get(0)).orElse(null);
    }
    if (algorithm != null) {
      reportIssue(methodName, getMessageForAlgorithm(algorithm.toString()));
    }
  }

  @Override
  protected void onConstructorFound(NewClassTree newClassTree) {
    getMessageForClass(newClassTree.identifier().symbolType().fullyQualifiedName())
      .ifPresent(message ->  reportIssue(newClassTree.identifier(), message));
  }

  private static MethodMatchers getWeakHashMethodInvocationMatchers() {
    ArrayList matchers = new ArrayList<>();
    matchers
      .add(MethodMatchers.create()
        .ofTypes("org.apache.commons.codec.digest.DigestUtils")
        .names("getDigest")
        .addParametersMatcher(JAVA_LANG_STRING)
        .build());

    matchers
      .add(MethodMatchers.create()
        .ofTypes("org.apache.commons.codec.digest.DigestUtils")
        .name(ALGORITHM_BY_METHOD_NAME::containsKey)
        .withAnyParameters()
        .build());

    matchers
      .add(MethodMatchers.create()
        .ofTypes(CRYPTO_APIS)
        .names(GET_INSTANCE)
        .addParametersMatcher(JAVA_LANG_STRING)
        .addParametersMatcher(JAVA_LANG_STRING, ANY)
        .build());

    matchers
      .add(MethodMatchers.create()
        .ofTypes("org.springframework.util.DigestUtils")
        .names("appendMd5DigestAsHex", "md5Digest", "md5DigestAsHex")
        .withAnyParameters()
        .build());

    for (DeprecatedSpringPasswordEncoder pe : DeprecatedSpringPasswordEncoder.values()) {
      matchers.add(MethodMatchers.create().ofTypes(pe.classFqn).names(pe.methodName).withAnyParameters().build());
    }

    matchers.add(MethodMatchers.create()
      .ofTypes("com.google.common.hash.Hashing")
      .names("md5", "sha1")
      .addWithoutParametersMatcher().build());

    return MethodMatchers.or(matchers);
  }

  private static Optional algorithm(ExpressionTree invocationArgument) {
    ExpressionTree expectedAlgorithm = invocationArgument;
    ExpressionTree defaultPropertyValue = JavaPropertiesHelper.retrievedPropertyDefaultValue(invocationArgument);
    if (defaultPropertyValue != null) {
      expectedAlgorithm = defaultPropertyValue;
    }
    Optional stringConstant = expectedAlgorithm.asConstant(String.class);
    if (stringConstant.isPresent()) {
      String algorithmName = stringConstant.get();
      return Arrays.stream(InsecureAlgorithm.values())
        .filter(alg -> alg.match(algorithmName))
        .findFirst();
    }
    return Optional.empty();
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy