From 023666087d97ee3d5ab48673d8471fc76786812a Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 15:43:20 +0200 Subject: [PATCH 01/24] feat(mapper): extend model for PyCryptodome support - DigestSize: add ofDefault() factory for compile-time-known sizes and propagate NodeOrigin through the copy constructor - SHA2: use DigestSize.ofDefault() for its built-in digest sizes - RIPEMD: add cast constructor (required by reorganizer rules) Signed-off-by: san-zrl --- .../mapper/mapper/pyca/PycaCipherMapper.java | 13 +- .../mapper/mapper/pyca/PycaDigestMapper.java | 18 +- .../ibm/mapper/mapper/pyca/PycaMacMapper.java | 13 + .../java/com/ibm/mapper/model/DigestSize.java | 24 +- .../ibm/mapper/model/algorithms/RIPEMD.java | 5 + .../com/ibm/mapper/model/algorithms/SHA2.java | 4 +- .../rules/KeyAgreementReorganizer.java | 87 ++++++ .../rules/SignatureReorganizer.java | 39 ++- .../rules/detection/PythonDetectionRules.java | 46 ++-- .../plugin/rules/detection/aead/PycaAEAD.java | 83 ------ .../plugin/rules/detection/aead/PycaAES.java | 97 ------- .../rules/detection/asymmetric/PycaDSA.java | 105 -------- .../asymmetric/PycaDiffieHellman.java | 99 ------- .../asymmetric/PycaEllipticCurve.java | 150 ----------- .../rules/detection/asymmetric/PycaRSA.java | 197 -------------- .../rules/detection/asymmetric/PycaSign.java | 65 ----- .../rules/detection/fernet/PycaFernet.java | 83 ------ .../plugin/rules/detection/hash/PycaHash.java | 105 -------- .../plugin/rules/detection/kdf/PycaKDF.java | 219 --------------- .../keyagreement/PycaKeyAgreement.java | 64 ----- .../plugin/rules/detection/mac/PycaMAC.java | 79 ------ .../rules/detection/padding/PycaPadding.java | 91 ------- .../rules/detection/symmetric/PycaCipher.java | 122 --------- .../detection/wrapping/PycaWrapping.java | 63 ----- .../reorganizer/PythonReorganizerRules.java | 14 + .../translator/PythonTranslator.java | 19 +- .../contexts/PycaCipherContextTranslator.java | 68 +++-- .../contexts/PycaDigestContextTranslator.java | 10 +- .../PycaKeyAgreementContextTranslator.java | 9 +- .../PycaKeyDerivationContextTranslator.java | 37 ++- .../contexts/PycaMacContextTranslator.java | 10 +- .../PycaPrivateKeyContextTranslator.java | 87 +----- .../PycaPublicKeyContextTranslator.java | 34 +-- .../contexts/PycaSecretContextTranslator.java | 62 ----- .../PycaSignatureContextTranslator.java | 12 + .../detection/aead/PycaAESGCMTestFile.py | 10 - .../aead/PycaChaCha20Poly1305TestFile.py | 11 - .../asymmetric/DSA/PycaDSANumbersTestFile.py | 13 - .../asymmetric/DSA/PycaDSASignTestFile.py | 11 - .../PycaDiffieHellmanGenerateTestFile.py | 6 - .../PycaDiffieHellmanNumbersTestFile.py | 13 - .../PycaEllipticCurveDeriveTestFile.py | 12 - .../PycaEllipticCurveKeyExchangeTestFile.py | 21 -- .../PycaEllipticCurveNumbersTestFile.py | 53 ---- .../PycaEllipticCurveSign2TestFile.py | 48 ---- .../PycaEllipticCurveSignTestFile.py | 37 --- .../PycaEllipticCurveVerifyTestFile.py | 8 - .../asymmetric/RSA/PycaRSADecryptTestFile.py | 19 -- .../asymmetric/RSA/PycaRSANumbersTestFile.py | 13 - .../asymmetric/RSA/PycaRSASign1TestFile.py | 19 -- .../asymmetric/RSA/PycaRSASign2TestFile.py | 16 -- .../fernet/PycaFernetDecryptTestFile.py | 15 -- .../fernet/PycaFernetEncryptTestFile.py | 16 -- .../fernet/PycaMultiFernetTestFile.py | 19 -- .../detection/hash/PycaHashDirectTest.py | 5 - .../kdf/PycaConcatKDFHMACTestFile.py | 15 -- .../kdf/PycaConcatKDFHashTestFile.py | 13 - .../detection/kdf/PycaHKDFExpandTestFile.py | 14 - .../rules/detection/kdf/PycaHKDFTestFile.py | 15 -- .../detection/kdf/PycaKBKDFCMACTestFile.py | 21 -- .../detection/kdf/PycaKBKDFHMACTestFile.py | 22 -- .../rules/detection/kdf/PycaPBKDF2TestFile.py | 16 -- .../rules/detection/kdf/PycaScryptTestFile.py | 15 -- .../detection/kdf/PycaX963KDFTestFile.py | 13 - .../keyagreement/PycaKeyAgreementTestFile.py | 38 --- .../rules/detection/mac/PycaCMACTestFile.py | 25 -- .../rules/detection/mac/PycaHMACTestFile.py | 25 -- ...ycaMacDetectionInCustomFunctionTestFile.py | 27 -- .../detection/mac/PycaPoly1305TestFile.py | 21 -- .../detection/padding/PycaPaddingTestFile.py | 16 -- .../symmetric/PycaCipher1TestFile.py | 21 -- .../symmetric/PycaCipher2TestFile.py | 12 - .../symmetric/PycaStreamCipher1TestFile.py | 11 - .../wrapping/PycaWrappingTestFile.py | 25 -- .../PycaWrappingWithPaddingTestFile.py | 25 -- .../NamedAllKeywordParametersTest.java | 92 ------- .../detection/NamedParameterWildcardTest.java | 71 ----- .../rules/detection/NamedParametersTest.java | 206 -------------- .../rules/detection/aead/PycaAESGCMTest.java | 148 ---------- .../aead/PycaChaCha20Poly1305Test.java | 135 ---------- .../asymmetric/DSA/PycaDSANumbersTest.java | 139 ---------- .../asymmetric/DSA/PycaDSASignTest.java | 156 ----------- .../PycaDiffieHellmanGenerateTest.java | 99 ------- .../PycaDiffieHellmanNumbersTest.java | 96 ------- .../PycaEllipticCurveDeriveTest.java | 103 ------- .../PycaEllipticCurveKeyExchangeTest.java | 206 -------------- .../PycaEllipticCurveNumbersTest.java | 52 ---- .../PycaEllipticCurveSign2Test.java | 185 ------------- .../PycaEllipticCurveSignTest.java | 158 ----------- .../PycaEllipticCurveVerifyTest.java | 55 ---- .../asymmetric/RSA/PycaRSADecryptTest.java | 252 ------------------ .../asymmetric/RSA/PycaRSANumbersTest.java | 141 ---------- .../asymmetric/RSA/PycaRSASign1Test.java | 231 ---------------- .../asymmetric/RSA/PycaRSASign2Test.java | 170 ------------ .../fernet/PycaFernetDecryptTest.java | 200 -------------- .../fernet/PycaFernetEncryptTest.java | 200 -------------- .../detection/fernet/PycaMultiFernetTest.java | 211 --------------- .../detection/hash/PycaHashDirectTest.java | 102 ------- .../detection/kdf/PycaConcatKDFHMACTest.java | 133 --------- .../detection/kdf/PycaConcatKDFHashTest.java | 131 --------- .../detection/kdf/PycaHKDFExpandTest.java | 133 --------- .../rules/detection/kdf/PycaHKDFTest.java | 132 --------- .../detection/kdf/PycaKBKDFCMACTest.java | 142 ---------- .../detection/kdf/PycaKBKDFHMACTest.java | 163 ----------- .../rules/detection/kdf/PycaPBKDF2Test.java | 147 ---------- .../rules/detection/kdf/PycaScryptTest.java | 101 ------- .../rules/detection/kdf/PycaX963KDFTest.java | 133 --------- .../keyagreement/PycaKeyAgreementTest.java | 178 ------------- .../rules/detection/mac/PycaCMACTest.java | 100 ------- .../rules/detection/mac/PycaHMACTest.java | 121 --------- .../PycaMacDetectionInCustomFunctionTest.java | 131 --------- .../rules/detection/mac/PycaPoly1305Test.java | 94 ------- .../detection/padding/PycaPaddingTest.java | 125 --------- .../detection/symmetric/PycaCipher1Test.java | 161 ----------- .../detection/symmetric/PycaCipher2Test.java | 104 -------- .../symmetric/PycaStreamCipher1Test.java | 89 ------- .../detection/wrapping/PycaWrappingTest.java | 87 ------ .../wrapping/PycaWrappingWithPaddingTest.java | 87 ------ 118 files changed, 389 insertions(+), 8499 deletions(-) delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAEAD.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAES.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDSA.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDiffieHellman.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaEllipticCurve.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaRSA.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaSign.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/fernet/PycaFernet.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHash.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/kdf/PycaKDF.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreement.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/mac/PycaMAC.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/padding/PycaPadding.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher.java delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/wrapping/PycaWrapping.java delete mode 100644 python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSecretContextTranslator.java delete mode 100644 python/src/test/files/rules/detection/aead/PycaAESGCMTestFile.py delete mode 100644 python/src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py delete mode 100644 python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py delete mode 100644 python/src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py delete mode 100644 python/src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py delete mode 100644 python/src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py delete mode 100644 python/src/test/files/rules/detection/hash/PycaHashDirectTest.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaHKDFTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaScryptTestFile.py delete mode 100644 python/src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py delete mode 100644 python/src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py delete mode 100644 python/src/test/files/rules/detection/mac/PycaCMACTestFile.py delete mode 100644 python/src/test/files/rules/detection/mac/PycaHMACTestFile.py delete mode 100644 python/src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py delete mode 100644 python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py delete mode 100644 python/src/test/files/rules/detection/padding/PycaPaddingTestFile.py delete mode 100644 python/src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py delete mode 100644 python/src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py delete mode 100644 python/src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py delete mode 100644 python/src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py delete mode 100644 python/src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/NamedAllKeywordParametersTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/NamedParameterWildcardTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/NamedParametersTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaAESGCMTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaChaCha20Poly1305Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSANumbersTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSASignTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSADecryptTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSANumbersTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign1Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign2Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetDecryptTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetEncryptTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaMultiFernetTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/hash/PycaHashDirectTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHMACTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHashTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFExpandTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFCMACTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFHMACTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaPBKDF2Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaScryptTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaX963KDFTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreementTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaCMACTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaHMACTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaMacDetectionInCustomFunctionTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaPoly1305Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/padding/PycaPaddingTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher1Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher2Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaStreamCipher1Test.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingTest.java delete mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingWithPaddingTest.java diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCipherMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCipherMapper.java index 5461d068d..6098f1b16 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCipherMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCipherMapper.java @@ -26,12 +26,15 @@ import com.ibm.mapper.model.algorithms.Camellia; import com.ibm.mapper.model.algorithms.ChaCha20; import com.ibm.mapper.model.algorithms.ChaCha20Poly1305; +import com.ibm.mapper.model.algorithms.DES; import com.ibm.mapper.model.algorithms.Fernet; import com.ibm.mapper.model.algorithms.IDEA; +import com.ibm.mapper.model.algorithms.RC2; import com.ibm.mapper.model.algorithms.RC4; import com.ibm.mapper.model.algorithms.RSA; import com.ibm.mapper.model.algorithms.SEED; import com.ibm.mapper.model.algorithms.SM4; +import com.ibm.mapper.model.algorithms.Salsa20; import com.ibm.mapper.model.algorithms.TripleDES; import com.ibm.mapper.model.algorithms.cast.CAST128; import com.ibm.mapper.utils.DetectionLocation; @@ -53,17 +56,21 @@ public final class PycaCipherMapper implements IMapper { case "AES128" -> Optional.of(new AES(128, detectionLocation)); case "AES256" -> Optional.of(new AES(256, detectionLocation)); case "CAMELLIA" -> Optional.of(new Camellia(detectionLocation)); - case "TRIPLEDES" -> Optional.of(new TripleDES(detectionLocation)); + case "TRIPLEDES", "3DES" -> Optional.of(new TripleDES(detectionLocation)); + case "DES" -> Optional.of(new DES(detectionLocation)); case "CAST5" -> Optional.of(new CAST128(detectionLocation)); case "SEED" -> Optional.of(new SEED(detectionLocation)); case "SM4" -> Optional.of(new SM4(detectionLocation)); case "BLOWFISH" -> Optional.of(new Blowfish(detectionLocation)); case "IDEA" -> Optional.of(new IDEA(detectionLocation)); case "CHACHA20" -> Optional.of(new ChaCha20(detectionLocation)); - case "ARC4" -> Optional.of(new RC4(detectionLocation)); + case "SALSA20" -> Optional.of(new Salsa20(detectionLocation)); + case "ARC4", "RC4" -> Optional.of(new RC4(detectionLocation)); + case "RC2" -> Optional.of(new RC2(detectionLocation)); case "FERNET" -> Optional.of(new Fernet(detectionLocation)); case "RSA" -> Optional.of(new RSA(detectionLocation)); - case "CHACHA20POLY1305" -> Optional.of(new ChaCha20Poly1305(detectionLocation)); + case "CHACHA20POLY1305", "CHACHA20_POLY1305" -> + Optional.of(new ChaCha20Poly1305(detectionLocation)); default -> Optional.empty(); }; } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaDigestMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaDigestMapper.java index 8e2a1ca3e..5ab5b40e2 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaDigestMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaDigestMapper.java @@ -21,14 +21,20 @@ import com.ibm.mapper.mapper.IMapper; import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.algorithms.KangarooTwelve; +import com.ibm.mapper.model.algorithms.Keccak; +import com.ibm.mapper.model.algorithms.MD2; +import com.ibm.mapper.model.algorithms.MD4; import com.ibm.mapper.model.algorithms.MD5; -import com.ibm.mapper.model.algorithms.Poly1305; +import com.ibm.mapper.model.algorithms.RIPEMD; import com.ibm.mapper.model.algorithms.SHA; import com.ibm.mapper.model.algorithms.SHA2; import com.ibm.mapper.model.algorithms.SHA3; import com.ibm.mapper.model.algorithms.SM3; +import com.ibm.mapper.model.algorithms.TupleHash; import com.ibm.mapper.model.algorithms.blake.BLAKE2b; import com.ibm.mapper.model.algorithms.blake.BLAKE2s; +import com.ibm.mapper.model.algorithms.shake.CSHAKE; import com.ibm.mapper.model.algorithms.shake.SHAKE; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -59,11 +65,19 @@ public final class PycaDigestMapper implements IMapper { case "SHA3_512" -> Optional.of(new SHA3(512, detectionLocation)); case "SHAKE128" -> Optional.of(new SHAKE(128, detectionLocation)); case "SHAKE256" -> Optional.of(new SHAKE(256, detectionLocation)); + case "MD2" -> Optional.of(new MD2(detectionLocation)); + case "MD4" -> Optional.of(new MD4(detectionLocation)); case "MD5" -> Optional.of(new MD5(detectionLocation)); case "BLAKE2B" -> Optional.of(new BLAKE2b(false, detectionLocation)); case "BLAKE2S" -> Optional.of(new BLAKE2s(false, detectionLocation)); case "SM3" -> Optional.of(new SM3(detectionLocation)); - case "POLY1305" -> Optional.of(new Poly1305(detectionLocation)); + case "RIPEMD160" -> Optional.of(new RIPEMD(160, detectionLocation)); + case "TUPLEHASH128" -> Optional.of(new TupleHash(128, detectionLocation)); + case "TUPLEHASH256" -> Optional.of(new TupleHash(256, detectionLocation)); + case "KECCAK" -> Optional.of(new Keccak(detectionLocation)); + case "CSHAKE128" -> Optional.of(new CSHAKE(128, detectionLocation)); + case "CSHAKE256" -> Optional.of(new CSHAKE(256, detectionLocation)); + case "KANGAROOTWELVE" -> Optional.of(new KangarooTwelve(detectionLocation)); default -> Optional.empty(); }; } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaMacMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaMacMapper.java index 777eca70b..962774180 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaMacMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaMacMapper.java @@ -28,9 +28,12 @@ import com.ibm.mapper.model.algorithms.ChaCha20; import com.ibm.mapper.model.algorithms.Fernet; import com.ibm.mapper.model.algorithms.IDEA; +import com.ibm.mapper.model.algorithms.KMAC; +import com.ibm.mapper.model.algorithms.MD2; import com.ibm.mapper.model.algorithms.MD5; import com.ibm.mapper.model.algorithms.Poly1305; import com.ibm.mapper.model.algorithms.RC4; +import com.ibm.mapper.model.algorithms.RIPEMD; import com.ibm.mapper.model.algorithms.RSA; import com.ibm.mapper.model.algorithms.SEED; import com.ibm.mapper.model.algorithms.SHA; @@ -42,6 +45,7 @@ import com.ibm.mapper.model.algorithms.blake.BLAKE2b; import com.ibm.mapper.model.algorithms.blake.BLAKE2s; import com.ibm.mapper.model.algorithms.cast.CAST128; +import com.ibm.mapper.model.algorithms.shake.CSHAKE; import com.ibm.mapper.model.algorithms.shake.SHAKE; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -104,13 +108,22 @@ public class PycaMacMapper implements IMapper { case "SHAKE128" -> Optional.of(new SHAKE(Mac.class, new SHAKE(128, detectionLocation))); case "SHAKE256" -> Optional.of(new SHAKE(Mac.class, new SHAKE(256, detectionLocation))); case "MD5" -> Optional.of(new MD5(Mac.class, detectionLocation)); + case "MD2" -> Optional.of(new MD2(Mac.class, detectionLocation)); case "BLAKE2B" -> Optional.of(new BLAKE2b(Mac.class, new BLAKE2b(false, detectionLocation))); case "BLAKE2S" -> Optional.of(new BLAKE2s(Mac.class, new BLAKE2s(false, detectionLocation))); case "SM3" -> Optional.of(new SM3(Mac.class, new SM3(detectionLocation))); + case "KMAC128" -> Optional.of(new KMAC(Mac.class, new KMAC(128, detectionLocation))); + case "KMAC256" -> Optional.of(new KMAC(Mac.class, new KMAC(256, detectionLocation))); case "POLY1305" -> Optional.of(new Poly1305(Mac.class, new Poly1305(detectionLocation))); + case "RIPEMD160" -> + Optional.of(new RIPEMD(Mac.class, new RIPEMD(160, detectionLocation))); + case "CSHAKE128" -> + Optional.of(new CSHAKE(Mac.class, new CSHAKE(128, detectionLocation))); + case "CSHAKE256" -> + Optional.of(new CSHAKE(Mac.class, new CSHAKE(256, detectionLocation))); default -> Optional.empty(); }; } diff --git a/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java b/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java index c5ac177c9..430cec48d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java +++ b/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java @@ -31,11 +31,33 @@ public DigestSize(@Nonnull Integer value, @Nonnull DetectionLocation detectionLo this.value = value; } + private DigestSize( + @Nonnull Integer value, + @Nonnull DetectionLocation detectionLocation, + @Nonnull NodeOrigin origin) { + super(DigestSize.class, detectionLocation, origin); + this.value = value; + } + private DigestSize(@Nonnull DigestSize digestSize) { - super(digestSize.type, digestSize.detectionLocation, digestSize.children); + super( + digestSize.type, + digestSize.detectionLocation, + digestSize.children, + digestSize.origin); this.value = digestSize.value; } + /** + * Creates a DigestSize with DEFAULT origin, for use in algorithm constructors where the size is + * a known constant rather than something directly detected in source code. + */ + @Nonnull + public static DigestSize ofDefault( + @Nonnull Integer value, @Nonnull DetectionLocation detectionLocation) { + return new DigestSize(value, detectionLocation, NodeOrigin.DEFAULT); + } + @Nonnull public Integer getValue() { return value; diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RIPEMD.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RIPEMD.java index 4ed5f0860..15766ae7d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RIPEMD.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RIPEMD.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -72,4 +73,8 @@ public RIPEMD(int digestSize, @Nonnull DetectionLocation detectionLocation) { this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + public RIPEMD(@Nonnull final Class asKind, @Nonnull RIPEMD ripemd) { + super(ripemd, asKind); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java index 9be9b27c8..5eb356f80 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java @@ -52,7 +52,7 @@ public final class SHA2 extends Algorithm implements MessageDigest { public SHA2(int digestSize, @Nonnull DetectionLocation detectionLocation) { super(NAME + digestSize, MessageDigest.class, detectionLocation); - this.put(new DigestSize(digestSize, detectionLocation)); + this.put(DigestSize.ofDefault(digestSize, detectionLocation)); } public SHA2( @@ -60,7 +60,7 @@ public SHA2( @Nonnull MessageDigest preHash, @Nonnull DetectionLocation detectionLocation) { super(buildPreHashName(digestSize, preHash), MessageDigest.class, detectionLocation); - this.put(new DigestSize(digestSize, detectionLocation)); + this.put(DigestSize.ofDefault(digestSize, detectionLocation)); this.put(preHash); } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyAgreementReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyAgreementReorganizer.java index bd0b22086..c4fbbef87 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyAgreementReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyAgreementReorganizer.java @@ -19,14 +19,27 @@ */ package com.ibm.mapper.reorganizer.rules; +import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.EllipticCurve; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.curves.Curve25519; +import com.ibm.mapper.model.curves.Curve448; import com.ibm.mapper.reorganizer.IReorganizerRule; import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; +import java.util.function.Function; +import javax.annotation.Nonnull; public final class KeyAgreementReorganizer { @@ -55,4 +68,78 @@ private KeyAgreementReorganizer() { } return roots; }); + + public static final IReorganizerRule REPLACE_ECDH_WITH_X25519_WHEN_CURVE25519 = + replaceEcdhWithXdh(Curve25519.class, X25519::new); + + public static final IReorganizerRule REPLACE_ECDH_WITH_X448_WHEN_CURVE448 = + replaceEcdhWithXdh(Curve448.class, X448::new); + + /** + * Returns a rule that replaces an {@link ECDH} key-agreement root node with a specific XDH + * algorithm node when both its {@link PrivateKey} and {@link PublicKey} children carry the + * given {@code curveKind}. The replacement node is constructed via {@code xdhSupplier}, which + * is expected to set the correct canonical OID and curve; all other children ({@code + * KeyGeneration}, {@code PrivateKey}, {@code PublicKey}) are transferred from the old node. + * + * @param curveKind the {@link EllipticCurve} subclass to match on both key children + * @param xdhSupplier factory that builds the replacement node from a {@link + * com.ibm.mapper.utils.DetectionLocation} + */ + @Nonnull + public static IReorganizerRule replaceEcdhWithXdh( + @Nonnull Class curveKind, + @Nonnull Function xdhSupplier) { + return new ReorganizerRuleBuilder() + .createReorganizerRule("REPLACE_ECDH_WITH_XDH_WHEN_" + curveKind.getSimpleName()) + .forNodeKind(KeyAgreement.class) + .withDetectionCondition( + (node, parent, roots) -> + node instanceof ECDH + && hasCurveInKey(node, PrivateKey.class, curveKind) + && hasCurveInKey(node, PublicKey.class, curveKind)) + .perform( + (node, parent, roots) -> { + Algorithm xdh = xdhSupplier.apply(((ECDH) node).getDetectionContext()); + transferChildren(node, xdh); + return replaceRoot(roots, node, xdh); + }); + } + + // ── helpers ────────────────────────────────────────────────────────────── + + /** + * Returns {@code true} when {@code ecdh} has a {@code keyKind} child whose own {@code + * PublicKeyEncryption} child contains an {@link EllipticCurve} child that is an instance of + * {@code curveKind}. + */ + private static boolean hasCurveInKey( + @Nonnull INode ecdh, + @Nonnull Class keyKind, + @Nonnull Class curveKind) { + return ecdh.hasChildOfType(keyKind) + .flatMap(key -> key.hasChildOfType(PublicKeyEncryption.class)) + .flatMap(pke -> pke.hasChildOfType(EllipticCurve.class)) + .filter(curveKind::isInstance) + .isPresent(); + } + + /** + * Copies all children of {@code source} into {@code target}, skipping {@link Oid} so that the + * target's own canonical OID (set by its constructor) is preserved. + */ + private static void transferChildren(@Nonnull INode source, @Nonnull INode target) { + source.getChildren().entrySet().stream() + .filter(e -> !e.getKey().equals(Oid.class)) + .forEach(e -> target.put(e.getValue())); + } + + /** Returns a new roots list with {@code oldNode} replaced by {@code newNode}. */ + @Nonnull + private static List replaceRoot( + @Nonnull List roots, @Nonnull INode oldNode, @Nonnull INode newNode) { + List newRoots = new LinkedList<>(roots); + newRoots.replaceAll(r -> r == oldNode ? newNode : r); + return newRoots; + } } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizer.java index 5a4708993..73095321f 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizer.java @@ -35,7 +35,6 @@ import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; @@ -194,12 +193,19 @@ public static IReorganizerRule moveNodesFromUnderFunctionalityUnderNode( .flatMap(p -> p.hasChildOfType(underNodeClazz)) .ifPresent( n -> { - for (Map.Entry, INode> - childKeyValue : - node.getChildren().entrySet()) { - n.put(childKeyValue.getValue()); - node.removeChildOfType(childKeyValue.getKey()); - } + // Snapshot the children before mutating so that + // we neither trigger + // ConcurrentModificationException + // nor corrupt a shared map aliased by Algorithm's + // copy constructor (Algorithm.java:39). + new java.util.ArrayList<>( + node.getChildren().values()) + .forEach( + child -> { + n.put(child); + node.removeChildOfType( + child.getKind()); + }); }); return null; }); @@ -252,12 +258,19 @@ public static IReorganizerRule moveNodesFromUnderFunctionalityUnderParent( Optional.ofNullable(parent) .ifPresent( p -> { - for (Map.Entry, INode> - childKeyValue : - node.getChildren().entrySet()) { - p.put(childKeyValue.getValue()); - node.removeChildOfType(childKeyValue.getKey()); - } + // Snapshot the children before mutating so that + // we neither trigger + // ConcurrentModificationException + // nor corrupt a shared map aliased by Algorithm's + // copy constructor (Algorithm.java:39). + new java.util.ArrayList<>( + node.getChildren().values()) + .forEach( + child -> { + p.put(child); + node.removeChildOfType( + child.getKind()); + }); }); return null; }); diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/PythonDetectionRules.java b/python/src/main/java/com/ibm/plugin/rules/detection/PythonDetectionRules.java index a004792e2..49285c613 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/PythonDetectionRules.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/PythonDetectionRules.java @@ -22,20 +22,28 @@ import com.ibm.engine.rule.DetectionRuleSet; import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.RuleSets; -import com.ibm.plugin.rules.detection.aead.PycaAEAD; -import com.ibm.plugin.rules.detection.aead.PycaAES; -import com.ibm.plugin.rules.detection.asymmetric.PycaDSA; -import com.ibm.plugin.rules.detection.asymmetric.PycaDiffieHellman; -import com.ibm.plugin.rules.detection.asymmetric.PycaEllipticCurve; -import com.ibm.plugin.rules.detection.asymmetric.PycaRSA; -import com.ibm.plugin.rules.detection.asymmetric.PycaSign; -import com.ibm.plugin.rules.detection.fernet.PycaFernet; -import com.ibm.plugin.rules.detection.hash.PycaHashWrapper; -import com.ibm.plugin.rules.detection.kdf.PycaKDF; -import com.ibm.plugin.rules.detection.keyagreement.PycaKeyAgreement; -import com.ibm.plugin.rules.detection.mac.PycaMAC; -import com.ibm.plugin.rules.detection.symmetric.PycaCipher; -import com.ibm.plugin.rules.detection.wrapping.PycaWrapping; +import com.ibm.plugin.rules.detection.pyca.aead.PycaAEAD; +import com.ibm.plugin.rules.detection.pyca.aead.PycaAES; +import com.ibm.plugin.rules.detection.pyca.asymmetric.PycaDSA; +import com.ibm.plugin.rules.detection.pyca.asymmetric.PycaDiffieHellman; +import com.ibm.plugin.rules.detection.pyca.asymmetric.PycaEllipticCurve; +import com.ibm.plugin.rules.detection.pyca.asymmetric.PycaRSA; +import com.ibm.plugin.rules.detection.pyca.asymmetric.PycaSign; +import com.ibm.plugin.rules.detection.pyca.fernet.PycaFernet; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHashWrapper; +import com.ibm.plugin.rules.detection.pyca.kdf.PycaKDF; +import com.ibm.plugin.rules.detection.pyca.keyagreement.PycaKeyAgreement; +import com.ibm.plugin.rules.detection.pyca.mac.PycaMAC; +import com.ibm.plugin.rules.detection.pyca.symmetric.PycaCipher; +import com.ibm.plugin.rules.detection.pyca.wrapping.PycaWrapping; +import com.ibm.plugin.rules.detection.pycrypto.cipher.PythonCryptoCipher; +import com.ibm.plugin.rules.detection.pycrypto.hash.PythonCryptoHash; +import com.ibm.plugin.rules.detection.pycrypto.kdf.PythonCryptoKDF; +import com.ibm.plugin.rules.detection.pycrypto.keyagreement.PythonCryptoKeyAgreement; +import com.ibm.plugin.rules.detection.pycrypto.mac.PythonCryptoMac; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoElGamal; +import com.ibm.plugin.rules.detection.pycrypto.random.PythonCryptoRandom; +import com.ibm.plugin.rules.detection.pycrypto.signature.PythonCryptoSignature; import java.util.List; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -61,7 +69,15 @@ protected List> buildRules() { RuleSets.rulesOf(PycaMAC.class).stream(), RuleSets.rulesOf(PycaWrapping.class).stream(), RuleSets.rulesOf(PycaKDF.class).stream(), - RuleSets.rulesOf(PycaFernet.class).stream()) + RuleSets.rulesOf(PycaFernet.class).stream(), + RuleSets.rulesOf(PythonCryptoHash.class).stream(), + RuleSets.rulesOf(PythonCryptoMac.class).stream(), + RuleSets.rulesOf(PythonCryptoRandom.class).stream(), + RuleSets.rulesOf(PythonCryptoCipher.class).stream(), + RuleSets.rulesOf(PythonCryptoElGamal.class).stream(), + RuleSets.rulesOf(PythonCryptoSignature.class).stream(), + RuleSets.rulesOf(PythonCryptoKDF.class).stream(), + RuleSets.rulesOf(PythonCryptoKeyAgreement.class).stream()) .flatMap(i -> i) .toList(); } diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAEAD.java b/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAEAD.java deleted file mode 100644 index 51ba6d76e..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAEAD.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.aead; - -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.SecretKeyContext; -import com.ibm.engine.model.factory.CipherActionFactory; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaAEAD extends DetectionRuleSet { - - private static final String TYPE = - "cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305"; - - private static final IDetectionRule ENCRYPT_CHACHA20POLY1305 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("encrypt") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) - .withAnyParameters() - .buildForContext(new CipherContext(Map.of("algorithm", "ChaCha20Poly1305"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule DECRYPT_CHACHA20POLY1305 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("decrypt") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) - .withAnyParameters() - .buildForContext(new CipherContext(Map.of("algorithm", "ChaCha20Poly1305"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule GENERATION_CHACHA20POLY1305 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("generate_key") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext( - new SecretKeyContext( - Map.of("algorithm", "ChaCha20Poly1305", "kind", "AEAD"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules( - List.of(ENCRYPT_CHACHA20POLY1305, DECRYPT_CHACHA20POLY1305)); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_CHACHA20POLY1305); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAES.java b/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAES.java deleted file mode 100644 index 6d65f92a6..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAES.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.aead; - -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.Size; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.SecretKeyContext; -import com.ibm.engine.model.factory.CipherActionFactory; -import com.ibm.engine.model.factory.KeySizeFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaAES extends DetectionRuleSet { - - private static final List aesAlgorithms = - Arrays.asList("AESGCM", "AESGCMIV", "AESOCB3", "AESSIV", "AESCCM"); - - private static final String AEAD_TYPE_PREFIX = "cryptography.hazmat.primitives.ciphers.aead."; - - private static @Nonnull IDetectionRule encryptAES(String aesAlgorithm) { - return new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(AEAD_TYPE_PREFIX + aesAlgorithm) - .forMethods("encrypt") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) - .withAnyParameters() - .buildForContext( - new CipherContext(Map.of("algorithm", aesAlgorithm, "kind", "AEAD"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - } - - private static @Nonnull IDetectionRule decryptAES(String aesAlgorithm) { - return new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(AEAD_TYPE_PREFIX + aesAlgorithm) - .forMethods("decrypt") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) - .withAnyParameters() - .buildForContext( - new CipherContext(Map.of("algorithm", aesAlgorithm, "kind", "AEAD"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - } - - private static @Nonnull List> generationRulesAES() { - LinkedList> rules = new LinkedList<>(); - for (String aesAlgorithm : aesAlgorithms) { - rules.add( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(AEAD_TYPE_PREFIX + aesAlgorithm) - .forMethods("generate_key") - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) - .buildForContext( - new SecretKeyContext( - Map.of("algorithm", aesAlgorithm, "kind", "AEAD"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules( - List.of(decryptAES(aesAlgorithm), encryptAES(aesAlgorithm)))); - } - return rules; - } - - @Nonnull - @Override - protected List> buildRules() { - return generationRulesAES(); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDSA.java b/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDSA.java deleted file mode 100644 index 73efeeebd..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDSA.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric; - -import static com.ibm.engine.detection.MethodMatcher.ANY; - -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.Size; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.model.factory.KeySizeFactory; -import com.ibm.engine.model.factory.SignatureActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.RuleSets; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.plugin.rules.detection.hash.PycaHash; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaDSA extends DetectionRuleSet { - - private static final String TYPE = "cryptography.hazmat.primitives.asymmetric.dsa"; - - private static final IDetectionRule SIGN_DSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE + ".generate_private_key") - .forMethods("sign") - .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) - .withMethodParameter(ANY) - .withMethodParameter( - "cryptography.hazmat.primitives.*") // This "type" accepts both hashes - // and pre-hashes - .addDependingDetectionRules( - RuleSets.rulesOf( - PycaHash.class)) // The parameter of sign can either be an - // immediate - // hash, or a hash enclosed in the pre-hash - .buildForContext(new SignatureContext(Map.of("algorithm", "DSA"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule GENERATION_DSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("generate_private_key") - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DSA"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(SIGN_DSA)); - - private static final IDetectionRule PUBLIC_NUMBERS_DSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("DSAPublicNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new PublicKeyContext(Map.of("algorithm", "DSA"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule PRIVATE_NUMBERS_DSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("DSAPrivateNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DSA"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_DSA, PUBLIC_NUMBERS_DSA, PRIVATE_NUMBERS_DSA); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDiffieHellman.java b/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDiffieHellman.java deleted file mode 100644 index 4a3024f30..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDiffieHellman.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric; - -import static com.ibm.engine.detection.MethodMatcher.ANY; - -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.Size; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.model.factory.KeySizeFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaDiffieHellman extends DetectionRuleSet { - - private static final String TYPE = "cryptography.hazmat.primitives.asymmetric.dh"; - - // The key size does not yet appear in PycaDiffieHellmanGenerateTestFile because - // of the TraceSymbol problem documented on the Github issue - private static final IDetectionRule GENERATE_PARAMETERS_DH = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("generate_parameters") - .withMethodParameter(ANY) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DH"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule GENERATION_DH = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE + ".generate_parameters") - .forMethods("generate_private_key") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext( - new PrivateKeyContext( - Map.of( - "algorithm", "DH", - "includePublicKey", "true"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(GENERATE_PARAMETERS_DH)); - - private static final IDetectionRule PUBLIC_NUMBERS_DH = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("DHPublicNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new PublicKeyContext(Map.of("algorithm", "DH"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule PRIVATE_NUMBERS_DH = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("DHPrivateNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new PublicKeyContext(Map.of("algorithm", "DH"))) - .inBundle(() -> "CryptographyDiffieHellman") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_DH, PUBLIC_NUMBERS_DH, PRIVATE_NUMBERS_DH); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaEllipticCurve.java b/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaEllipticCurve.java deleted file mode 100644 index 6d8d61836..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaEllipticCurve.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric; - -import static com.ibm.engine.detection.MethodMatcher.ANY; - -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.context.KeyAgreementContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.engine.model.factory.AlgorithmFactory; -import com.ibm.engine.model.factory.CurveFactory; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.model.factory.SignatureActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.RuleSets; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.plugin.rules.detection.hash.PycaHash; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaEllipticCurve extends DetectionRuleSet { - - private static final String TYPE = "cryptography.hazmat.primitives.asymmetric.ec"; - private static final String GENERATE_METHOD = "generate_private_key"; - - // ECDSA is the only algorithm accepted as in the sign/verify functions (it is the only subclass - // of EllipticCurveSignatureAlgorithm) - private static final IDetectionRule ECDSA_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("ECDSA") - .withMethodParameter( - "cryptography.hazmat.primitives.*") // This "type" accepts both hashes - // and pre-hashes - .addDependingDetectionRules( - RuleSets.rulesOf( - PycaHash.class)) // The parameter of ECDSA can either be an - // immediate - // hash, or a hash enclosed in the pre-hash function - .buildForContext(new SignatureContext(Map.of("algorithm", "ECDSA"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - public static final IDetectionRule KEY_EXCHANGE_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE + "." + GENERATE_METHOD) - .forMethods("exchange") - .withMethodParameter(TYPE + ".*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter(ANY) - .buildForContext(new KeyAgreementContext(Map.of("algorithm", "EC"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule SIGN_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE + "." + GENERATE_METHOD) - .forMethods("sign") - .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) - .withMethodParameter(ANY) - .withMethodParameter(TYPE + ".*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .addDependingDetectionRules(List.of(ECDSA_EC)) - .buildForContext(new SignatureContext(Map.of("algorithm", "EC"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule GENERATION_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods(GENERATE_METHOD) - .withMethodParameter(ANY) - .shouldBeDetectedAs(new CurveFactory<>()) - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(SIGN_EC, KEY_EXCHANGE_EC)); - - private static final IDetectionRule DERIVATION_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("derive_private_key") - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .shouldBeDetectedAs(new CurveFactory<>()) - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(SIGN_EC, KEY_EXCHANGE_EC)); - - // Private numbers relies on information (the curve) given by the public key - // For now; we only use it as a depending detection rule of PRIVATE_NUMBERS_EC - private static final IDetectionRule PRIVATE_NUMBERS_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("EllipticCurvePrivateNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule PUBLIC_NUMBERS_EC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(TYPE) - .forMethods("EllipticCurvePublicNumbers") - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .buildForContext(new PublicKeyContext(Map.of("algorithm", "EC"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(PRIVATE_NUMBERS_EC)); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_EC, DERIVATION_EC, PUBLIC_NUMBERS_EC); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaRSA.java b/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaRSA.java deleted file mode 100644 index f564bd490..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaRSA.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric; - -import static com.ibm.engine.detection.MethodMatcher.ANY; - -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.Size; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.engine.model.factory.CipherActionFactory; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.model.factory.KeySizeFactory; -import com.ibm.engine.model.factory.SignatureActionFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.RuleSets; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.plugin.rules.detection.hash.PycaHash; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaRSA extends DetectionRuleSet { - - private static final String PADDING_TYPE = "cryptography.hazmat.primitives.asymmetric.padding"; - private static final String HASH_TYPE = "cryptography.hazmat.primitives.*"; - private static final String RSA_TYPE = "cryptography.hazmat.primitives.asymmetric.rsa"; - - private static final IDetectionRule MGF1 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(PADDING_TYPE) - .forMethods("MGF1") - .shouldBeDetectedAs(new ValueActionFactory<>("MGF1")) - .withMethodParameter(HASH_TYPE) // This "type" accepts both hashes - // and pre-hashes - .addDependingDetectionRules(RuleSets.rulesOf(PycaHash.class)) - .buildForContext(new SignatureContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule PSS = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(PADDING_TYPE) - .forMethods("PSS") - .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) - .withMethodParameter(ANY) - .addDependingDetectionRules(List.of(MGF1)) - .withMethodParameter(ANY) - .buildForContext(new SignatureContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule PKCS1v15 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(PADDING_TYPE) - .forMethods("PKCS1v15") - .shouldBeDetectedAs( - new ValueActionFactory<>( - "PKCS1v15")) // this is necessary to capture something to - // trigger the translation - .withAnyParameters() - .buildForContext(new SignatureContext(Map.of("kind", "padding"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule OAEP = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(PADDING_TYPE) - .forMethods("OAEP") - .shouldBeDetectedAs(new ValueActionFactory<>("OAEP")) - .withMethodParameter(ANY) - // .shouldBeDetectedAs(new AlgorithmFactory<>()) - // .asChildOfParameterWithId(-1) - .addDependingDetectionRules(List.of(MGF1)) - .withMethodParameter(HASH_TYPE) // This "type" accepts both hashes - // and pre-hashes - .addDependingDetectionRules( - RuleSets.rulesOf( - PycaHash.class)) // The parameter of sign can either be an - // immediate - // hash, or a hash enclosed in the pre-hash - .withMethodParameter(ANY) - .buildForContext(new CipherContext(Map.of("kind", "padding"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule SIGN_RSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes( - "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key") - .forMethods("sign") - .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) - .withMethodParameter(ANY) - .withMethodParameter("cryptography.hazmat.primitives.asymmetric.padding.*") - .addDependingDetectionRules( - List.of( - PSS, - PKCS1v15)) // For signatures, padding can only be PSS or PKCSv15 - .withMethodParameter( - HASH_TYPE) // This "type" accepts both hashes and pre-hashes - .addDependingDetectionRules( - RuleSets.rulesOf( - PycaHash.class)) // The parameter of sign can either be an - // immediate - // hash, or a hash enclosed in the pre-hash - .buildForContext(new SignatureContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule DECRYPT_RSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes( - "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key") - .forMethods("decrypt") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) - .withMethodParameter(ANY) - .withMethodParameter("cryptography.hazmat.primitives.asymmetric.padding.*") - .addDependingDetectionRules( - List.of( - OAEP, - PKCS1v15)) // For encryption/decryption, padding can only be - // OAEP or PKCSv15 - .buildForContext(new CipherContext(Map.of("algorithm", "RSA"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule GENERATION_RSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(RSA_TYPE) - .forMethods("generate_private_key") - .withMethodParameter(ANY) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "RSA"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(SIGN_RSA /*,VERIFY_RSA*/, DECRYPT_RSA)); - - private static final IDetectionRule PUBLIC_NUMBERS_RSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(RSA_TYPE) - .forMethods("RSAPublicNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new PublicKeyContext(Map.of("algorithm", "RSA"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(SIGN_RSA /*, VERIFY_RSA*/, DECRYPT_RSA)); - - private static final IDetectionRule PRIVATE_NUMBERS_RSA = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(RSA_TYPE) - .forMethods("RSAPrivateNumbers") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "RSA"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(List.of(SIGN_RSA /*, VERIFY_RSA*/, DECRYPT_RSA)); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_RSA, PUBLIC_NUMBERS_RSA, PRIVATE_NUMBERS_RSA); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaSign.java b/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaSign.java deleted file mode 100644 index dd59824be..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaSign.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric; - -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaSign extends DetectionRuleSet { - - private static final IDetectionRule SIGN_ED25519 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes( - "cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey") - .forMethods("generate") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withoutParameters() - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "Ed25519"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule SIGN_ED448 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes( - "cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey") - .forMethods("generate") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withoutParameters() - .buildForContext(new PrivateKeyContext(Map.of("algorithm", "Ed448"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(SIGN_ED25519, SIGN_ED448); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/fernet/PycaFernet.java b/python/src/main/java/com/ibm/plugin/rules/detection/fernet/PycaFernet.java deleted file mode 100644 index 37d26a2ce..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/fernet/PycaFernet.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.fernet; - -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.KeyContext; -import com.ibm.engine.model.factory.CipherActionFactory; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaFernet extends DetectionRuleSet { - - private static @Nonnull List> encryptDecryptFernet() { - List methodNames = - List.of("encrypt", "encrypt_at_time", "decrypt", "decrypt_at_time"); - List objectNames = List.of("Fernet", "MultiFernet"); - List> rules = new LinkedList<>(); - - for (String method : methodNames) { - for (String object : objectNames) { - rules.add( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.fernet." + object) - .forMethods(method) - .shouldBeDetectedAs( - new CipherActionFactory<>( - method.startsWith("encrypt") - ? CipherAction.Action.ENCRYPT - : CipherAction.Action.DECRYPT)) - .withAnyParameters() - .buildForContext(new CipherContext(Map.of("algorithm", "Fernet"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules()); - } - } - return rules; - } - - private static final IDetectionRule GENERATION_FERNET = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.fernet.Fernet") - .forMethods("generate_key") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withAnyParameters() - .buildForContext(new KeyContext(Map.of("algorithm", "Fernet"))) - .inBundle(() -> "Pyca") - .withDependingDetectionRules(encryptDecryptFernet()); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_FERNET); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHash.java b/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHash.java deleted file mode 100644 index 283c32ecd..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHash.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.hash; - -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.factory.AlgorithmFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaHash extends DetectionRuleSet { - - @SuppressWarnings("java:S2386") - public static final List hashes = - Arrays.asList( - "SHA1", - "SHA512_224", - "SHA512_256", - "SHA224", - "SHA256", - "SHA384", - "SHA512", - "SHA3_224", - "SHA3_256", - "SHA3_384", - "SHA3_512", - "SHAKE128", - "SHAKE256", - "MD5", - "BLAKE2b", - "BLAKE2s", - "SM3"); - - private static @Nonnull List> hashesRules() { - LinkedList> rules = new LinkedList<>(); - for (final String hash : PycaHash.hashes) { - rules.add( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.hashes") - .forMethods(hash) - .shouldBeDetectedAs(new ValueActionFactory<>(hash)) - .withAnyParameters() - .buildForContext(new DigestContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules()); - } - return rules; - } - - private static final IDetectionRule PRE_HASH = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.asymmetric.utils") - .forMethods("Prehashed") - .withMethodParameter("cryptography.hazmat.primitives.hashes.*") - .addDependingDetectionRules(hashesRules()) - .buildForContext(new DigestContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - // Detects hashes.Hash(hashes.SHA256()) and similar direct hash-computation usages. - static final IDetectionRule HASH_WRAPPER = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.hashes") - .forMethods("Hash") - .withMethodParameter("cryptography.hazmat.primitives.hashes.*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .buildForContext(new DigestContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - final List> hashAndPrehashRules = new LinkedList<>(hashesRules()); - hashAndPrehashRules.add(PRE_HASH); - return hashAndPrehashRules; - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/kdf/PycaKDF.java b/python/src/main/java/com/ibm/plugin/rules/detection/kdf/PycaKDF.java deleted file mode 100644 index d6624b334..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/kdf/PycaKDF.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static com.ibm.engine.detection.MethodMatcher.ANY; - -import com.ibm.engine.model.AlgorithmParameter; -import com.ibm.engine.model.Size; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.engine.model.factory.AlgorithmFactory; -import com.ibm.engine.model.factory.AlgorithmParameterFactory; -import com.ibm.engine.model.factory.KeySizeFactory; -import com.ibm.engine.model.factory.ModeFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaKDF extends DetectionRuleSet { - - private static final String HASH_TYPE = "cryptography.hazmat.primitives.hashes.*"; - private static final String KDF_TYPE_PREFIX = "cryptography.hazmat.primitives.kdf."; - - private static final IDetectionRule X963KDF = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "x963kdf") - .forMethods("X963KDF") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "x963"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule KBKDFCMAC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "kbkdf") - .forMethods("KBKDFCMAC") - .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter(ANY) - .shouldBeDetectedAs(new ModeFactory<>()) - .asChildOfParameterWithId(0) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "cmac"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule KBKDFHMAC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "kbkdf") - .forMethods("KBKDFHMAC") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter(ANY) - .shouldBeDetectedAs(new ModeFactory<>()) - .asChildOfParameterWithId(0) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "hmac"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule HKDF_EXPAND = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "hkdf") - .forMethods("HKDFExpand") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "hkdf"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule HKDF = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "hkdf") - .forMethods("HKDF") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "hkdf"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule CONCAT_KDF_HMAC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "concatkdf") - .forMethods("ConcatKDFHMAC") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "concatkdf"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule CONCAT_KDF = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "concatkdf") - .forMethods("ConcatKDFHash") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "concatkdf"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule SCRYPT = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "scrypt") - .forMethods("Scrypt") - .shouldBeDetectedAs(new ValueActionFactory<>("Scrypt")) - .withMethodParameter(ANY) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter("int") - .withMethodParameter("int") - .withMethodParameter("int") - .buildForContext(new KeyDerivationFunctionContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule PBKDF2 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes(KDF_TYPE_PREFIX + "pbkdf2") - .forMethods("PBKDF2HMAC") - .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .withMethodParameter("int") - .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) - .asChildOfParameterWithId(0) - .withMethodParameter(ANY) - .withMethodParameter("int") - .shouldBeDetectedAs( - new AlgorithmParameterFactory<>(AlgorithmParameter.Kind.ITERATIONS)) - .asChildOfParameterWithId(0) - .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "pbkdf2"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of( - PBKDF2, - SCRYPT, - CONCAT_KDF, - CONCAT_KDF_HMAC, - HKDF, - HKDF_EXPAND, - KBKDFHMAC, - KBKDFCMAC, - X963KDF); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreement.java b/python/src/main/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreement.java deleted file mode 100644 index 832cb375f..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreement.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.keyagreement; - -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.KeyAgreementContext; -import com.ibm.engine.model.factory.KeyActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaKeyAgreement extends DetectionRuleSet { - - private static final IDetectionRule GENERATION_X25519 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes( - "cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey") - .forMethods("generate") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withoutParameters() - .buildForContext(new KeyAgreementContext(Map.of("algorithm", "x25519"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule GENERATION_X448 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey") - .forMethods("generate") - .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) - .withoutParameters() - .buildForContext(new KeyAgreementContext(Map.of("algorithm", "x448"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(GENERATION_X25519, GENERATION_X448); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/mac/PycaMAC.java b/python/src/main/java/com/ibm/plugin/rules/detection/mac/PycaMAC.java deleted file mode 100644 index b866cdd0d..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/mac/PycaMAC.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.mac; - -import static com.ibm.engine.detection.MethodMatcher.ANY; - -import com.ibm.engine.model.context.MacContext; -import com.ibm.engine.model.factory.AlgorithmFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaMAC extends DetectionRuleSet { - - private static final IDetectionRule NEW_CMAC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.cmac") - .forMethods("CMAC") - .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .buildForContext(new MacContext(Map.of("kind", "cmac"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule NEW_HMAC = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.hmac") - .forMethods("HMAC") - .withMethodParameter(ANY) - .withMethodParameter( - "cryptography.hazmat.primitives.hashes.*") // Accepts only hashes (not - // pre-hashes) - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .buildForContext(new MacContext(Map.of("kind", "hmac"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule NEW_POLY1305 = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.poly1305") - .forMethods("Poly1305") - .shouldBeDetectedAs(new ValueActionFactory<>("Poly1305")) - .withAnyParameters() - .buildForContext(new MacContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(NEW_CMAC, NEW_HMAC, NEW_POLY1305); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/padding/PycaPadding.java b/python/src/main/java/com/ibm/plugin/rules/detection/padding/PycaPadding.java deleted file mode 100644 index 9d5b7a972..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/padding/PycaPadding.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.padding; - -import com.ibm.engine.model.Size; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.factory.BlockSizeFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.plugin.rules.detection.symmetric.PycaCipher; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaPadding extends DetectionRuleSet { - - private static final List paddings = Arrays.asList("PKCS7", "ANSIX923"); - - private static @Nonnull List> newPadding() { - final LinkedList> rules = new LinkedList<>(); - - for (String padding : paddings) { - rules.add( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.padding") - .forMethods(padding) - .shouldBeDetectedAs(new ValueActionFactory<>(padding)) - .withMethodParameter("int") - .shouldBeDetectedAs(new BlockSizeFactory<>(Size.UnitType.BIT)) - .asChildOfParameterWithId(0) - .buildForContext(new CipherContext(Map.of("kind", "padding"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules()); - } - // When the block size is specified using a `block_size` attribute - for (String padding : paddings) { - for (String cipherAlgorithm : PycaCipher.blockCiphers) { - rules.add( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.padding") - .forMethods(padding) - .shouldBeDetectedAs(new ValueActionFactory<>(padding)) - .withMethodParameter( - "cryptography.hazmat.primitives.ciphers.algorithms." - + cipherAlgorithm - + ".block_size") - .shouldBeDetectedAs(new BlockSizeFactory<>(Size.UnitType.BIT)) - .asChildOfParameterWithId(0) - .buildForContext(new CipherContext(Map.of("kind", "padding"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules()); - } - } - return rules; - } - - // It should be better to only detect Padding when it actually gets implied (i.e. there is - // `padder.update` function call). However, it does not bring much, and creates problems - // because the type handler may not distinguish an `encryptor.update` from `padder.update`. - - @Nonnull - @Override - protected List> buildRules() { - return newPadding(); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher.java deleted file mode 100644 index 79a725b55..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.symmetric; - -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.factory.AlgorithmFactory; -import com.ibm.engine.model.factory.CipherActionFactory; -import com.ibm.engine.model.factory.ModeFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.RuleSets; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.plugin.rules.detection.padding.PycaPadding; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings({"java:S2386", "java:S1192"}) -public final class PycaCipher extends DetectionRuleSet { - - public static final List blockCiphers = - Arrays.asList( - "AES", - "AES128", - "AES256", - "Camellia", - "TripleDES", - "CAST5", - "SEED", - "SM4", - "Blowfish", - "IDEA"); - public static final List streamCiphers = Arrays.asList("ChaCha20", "ARC4"); - - public static final List modes = - Arrays.asList("CBC", "CTR", "OFB", "CFB", "CFB8", "GCM", "XTS", "ECB"); - - private static final IDetectionRule ENCRYPT_CIPHER = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.ciphers.Cipher") - .forMethods("encryptor") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) - .withAnyParameters() - .buildForContext(new CipherContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule DECRYPT_CIPHER = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.ciphers.Cipher") - .forMethods("decryptor") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) - .withAnyParameters() - .buildForContext(new CipherContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static @Nonnull List> followingNewCipherRules() { - final List> encryptionRules = - new LinkedList<>(List.of(DECRYPT_CIPHER, ENCRYPT_CIPHER)); - encryptionRules.addAll(RuleSets.rulesOf(PycaPadding.class)); - return encryptionRules; - } - - private static final IDetectionRule NEW_CIPHER = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.ciphers") - .forMethods("Cipher") - .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .addDependingDetectionRules(followingNewCipherRules()) - .withMethodParameter("cryptography.hazmat.primitives.ciphers.modes.*") - .shouldBeDetectedAs(new ModeFactory<>()) - .asChildOfParameterWithId(0) - .buildForContext(new CipherContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule STREAM_CIPHER = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.ciphers") - .forMethods("Cipher") - .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") - .shouldBeDetectedAs(new AlgorithmFactory<>()) - .addDependingDetectionRules(followingNewCipherRules()) - .withMethodParameter("None") - .shouldBeDetectedAs(new ModeFactory<>()) - .asChildOfParameterWithId(0) - .buildForContext(new CipherContext()) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(NEW_CIPHER, STREAM_CIPHER); - } -} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/wrapping/PycaWrapping.java b/python/src/main/java/com/ibm/plugin/rules/detection/wrapping/PycaWrapping.java deleted file mode 100644 index ba51f19da..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/wrapping/PycaWrapping.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.wrapping; - -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.factory.CipherActionFactory; -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -@SuppressWarnings("java:S1192") -public final class PycaWrapping extends DetectionRuleSet { - - private static final IDetectionRule AES_KEY_WRAP = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.keywrap") - .forMethods("aes_key_wrap") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.WRAP)) - .withAnyParameters() - .buildForContext(new CipherContext(Map.of("algorithm", "AES"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - private static final IDetectionRule AES_KEY_WRAP_WITH_PADDING = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("cryptography.hazmat.primitives.keywrap") - .forMethods("aes_key_wrap_with_padding") - .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.WRAP)) - .withAnyParameters() - .buildForContext(new CipherContext(Map.of("algorithm", "AES"))) - .inBundle(() -> "Pyca") - .withoutDependingDetectionRules(); - - @Nonnull - @Override - protected List> buildRules() { - return List.of(AES_KEY_WRAP, AES_KEY_WRAP_WITH_PADDING); - } -} diff --git a/python/src/main/java/com/ibm/plugin/translation/reorganizer/PythonReorganizerRules.java b/python/src/main/java/com/ibm/plugin/translation/reorganizer/PythonReorganizerRules.java index 6bb880f0e..fd6119d61 100644 --- a/python/src/main/java/com/ibm/plugin/translation/reorganizer/PythonReorganizerRules.java +++ b/python/src/main/java/com/ibm/plugin/translation/reorganizer/PythonReorganizerRules.java @@ -21,12 +21,15 @@ import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; import com.ibm.mapper.model.PublicKeyEncryption; import com.ibm.mapper.model.Signature; import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.model.functionality.Verify; import com.ibm.mapper.reorganizer.IReorganizerRule; import com.ibm.mapper.reorganizer.rules.KeyAgreementReorganizer; import com.ibm.mapper.reorganizer.rules.KeyDerivationReorganizer; +import com.ibm.mapper.reorganizer.rules.MessageDigestReorganizer; import com.ibm.mapper.reorganizer.rules.PaddingReorganizer; import com.ibm.mapper.reorganizer.rules.SignatureReorganizer; import java.util.List; @@ -42,6 +45,15 @@ private PythonReorganizerRules() { @Nonnull public static List rules() { return Stream.of( + MessageDigestReorganizer.WRAP_SHA512_TRUNCATED, + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + Verify.class, ProbabilisticSignatureScheme.class), + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + Verify.class, Signature.class), + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + Sign.class, ProbabilisticSignatureScheme.class), + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + Sign.class, Signature.class), SignatureReorganizer.moveNodesFromUnderFunctionalityUnderNode( Sign.class, PublicKeyEncryption.class), SignatureReorganizer.moveNodesFromUnderFunctionalityUnderNode( @@ -51,6 +63,8 @@ public static List rules() { SignatureReorganizer.MAKE_RSA_TO_SIGNATURE, KeyDerivationReorganizer.moveModeFromParentToNode(BlockCipher.class), KeyDerivationReorganizer.moveModeFromParentToNode(MessageDigest.class), + KeyAgreementReorganizer.REPLACE_ECDH_WITH_X25519_WHEN_CURVE25519, + KeyAgreementReorganizer.REPLACE_ECDH_WITH_X448_WHEN_CURVE448, KeyAgreementReorganizer.MERGE_KEYAGREEMENT_WITH_PKE_UNDER_PRIVATE_KEY, PaddingReorganizer.MOVE_OAEP_UNDER_ALGORITHM) .toList(); diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/PythonTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/PythonTranslator.java index bfb1f5d9a..3de015c65 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/PythonTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/PythonTranslator.java @@ -27,6 +27,7 @@ import com.ibm.engine.model.context.KeyContext; import com.ibm.engine.model.context.KeyDerivationFunctionContext; import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.model.context.PRNGContext; import com.ibm.engine.model.context.PrivateKeyContext; import com.ibm.engine.model.context.PublicKeyContext; import com.ibm.engine.model.context.SecretKeyContext; @@ -38,11 +39,12 @@ import com.ibm.plugin.translation.translator.contexts.PycaCipherContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaDigestContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaKeyAgreementContextTranslator; +import com.ibm.plugin.translation.translator.contexts.PycaKeyContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaKeyDerivationContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaMacContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaPrivateKeyContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaPublicKeyContextTranslator; -import com.ibm.plugin.translation.translator.contexts.PycaSecretContextTranslator; +import com.ibm.plugin.translation.translator.contexts.PycaRandomContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaSecretKeyContextTranslator; import com.ibm.plugin.translation.translator.contexts.PycaSignatureContextTranslator; import java.util.List; @@ -86,11 +88,6 @@ public Optional translate( new PycaKeyDerivationContextTranslator(); return pycaKeyDerivationContextTranslator.translate( bundleIdentifier, value, detectionValueContext, detectionLocation); - } else if (detectionValueContext.is(KeyContext.class)) { - final PycaSecretContextTranslator pycaSecretContextTranslator = - new PycaSecretContextTranslator(); - return pycaSecretContextTranslator.translate( - bundleIdentifier, value, detectionValueContext, detectionLocation); } else if (detectionValueContext.is(PrivateKeyContext.class)) { final PycaPrivateKeyContextTranslator pycaPrivateKeyContextTranslator = new PycaPrivateKeyContextTranslator(); @@ -106,6 +103,11 @@ public Optional translate( new PycaPublicKeyContextTranslator(); return pycaPublicKeyContextTranslator.translate( bundleIdentifier, value, detectionValueContext, detectionLocation); + } else if (detectionValueContext.is(KeyContext.class)) { + final PycaKeyContextTranslator pycaKeyContextTranslator = + new PycaKeyContextTranslator(); + return pycaKeyContextTranslator.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); } else if (detectionValueContext.is(DigestContext.class)) { final PycaDigestContextTranslator pycaDigestContextTranslator = new PycaDigestContextTranslator(); @@ -126,6 +128,11 @@ public Optional translate( new PycaMacContextTranslator(); return pycaMacContextTranslator.translate( bundleIdentifier, value, detectionValueContext, detectionLocation); + } else if (detectionValueContext.is(PRNGContext.class)) { + final PycaRandomContextTranslator pycaRandomContextTranslator = + new PycaRandomContextTranslator(); + return pycaRandomContextTranslator.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); } return Optional.empty(); } diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaCipherContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaCipherContextTranslator.java index ec5341e09..4281ecc44 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaCipherContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaCipherContextTranslator.java @@ -19,6 +19,7 @@ */ package com.ibm.plugin.translation.translator.contexts; +import com.ibm.engine.model.Algorithm; import com.ibm.engine.model.CipherAction; import com.ibm.engine.model.IValue; import com.ibm.engine.model.KeySize; @@ -29,10 +30,13 @@ import com.ibm.mapper.IContextTranslation; import com.ibm.mapper.mapper.pyca.PycaCipherMapper; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.EllipticCurveAlgorithm; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.KeyWrap; +import com.ibm.mapper.model.PublicKeyEncryption; import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.RSA; import com.ibm.mapper.model.functionality.Decrypt; import com.ibm.mapper.model.functionality.Encapsulate; import com.ibm.mapper.model.functionality.Encrypt; @@ -40,15 +44,19 @@ import com.ibm.mapper.model.mode.CCM; import com.ibm.mapper.model.mode.CFB; import com.ibm.mapper.model.mode.CTR; +import com.ibm.mapper.model.mode.EAX; import com.ibm.mapper.model.mode.ECB; import com.ibm.mapper.model.mode.GCM; import com.ibm.mapper.model.mode.GCMSIV; +import com.ibm.mapper.model.mode.KW; +import com.ibm.mapper.model.mode.KWP; import com.ibm.mapper.model.mode.OCB; import com.ibm.mapper.model.mode.OFB; import com.ibm.mapper.model.mode.SIV; import com.ibm.mapper.model.mode.XTS; import com.ibm.mapper.model.padding.ANSIX923; import com.ibm.mapper.model.padding.OAEP; +import com.ibm.mapper.model.padding.PKCS1; import com.ibm.mapper.model.padding.PKCS7; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -65,7 +73,7 @@ public final class PycaCipherContextTranslator implements IContextTranslation) { + if (value instanceof Algorithm) { if (detectionContext.get("kind").map(k -> k.equals("AEAD")).orElse(false)) { return switch (value.asString().toUpperCase().trim()) { case "AESGCM" -> @@ -82,28 +90,52 @@ public final class PycaCipherContextTranslator implements IContextTranslation i); - } else if (value instanceof ValueAction - && detectionContext - .get("kind") - .map(k -> k.equals("padding")) - .orElse(false) // padding case - ) { - return switch (value.asString().toUpperCase().trim()) { - case "PKCS7" -> Optional.of(new PKCS7(detectionLocation)); - case "ANSIX923" -> Optional.of(new ANSIX923(detectionLocation)); - case "OAEP" -> Optional.of(new OAEP(detectionLocation)); - default -> Optional.empty(); + } else if (value instanceof ValueAction) { + if (detectionContext + .get("kind") + .map(k -> k.equals("padding")) + .orElse(false)) { // padding case + return switch (value.asString().toUpperCase().trim()) { + case "PKCS7" -> Optional.of(new PKCS7(detectionLocation)); + case "ANSIX923" -> Optional.of(new ANSIX923(detectionLocation)); + case "OAEP" -> Optional.of(new OAEP(detectionLocation)); + default -> Optional.empty(); + }; + } + + // Handle ValueAction with algorithm name (e.g., "AES", "DES", "DES3") + // Get the algorithm name from the ValueAction + String algorithmName = value.asString(); + return switch (algorithmName.trim().toUpperCase()) { + case "PKCS1_OAEP" -> { + RSA rsaOaep = new RSA(PublicKeyEncryption.class, detectionLocation); + rsaOaep.put(new OAEP(detectionLocation)); + yield Optional.of((INode) rsaOaep); + } + case "PKCS1_V1_5" -> { + RSA rsaPkcs1 = new RSA(PublicKeyEncryption.class, detectionLocation); + rsaPkcs1.put(new PKCS1(detectionLocation)); + yield Optional.of((INode) rsaPkcs1); + } + case "HPKE" -> Optional.of(new EllipticCurveAlgorithm(detectionLocation)); + default -> pycaCipherMapper.parse(algorithmName, detectionLocation).map(i -> i); }; } else if (value instanceof Mode mode) { return switch (mode.asString().toUpperCase().trim()) { - case "CBC" -> Optional.of(new CBC(detectionLocation)); - case "CTR" -> Optional.of(new CTR(detectionLocation)); - case "OFB" -> Optional.of(new OFB(detectionLocation)); - case "CFB" -> Optional.of(new CFB(detectionLocation)); + case "CBC", "MODE_CBC" -> Optional.of(new CBC(detectionLocation)); + case "CTR", "MODE_CTR" -> Optional.of(new CTR(detectionLocation)); + case "OFB", "MODE_OFB" -> Optional.of(new OFB(detectionLocation)); + case "MODE_OCB" -> Optional.of(new OCB(detectionLocation)); + case "CFB", "MODE_CFB" -> Optional.of(new CFB(detectionLocation)); case "CFB8" -> Optional.of(new CFB(8, detectionLocation)); - case "GCM" -> Optional.of(new GCM(detectionLocation)); + case "GCM", "MODE_GCM" -> Optional.of(new GCM(detectionLocation)); case "XTS" -> Optional.of(new XTS(detectionLocation)); - case "ECB" -> Optional.of(new ECB(detectionLocation)); + case "ECB", "MODE_ECB" -> Optional.of(new ECB(detectionLocation)); + case "MODE_EAX" -> Optional.of(new EAX(detectionLocation)); + case "CCM", "MODE_CCM" -> Optional.of(new CCM(detectionLocation)); + case "MODE_SIV" -> Optional.of(new SIV(detectionLocation)); + case "MODE_KW" -> Optional.of(new KW(detectionLocation)); + case "MODE_KWP" -> Optional.of(new KWP(detectionLocation)); default -> Optional.empty(); }; } else if (value instanceof CipherAction cipherAction) { diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaDigestContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaDigestContextTranslator.java index 7c09567e2..978667844 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaDigestContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaDigestContextTranslator.java @@ -19,6 +19,8 @@ */ package com.ibm.plugin.translation.translator.contexts; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.DigestSize; import com.ibm.engine.model.IValue; import com.ibm.engine.model.ValueAction; import com.ibm.engine.model.context.DetectionContext; @@ -41,7 +43,13 @@ public final class PycaDigestContextTranslator implements IContextTranslation value, @Nonnull DetectionContext detectionContext, @Nonnull DetectionLocation detectionLocation) { - if (value instanceof ValueAction || value instanceof com.ibm.engine.model.Algorithm) { + if (value instanceof DigestSize digestSize) { + // Detected truncation parameter (e.g. SHA512.new(truncate=224)): + // produce a DigestSize node; a reorganizer rule will reshape the parent. + return Optional.of( + new com.ibm.mapper.model.DigestSize(digestSize.getValue(), detectionLocation)); + } + if (value instanceof ValueAction || value instanceof Algorithm) { final PycaDigestMapper pycaDigestMapper = new PycaDigestMapper(); return pycaDigestMapper .parse(value.asString(), detectionLocation) diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyAgreementContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyAgreementContextTranslator.java index 614573d88..a8a15b3ca 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyAgreementContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyAgreementContextTranslator.java @@ -22,6 +22,7 @@ import com.ibm.engine.model.Algorithm; import com.ibm.engine.model.IValue; import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.ValueAction; import com.ibm.engine.model.context.DetectionContext; import com.ibm.engine.rule.IBundle; import com.ibm.mapper.IContextTranslation; @@ -45,17 +46,19 @@ public class PycaKeyAgreementContextTranslator implements IContextTranslation value, @Nonnull DetectionContext detectionContext, @Nonnull DetectionLocation detectionLocation) { - if (value instanceof Algorithm algorithm) { - return Optional.of(algorithm) + if (value instanceof ValueAction || value instanceof Algorithm) { + return Optional.of(value.asString().toUpperCase().trim()) .map( algo -> - switch (algo.asString().toUpperCase().trim()) { + switch (algo) { case "ECDH" -> new ECDH(detectionLocation); case "EC" -> new EllipticCurveAlgorithm( KeyAgreement.class, new EllipticCurveAlgorithm( detectionLocation)); + case "X25519" -> new X25519(detectionLocation); + case "X448" -> new X448(detectionLocation); default -> null; }) .map( diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyDerivationContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyDerivationContextTranslator.java index 9dca1a1c6..db285ddc4 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyDerivationContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyDerivationContextTranslator.java @@ -21,8 +21,10 @@ import com.ibm.engine.model.Algorithm; import com.ibm.engine.model.IValue; +import com.ibm.engine.model.IterationCount; import com.ibm.engine.model.KeySize; import com.ibm.engine.model.Mode; +import com.ibm.engine.model.SaltSize; import com.ibm.engine.model.ValueAction; import com.ibm.engine.model.context.DetectionContext; import com.ibm.engine.rule.IBundle; @@ -32,11 +34,15 @@ import com.ibm.mapper.model.Cipher; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.NumberOfIterations; +import com.ibm.mapper.model.SaltLength; import com.ibm.mapper.model.algorithms.ANSIX963; import com.ibm.mapper.model.algorithms.CMAC; import com.ibm.mapper.model.algorithms.ConcatenationKDF; import com.ibm.mapper.model.algorithms.HKDF; import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.model.algorithms.KDFCounter; +import com.ibm.mapper.model.algorithms.PBKDF1; import com.ibm.mapper.model.algorithms.PBKDF2; import com.ibm.mapper.model.algorithms.Scrypt; import com.ibm.mapper.model.functionality.KeyDerivation; @@ -99,6 +105,17 @@ public class PycaKeyDerivationContextTranslator implements IContextTranslation { + final PycaDigestMapper digestMapper = new PycaDigestMapper(); + yield digestMapper + .parse(algorithm.asString(), detectionLocation) + .map( + kdf -> { + final PBKDF1 pbkdf1 = new PBKDF1(kdf); + pbkdf1.put(new KeyDerivation(detectionLocation)); + return pbkdf1; + }); + } case "pbkdf2" -> { final PycaDigestMapper digestMapper = new PycaDigestMapper(); yield digestMapper @@ -110,6 +127,14 @@ public class PycaKeyDerivationContextTranslator implements IContextTranslation { + // ValueAction already creates the root KDF node for these rules; + // the Algorithm child should contribute only the digest. + final PycaDigestMapper digestMapper = new PycaDigestMapper(); + yield digestMapper + .parse(algorithm.asString(), detectionLocation) + .map(digest -> (INode) digest); + } case "concatkdf" -> { final PycaDigestMapper digestMapper = new PycaDigestMapper(); yield digestMapper @@ -144,12 +169,22 @@ public class PycaKeyDerivationContextTranslator implements IContextTranslation keySize) { return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); + } else if (value instanceof IterationCount iterationCount) { + return Optional.of( + new NumberOfIterations(iterationCount.getValue(), detectionLocation)); + } else if (value instanceof SaltSize saltSize) { + return Optional.of(new SaltLength(saltSize.getValue(), detectionLocation)); } else if (value instanceof ValueAction action) { return Optional.of(action.asString().toUpperCase().trim()) .map( str -> - switch (action.asString().toUpperCase().trim()) { + switch (str) { + case "PBKDF1" -> new PBKDF1(detectionLocation); + case "PBKDF2" -> new PBKDF2(detectionLocation); + case "HKDF" -> new HKDF(detectionLocation); case "SCRYPT" -> new Scrypt(detectionLocation); + case "SP800_108_COUNTER" -> + new KDFCounter(detectionLocation); default -> null; }) .map( diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java index 26db48048..d06066ce0 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java @@ -19,6 +19,7 @@ */ package com.ibm.plugin.translation.translator.contexts; +import com.ibm.engine.model.Algorithm; import com.ibm.engine.model.IValue; import com.ibm.engine.model.ValueAction; import com.ibm.engine.model.context.DetectionContext; @@ -26,11 +27,11 @@ import com.ibm.mapper.IContextTranslation; import com.ibm.mapper.mapper.pyca.PycaCipherMapper; import com.ibm.mapper.mapper.pyca.PycaDigestMapper; +import com.ibm.mapper.mapper.pyca.PycaMacMapper; import com.ibm.mapper.model.Cipher; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.algorithms.CMAC; import com.ibm.mapper.model.algorithms.HMAC; -import com.ibm.mapper.model.algorithms.Poly1305; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; import javax.annotation.Nonnull; @@ -46,7 +47,7 @@ public final class PycaMacContextTranslator implements IContextTranslation @Nonnull DetectionContext detectionContext, @Nonnull DetectionLocation detectionLocation) { - if (value instanceof com.ibm.engine.model.Algorithm algorithm) { + if (value instanceof Algorithm algorithm) { // hash algorithm Optional possibleKind = detectionContext.get("kind"); if (possibleKind.isPresent()) { @@ -74,9 +75,8 @@ public final class PycaMacContextTranslator implements IContextTranslation }; } } else if (value instanceof ValueAction action) { - if (action.asString().equalsIgnoreCase("poly1305")) { - return Optional.of(new HMAC(new Poly1305(detectionLocation))); - } + final PycaMacMapper macMapper = new PycaMacMapper(); + return macMapper.parse(action.asString(), detectionLocation).map(n -> n); } return Optional.empty(); } diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java index a997d583f..dc1e4a91b 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java @@ -26,35 +26,13 @@ import com.ibm.engine.model.context.DetectionContext; import com.ibm.engine.rule.IBundle; import com.ibm.mapper.IContextTranslation; -import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.mapper.pyca.PycaCurveMapper; +import com.ibm.mapper.mapper.pyca.PycaKeyBasedAlgorithmMapper; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.PrivateKey; import com.ibm.mapper.model.PublicKeyEncryption; -import com.ibm.mapper.model.algorithms.DH; -import com.ibm.mapper.model.algorithms.DSA; -import com.ibm.mapper.model.algorithms.Ed25519; -import com.ibm.mapper.model.algorithms.Ed448; -import com.ibm.mapper.model.algorithms.RSA; -import com.ibm.mapper.model.curves.Brainpoolp256r1; -import com.ibm.mapper.model.curves.Brainpoolp384r1; -import com.ibm.mapper.model.curves.Brainpoolp512r1; -import com.ibm.mapper.model.curves.Secp192r1; -import com.ibm.mapper.model.curves.Secp224r1; -import com.ibm.mapper.model.curves.Secp256k1; -import com.ibm.mapper.model.curves.Secp256r1; -import com.ibm.mapper.model.curves.Secp384r1; -import com.ibm.mapper.model.curves.Secp521r1; -import com.ibm.mapper.model.curves.Sect163k1; -import com.ibm.mapper.model.curves.Sect163r2; -import com.ibm.mapper.model.curves.Sect233k1; -import com.ibm.mapper.model.curves.Sect233r1; -import com.ibm.mapper.model.curves.Sect283k1; -import com.ibm.mapper.model.curves.Sect283r1; -import com.ibm.mapper.model.curves.Sect409k1; -import com.ibm.mapper.model.curves.Sect409r1; -import com.ibm.mapper.model.curves.Sect571k1; -import com.ibm.mapper.model.curves.Sect571r1; import com.ibm.mapper.model.functionality.KeyGeneration; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -83,41 +61,13 @@ public final class PycaPrivateKeyContextTranslator implements IContextTranslatio .get("algorithm") .map(a -> a.equalsIgnoreCase("EC")) .orElse(false)) { - return Optional.of(curve.asString()) - .map( - str -> - switch (str.toUpperCase().trim()) { - case "SECP256R1" -> new Secp256r1(detectionLocation); - case "SECP384R1" -> new Secp384r1(detectionLocation); - case "SECP521R1" -> new Secp521r1(detectionLocation); - case "SECP224R1" -> new Secp224r1(detectionLocation); - case "SECP192R1" -> new Secp192r1(detectionLocation); - case "SECP256K1" -> new Secp256k1(detectionLocation); - case "BRAINPOOLP256R1" -> - new Brainpoolp256r1(detectionLocation); - case "BRAINPOOLP384R1" -> - new Brainpoolp384r1(detectionLocation); - case "BRAINPOOLP512R1" -> - new Brainpoolp512r1(detectionLocation); - case "SECT571K1" -> new Sect571k1(detectionLocation); - case "SECT409K1" -> new Sect409k1(detectionLocation); - case "SECT283K1" -> new Sect283k1(detectionLocation); - case "SECT233K1" -> new Sect233k1(detectionLocation); - case "SECT163K1" -> new Sect163k1(detectionLocation); - case "SECT571R1" -> new Sect571r1(detectionLocation); - case "SECT409R1" -> new Sect409r1(detectionLocation); - case "SECT283R1" -> new Sect283r1(detectionLocation); - case "SECT233R1" -> new Sect233r1(detectionLocation); - case "SECT163R2" -> new Sect163r2(detectionLocation); - default -> null; - }) - .map(EllipticCurveAlgorithm::new) + final PycaCurveMapper mapper = new PycaCurveMapper(); + return mapper.parse(curve.asString(), detectionLocation) .map( ec -> { PrivateKey privateKey = new PrivateKey((PublicKeyEncryption) ec); - privateKey.put( - new KeyGeneration( - detectionLocation)); // currently only GENERATE is + privateKey.put(new KeyGeneration(detectionLocation)); + // currently only GENERATE is // used as key action is this // context return privateKey; @@ -130,27 +80,14 @@ public final class PycaPrivateKeyContextTranslator implements IContextTranslatio @Nonnull DetectionContext detectionContext, @Nullable Integer keySize, @Nonnull DetectionLocation detectionLocation) { + final PycaKeyBasedAlgorithmMapper mapper = new PycaKeyBasedAlgorithmMapper(); return detectionContext .get("algorithm") + .flatMap(str -> mapper.parse(str, detectionLocation)) + .map(algorithm -> new PrivateKey(new Key(algorithm))) .map( - str -> - switch (str.toUpperCase().trim()) { - case "DH" -> new DH(detectionLocation); - case "RSA" -> new RSA(detectionLocation); - case "DSA" -> new DSA(detectionLocation); - case "EC" -> new EllipticCurveAlgorithm(detectionLocation); - case "ED25519" -> new Ed25519(detectionLocation); - case "ED448" -> new Ed448(detectionLocation); - default -> null; - }) - .map( - algorithm -> { - PrivateKey privateKey = new PrivateKey(algorithm); - privateKey.put( - new KeyGeneration( - detectionLocation)); // currently only GENERATE is - // used as key action is this - // context + privateKey -> { + privateKey.put(new KeyGeneration(detectionLocation)); return privateKey; }) .map( diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java index b876f11d4..f747fd4f0 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java @@ -19,16 +19,18 @@ */ package com.ibm.plugin.translation.translator.contexts; +import com.ibm.engine.model.Curve; import com.ibm.engine.model.IValue; import com.ibm.engine.model.KeyAction; import com.ibm.engine.model.context.DetectionContext; import com.ibm.engine.rule.IBundle; import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.pyca.PycaCurveMapper; +import com.ibm.mapper.mapper.pyca.PycaKeyBasedAlgorithmMapper; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; import com.ibm.mapper.model.PublicKey; -import com.ibm.mapper.model.algorithms.DH; -import com.ibm.mapper.model.algorithms.DSA; -import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.PublicKeyEncryption; import com.ibm.mapper.model.functionality.KeyGeneration; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -45,24 +47,26 @@ public final class PycaPublicKeyContextTranslator implements IContextTranslation @Nonnull DetectionContext detectionContext, @Nonnull DetectionLocation detectionLocation) { if (value instanceof KeyAction) { + final PycaKeyBasedAlgorithmMapper mapper = new PycaKeyBasedAlgorithmMapper(); return detectionContext .get("algorithm") + .flatMap(str -> mapper.parse(str, detectionLocation)) + .map(algorithm -> new PublicKey(new Key(algorithm))) .map( - algorithm -> - switch (algorithm.toUpperCase().trim()) { - case "DH" -> new DH(detectionLocation); - case "RSA" -> new RSA(detectionLocation); - case "DSA" -> new DSA(detectionLocation); - default -> null; - }) + publicKey -> { + publicKey.put(new KeyGeneration(detectionLocation)); + return publicKey; + }); + } else if (value instanceof Curve) { + final PycaCurveMapper mapper = new PycaCurveMapper(); + return mapper.parse(value.asString(), detectionLocation) .map( - algorithm -> { - PublicKey publicKey = new PublicKey(algorithm); - publicKey.put( - new KeyGeneration( - detectionLocation)); // currently only GENERATE is + algo -> { + PublicKey publicKey = new PublicKey((PublicKeyEncryption) algo); + // currently only GENERATE is // used as key action is this // context + publicKey.put(new KeyGeneration(detectionLocation)); return publicKey; }); } diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSecretContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSecretContextTranslator.java deleted file mode 100644 index 91703e972..000000000 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSecretContextTranslator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.translation.translator.contexts; - -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.DetectionContext; -import com.ibm.engine.rule.IBundle; -import com.ibm.mapper.IContextTranslation; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.SecretKey; -import com.ibm.mapper.model.algorithms.Fernet; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.utils.DetectionLocation; -import java.util.Optional; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -public final class PycaSecretContextTranslator implements IContextTranslation { - @Override - public @Nonnull Optional translate( - @Nonnull IBundle bundleIdentifier, - @Nonnull IValue value, - @Nonnull DetectionContext detectionContext, - @Nonnull DetectionLocation detectionLocation) { - if (value instanceof KeyAction) { - // action is always "generate" - return detectionContext - .get("algorithm") - .map( - str -> - switch (str.toUpperCase().trim()) { - case "FERNET" -> new Fernet(detectionLocation); - default -> null; - }) - .map( - algo -> { - final SecretKey key = new SecretKey(algo); - key.put(new KeyGeneration(detectionLocation)); - return key; - }); - } - return Optional.empty(); - } -} diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSignatureContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSignatureContextTranslator.java index 78bb48ba5..4f7072294 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSignatureContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSignatureContextTranslator.java @@ -29,12 +29,15 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.ProbabilisticSignatureScheme; import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.DSS; import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.EdDSA; import com.ibm.mapper.model.algorithms.MGF1; import com.ibm.mapper.model.algorithms.RSA; import com.ibm.mapper.model.algorithms.RSAssaPSS; import com.ibm.mapper.model.functionality.Sign; import com.ibm.mapper.model.functionality.Verify; +import com.ibm.mapper.model.padding.PKCS1; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; import javax.annotation.Nonnull; @@ -82,7 +85,16 @@ public final class PycaSignatureContextTranslator implements IContextTranslation }; } else { return switch (value.asString().toUpperCase().trim()) { + case "RSA" -> Optional.of(new RSA(Signature.class, detectionLocation)); + case "DSS" -> Optional.of(new DSS(detectionLocation)); + case "ECDSA" -> Optional.of(new ECDSA(detectionLocation)); + case "EDDSA" -> Optional.of(new EdDSA(detectionLocation)); case "MGF1" -> Optional.of(new MGF1(detectionLocation)); + case "RSA-PKCS1V15" -> { + RSA rsaPkcs1 = new RSA(Signature.class, detectionLocation); + rsaPkcs1.put(new PKCS1(detectionLocation)); + yield Optional.of((INode) rsaPkcs1); + } case "RSA-PSS" -> Optional.of(new RSAssaPSS(detectionLocation)); default -> Optional.empty(); }; diff --git a/python/src/test/files/rules/detection/aead/PycaAESGCMTestFile.py b/python/src/test/files/rules/detection/aead/PycaAESGCMTestFile.py deleted file mode 100644 index 84de0996e..000000000 --- a/python/src/test/files/rules/detection/aead/PycaAESGCMTestFile.py +++ /dev/null @@ -1,10 +0,0 @@ -import os -from cryptography.hazmat.primitives.ciphers.aead import AESGCM - -data = b"a secret message" -aad = b"authenticated but unencrypted data" -key = AESGCM.generate_key(bit_length=128) # Noncompliant {{(SecretKey) AES}} -aesgcm = AESGCM(key) -nonce = os.urandom(12) -ct = aesgcm.encrypt(nonce, data, aad) -aesgcm.decrypt(nonce, ct, aad) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py b/python/src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py deleted file mode 100644 index 135304288..000000000 --- a/python/src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 - -data = b"a secret message" -aad = b"authenticated but unencrypted data" -key = ChaCha20Poly1305.generate_key() # Noncompliant {{(SecretKey) ChaCha20}} -chacha = ChaCha20Poly1305(key) -nonce = os.urandom(12) -ct = chacha.encrypt(nonce, data, aad) -nonce2 = os.urandom(12) -chacha.decrypt(nonce2, ct, aad) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py b/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py deleted file mode 100644 index a2205d08d..000000000 --- a/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py +++ /dev/null @@ -1,13 +0,0 @@ -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import dsa -from cryptography.hazmat.primitives.asymmetric.dsa import DSAPrivateNumbers - -def generate_dsa_key_from_parameters( - p, q, g, x, y -) -> dsa.DSAPrivateKey: - """ - Generates a DSA private key from parameters p, q, g, x, and y. - """ - public_numbers = dsa.DSAPublicNumbers(y, dsa.DSAParameterNumbers(p, q, g)) # Noncompliant {{(PublicKey) DSA}} - private_numbers = DSAPrivateNumbers(x, public_numbers) # Noncompliant {{(PrivateKey) DSA}} - return private_numbers.private_key(default_backend()) diff --git a/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py b/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py deleted file mode 100644 index 4bed251fe..000000000 --- a/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py +++ /dev/null @@ -1,11 +0,0 @@ -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import dsa - -private_key = dsa.generate_private_key( # Noncompliant {{(PrivateKey) DSA}} - key_size=1024, -) -data = b"this is some data I'd like to sign" -signature = private_key.sign( - data, - hashes.SHA256() -) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py b/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py deleted file mode 100644 index ea45be61b..000000000 --- a/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py +++ /dev/null @@ -1,6 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric import dh - -# Generate some parameters. These can be reused. -parameters = dh.generate_parameters(generator=2, key_size=2048) -# Generate a private key for use in the exchange. -server_private_key = parameters.generate_private_key() # Noncompliant {{(PrivateKey) FFDH}} diff --git a/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py b/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py deleted file mode 100644 index 06a8ae83b..000000000 --- a/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py +++ /dev/null @@ -1,13 +0,0 @@ -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import dh -from cryptography.hazmat.primitives.asymmetric.dh import DHPrivateNumbers - -def generate_dh_key_from_parameters( - p, g, x, y -) -> dh.DHPrivateKey: - """ - Generates a DH private key from parameters p, g, x, and y. - """ - public_numbers = dh.DHPublicNumbers(y, p, g) # Noncompliant {{(PublicKey) FFDH}} - private_numbers = DHPrivateNumbers(x, public_numbers) # Noncompliant {{(PublicKey) FFDH}} - return private_numbers.private_key(default_backend()) diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py b/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py deleted file mode 100644 index e5709f211..000000000 --- a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py +++ /dev/null @@ -1,12 +0,0 @@ -# Code inspired by https://github.com/dimaqq/minioidc/blob/main/tests/test_minioidc.py - -import cryptography.hazmat.primitives.asymmetric.ec -import base64 - -TEST_PRIVATE_KEY = cryptography.hazmat.primitives.asymmetric.ec.derive_private_key( # Noncompliant {{(PrivateKey) EC-secp256r1}} - int.from_bytes( - base64.urlsafe_b64decode("870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" + "==="), - "big", - ), - cryptography.hazmat.primitives.asymmetric.ec.SECP256R1(), -) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py b/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py deleted file mode 100644 index b1d727b32..000000000 --- a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py +++ /dev/null @@ -1,21 +0,0 @@ -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.kdf.hkdf import HKDF - -# Generate a private key for use in the exchange. -server_private_key = ec.generate_private_key( # Noncompliant {{(PrivateKey) EC-secp384r1}} - ec.SECP384R1() -) - -def exchange(public_key): - shared_key = server_private_key.exchange( - ec.ECDH(), public_key) - - # Perform key derivation. // TODO: How should this key derivation be linked to the private key? - derived_key = HKDF( # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} - algorithm=hashes.SHA256(), - length=32, - salt=None, - info=b'handshake data', - ).derive(shared_key) - return derived_key \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py b/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py deleted file mode 100644 index 38c65d80d..000000000 --- a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py +++ /dev/null @@ -1,53 +0,0 @@ -# Code inspired by https://github.com/ydb-platform/ydb/blob/284b7efb67edcdade0b12c849b7fad40739ad62b/contrib/python/Twisted/py2/twisted/conch/ssh/keys.py#L799 - -from cryptography.hazmat.primitives.asymmetric import dsa, rsa, padding, ec - -_curveTable = { - b'ecdsa-sha2-nistp256': ec.SECP256R1(), - b'ecdsa-sha2-nistp384': ec.SECP384R1(), - b'ecdsa-sha2-nistp521': ec.SECP521R1(), -} - -def default_backend(): - global _default_backend - - if _default_backend is None: - from cryptography.hazmat.backends.openssl.backend import backend - - _default_backend = backend - - return _default_backend - -class Key(object): - @classmethod - def _fromECComponents(cls, x, y, curve, privateValue=None): - """ - Build a key from EC components. - - @param x: The affine x component of the public point used for verifying. - @type x: L{int} - - @param y: The affine y component of the public point used for verifying. - @type y: L{int} - - @param curve: NIST name of elliptic curve. - @type curve: L{bytes} - - @param privateValue: The private value. - @type privateValue: L{int} - """ - - publicNumbers = ec.EllipticCurvePublicNumbers( - x=x, y=y, curve=_curveTable[curve]) - if privateValue is None: - # We have public components. - keyObject = publicNumbers.public_key(default_backend()) - else: - privateNumbers = ec.EllipticCurvePrivateNumbers( - private_value=privateValue, public_numbers=publicNumbers) - keyObject = privateNumbers.private_key(default_backend()) - - return cls(keyObject) - -some_var = b'ecdsa-sha2-nistp256' -Key._fromECComponents(None, None, None, some_var, None) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py b/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py deleted file mode 100644 index 56aab2093..000000000 --- a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py +++ /dev/null @@ -1,48 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey - -private_key = Ed25519PrivateKey.generate() # Noncompliant {{(PrivateKey) Ed25519}} -signature = private_key.sign(b"my authenticated message") -public_key = private_key.public_key() -# Raises InvalidSignature if verification fails -public_key.verify(signature, b"my authenticated message") - -private_key = Ed448PrivateKey.generate() # Noncompliant {{(PrivateKey) Ed448}} -signature = private_key.sign(b"my authenticated message") -public_key = private_key.public_key() -# Raises InvalidSignature if verification fails -public_key.verify(signature, b"my authenticated message") - -# False positives that should NOT be detected (PR-429 fix) -# These are unrelated generate() methods with parameters -class VLMModel: - def generate(self, **gen_kwargs): - return [1, 2, 3] - -class TextModel: - def generate(self, *prompts): - return "generated text" - -vlm_model = VLMModel() -text_model = TextModel() - -# These should NOT trigger detection (not cryptography-related) -generated_ids = vlm_model.generate(**{"max_length": 100}) -generated_text = text_model.generate("prompt1", "prompt2") - -# GROUND TRUTH (translation of the 1st finding) -# -# PrivateKey EC -# Signature EdDSA -# MessageDigest SHA-512 -# EllipticCurveAlgorithm EC -# EllipticCurve Curve25519 -# Sign SIGN -# EllipticCurveAlgorithm EC -# EllipticCurve Curve25519 -# KeyGeneration KEYGENERATION -# PublicKey EC -# EllipticCurveAlgorithm EC -# EllipticCurve Curve25519 -# KeyGeneration KEYGENERATION -# \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py b/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py deleted file mode 100644 index 56fdeec3d..000000000 --- a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py +++ /dev/null @@ -1,37 +0,0 @@ -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.asymmetric import utils - -# param = ec.SECP192R1() -param = ec.SECP384R1() -# private_key, other_var = ec.generate_private_key(param), 42 # TODO: because of TraceSeymbols not yet supporting multi-var assignments, this does not work -private_key = ec.generate_private_key(param) # Noncompliant {{(PrivateKey) EC-secp384r1}} - -# Ploys that should not be detected -b = ec.ECDSA(utils.Prehashed(hashes.SHA3_224())) # TODO: The test should pass also when removing "b =" -utils.Prehashed(hashes.SHA3_224()) -hashes.SHA3_224() - -digest = b"\x00" * 64 -sig = private_key.sign(digest, ec.ECDSA(utils.Prehashed(hashes.SHA3_512()))) - -# TODO: Make it work when uncommented -# pk = private_key.public_key() -# pk.verify(sig, digest, ec.ECDSA(hashes.SHA3_512())) - -# GROUND TRUTH (translation) -# -# PrivateKey EC -# Signature ECDSA -# MessageDigest SHA3-512 -# EllipticCurveAlgorithm EC -# EllipticCurve SECP384R1 -# Sign SIGN -# EllipticCurveAlgorithm EC -# EllipticCurve SECP384R1 -# KeyGeneration KEYGENERATION -# PublicKey EC -# EllipticCurveAlgorithm EC -# EllipticCurve SECP384R1 -# KeyGeneration KEYGENERATION -# diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py b/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py deleted file mode 100644 index c357dea0f..000000000 --- a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py +++ /dev/null @@ -1,8 +0,0 @@ -# Code inspired by https://github.com/redis/redis-py/blob/master/redis/ocsp.py - -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec - -def verify(pubkey, signature, digest): - if isinstance(pubkey, ec.EllipticCurvePublicKey): - pubkey.verify(signature, digest, ec.ECDSA(hashes.SHA3_512())) diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py b/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py deleted file mode 100644 index a63b81590..000000000 --- a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py +++ /dev/null @@ -1,19 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import padding - -private_key = rsa.generate_private_key( # Noncompliant {{(PrivateKey) RSA}} - public_exponent=65537, - key_size=1024, -) - -def decrypt(ciphertext): - plaintext = private_key.decrypt( - ciphertext, - padding.OAEP( - mgf=padding.MGF1(algorithm=hashes.SHA384()), - algorithm=hashes.SHA256(), - label=None - ) - ) - return plaintext \ No newline at end of file diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py b/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py deleted file mode 100644 index f84461215..000000000 --- a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py +++ /dev/null @@ -1,13 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric.rsa import * -from cryptography.hazmat.backends import default_backend - -def generate_rsa_key_from_parameters( - p, q, d, dmp1, dmq1, iqmp, e, n -) -> RSAPrivateKey: - """ - Note: from certbot dp is dmp1, dq is dmq1 and qi is iqmp - """ - public_numbers = RSAPublicNumbers(e, n) # Noncompliant {{(PublicKey) RSA}} - return RSAPrivateNumbers( # Noncompliant {{(PrivateKey) RSA}} - p, q, d, dmp1, dmq1, iqmp, public_numbers - ).private_key(default_backend()) diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py b/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py deleted file mode 100644 index 3d25ad2dd..000000000 --- a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py +++ /dev/null @@ -1,19 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric import utils - -private_key = rsa.generate_private_key( # Noncompliant {{(PrivateKey) RSA}} - public_exponent=65537, - key_size=2048, -) - -message = b"A message I want to sign" -signature = private_key.sign( - message, - padding.PSS( - mgf=padding.MGF1(hashes.SHA256()), - salt_length=padding.PSS.MAX_LENGTH - ), - utils.Prehashed(hashes.SHA384()) -) diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py b/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py deleted file mode 100644 index 43fd0ae39..000000000 --- a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py +++ /dev/null @@ -1,16 +0,0 @@ -from cryptography.hazmat.primitives.asymmetric import rsa -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives.asymmetric import utils - -private_key = rsa.generate_private_key( # Noncompliant {{(PrivateKey) RSA}} - public_exponent=65537, - key_size=2048, -) - -message = b"A message I want to sign" -signature = private_key.sign( - message, - padding.PKCS1v15(), - hashes.SHA3_384() -) diff --git a/python/src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py b/python/src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py deleted file mode 100644 index 0ba848d20..000000000 --- a/python/src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py +++ /dev/null @@ -1,15 +0,0 @@ -from cryptography.fernet import Fernet - -def test1(): - key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} - - def dec(ciphertext): - f = Fernet(key) - return f.decrypt(ciphertext) - -def test2(): - key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} - - def dec(ciphertext, time): - f = Fernet(key) - return f.decrypt_at_time(ciphertext, time) diff --git a/python/src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py b/python/src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py deleted file mode 100644 index e7b3eeb58..000000000 --- a/python/src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py +++ /dev/null @@ -1,16 +0,0 @@ -from cryptography.fernet import Fernet - -def test1(): - key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} - - def enc(data): - f = Fernet(key) - return f.encrypt(data) - -def test2(): - key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} - - def enc(data, time): - f = Fernet(key) - return f.encrypt_at_time(data, time) - diff --git a/python/src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py b/python/src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py deleted file mode 100644 index 669b8f0d4..000000000 --- a/python/src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py +++ /dev/null @@ -1,19 +0,0 @@ -from cryptography.fernet import Fernet, MultiFernet - -key1 = Fernet(Fernet.generate_key()) # Noncompliant {{(SecretKey) Fernet}} -key2 = Fernet(Fernet.generate_key()) # Noncompliant {{(SecretKey) Fernet}} - -def enc(data): - return MultiFernet([key1, key2]).encrypt(data) - -def dec(data): - return MultiFernet([key1, key2]).decrypt(data) - -# TODO: When using the following code instead, the depending `encrypt` and `decrypt` are detected twice because the type resolution of `f` does not succeed (so it could be Fernet as well as MultiFernet) - -# f = MultiFernet([key1, key2]) -# def enc(data): -# return f.encrypt(data) - -# def dec(data): -# return f.decrypt(data) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/hash/PycaHashDirectTest.py b/python/src/test/files/rules/detection/hash/PycaHashDirectTest.py deleted file mode 100644 index 3b6afb9c1..000000000 --- a/python/src/test/files/rules/detection/hash/PycaHashDirectTest.py +++ /dev/null @@ -1,5 +0,0 @@ -from cryptography.hazmat.primitives import hashes - -sha256_obj = hashes.Hash(hashes.SHA256()) # Noncompliant {{(MessageDigest) SHA-256}} -sha256_obj.update(b"data") -digest = sha256_obj.finalize() diff --git a/python/src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py b/python/src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py deleted file mode 100644 index bdd95889d..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC - -salt = os.urandom(16) -otherinfo = b"concatkdf-example" - -ckdf = ConcatKDFHMAC( # Noncompliant {{(KeyDerivationFunction) ConcatenationKDF}} - algorithm=hashes.SHA256(), - length=32, - salt=salt, - otherinfo=otherinfo, -) - -key = ckdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py b/python/src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py deleted file mode 100644 index f4be827c0..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash - -otherinfo = b"concatkdf-example" - -ckdf = ConcatKDFHash( # Noncompliant {{(KeyDerivationFunction) ConcatenationKDF}} - algorithm=hashes.SHA256(), - length=64, - otherinfo=otherinfo, -) - -key = ckdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py b/python/src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py deleted file mode 100644 index 081088184..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py +++ /dev/null @@ -1,14 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand - -info = b"hkdf-example" -key_material = os.urandom(16) - -hkdf = HKDFExpand( # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} - algorithm=hashes.SHA256(), - length=32, - info=info, -) - -key = hkdf.derive(key_material) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaHKDFTestFile.py b/python/src/test/files/rules/detection/kdf/PycaHKDFTestFile.py deleted file mode 100644 index eb0a673b0..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaHKDFTestFile.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.hkdf import HKDF - -salt = os.urandom(16) -info = b"hkdf-example" - -hkdf = HKDF( # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} - algorithm=hashes.SHA256(), - length=32, - salt=salt, - info=info, -) - -key = hkdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py b/python/src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py deleted file mode 100644 index abd8f7afb..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py +++ /dev/null @@ -1,21 +0,0 @@ -from cryptography.hazmat.primitives.ciphers import algorithms -from cryptography.hazmat.primitives.kdf.kbkdf import ( - CounterLocation, KBKDFCMAC, Mode -) - -label = b"KBKDF CMAC Label" -context = b"KBKDF CMAC Context" - -kdf = KBKDFCMAC( # Noncompliant {{(Mac) CMAC-AES}} - algorithm=algorithms.AES, - mode=Mode.CounterMode, - length=32, - rlen=4, - llen=4, - location=CounterLocation.BeforeFixed, - label=label, - context=context, - fixed=None, -) - -key = kdf.derive(b"32 bytes long input key material") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py b/python/src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py deleted file mode 100644 index f29af21d6..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.kbkdf import ( - CounterLocation, KBKDFHMAC, Mode -) - -label = b"KBKDF HMAC Label" -context = b"KBKDF HMAC Context" - -kdf = KBKDFHMAC( # Noncompliant {{(Mac) HMAC-SHA-256}} - algorithm=hashes.SHA256(), - mode=Mode.CounterMode, - length=32, - rlen=4, - llen=4, - location=CounterLocation.BeforeFixed, - label=label, - context=context, - fixed=None, -) - -key = kdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py b/python/src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py deleted file mode 100644 index 6baeb886c..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - -# Salts should be randomly generated -salt = os.urandom(16) - -# derive -kdf = PBKDF2HMAC( # Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-256}} - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=480000, -) - -key = kdf.derive(b"my great password") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaScryptTestFile.py b/python/src/test/files/rules/detection/kdf/PycaScryptTestFile.py deleted file mode 100644 index 42c5ad086..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaScryptTestFile.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -from cryptography.hazmat.primitives.kdf.scrypt import Scrypt - -salt = os.urandom(16) - -# derive -kdf = Scrypt( # Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} - salt=salt, - length=32, - n=2**14, - r=8, - p=1, -) - -key = kdf.derive(b"my great password") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py b/python/src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py deleted file mode 100644 index 10c0f51c7..000000000 --- a/python/src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF - -sharedinfo = b"ANSI-KDF-X9.63 Example" - -xkdf = X963KDF( # Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.63}} - algorithm=hashes.SHA256(), - length=32, - sharedinfo=sharedinfo, -) - -key = xkdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py b/python/src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py deleted file mode 100644 index f3212f37a..000000000 --- a/python/src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py +++ /dev/null @@ -1,38 +0,0 @@ -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey -from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey - -# Generate a private key for use in the exchange. -private_key = X25519PrivateKey.generate() # Noncompliant {{(KeyAgreement) x25519}} -# In a real handshake the peer_public_key will be received from the -# other party. For this example we'll generate another private key and -# get a public key from that. Note that in a DH handshake both peers -# must agree on a common set of parameters. -peer_public_key = X25519PrivateKey.generate().public_key() # Noncompliant {{(KeyAgreement) x25519}} -shared_key = private_key.exchange(peer_public_key) - -# Generate a private key for use in the exchange. -private_key = X448PrivateKey.generate() # Noncompliant {{(KeyAgreement) x448}} -# In a real handshake the peer_public_key will be received from the -# other party. For this example we'll generate another private key and -# get a public key from that. Note that in a DH handshake both peers -# must agree on a common set of parameters. -peer_public_key = X448PrivateKey.generate().public_key() # Noncompliant {{(KeyAgreement) x448}} -shared_key = private_key.exchange(peer_public_key) - -# False positives that should NOT be detected (PR-429 fix) -# These are unrelated generate() methods with parameters -class DataGenerator: - def generate(self, **kwargs): - return [1, 2, 3] - -class ModelGenerator: - def generate(self, *args): - return "generated" - -data_gen = DataGenerator() -model_gen = ModelGenerator() - -# These should NOT trigger detection (not cryptography-related) -result1 = data_gen.generate(**{"size": 100}) -result2 = model_gen.generate("prompt1", "prompt2") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/mac/PycaCMACTestFile.py b/python/src/test/files/rules/detection/mac/PycaCMACTestFile.py deleted file mode 100644 index 1d86fa264..000000000 --- a/python/src/test/files/rules/detection/mac/PycaCMACTestFile.py +++ /dev/null @@ -1,25 +0,0 @@ -from cryptography.hazmat.primitives import cmac -from cryptography.hazmat.primitives.ciphers import algorithms - -def generate_cmac(key, data): - # Selecting the desired algorithm (e.g., CMAC-AES) - algorithm = algorithms.AES(key) - - # Creating the CMAC context - cmac_ctx = cmac.CMAC(algorithm) # Noncompliant {{(Mac) CMAC-AES}} - - # Updating the context with the data - cmac_ctx.update(data) - - # Finalizing the CMAC computation and getting the CMAC value - cmac_value = cmac_ctx.finalize() - - return cmac_value - -# Example usage -if __name__ == "__main__": - key = b'Sixteen byte key' # 16-byte key for AES - data = b'This is some data' # Data to generate CMAC for - - cmac_value = generate_cmac(key, data) - print("Generated CMAC:", cmac_value.hex()) diff --git a/python/src/test/files/rules/detection/mac/PycaHMACTestFile.py b/python/src/test/files/rules/detection/mac/PycaHMACTestFile.py deleted file mode 100644 index 70490920a..000000000 --- a/python/src/test/files/rules/detection/mac/PycaHMACTestFile.py +++ /dev/null @@ -1,25 +0,0 @@ -from cryptography.hazmat.primitives import hmac -from cryptography.hazmat.primitives import hashes - -def generate_hmac(key, data): - # Selecting the desired hash algorithm (e.g., SHA-256) - algorithm = hashes.SHA256() - - # Creating the HMAC context - hmac_ctx = hmac.HMAC(key, algorithm) # Noncompliant {{(Mac) HMAC-SHA-256}} - - # Updating the context with the data - hmac_ctx.update(data) - - # Finalizing the HMAC computation and getting the HMAC value - hmac_value = hmac_ctx.finalize() - - return hmac_value - -# Example usage -if __name__ == "__main__": - key = b'SecretKey123' # Key for HMAC - data = b'This is some data' # Data to generate HMAC for - - hmac_value = generate_hmac(key, data) - print("Generated HMAC:", hmac_value.hex()) diff --git a/python/src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py b/python/src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py deleted file mode 100644 index ec0a20fc1..000000000 --- a/python/src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py +++ /dev/null @@ -1,27 +0,0 @@ -from cryptography.hazmat.primitives import hmac -from cryptography.hazmat.primitives import hashes - -def custom_sign(key, data): - # Custom function with cryptographic operation - algorithm = hashes.SHA256() - hmac_obj = hmac.HMAC(key, algorithm) # Noncompliant {{(Mac) HMAC-SHA-256}} - hmac_obj.update(data) - return hmac_obj.finalize() - -def non_crypto_function(text): - # Non-cryptographic function - should not trigger detection - result = "not crypto: " + text - return result.upper() - -# Example usage -if __name__ == "__main__": - key = b'SecretKey123' - data = b'This is some data' - - # Cryptographic operation in custom function is detected - result = custom_sign(key, data) - print("HMAC Result:", result.hex()) - - # Non-cryptographic function call does not trigger detection - text_result = non_crypto_function("hello") - print("Text Result:", text_result) diff --git a/python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py b/python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py deleted file mode 100644 index b51c92c91..000000000 --- a/python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py +++ /dev/null @@ -1,21 +0,0 @@ -from cryptography.hazmat.primitives.poly1305 import Poly1305 - -def generate_poly1305(key, data): - # Create a Poly1305 context with the given key - poly1305_ctx = Poly1305(key) # Noncompliant {{(Mac) HMAC-Poly1305}} - - # Update the context with the data - poly1305_ctx.update(data) - - # Finalize the Poly1305 computation and get the authentication tag - poly1305_tag = poly1305_ctx.finalize() - - return poly1305_tag - -# Example usage -if __name__ == "__main__": - key = b'Sixteen byte key' # 16-byte key for Poly1305 - data = b'This is some data' # Data to generate Poly1305 tag for - - poly1305_tag = generate_poly1305(key, data) - print("Generated Poly1305 Tag:", poly1305_tag.hex()) diff --git a/python/src/test/files/rules/detection/padding/PycaPaddingTestFile.py b/python/src/test/files/rules/detection/padding/PycaPaddingTestFile.py deleted file mode 100644 index 7485f8b52..000000000 --- a/python/src/test/files/rules/detection/padding/PycaPaddingTestFile.py +++ /dev/null @@ -1,16 +0,0 @@ -import os -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives import padding - -key = os.urandom(32) -iv = os.urandom(16) -# Create a cipher object -cipher = Cipher(algorithms.CAST5(key), modes.CFB(iv)) # Noncompliant {{(BlockCipher) CAST5-CFB}} - -padder = padding.ANSIX923(128).padder() -padded_data = padder.update(b"a secret message") -print(padded_data) -padded_data += padder.finalize() -print(padded_data) - -# Then, one could use the cipher to encrypt the padded data \ No newline at end of file diff --git a/python/src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py b/python/src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py deleted file mode 100644 index b95d70dac..000000000 --- a/python/src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.padding import PKCS7 - -key = os.urandom(32) -iv = os.urandom(16) -# Create a cipher object -cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) # Noncompliant {{(BlockCipher) AES-CBC-PKCS7}} - -# Specify padding (PKCS7 in this case) -padder = PKCS7(algorithms.AES.block_size).padder() - -# Encrypt -encryptor = cipher.encryptor() -padded_data = padder.update(b"a secret message") + padder.finalize() -ct = encryptor.update(padded_data) + encryptor.finalize() - -# Decrypt -decryptor = cipher.decryptor() -padded_res = decryptor.update(ct) + decryptor.finalize() -unpadded_res = padder.update(padded_res) + padder.finalize() \ No newline at end of file diff --git a/python/src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py b/python/src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py deleted file mode 100644 index 3372719a6..000000000 --- a/python/src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py +++ /dev/null @@ -1,12 +0,0 @@ -import os -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.padding import PKCS7 - -key = os.urandom(32) -iv = os.urandom(16) -# Create a cipher object -cipher = Cipher(algorithms.Camellia(key), modes.OFB(iv)) # Noncompliant {{(BlockCipher) CAMELLIA-OFB}} - -# Encrypt -encryptor = cipher.encryptor() -ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py b/python/src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py deleted file mode 100644 index a1ff78550..000000000 --- a/python/src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - -key = os.urandom(32) -iv = os.urandom(16) -# Create a cipher object -cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None) # Noncompliant {{(StreamCipher) ChaCha20}} - -# Encrypt -encryptor = cipher.encryptor() -ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py b/python/src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py deleted file mode 100644 index f4b3093e4..000000000 --- a/python/src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py +++ /dev/null @@ -1,25 +0,0 @@ -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.keywrap import aes_key_wrap, aes_key_unwrap - -def aes_key_wrap_example(): - # Generate a key to wrap - key_to_wrap = b'Sixteen byte key' - - # Generate wrapping key (must be 128, 192, or 256 bits long) - wrapping_key = b'ABCDEFGHIJKLMNOP' - - # Wrap the key - wrapped_key = aes_key_wrap(wrapping_key, key_to_wrap, default_backend()) # Noncompliant {{(KeyWrap) AES-128}} - - print("Wrapped Key:", wrapped_key.hex()) - - # Unwrap the key - unwrapped_key = aes_key_unwrap(wrapping_key, wrapped_key, default_backend()) - - print("Unwrapped Key:", unwrapped_key.hex()) - - # Ensure that the unwrapped key matches the original key - assert unwrapped_key == key_to_wrap - -if __name__ == "__main__": - aes_key_wrap_example() diff --git a/python/src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py b/python/src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py deleted file mode 100644 index 62580615e..000000000 --- a/python/src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py +++ /dev/null @@ -1,25 +0,0 @@ -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.keywrap import aes_key_wrap_with_padding, aes_key_unwrap_with_padding - -def aes_key_wrap_example(): - # Generate a key to wrap - key_to_wrap = b'Sixteen byte key' - - # Generate wrapping key (must be 128, 192, or 256 bits long) - wrapping_key = b'ABCDEFGHIJKLMNOP' - - # Wrap the key - wrapped_key = aes_key_wrap_with_padding(wrapping_key, key_to_wrap, default_backend()) # Noncompliant {{(KeyWrap) AES-128}} - - print("Wrapped Key:", wrapped_key.hex()) - - # Unwrap the key - unwrapped_key = aes_key_unwrap_with_padding(wrapping_key, wrapped_key, default_backend()) - - print("Unwrapped Key:", unwrapped_key.hex()) - - # Ensure that the unwrapped key matches the original key - assert unwrapped_key == key_to_wrap - -if __name__ == "__main__": - aes_key_wrap_example() diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/NamedAllKeywordParametersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/NamedAllKeywordParametersTest.java deleted file mode 100644 index 10a25efae..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/NamedAllKeywordParametersTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.AlgorithmParameter; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.factory.AlgorithmParameterFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.mapper.model.INode; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class NamedAllKeywordParametersTest extends TestBase { - private int findingCount; - - NamedAllKeywordParametersTest() { - super( - List.of( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("test.module.Foo") - .forMethods("f") - .shouldBeDetectedAs(new ValueActionFactory<>("f")) - .withNamedMethodParameter("a", "str") - .withNamedMethodParameter("b", "int") - .withOptionalNamedMethodParameter("c", "str") - .shouldBeDetectedAs( - new AlgorithmParameterFactory<>( - AlgorithmParameter.Kind.ANY)) - .asChildOfParameterWithId(2) - .buildForContext(new DigestContext()) - .inBundle(() -> "Test") - .withoutDependingDetectionRules())); - } - - @Test - void namedDeclarationsAcceptAllKeywords() { - PythonCheckVerifier.verifyNoIssue( - "src/test/files/rules/detection/named_parameters/FAllByKeywordTest.py", this); - assertThat(findingCount).isEqualTo(1); - } - - @Test - void namedDeclarationsStillAcceptAllPositionalArguments() { - PythonCheckVerifier.verifyNoIssue( - "src/test/files/rules/detection/named_parameters/FAllPositionalTest.py", this); - assertThat(findingCount).isEqualTo(1); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - findingCount++; - assertThat(detectionStore.getActionValue()).containsInstanceOf(ValueAction.class); - DetectionStore child = - getStoreOfValueType(AlgorithmParameter.class, detectionStore.getChildren()); - assertThat(child).isNotNull(); - assertThat(child.getDetectionValues()).extracting(IValue::asString).containsExactly("yes"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/NamedParameterWildcardTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/NamedParameterWildcardTest.java deleted file mode 100644 index e6a49d629..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/NamedParameterWildcardTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection; - -import static com.ibm.engine.detection.MethodMatcher.ANY; -import static org.assertj.core.api.Assertions.fail; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.mapper.model.INode; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class NamedParameterWildcardTest extends TestBase { - - NamedParameterWildcardTest() { - super( - List.of( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("test.module.Foo") - .forMethods("f") - .shouldBeDetectedAs(new ValueActionFactory<>("f")) - .withMethodParameter(ANY) - .withNamedMethodParameter("b", "int") - .buildForContext(new DigestContext()) - .inBundle(() -> "Test") - .withoutDependingDetectionRules())); - } - - @Test - void keywordForNamedParameterCannotFillPositionalSlot() { - PythonCheckVerifier.verifyNoIssue( - "src/test/files/rules/detection/named_parameters/FNegativePositionalSlotClaimedTest.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - fail("A missing positional argument must not produce a finding"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/NamedParametersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/NamedParametersTest.java deleted file mode 100644 index fe1111d99..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/NamedParametersTest.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.AlgorithmParameter; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.factory.AlgorithmParameterFactory; -import com.ibm.engine.model.factory.ValueActionFactory; -import com.ibm.engine.rule.IDetectionRule; -import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import com.ibm.mapper.model.INode; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -/** - * Tests positional {@code a}, required named {@code b}, and optional named {@code c} in {@code - * Foo.f}. Parameter {@code a} must be passed positionally; the all-named variant is tested - * separately. The rule captures {@code c} as a child, and rejected calls must produce no finding, - * not just no Sonar issue. - */ -class NamedParametersTest extends TestBase { - - private static final String BASE_PATH = "src/test/files/rules/detection/named_parameters/"; - - private static final IDetectionRule RULE = - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("test.module.Foo") - .forMethods("f") - .shouldBeDetectedAs(new ValueActionFactory<>("f")) - .withMethodParameter("str") - .withNamedMethodParameter("b", "int") - .withOptionalNamedMethodParameter("c", "str") - .shouldBeDetectedAs( - new AlgorithmParameterFactory<>(AlgorithmParameter.Kind.ANY)) - .asChildOfParameterWithId(2) - .buildForContext(new DigestContext()) - .inBundle(() -> "Test") - .withoutDependingDetectionRules(); - - private boolean expectCChild = false; - - private int findingCount = 0; - - NamedParametersTest() { - super(List.of(RULE)); - } - - @Test - void testAllPositional() { - expectCChild = true; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FAllPositionalTest.py", this); - } - - @Test - void testCByKeyword() { - expectCChild = true; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FCByKeywordTest.py", this); - } - - @Test - void testBCByKeywordCanonicalOrder() { - expectCChild = true; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FBCByKeywordCanonicalOrderTest.py", this); - } - - @Test - void testBCByKeywordReordered() { - expectCChild = true; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FBCByKeywordReorderedTest.py", this); - } - - @Test - void rejectsKeywordForPositionalParameter() { - verifyNoDetection("FAllByKeywordTest.py"); - } - - @Test - void testExtraUnknownKwarg() { - expectCChild = true; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FExtraUnknownKwargTest.py", this); - } - - @Test - void testCAbsentPositional() { - expectCChild = false; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FCAbsentPositionalTest.py", this); - } - - @Test - void testCAbsentBByKeyword() { - expectCChild = false; - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FCAbsentBByKeywordTest.py", this); - } - - @Test - void rejectsKeywordForPositionalParameterWhenOptionalIsAbsent() { - verifyNoDetection("FCAbsentAllKeywordTest.py"); - } - - @Test - void testOptionalWrongTypeDoesNotCaptureChild() { - PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FOptionalWrongTypeTest.py", this); - assertThat(findingCount).isEqualTo(1); - } - - @Test - void testNegativeBAbsent() { - verifyNoDetection("FNegativeBAbsentTest.py"); - } - - @Test - void testNegativeWrongTypeA() { - verifyNoDetection("FNegativeWrongTypeATest.py"); - } - - @Test - void testNegativeWrongTypeB() { - verifyNoDetection("FNegativeWrongTypeBTest.py"); - } - - @Test - void testNegativeWrongTypeBByKeyword() { - verifyNoDetection("FNegativeWrongTypeBByKeywordTest.py"); - } - - @Test - void testNegativeAAbsent() { - verifyNoDetection("FNegativeAAbsentTest.py"); - } - - @Test - void testNegativeUnrelatedKeywordCannotFillPositionalSlot() { - verifyNoDetection("FNegativeUnrelatedKeywordTest.py"); - } - - @Test - void testNegativeDictUnpack() { - verifyNoDetection("FNegativeDictUnpackTest.py"); - } - - private void verifyNoDetection(String filename) { - PythonCheckVerifier.verifyNoIssue(BASE_PATH + filename, this); - assertThat(findingCount).as("findings for " + filename).isZero(); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - findingCount++; - - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); - - IValue rootValue = detectionStore.getDetectionValues().get(0); - assertThat(rootValue).isInstanceOf(ValueAction.class); - assertThat(rootValue.asString()).isEqualTo("f"); - - DetectionStore cStore = - getStoreOfValueType(AlgorithmParameter.class, detectionStore.getChildren()); - if (expectCChild) { - assertThat(cStore) - .as("expected a child detection store for c but found none") - .isNotNull(); - assertThat(cStore.getDetectionValues()).hasSize(1); - IValue cValue = cStore.getDetectionValues().get(0); - assertThat(cValue).isInstanceOf(AlgorithmParameter.class); - assertThat(cValue.asString()).isEqualTo("yes"); - } else { - assertThat(cStore) - .as("expected no child detection store for c but one was produced") - .isNull(); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaAESGCMTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaAESGCMTest.java deleted file mode 100644 index d3409b362..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaAESGCMTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.aead; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.SecretKeyContext; -import com.ibm.mapper.model.AuthenticatedEncryption; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.SecretKey; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaAESGCMTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/aead/PycaAESGCMTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - IValue value = detectionStore.getDetectionValues().get(0); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(SecretKeyContext.class); - assertThat(value).isInstanceOf(KeySize.class); - assertThat(value.asString()).isEqualTo("128"); - - assertThat(detectionStore.getChildren()).hasSize(2); - - DetectionStore store = - detectionStore.getChildren().get(0); - IValue decryptValue = store.getDetectionValues().get(0); - assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); - assertThat(decryptValue).isInstanceOf(CipherAction.class); - assertThat(decryptValue.asString()).isEqualTo("DECRYPT"); - - store = detectionStore.getChildren().get(1); - IValue encryptValue = store.getDetectionValues().get(0); - assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); - assertThat(encryptValue).isInstanceOf(CipherAction.class); - assertThat(encryptValue.asString()).isEqualTo("ENCRYPT"); - - /* - * Translation - */ - - assertThat(nodes).hasSize(1); - - // SecretKey - INode secretKeyNode = nodes.get(0); - assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); - assertThat(secretKeyNode.getChildren()).hasSize(4); - assertThat(secretKeyNode.asString()).isEqualTo("AES"); - - // AuthenticatedEncryption under SecretKey - INode authenticatedEncryptionNode = - secretKeyNode.getChildren().get(AuthenticatedEncryption.class); - assertThat(authenticatedEncryptionNode).isNotNull(); - assertThat(authenticatedEncryptionNode.getChildren()).hasSize(4); - assertThat(authenticatedEncryptionNode.asString()).isEqualTo("AES-128-GCM"); - - // Mode under AuthenticatedEncryption under SecretKey - INode modeNode = authenticatedEncryptionNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("GCM"); - - // BlockSize under AuthenticatedEncryption under SecretKey - INode blockSizeNode = authenticatedEncryptionNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("128"); - - // Oid under AuthenticatedEncryption under SecretKey - INode oidNode = authenticatedEncryptionNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1.6"); - - // KeyLength under AuthenticatedEncryption under SecretKey - INode keyLengthNode = authenticatedEncryptionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("128"); - - // Encrypt under SecretKey - INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - - // Decrypt under SecretKey - INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); - assertThat(decryptNode).isNotNull(); - assertThat(decryptNode.getChildren()).isEmpty(); - assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); - - // Generate under SecretKey - INode generateNode = secretKeyNode.getChildren().get(KeyGeneration.class); - assertThat(generateNode).isNotNull(); - assertThat(generateNode.getChildren()).isEmpty(); - assertThat(generateNode.asString()).isEqualTo("KEYGENERATION"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaChaCha20Poly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaChaCha20Poly1305Test.java deleted file mode 100644 index 004201c31..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaChaCha20Poly1305Test.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.aead; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.SecretKeyContext; -import com.ibm.mapper.model.AuthenticatedEncryption; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.SecretKey; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaChaCha20Poly1305Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - IValue value = detectionStore.getDetectionValues().get(0); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(SecretKeyContext.class); - assertThat(value).isInstanceOf(KeyAction.class); - assertThat(value.asString()).isEqualTo("GENERATION"); - - assertThat(detectionStore.getChildren()).hasSize(2); - - DetectionStore store = - detectionStore.getChildren().get(0); - IValue decryptValue = store.getDetectionValues().get(0); - assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); - assertThat(decryptValue).isInstanceOf(CipherAction.class); - assertThat(decryptValue.asString()).isEqualTo("ENCRYPT"); - - store = detectionStore.getChildren().get(1); - IValue encryptValue = store.getDetectionValues().get(0); - assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); - assertThat(encryptValue).isInstanceOf(CipherAction.class); - assertThat(encryptValue.asString()).isEqualTo("DECRYPT"); - - /* - * Translation - */ - - assertThat(nodes).hasSize(1); - - // SecretKey - INode secretKeyNode = nodes.get(0); - assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); - assertThat(secretKeyNode.getChildren()).hasSize(4); - assertThat(secretKeyNode.asString()).isEqualTo("ChaCha20"); - - // Encrypt under SecretKey - INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - - // Decrypt under SecretKey - INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); - assertThat(decryptNode).isNotNull(); - assertThat(decryptNode.getChildren()).isEmpty(); - assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); - - // KeyGeneration under SecretKey - INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // AuthenticatedEncryption under SecretKey - INode authenticatedEncryptionNode = - secretKeyNode.getChildren().get(AuthenticatedEncryption.class); - assertThat(authenticatedEncryptionNode).isNotNull(); - assertThat(authenticatedEncryptionNode.getChildren()).hasSize(1); - assertThat(authenticatedEncryptionNode.asString()).isEqualTo("ChaCha20-Poly1305"); - - // MessageDigest under AuthenticatedEncryption under SecretKey - INode messageDigestNode = - authenticatedEncryptionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(1); - assertThat(messageDigestNode.asString()).isEqualTo("Poly1305"); - - // Digest under MessageDigest under AuthenticatedEncryption under SecretKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSANumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSANumbersTest.java deleted file mode 100644 index 2178efc29..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSANumbersTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.DSA; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.PublicKey; -import com.ibm.mapper.model.Signature; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaDSANumbersTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - if (findingId == 0) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PublicKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PublicKey - INode publicKeyNode = nodes.get(0); - assertThat(publicKeyNode.getKind()).isEqualTo(PublicKey.class); - assertThat(publicKeyNode.getChildren()).hasSize(2); - assertThat(publicKeyNode.asString()).isEqualTo("DSA"); - - // Signature under PublicKey - INode signatureNode = publicKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(1); - assertThat(signatureNode.asString()).isEqualTo("DSA"); - - // Oid under Signature under PublicKey - INode oidNode = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.10040.4.1"); - - // KeyGeneration under PublicKey - INode keyGenerationNode = publicKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } else { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("DSA"); - - // Signature under PrivateKey - INode signatureNode = privateKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(1); - assertThat(signatureNode.asString()).isEqualTo("DSA"); - - // Oid under Signature under PrivateKey - INode oidNode = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.10040.4.1"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSASignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSASignTest.java deleted file mode 100644 index 295196b08..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSASignTest.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.DSA; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.Signature; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Sign; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaDSASignTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeySize.class); - assertThat(value0.asString()).isEqualTo("1024"); - - DetectionStore store_1 = - getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(SignatureAction.class); - assertThat(value0_1.asString()).isEqualTo("SIGN"); - - DetectionStore store_1_1 = - getStoreOfValueType(ValueAction.class, store_1.getChildren()); - assertThat(store_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(DigestContext.class); - IValue value0_1_1 = store_1_1.getDetectionValues().get(0); - assertThat(value0_1_1).isInstanceOf(ValueAction.class); - assertThat(value0_1_1.asString()).isEqualTo("SHA256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(4); - assertThat(privateKeyNode.asString()).isEqualTo("DSA"); - - // Signature under PrivateKey - INode signatureNode = privateKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(2); - assertThat(signatureNode.asString()).isEqualTo("DSA-SHA-256"); - - // Oid under Signature under PrivateKey - INode oidNode = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.3.2"); - - // MessageDigest under Signature under PrivateKey - INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // BlockSize under MessageDigest under Signature under PrivateKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Oid under MessageDigest under Signature under PrivateKey - INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under Signature under PrivateKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Digest under MessageDigest under Signature under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // Sign under PrivateKey - INode signNode = privateKeyNode.getChildren().get(Sign.class); - assertThat(signNode).isNotNull(); - assertThat(signNode.getChildren()).isEmpty(); - assertThat(signNode.asString()).isEqualTo("SIGN"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java deleted file mode 100644 index 06ce44018..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.DiffieHellman; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.PublicKeyEncryption; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -public final class PycaDiffieHellmanGenerateTest extends TestBase { - - // The key size does not yet appear because - // of the TraceSymbol problem documented on the Github issue - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("FFDH"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // PublicKeyEncryption under PrivateKey - INode publicKeyEncryptionNode = privateKeyNode.getChildren().get(PublicKeyEncryption.class); - assertThat(publicKeyEncryptionNode).isNotNull(); - assertThat(publicKeyEncryptionNode.getChildren()).hasSize(1); - assertThat(publicKeyEncryptionNode.asString()).isEqualTo("FFDH"); - - // Oid under PublicKeyEncryption under PrivateKey - INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.3.1"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java deleted file mode 100644 index bdd8bf7dc..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.DiffieHellman; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PublicKey; -import com.ibm.mapper.model.PublicKeyEncryption; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaDiffieHellmanNumbersTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PublicKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PublicKey - INode publicKeyNode1 = nodes.get(0); - assertThat(publicKeyNode1.getKind()).isEqualTo(PublicKey.class); - assertThat(publicKeyNode1.getChildren()).hasSize(2); - assertThat(publicKeyNode1.asString()).isEqualTo("FFDH"); - - // PublicKeyEncryption under PublicKey - INode publicKeyEncryptionNode1 = - publicKeyNode1.getChildren().get(PublicKeyEncryption.class); - assertThat(publicKeyEncryptionNode1).isNotNull(); - assertThat(publicKeyEncryptionNode1.getChildren()).hasSize(1); - assertThat(publicKeyEncryptionNode1.asString()).isEqualTo("FFDH"); - - // Oid under PublicKeyEncryption under PublicKey - INode oidNode1 = publicKeyEncryptionNode1.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.1.3.1"); - - // KeyGeneration under PublicKey - INode keyGenerationNode1 = publicKeyNode1.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode1).isNotNull(); - assertThat(keyGenerationNode1.getChildren()).isEmpty(); - assertThat(keyGenerationNode1.asString()).isEqualTo("KEYGENERATION"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java deleted file mode 100644 index dde89a44a..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Curve; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.mapper.model.EllipticCurve; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.PublicKeyEncryption; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaEllipticCurveDeriveTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - IValue value = detectionStore.getDetectionValues().get(0); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); - assertThat(value).isInstanceOf(Curve.class); - assertThat(value.asString()).isEqualTo("SECP256R1"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("EC-secp256r1"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // PublicKeyEncryption under PrivateKey - INode publicKeyEncryptionNode = privateKeyNode.getChildren().get(PublicKeyEncryption.class); - assertThat(publicKeyEncryptionNode).isNotNull(); - assertThat(publicKeyEncryptionNode.getChildren()).hasSize(2); - assertThat(publicKeyEncryptionNode.asString()).isEqualTo("EC-secp256r1"); - - // EllipticCurve under PublicKeyEncryption under PrivateKey - INode ellipticCurveNode = publicKeyEncryptionNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("secp256r1"); - - // Oid under PublicKeyEncryption under PrivateKey - INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.10045.2.1"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java deleted file mode 100644 index ca421610e..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.Curve; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyAgreementContext; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.EllipticCurve; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyAgreement; -import com.ibm.mapper.model.KeyDerivationFunction; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaEllipticCurveKeyExchangeTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - if (findingId == 0) { - /* - * Detection Store - */ - - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Curve.class); - assertThat(value0.asString()).isEqualTo("SECP384R1"); - - DetectionStore store_1 = - getStoreOfValueType(Algorithm.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(KeyAgreementContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(Algorithm.class); - assertThat(value0_1.asString()).isEqualTo("ECDH"); - - /* - * Translation - */ - - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("EC-secp384r1"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // KeyAgreement under PrivateKey - INode keyAgreementNode = privateKeyNode.getChildren().get(KeyAgreement.class); - assertThat(keyAgreementNode).isNotNull(); - assertThat(keyAgreementNode.getChildren()).hasSize(3); - assertThat(keyAgreementNode.asString()).isEqualTo("ECDH"); - - // EllipticCurve under KeyAgreement under PrivateKey - INode ellipticCurveNode = keyAgreementNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("secp384r1"); - - // Oid under KeyAgreement under PrivateKey - INode oidNode = keyAgreementNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.3.132.1.12"); - - // KeyGeneration under PrivateKey - keyGenerationNode = keyAgreementNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } else if (findingId == 1) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyDerivationFunction - INode keyDerivationFunctionNode = nodes.get(0); - assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); - assertThat(keyDerivationFunctionNode.asString()).isEqualTo("HKDF-SHA-256"); - - // KeyDerivation under KeyDerivationFunction - INode keyDerivationNode = - keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // KeyLength under KeyDerivationFunction - INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - - // MessageDigest under KeyDerivationFunction - INode messageDigestNode = - keyDerivationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Digest under MessageDigest under KeyDerivationFunction - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under KeyDerivationFunction - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Oid under MessageDigest under KeyDerivationFunction - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under KeyDerivationFunction - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java deleted file mode 100644 index 2c95fc794..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.mapper.model.INode; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.Ignore; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaEllipticCurveNumbersTest extends TestBase { - - @Ignore("In this testcase the name of a var is resolved, but not teh actual value.") - @Test - void test() { - PythonCheckVerifier.verifyNoIssue( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - // TODO: - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java deleted file mode 100644 index c6156730e..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.EllipticCurve; -import com.ibm.mapper.model.ExtendableOutputFunction; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.Signature; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaEllipticCurveSign2Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - if (findingId == 0) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("Ed25519"); - - // Signature under PrivateKey - INode signatureNode = privateKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(3); - assertThat(signatureNode.asString()).isEqualTo("Ed25519"); - - // MessageDigest under Signature under PrivateKey - INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-512"); - - // Oid under MessageDigest under Signature under PrivateKey - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); - - // DigestSize under MessageDigest under Signature under PrivateKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("512"); - - // Digest under MessageDigest under Signature under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Signature under PrivateKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("1024"); - - // Oid under Signature under PrivateKey - INode oidNode1 = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.3.101.112"); - - // EllipticCurve under Signature under PrivateKey - INode ellipticCurveNode = signatureNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("Edwards25519"); - } else if (findingId == 1) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("Ed448"); - - // Signature under PrivateKey - INode signatureNode = privateKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(3); - assertThat(signatureNode.asString()).isEqualTo("Ed448"); - - // MessageDigest under Signature under PrivateKey - INode messageDigestNode = - signatureNode.getChildren().get(ExtendableOutputFunction.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(2); - assertThat(messageDigestNode.asString()).isEqualTo("SHAKE256"); - - // Digest under MessageDigest under Signature under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Oid under Signature under PrivateKey - INode oidNode = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.3.101.113"); - - // EllipticCurve under Signature under PrivateKey - INode ellipticCurveNode = signatureNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("Edwards448"); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java deleted file mode 100644 index 42e6c8347..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.Curve; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.EllipticCurve; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.Signature; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Sign; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaEllipticCurveSignTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Curve.class); - assertThat(value0.asString()).isEqualTo("SECP384R1"); - - DetectionStore store_1 = - getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(2); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(SignatureAction.class); - assertThat(value0_1.asString()).isEqualTo("SIGN"); - - IValue value1_1 = store_1.getDetectionValues().get(1); - assertThat(value1_1).isInstanceOf(Algorithm.class); - assertThat(value1_1.asString()).isEqualTo("ECDSA"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(3); - assertThat(privateKeyNode.asString()).isEqualTo("EC-secp384r1"); - - // Signature under PrivateKey - INode signatureNode = privateKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(3); - assertThat(signatureNode.asString()).isEqualTo("ECDSA-secp384r1-SHA3-512"); - - // MessageDigest under Signature under PrivateKey - INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA3-512"); - - // BlockSize under MessageDigest under Signature under PrivateKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("576"); - - // Oid under MessageDigest under Signature under PrivateKey - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.10"); - - // DigestSize under MessageDigest under Signature under PrivateKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("512"); - - // Digest under MessageDigest under Signature under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Oid under Signature under PrivateKey - INode oidNode1 = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.3.12"); - - // EllipticCurve under Signature under PrivateKey - INode ellipticCurveNode = signatureNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("secp384r1"); - - // Sign under PrivateKey - INode signNode = privateKeyNode.getChildren().get(Sign.class); - assertThat(signNode).isNotNull(); - assertThat(signNode.getChildren()).isEmpty(); - assertThat(signNode.asString()).isEqualTo("SIGN"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java deleted file mode 100644 index ab38d7bcb..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.mapper.model.INode; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaEllipticCurveVerifyTest extends TestBase { - - // junit4 - @Disabled( - "Reenable once we have an approach to detect `verify` (either make it an entry\n" - + "point, or better handle file imports for depending detection rule)") - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - // TODO - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSADecryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSADecryptTest.java deleted file mode 100644 index 416e98ba3..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSADecryptTest.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MaskGenerationFunction; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.Padding; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.PublicKeyEncryption; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaRSADecryptTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - if (findingId == 0) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeySize.class); - assertThat(value0.asString()).isEqualTo("1024"); - - DetectionStore store_1 = - getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); - assertThat(store_1).isNotNull(); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(CipherAction.class); - assertThat(value0_1.asString()).isEqualTo("DECRYPT"); - - DetectionStore store_1_1 = - getStoreOfValueType(ValueAction.class, store_1.getChildren()); - assertThat(store_1_1).isNotNull(); - assertThat(store_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1_1 = store_1_1.getDetectionValues().get(0); - assertThat(value0_1_1).isInstanceOf(ValueAction.class); - assertThat(value0_1_1.asString()).isEqualTo("OAEP"); - - List> stores = - getStoresOfValueType(ValueAction.class, store_1_1.getChildren()); - assertThat(stores).isNotNull(); - for (DetectionStore s : stores) { - assertThat(s.getDetectionValues()).hasSize(1); - assertThat(s.getDetectionValueContext()) - .isInstanceOfAny(SignatureContext.class, DigestContext.class); - IValue v = s.getDetectionValues().get(0); - assertThat(v).isInstanceOf(ValueAction.class); - assertThat(v.asString()) - .satisfiesAnyOf( - str -> assertThat(str).isEqualTo("MGF1"), - str -> assertThat(str).isEqualTo("SHA256")); - - if (s.getDetectionValueContext().is(SignatureContext.class)) { - DetectionStore store_1_1_1_1 = - getStoreOfValueType(ValueAction.class, s.getChildren()); - assertThat(store_1_1_1_1).isNotNull(); - assertThat(store_1_1_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1_1_1.getDetectionValueContext()) - .isInstanceOf(DigestContext.class); - IValue value0_1_1_1_1 = store_1_1_1_1.getDetectionValues().get(0); - assertThat(value0_1_1_1_1).isInstanceOf(ValueAction.class); - assertThat(value0_1_1_1_1.asString()).isEqualTo("SHA384"); - } - } - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(4); - assertThat(privateKeyNode.asString()).isEqualTo("RSA"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // PublicKeyEncryption under PrivateKey - INode pke = privateKeyNode.getChildren().get(PublicKeyEncryption.class); - assertThat(pke).isNotNull(); - assertThat(pke.getChildren()).hasSize(2); - assertThat(pke.asString()).isEqualTo("RSA-OAEP"); - - // Oid under Signature under PrivateKey - INode oidNode = pke.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.1.7"); - - // Padding under Signature under PrivateKey - INode paddingNode = pke.getChildren().get(Padding.class); - assertThat(paddingNode).isNotNull(); - assertThat(paddingNode.getChildren()).hasSize(2); - assertThat(paddingNode.asString()).isEqualTo("OAEP"); - - // MessageDigest under Padding under Signature under PrivateKey - INode messageDigestNode = paddingNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Oid under MessageDigest under Padding under Signature under PrivateKey - INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // Digest under MessageDigest under Padding under Signature under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Padding under Signature under PrivateKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // DigestSize under MessageDigest under Padding under Signature under PrivateKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // MaskGenerationFunction under Padding under Signature under PrivateKey - INode maskGenerationFunctionNode = - paddingNode.getChildren().get(MaskGenerationFunction.class); - assertThat(maskGenerationFunctionNode).isNotNull(); - assertThat(maskGenerationFunctionNode.getChildren()).hasSize(2); - assertThat(maskGenerationFunctionNode.asString()).isEqualTo("MGF1"); - - // Oid under MaskGenerationFunction under Padding under Signature under PrivateKey - INode oidNode2 = maskGenerationFunctionNode.getChildren().get(Oid.class); - assertThat(oidNode2).isNotNull(); - assertThat(oidNode2.getChildren()).isEmpty(); - assertThat(oidNode2.asString()).isEqualTo("1.2.840.113549.1.1.8"); - - // MessageDigest under MaskGenerationFunction under Padding under Signature under - // PrivateKey - INode messageDigestNode1 = - maskGenerationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode1).isNotNull(); - assertThat(messageDigestNode1.getChildren()).hasSize(4); - assertThat(messageDigestNode1.asString()).isEqualTo("SHA-384"); - - // Oid under MessageDigest under MaskGenerationFunction under Padding under Signature - // under PrivateKey - INode oidNode3 = messageDigestNode1.getChildren().get(Oid.class); - assertThat(oidNode3).isNotNull(); - assertThat(oidNode3.getChildren()).isEmpty(); - assertThat(oidNode3.asString()).isEqualTo("2.16.840.1.101.3.4.2.2"); - - // Digest under MessageDigest under MaskGenerationFunction under Padding under Signature - // under PrivateKey - INode digestNode1 = messageDigestNode1.getChildren().get(Digest.class); - assertThat(digestNode1).isNotNull(); - assertThat(digestNode1.getChildren()).isEmpty(); - assertThat(digestNode1.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under MaskGenerationFunction under Padding under - // Signature under PrivateKey - INode blockSizeNode1 = messageDigestNode1.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("1024"); - - // DigestSize under MessageDigest under MaskGenerationFunction under Padding under - // Signature under PrivateKey - INode digestSizeNode1 = messageDigestNode1.getChildren().get(DigestSize.class); - assertThat(digestSizeNode1).isNotNull(); - assertThat(digestSizeNode1.getChildren()).isEmpty(); - assertThat(digestSizeNode1.asString()).isEqualTo("384"); - - // Decrypt under PrivateKey - INode decryptNode = privateKeyNode.getChildren().get(Decrypt.class); - assertThat(decryptNode).isNotNull(); - assertThat(decryptNode.getChildren()).isEmpty(); - assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); - - // KeyLength under PrivateKey - INode keyLengthNode = privateKeyNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("1024"); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSANumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSANumbersTest.java deleted file mode 100644 index c78a44f64..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSANumbersTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.PublicKeyContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.PublicKey; -import com.ibm.mapper.model.PublicKeyEncryption; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaRSANumbersTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - if (findingId == 0) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PublicKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PublicKey - INode publicKeyNode = nodes.get(0); - assertThat(publicKeyNode.getKind()).isEqualTo(PublicKey.class); - assertThat(publicKeyNode.getChildren()).hasSize(2); - assertThat(publicKeyNode.asString()).isEqualTo("RSA"); - - // PublicKeyEncryption under PublicKey - INode publicKeyEncryptionNode = - publicKeyNode.getChildren().get(PublicKeyEncryption.class); - assertThat(publicKeyEncryptionNode).isNotNull(); - assertThat(publicKeyEncryptionNode.getChildren()).hasSize(1); - assertThat(publicKeyEncryptionNode.asString()).isEqualTo("RSA"); - - // Oid under PublicKeyEncryption under PublicKey - INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.1.1"); - - // KeyGeneration under PublicKey - INode keyGenerationNode = publicKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } else if (findingId == 1) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(2); - assertThat(privateKeyNode.asString()).isEqualTo("RSA"); - - // PublicKeyEncryption under PrivateKey - INode publicKeyEncryptionNode = - privateKeyNode.getChildren().get(PublicKeyEncryption.class); - assertThat(publicKeyEncryptionNode).isNotNull(); - assertThat(publicKeyEncryptionNode.getChildren()).hasSize(1); - assertThat(publicKeyEncryptionNode.asString()).isEqualTo("RSA"); - - // Oid under PublicKeyEncryption under PrivateKey - INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.1.1"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign1Test.java deleted file mode 100644 index e57bdb78a..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign1Test.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MaskGenerationFunction; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.ProbabilisticSignatureScheme; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Sign; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaRSASign1Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeySize.class); - assertThat(value0.asString()).isEqualTo("2048"); - - DetectionStore store_1 = - getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(SignatureAction.class); - assertThat(value0_1.asString()).isEqualTo("SIGN"); - - DetectionStore store_1_1 = - getStoreOfValueType(ValueAction.class, store_1.getChildren()); - assertThat(store_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); - IValue value0_1_1 = store_1_1.getDetectionValues().get(0); - assertThat(value0_1_1).isInstanceOf(ValueAction.class); - assertThat(value0_1_1.asString()).isEqualTo("RSA-PSS"); - - DetectionStore store_1_1_1 = - getStoreOfValueType(ValueAction.class, store_1_1.getChildren()); - assertThat(store_1_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); - IValue value0_1_1_1 = store_1_1_1.getDetectionValues().get(0); - assertThat(value0_1_1_1).isInstanceOf(ValueAction.class); - assertThat(value0_1_1_1.asString()).isEqualTo("MGF1"); - - DetectionStore store_1_1_1_1 = - getStoreOfValueType(ValueAction.class, store_1_1_1.getChildren()); - assertThat(store_1_1_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1_1_1.getDetectionValueContext()).isInstanceOf(DigestContext.class); - IValue value0_1_1_1_1 = store_1_1_1_1.getDetectionValues().get(0); - assertThat(value0_1_1_1_1).isInstanceOf(ValueAction.class); - assertThat(value0_1_1_1_1.asString()).isEqualTo("SHA256"); - - /* - * Translation - */ - - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(4); - assertThat(privateKeyNode.asString()).isEqualTo("RSA"); - - // Sign under PrivateKey - INode signNode = privateKeyNode.getChildren().get(Sign.class); - assertThat(signNode).isNotNull(); - assertThat(signNode.getChildren()).isEmpty(); - assertThat(signNode.asString()).isEqualTo("SIGN"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // ProbabilisticSignatureScheme under PrivateKey - INode probabilisticSignatureSchemeNode = - privateKeyNode.getChildren().get(ProbabilisticSignatureScheme.class); - assertThat(probabilisticSignatureSchemeNode).isNotNull(); - assertThat(probabilisticSignatureSchemeNode.getChildren()).hasSize(3); - assertThat(probabilisticSignatureSchemeNode.asString()).isEqualTo("RSA-PSS"); - - // MaskGenerationFunction under ProbabilisticSignatureScheme under PrivateKey - INode maskGenerationFunctionNode = - probabilisticSignatureSchemeNode.getChildren().get(MaskGenerationFunction.class); - assertThat(maskGenerationFunctionNode).isNotNull(); - assertThat(maskGenerationFunctionNode.getChildren()).hasSize(2); - assertThat(maskGenerationFunctionNode.asString()).isEqualTo("MGF1"); - - // MessageDigest under MaskGenerationFunction under ProbabilisticSignatureScheme under - // PrivateKey - INode messageDigestNode = maskGenerationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // BlockSize under MessageDigest under MaskGenerationFunction under - // ProbabilisticSignatureScheme under PrivateKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // DigestSize under MessageDigest under MaskGenerationFunction under - // ProbabilisticSignatureScheme under PrivateKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under MaskGenerationFunction under ProbabilisticSignatureScheme - // under PrivateKey - INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // Digest under MessageDigest under MaskGenerationFunction under - // ProbabilisticSignatureScheme under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Oid under MaskGenerationFunction under ProbabilisticSignatureScheme under PrivateKey - INode oidNode2 = maskGenerationFunctionNode.getChildren().get(Oid.class); - assertThat(oidNode2).isNotNull(); - assertThat(oidNode2.getChildren()).isEmpty(); - assertThat(oidNode2.asString()).isEqualTo("1.2.840.113549.1.1.8"); - - // MessageDigest under ProbabilisticSignatureScheme under PrivateKey - INode messageDigestNode1 = - probabilisticSignatureSchemeNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode1).isNotNull(); - assertThat(messageDigestNode1.getChildren()).hasSize(4); - assertThat(messageDigestNode1.asString()).isEqualTo("SHA-384"); - - // BlockSize under MessageDigest under ProbabilisticSignatureScheme under PrivateKey - INode blockSizeNode1 = messageDigestNode1.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("1024"); - - // DigestSize under MessageDigest under ProbabilisticSignatureScheme under PrivateKey - INode digestSizeNode1 = messageDigestNode1.getChildren().get(DigestSize.class); - assertThat(digestSizeNode1).isNotNull(); - assertThat(digestSizeNode1.getChildren()).isEmpty(); - assertThat(digestSizeNode1.asString()).isEqualTo("384"); - - // Oid under MessageDigest under ProbabilisticSignatureScheme under PrivateKey - INode oidNode3 = messageDigestNode1.getChildren().get(Oid.class); - assertThat(oidNode3).isNotNull(); - assertThat(oidNode3.getChildren()).isEmpty(); - assertThat(oidNode3.asString()).isEqualTo("2.16.840.1.101.3.4.2.2"); - - // Digest under MessageDigest under ProbabilisticSignatureScheme under PrivateKey - INode digestNode1 = messageDigestNode1.getChildren().get(Digest.class); - assertThat(digestNode1).isNotNull(); - assertThat(digestNode1.getChildren()).isEmpty(); - assertThat(digestNode1.asString()).isEqualTo("DIGEST"); - - // Oid under ProbabilisticSignatureScheme under PrivateKey - INode oidNode4 = probabilisticSignatureSchemeNode.getChildren().get(Oid.class); - assertThat(oidNode4).isNotNull(); - assertThat(oidNode4.getChildren()).isEmpty(); - assertThat(oidNode4.asString()).isEqualTo("1.2.840.113549.1.1.10"); - - // KeyLength under PrivateKey - INode keyLengthNode = privateKeyNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("2048"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign2Test.java deleted file mode 100644 index bb5181e56..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign2Test.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.SignatureAction; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.engine.model.context.PrivateKeyContext; -import com.ibm.engine.model.context.SignatureContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PrivateKey; -import com.ibm.mapper.model.Signature; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Sign; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaRSASign2Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeySize.class); - assertThat(value0.asString()).isEqualTo("2048"); - - DetectionStore store_1 = - getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(SignatureAction.class); - assertThat(value0_1.asString()).isEqualTo("SIGN"); - - List> stores = - getStoresOfValueType(ValueAction.class, store_1.getChildren()); - assertThat(stores).isNotNull(); - for (DetectionStore store : stores) { - assertThat(store.getDetectionValues()).hasSize(1); - assertThat(store.getDetectionValueContext()) - .isInstanceOfAny(SignatureContext.class, DigestContext.class); - IValue v = store.getDetectionValues().get(0); - assertThat(v).isInstanceOf(ValueAction.class); - assertThat(v.asString()) - .satisfiesAnyOf( - s -> assertThat(s).isEqualTo("PKCS1v15"), - s -> assertThat(s).isEqualTo("SHA3_384")); - } - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PrivateKey - INode privateKeyNode = nodes.get(0); - assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); - assertThat(privateKeyNode.getChildren()).hasSize(4); - assertThat(privateKeyNode.asString()).isEqualTo("RSA"); - - // KeyGeneration under PrivateKey - INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // Sign under PrivateKey - INode signNode = privateKeyNode.getChildren().get(Sign.class); - assertThat(signNode).isNotNull(); - assertThat(signNode.getChildren()).isEmpty(); - assertThat(signNode.asString()).isEqualTo("SIGN"); - - // KeyLength under PrivateKey - INode keyLengthNode = privateKeyNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("2048"); - - // Signature under PrivateKey - INode signatureNode = privateKeyNode.getChildren().get(Signature.class); - assertThat(signatureNode).isNotNull(); - assertThat(signatureNode.getChildren()).hasSize(2); - assertThat(signatureNode.asString()).isEqualTo("RSA-PKCS1-1.5-SHA3-384"); - - // MessageDigest under Signature under PrivateKey - INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA3-384"); - - // DigestSize under MessageDigest under Signature under PrivateKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("384"); - - // Oid under MessageDigest under Signature under PrivateKey - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.9"); - - // BlockSize under MessageDigest under Signature under PrivateKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("832"); - - // Digest under MessageDigest under Signature under PrivateKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Oid under Signature under PrivateKey - INode oidNode1 = signatureNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.3.15"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetDecryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetDecryptTest.java deleted file mode 100644 index 10119414d..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetDecryptTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.fernet; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.KeyContext; -import com.ibm.mapper.model.AuthenticatedEncryption; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.Padding; -import com.ibm.mapper.model.SecretKey; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaFernetDecryptTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(KeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - DetectionStore store_1 = - getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(CipherAction.class); - assertThat(value0_1.asString()).isEqualTo("DECRYPT"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // SecretKey - INode secretKeyNode = nodes.get(0); - assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); - assertThat(secretKeyNode.getChildren()).hasSize(3); - assertThat(secretKeyNode.asString()).isEqualTo("Fernet"); - - // KeyGeneration under SecretKey - INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // Decrypt under SecretKey - INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); - assertThat(decryptNode).isNotNull(); - assertThat(decryptNode.getChildren()).isEmpty(); - assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); - - // AuthenticatedEncryption under SecretKey - INode authenticatedEncryptionNode = - secretKeyNode.getChildren().get(AuthenticatedEncryption.class); - assertThat(authenticatedEncryptionNode).isNotNull(); - assertThat(authenticatedEncryptionNode.getChildren()).hasSize(2); - assertThat(authenticatedEncryptionNode.asString()).isEqualTo("Fernet"); - - // Mac under AuthenticatedEncryption under SecretKey - INode macNode = authenticatedEncryptionNode.getChildren().get(Mac.class); - assertThat(macNode).isNotNull(); - assertThat(macNode.getChildren()).hasSize(3); - assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); - - // MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Digest under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // DigestSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // Tag under Mac under AuthenticatedEncryption under SecretKey - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // Oid under Mac under AuthenticatedEncryption under SecretKey - INode oidNode1 = macNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); - - // BlockCipher under AuthenticatedEncryption under SecretKey - INode blockCipherNode = authenticatedEncryptionNode.getChildren().get(BlockCipher.class); - assertThat(blockCipherNode).isNotNull(); - assertThat(blockCipherNode.getChildren()).hasSize(5); - assertThat(blockCipherNode.asString()).isEqualTo("AES-128-CBC-PKCS7"); - - // Mode under BlockCipher under AuthenticatedEncryption under SecretKey - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CBC"); - - // BlockSize under BlockCipher under AuthenticatedEncryption under SecretKey - INode blockSizeNode1 = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("128"); - - // Padding under BlockCipher under AuthenticatedEncryption under SecretKey - INode paddingNode = blockCipherNode.getChildren().get(Padding.class); - assertThat(paddingNode).isNotNull(); - assertThat(paddingNode.getChildren()).isEmpty(); - assertThat(paddingNode.asString()).isEqualTo("PKCS7"); - - // KeyLength under BlockCipher under AuthenticatedEncryption under SecretKey - INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("128"); - - // Oid under BlockCipher under AuthenticatedEncryption under SecretKey - INode oidNode2 = blockCipherNode.getChildren().get(Oid.class); - assertThat(oidNode2).isNotNull(); - assertThat(oidNode2.getChildren()).isEmpty(); - assertThat(oidNode2.asString()).isEqualTo("2.16.840.1.101.3.4.1.2"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetEncryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetEncryptTest.java deleted file mode 100644 index 759916b45..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetEncryptTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.fernet; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.KeyContext; -import com.ibm.mapper.model.AuthenticatedEncryption; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.Padding; -import com.ibm.mapper.model.SecretKey; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaFernetEncryptTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(KeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - DetectionStore store_1 = - getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(CipherAction.class); - assertThat(value0_1.asString()).isEqualTo("ENCRYPT"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // SecretKey - INode secretKeyNode = nodes.get(0); - assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); - assertThat(secretKeyNode.getChildren()).hasSize(3); - assertThat(secretKeyNode.asString()).isEqualTo("Fernet"); - - // Encrypt under SecretKey - INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - - // KeyGeneration under SecretKey - INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // AuthenticatedEncryption under SecretKey - INode authenticatedEncryptionNode = - secretKeyNode.getChildren().get(AuthenticatedEncryption.class); - assertThat(authenticatedEncryptionNode).isNotNull(); - assertThat(authenticatedEncryptionNode.getChildren()).hasSize(2); - assertThat(authenticatedEncryptionNode.asString()).isEqualTo("Fernet"); - - // Mac under AuthenticatedEncryption under SecretKey - INode macNode = authenticatedEncryptionNode.getChildren().get(Mac.class); - assertThat(macNode).isNotNull(); - assertThat(macNode.getChildren()).hasSize(3); - assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); - - // MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Digest under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Oid under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // BlockSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // DigestSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Tag under Mac under AuthenticatedEncryption under SecretKey - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // Oid under Mac under AuthenticatedEncryption under SecretKey - INode oidNode1 = macNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); - - // BlockCipher under AuthenticatedEncryption under SecretKey - INode blockCipherNode = authenticatedEncryptionNode.getChildren().get(BlockCipher.class); - assertThat(blockCipherNode).isNotNull(); - assertThat(blockCipherNode.getChildren()).hasSize(5); - assertThat(blockCipherNode.asString()).isEqualTo("AES-128-CBC-PKCS7"); - - // Mode under BlockCipher under AuthenticatedEncryption under SecretKey - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CBC"); - - // Padding under BlockCipher under AuthenticatedEncryption under SecretKey - INode paddingNode = blockCipherNode.getChildren().get(Padding.class); - assertThat(paddingNode).isNotNull(); - assertThat(paddingNode.getChildren()).isEmpty(); - assertThat(paddingNode.asString()).isEqualTo("PKCS7"); - - // KeyLength under BlockCipher under AuthenticatedEncryption under SecretKey - INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("128"); - - // Oid under BlockCipher under AuthenticatedEncryption under SecretKey - INode oidNode2 = blockCipherNode.getChildren().get(Oid.class); - assertThat(oidNode2).isNotNull(); - assertThat(oidNode2.getChildren()).isEmpty(); - assertThat(oidNode2.asString()).isEqualTo("2.16.840.1.101.3.4.1.2"); - - // BlockSize under BlockCipher under AuthenticatedEncryption under SecretKey - INode blockSizeNode1 = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("128"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaMultiFernetTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaMultiFernetTest.java deleted file mode 100644 index 36fe6893b..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaMultiFernetTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.fernet; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.engine.model.context.KeyContext; -import com.ibm.mapper.model.AuthenticatedEncryption; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.Padding; -import com.ibm.mapper.model.SecretKey; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaMultiFernetTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(KeyContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - DetectionStore store_2 = - getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); - assertThat(store_2.getDetectionValues()).hasSize(1); - assertThat(store_2.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_2 = store_2.getDetectionValues().get(0); - assertThat(value0_2).isInstanceOf(CipherAction.class); - assertThat(value0_2.asString()) - .satisfiesAnyOf( - s -> assertThat(s).isEqualTo("ENCRYPT"), - s -> assertThat(s).isEqualTo("DECRYPT")); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // SecretKey - INode secretKeyNode = nodes.get(0); - assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); - assertThat(secretKeyNode.getChildren()).hasSize(4); - assertThat(secretKeyNode.asString()).isEqualTo("Fernet"); - - // Decrypt under SecretKey - INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); - assertThat(decryptNode).isNotNull(); - assertThat(decryptNode.getChildren()).isEmpty(); - assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); - - // AuthenticatedEncryption under SecretKey - INode authenticatedEncryptionNode = - secretKeyNode.getChildren().get(AuthenticatedEncryption.class); - assertThat(authenticatedEncryptionNode).isNotNull(); - assertThat(authenticatedEncryptionNode.getChildren()).hasSize(2); - assertThat(authenticatedEncryptionNode.asString()).isEqualTo("Fernet"); - - // BlockCipher under AuthenticatedEncryption under SecretKey - INode blockCipherNode = authenticatedEncryptionNode.getChildren().get(BlockCipher.class); - assertThat(blockCipherNode).isNotNull(); - assertThat(blockCipherNode.getChildren()).hasSize(5); - assertThat(blockCipherNode.asString()).isEqualTo("AES-128-CBC-PKCS7"); - - // BlockSize under BlockCipher under AuthenticatedEncryption under SecretKey - INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("128"); - - // KeyLength under BlockCipher under AuthenticatedEncryption under SecretKey - INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("128"); - - // Oid under BlockCipher under AuthenticatedEncryption under SecretKey - INode oidNode = blockCipherNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1.2"); - - // Mode under BlockCipher under AuthenticatedEncryption under SecretKey - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CBC"); - - // Padding under BlockCipher under AuthenticatedEncryption under SecretKey - INode paddingNode = blockCipherNode.getChildren().get(Padding.class); - assertThat(paddingNode).isNotNull(); - assertThat(paddingNode.getChildren()).isEmpty(); - assertThat(paddingNode.asString()).isEqualTo("PKCS7"); - - // Mac under AuthenticatedEncryption under SecretKey - INode macNode = authenticatedEncryptionNode.getChildren().get(Mac.class); - assertThat(macNode).isNotNull(); - assertThat(macNode.getChildren()).hasSize(3); - assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); - - // Tag under Mac under AuthenticatedEncryption under SecretKey - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // Oid under Mac under AuthenticatedEncryption under SecretKey - oidNode = macNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.2.9"); - - // MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // BlockSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode blockSizeNode1 = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("512"); - - // Oid under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under Mac under AuthenticatedEncryption under - // SecretKey - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Digest under MessageDigest under Mac under AuthenticatedEncryption under SecretKey - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Encrypt under SecretKey - INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - - // KeyGeneration under SecretKey - INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/hash/PycaHashDirectTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/hash/PycaHashDirectTest.java deleted file mode 100644 index 73cef79d4..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/hash/PycaHashDirectTest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.hash; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.DigestContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaHashDirectTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/hash/PycaHashDirectTest.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // MessageDigest (SHA256) - INode messageDigestNode = nodes.get(0); - assertThat(messageDigestNode.getKind()).isEqualTo(MessageDigest.class); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // DigestSize under MessageDigest - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // BlockSize under MessageDigest - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Oid under MessageDigest - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // Digest functionality under MessageDigest - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHMACTest.java deleted file mode 100644 index 4c72b5be8..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHMACTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyDerivationFunction; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaConcatKDFHMACTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(macNode.getChildren()).hasSize(3); - assertThat(macNode.asString()).isEqualTo("ConcatenationKDF"); - - // MessageDigest under Mac - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // DigestSize under MessageDigest under Mac - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under Mac - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // Digest under MessageDigest under Mac - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Mac - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // KeyDerivation under Mac - INode keyDerivationNode = macNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // KeyLength under Mac - INode keyLengthNode = macNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHashTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHashTest.java deleted file mode 100644 index 68a47dbc9..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHashTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyDerivationFunction; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaConcatKDFHashTest extends TestBase { - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("512"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - // KeyDerivationFunction - INode keyDerivationFunctionNode = nodes.get(0); - assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); - assertThat(keyDerivationFunctionNode.asString()).isEqualTo("ConcatenationKDF"); - - // MessageDigest under KeyDerivationFunction - INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // BlockSize under MessageDigest under KeyDerivationFunction - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Digest under MessageDigest under KeyDerivationFunction - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // Oid under MessageDigest under KeyDerivationFunction - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under KeyDerivationFunction - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // KeyLength under KeyDerivationFunction - INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("512"); - - // KeyDerivation under KeyDerivationFunction - INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFExpandTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFExpandTest.java deleted file mode 100644 index 0eade96a6..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFExpandTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyDerivationFunction; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaHKDFExpandTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyDerivationFunction - INode keyDerivationFunctionNode = nodes.get(0); - assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); - assertThat(keyDerivationFunctionNode.asString()).isEqualTo("HKDF-SHA-256"); - - // KeyDerivation under KeyDerivationFunction - INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // KeyLength under KeyDerivationFunction - INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - - // MessageDigest under KeyDerivationFunction - INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // BlockSize under MessageDigest under KeyDerivationFunction - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Digest under MessageDigest under KeyDerivationFunction - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // DigestSize under MessageDigest under KeyDerivationFunction - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under KeyDerivationFunction - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFTest.java deleted file mode 100644 index e7a4a5f16..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyDerivationFunction; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaHKDFTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify("src/test/files/rules/detection/kdf/PycaHKDFTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyDerivationFunction - INode keyDerivationFunctionNode = nodes.get(0); - assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); - assertThat(keyDerivationFunctionNode.asString()).isEqualTo("HKDF-SHA-256"); - - // MessageDigest under KeyDerivationFunction - INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // DigestSize under MessageDigest under KeyDerivationFunction - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under KeyDerivationFunction - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // BlockSize under MessageDigest under KeyDerivationFunction - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Digest under MessageDigest under KeyDerivationFunction - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // KeyLength under KeyDerivationFunction - INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - - // KeyDerivation under KeyDerivationFunction - INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFCMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFCMACTest.java deleted file mode 100644 index 0436832c1..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFCMACTest.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaKBKDFCMACTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("AES"); - - DetectionStore store_1 = - getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(com.ibm.engine.model.Mode.class); - assertThat(value0_1.asString()).isEqualTo("CounterMode"); - - DetectionStore store_2 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_2.getDetectionValues()).hasSize(1); - assertThat(store_2.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_2 = store_2.getDetectionValues().get(0); - assertThat(value0_2).isInstanceOf(KeySize.class); - assertThat(value0_2.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(4); - assertThat(macNode.asString()).isEqualTo("CMAC-AES"); - - // Tag under Mac - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // KeyDerivation under Mac - INode keyDerivationNode = macNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // BlockCipher under Mac - INode blockCipherNode = macNode.getChildren().get(BlockCipher.class); - assertThat(blockCipherNode).isNotNull(); - assertThat(blockCipherNode.getChildren()).hasSize(3); - assertThat(blockCipherNode.asString()).isEqualTo("AES-CTR"); - - // BlockSize under BlockCipher under Mac - INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("128"); - - // Mode under BlockCipher under Mac - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CTR"); - - // Oid under BlockCipher under Mac - INode oidNode = blockCipherNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); - - // KeyLength under Mac - INode keyLengthNode = macNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFHMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFHMACTest.java deleted file mode 100644 index ef0fad6b9..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFHMACTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaKBKDFHMACTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(com.ibm.engine.model.Mode.class); - assertThat(value0_1.asString()).isEqualTo("CounterMode"); - - DetectionStore store_2 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_2.getDetectionValues()).hasSize(1); - assertThat(store_2.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_2 = store_2.getDetectionValues().get(0); - assertThat(value0_2).isInstanceOf(KeySize.class); - assertThat(value0_2.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(5); - assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); - - // Tag under Mac - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // Oid under Mac - INode oidNode = macNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.2.9"); - - // KeyLength under Mac - INode keyLengthNode = macNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - - // KeyDerivation under Mac - INode keyDerivationNode = macNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // MessageDigest under Mac - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(5); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Mode under MessageDigest under Mac - INode modeNode = messageDigestNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CTR"); - - // DigestSize under MessageDigest under Mac - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under Mac - INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // Digest under MessageDigest under Mac - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Mac - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaPBKDF2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaPBKDF2Test.java deleted file mode 100644 index bdcc9af1f..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaPBKDF2Test.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.AlgorithmParameter; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaPBKDF2Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - DetectionStore store_2 = - getStoreOfValueType(AlgorithmParameter.class, detectionStore.getChildren()); - assertThat(store_2.getDetectionValues()).hasSize(1); - assertThat(store_2.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_2 = store_2.getDetectionValues().get(0); - assertThat(value0_2).isInstanceOf(AlgorithmParameter.class); - assertThat(value0_2.asString()).isEqualTo("480000"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PasswordBasedKeyDerivationFunction - INode passwordBasedKeyDerivationFunctionNode = nodes.get(0); - assertThat(passwordBasedKeyDerivationFunctionNode.getKind()) - .isEqualTo(PasswordBasedKeyDerivationFunction.class); - assertThat(passwordBasedKeyDerivationFunctionNode.getChildren()).hasSize(3); - assertThat(passwordBasedKeyDerivationFunctionNode.asString()).isEqualTo("PBKDF2-SHA-256"); - - // KeyDerivation under PasswordBasedKeyDerivationFunction - INode keyDerivationNode = - passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // MessageDigest under PasswordBasedKeyDerivationFunction - INode messageDigestNode = - passwordBasedKeyDerivationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Digest under MessageDigest under PasswordBasedKeyDerivationFunction - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under PasswordBasedKeyDerivationFunction - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // DigestSize under MessageDigest under PasswordBasedKeyDerivationFunction - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Oid under MessageDigest under PasswordBasedKeyDerivationFunction - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // KeyLength under PasswordBasedKeyDerivationFunction - INode keyLengthNode = - passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaScryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaScryptTest.java deleted file mode 100644 index b137aabb5..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaScryptTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaScryptTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaScryptTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(ValueAction.class); - assertThat(value0.asString()).isEqualTo("Scrypt"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // PasswordBasedKeyDerivationFunction - INode passwordBasedKeyDerivationFunctionNode = nodes.get(0); - assertThat(passwordBasedKeyDerivationFunctionNode.getKind()) - .isEqualTo(PasswordBasedKeyDerivationFunction.class); - assertThat(passwordBasedKeyDerivationFunctionNode.getChildren()).hasSize(2); - assertThat(passwordBasedKeyDerivationFunctionNode.asString()).isEqualTo("scrypt"); - - // KeyDerivation under PasswordBasedKeyDerivationFunction - INode keyDerivationNode = - passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // KeyLength under PasswordBasedKeyDerivationFunction - INode keyLengthNode = - passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaX963KDFTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaX963KDFTest.java deleted file mode 100644 index 786fa46cb..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaX963KDFTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.kdf; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.context.KeyDerivationFunctionContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyDerivationFunction; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.KeyDerivation; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaX963KDFTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - DetectionStore store_1 = - getStoreOfValueType(KeySize.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()) - .isInstanceOf(KeyDerivationFunctionContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(KeySize.class); - assertThat(value0_1.asString()).isEqualTo("256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyDerivationFunction - INode keyDerivationFunctionNode = nodes.get(0); - assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); - assertThat(keyDerivationFunctionNode.asString()).isEqualTo("ANSI-KDF-X9.63"); - - // KeyDerivation under KeyDerivationFunction - INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); - assertThat(keyDerivationNode).isNotNull(); - assertThat(keyDerivationNode.getChildren()).isEmpty(); - assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); - - // KeyLength under KeyDerivationFunction - INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("256"); - - // MessageDigest under KeyDerivationFunction - INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // BlockSize under MessageDigest under KeyDerivationFunction - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Oid under MessageDigest under KeyDerivationFunction - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under KeyDerivationFunction - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Digest under MessageDigest under KeyDerivationFunction - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreementTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreementTest.java deleted file mode 100644 index bb88f0e77..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreementTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.keyagreement; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeyAction; -import com.ibm.engine.model.context.KeyAgreementContext; -import com.ibm.mapper.model.EllipticCurve; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyAgreement; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.KeyGeneration; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaKeyAgreementTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - if (findingId == 0) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyAgreementContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyAgreement - INode keyAgreementNode = nodes.get(0); - assertThat(keyAgreementNode.getKind()).isEqualTo(KeyAgreement.class); - assertThat(keyAgreementNode.getChildren()).hasSize(3); - assertThat(keyAgreementNode.asString()).isEqualTo("x25519"); - - // EllipticCurve under KeyAgreement - INode ellipticCurveNode = keyAgreementNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("Curve25519"); - - // KeyGeneration under KeyAgreement - INode keyGenerationNode = keyAgreementNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // Oid under KeyAgreement - INode oidNode1 = keyAgreementNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.3.101.110"); - - } else if (findingId == 1) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyAgreementContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyAgreement - INode keyAgreementNode = nodes.get(0); - assertThat(keyAgreementNode.getKind()).isEqualTo(KeyAgreement.class); - assertThat(keyAgreementNode.getChildren()).hasSize(3); - assertThat(keyAgreementNode.asString()).isEqualTo("x25519"); - - // EllipticCurve under KeyAgreement - INode ellipticCurveNode = keyAgreementNode.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode).isNotNull(); - assertThat(ellipticCurveNode.getChildren()).isEmpty(); - assertThat(ellipticCurveNode.asString()).isEqualTo("Curve25519"); - - // KeyGeneration under KeyAgreement - INode keyGenerationNode = keyAgreementNode.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode).isNotNull(); - assertThat(keyGenerationNode.getChildren()).isEmpty(); - assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); - - // Oid under KeyAgreement - INode oidNode1 = keyAgreementNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.3.101.110"); - - } else { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()) - .isInstanceOf(KeyAgreementContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(KeyAction.class); - assertThat(value0.asString()).isEqualTo("GENERATION"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // KeyAgreement - INode keyAgreementNode1 = nodes.get(0); - assertThat(keyAgreementNode1.getKind()).isEqualTo(KeyAgreement.class); - assertThat(keyAgreementNode1.getChildren()).hasSize(3); - assertThat(keyAgreementNode1.asString()).isEqualTo("x448"); - - // EllipticCurve under KeyAgreement - INode ellipticCurveNode1 = keyAgreementNode1.getChildren().get(EllipticCurve.class); - assertThat(ellipticCurveNode1).isNotNull(); - assertThat(ellipticCurveNode1.getChildren()).isEmpty(); - assertThat(ellipticCurveNode1.asString()).isEqualTo("Curve448"); - - // KeyGeneration under KeyAgreement - INode keyGenerationNode1 = keyAgreementNode1.getChildren().get(KeyGeneration.class); - assertThat(keyGenerationNode1).isNotNull(); - assertThat(keyGenerationNode1.getChildren()).isEmpty(); - assertThat(keyGenerationNode1.asString()).isEqualTo("KEYGENERATION"); - - // Oid under KeyAgreement - INode oidNode3 = keyAgreementNode1.getChildren().get(Oid.class); - assertThat(oidNode3).isNotNull(); - assertThat(oidNode3.getChildren()).isEmpty(); - assertThat(oidNode3.asString()).isEqualTo("1.3.101.111"); - } - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaCMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaCMACTest.java deleted file mode 100644 index bbc0f3f2b..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaCMACTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.mac; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.MacContext; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaCMACTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify("src/test/files/rules/detection/mac/PycaCMACTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("AES"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(2); - assertThat(macNode.asString()).isEqualTo("CMAC-AES"); - - // BlockCipher under Mac - INode blockCipherNode = macNode.getChildren().get(BlockCipher.class); - assertThat(blockCipherNode).isNotNull(); - assertThat(blockCipherNode.getChildren()).hasSize(2); - assertThat(blockCipherNode.asString()).isEqualTo("AES"); - - // BlockSize under BlockCipher under Mac - INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("128"); - - // Oid under BlockCipher under Mac - INode oidNode = blockCipherNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); - - // Tag under Mac - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaHMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaHMACTest.java deleted file mode 100644 index 9957f5381..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaHMACTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.mac; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.MacContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaHMACTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify("src/test/files/rules/detection/mac/PycaHMACTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(3); - assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); - - // MessageDigest under Mac - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Digest under MessageDigest under Mac - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Mac - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Oid under MessageDigest under Mac - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under Mac - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Tag under Mac - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // Oid under Mac - INode oidNode1 = macNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaMacDetectionInCustomFunctionTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaMacDetectionInCustomFunctionTest.java deleted file mode 100644 index 9a89f28eb..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaMacDetectionInCustomFunctionTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.mac; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.MacContext; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.DigestSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -/** - * Verifies that cryptographic operations inside user-defined functions are detected during standard - * AST traversal. This test covers both positive cases (cryptographic operations) and negative cases - * (non-cryptographic functions). - */ -class PycaMacDetectionInCustomFunctionTest extends TestBase { - - @Test - void testCryptographicOperationInCustomFunction() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py", - this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - // Verifies that cryptographic operations inside user-defined functions are detected - // during standard AST traversal. - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("SHA256"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(3); - assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); - - // MessageDigest under Mac - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(4); - assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); - - // Digest under MessageDigest under Mac - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - - // BlockSize under MessageDigest under Mac - INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("512"); - - // Oid under MessageDigest under Mac - INode oidNode = messageDigestNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); - - // DigestSize under MessageDigest under Mac - INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); - assertThat(digestSizeNode).isNotNull(); - assertThat(digestSizeNode.getChildren()).isEmpty(); - assertThat(digestSizeNode.asString()).isEqualTo("256"); - - // Tag under Mac - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // Oid under Mac - INode oidNode1 = macNode.getChildren().get(Oid.class); - assertThat(oidNode1).isNotNull(); - assertThat(oidNode1.getChildren()).isEmpty(); - assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaPoly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaPoly1305Test.java deleted file mode 100644 index fecc5bd37..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaPoly1305Test.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.mac; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.MacContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mac; -import com.ibm.mapper.model.MessageDigest; -import com.ibm.mapper.model.functionality.Digest; -import com.ibm.mapper.model.functionality.Tag; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaPoly1305Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/mac/PycaPoly1305TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(ValueAction.class); - assertThat(value0.asString()).isEqualTo("Poly1305"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // Mac - INode macNode = nodes.get(0); - assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(2); - assertThat(macNode.asString()).isEqualTo("HMAC-Poly1305"); - - // Tag under Mac - INode tagNode = macNode.getChildren().get(Tag.class); - assertThat(tagNode).isNotNull(); - assertThat(tagNode.getChildren()).isEmpty(); - assertThat(tagNode.asString()).isEqualTo("TAG"); - - // MessageDigest under Mac - INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); - assertThat(messageDigestNode).isNotNull(); - assertThat(messageDigestNode.getChildren()).hasSize(1); - assertThat(messageDigestNode.asString()).isEqualTo("Poly1305"); - - // Digest under MessageDigest under Mac - INode digestNode = messageDigestNode.getChildren().get(Digest.class); - assertThat(digestNode).isNotNull(); - assertThat(digestNode.getChildren()).isEmpty(); - assertThat(digestNode.asString()).isEqualTo("DIGEST"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/padding/PycaPaddingTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/padding/PycaPaddingTest.java deleted file mode 100644 index a2f29030a..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/padding/PycaPaddingTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.padding; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Padding; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaPaddingTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/padding/PycaPaddingTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("CAST5"); - - DetectionStore store_1 = - getStoreOfValueType(ValueAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(ValueAction.class); - assertThat(value0_1.asString()).isEqualTo("ANSIX923"); - - DetectionStore store_1_1 = - getStoreOfValueType(com.ibm.engine.model.BlockSize.class, store_1.getChildren()); - assertThat(store_1_1.getDetectionValues()).hasSize(1); - assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1_1 = store_1_1.getDetectionValues().get(0); - assertThat(value0_1_1).isInstanceOf(com.ibm.engine.model.BlockSize.class); - assertThat(value0_1_1.asString()).isEqualTo("128"); - - DetectionStore store_2 = - getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); - assertThat(store_2.getDetectionValues()).hasSize(1); - assertThat(store_2.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_2 = store_2.getDetectionValues().get(0); - assertThat(value0_2).isInstanceOf(com.ibm.engine.model.Mode.class); - assertThat(value0_2.asString()).isEqualTo("CFB"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // BlockCipher - INode blockCipherNode = nodes.get(0); - assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); - assertThat(blockCipherNode.getChildren()).hasSize(3); - assertThat(blockCipherNode.asString()).isEqualTo("CAST5-CFB"); - - // BlockSize under BlockCipher - INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("64"); - - // Mode under BlockCipher - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CFB"); - - // Padding under BlockCipher - INode paddingNode = blockCipherNode.getChildren().get(Padding.class); - assertThat(paddingNode).isNotNull(); - assertThat(paddingNode.getChildren()).hasSize(1); - assertThat(paddingNode.asString()).isEqualTo("ANSI X9.23"); - - // BlockSize under Padding under BlockCipher - INode blockSizeNode1 = paddingNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("128"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher1Test.java deleted file mode 100644 index 73a1d6ebb..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher1Test.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.symmetric; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.ValueAction; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.BlockSize; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.Oid; -import com.ibm.mapper.model.Padding; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaCipher1Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("AES"); - - List> store_1 = - getStoresOfValueType(CipherAction.class, detectionStore.getChildren()); - for (DetectionStore store : store_1) { - assertThat(store.getDetectionValues()).hasSize(1); - assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(CipherAction.class); - assertThat(value0_1.asString()) - .satisfiesAnyOf( - s -> assertThat(s).isEqualTo("DECRYPT"), - s -> assertThat(s).isEqualTo("ENCRYPT")); - } - - DetectionStore store_3 = - getStoreOfValueType(ValueAction.class, detectionStore.getChildren()); - assertThat(store_3.getDetectionValues()).hasSize(1); - assertThat(store_3.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_3 = store_3.getDetectionValues().get(0); - assertThat(value0_3).isInstanceOf(ValueAction.class); - assertThat(value0_3.asString()).isEqualTo("PKCS7"); - - DetectionStore store_3_1 = - getStoreOfValueType(com.ibm.engine.model.BlockSize.class, store_3.getChildren()); - assertThat(store_3_1.getDetectionValues()).hasSize(1); - assertThat(store_3_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_3_1 = store_3_1.getDetectionValues().get(0); - assertThat(value0_3_1).isInstanceOf(com.ibm.engine.model.BlockSize.class); - assertThat(value0_3_1.asString()).isEqualTo("80"); - - DetectionStore store_4 = - getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); - assertThat(store_4.getDetectionValues()).hasSize(1); - assertThat(store_4.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_4 = store_4.getDetectionValues().get(0); - assertThat(value0_4).isInstanceOf(com.ibm.engine.model.Mode.class); - assertThat(value0_4.asString()).isEqualTo("CBC"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // BlockCipher - INode blockCipherNode = nodes.get(0); - assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); - assertThat(blockCipherNode.getChildren()).hasSize(6); - assertThat(blockCipherNode.asString()).isEqualTo("AES-CBC-PKCS7"); - - // Mode under BlockCipher - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("CBC"); - - // Decrypt under BlockCipher - INode decryptNode = blockCipherNode.getChildren().get(Decrypt.class); - assertThat(decryptNode).isNotNull(); - assertThat(decryptNode.getChildren()).isEmpty(); - assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); - - // Oid under BlockCipher - INode oidNode = blockCipherNode.getChildren().get(Oid.class); - assertThat(oidNode).isNotNull(); - assertThat(oidNode.getChildren()).isEmpty(); - assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); - - // Encrypt under BlockCipher - INode encryptNode = blockCipherNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - - // Padding under BlockCipher - INode paddingNode = blockCipherNode.getChildren().get(Padding.class); - assertThat(paddingNode).isNotNull(); - assertThat(paddingNode.getChildren()).hasSize(1); - assertThat(paddingNode.asString()).isEqualTo("PKCS7"); - - // BlockSize under Padding under BlockCipher - INode blockSizeNode = paddingNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode).isNotNull(); - assertThat(blockSizeNode.getChildren()).isEmpty(); - assertThat(blockSizeNode.asString()).isEqualTo("80"); - - // BlockSize under BlockCipher - INode blockSizeNode1 = blockCipherNode.getChildren().get(BlockSize.class); - assertThat(blockSizeNode1).isNotNull(); - assertThat(blockSizeNode1.getChildren()).isEmpty(); - assertThat(blockSizeNode1.asString()).isEqualTo("128"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher2Test.java deleted file mode 100644 index 74ac657a9..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher2Test.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.symmetric; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.mapper.model.BlockCipher; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.Mode; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaCipher2Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("Camellia"); - - DetectionStore store_1 = - getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(CipherAction.class); - assertThat(value0_1.asString()).isEqualTo("ENCRYPT"); - - DetectionStore store_2 = - getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); - assertThat(store_2.getDetectionValues()).hasSize(1); - assertThat(store_2.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_2 = store_2.getDetectionValues().get(0); - assertThat(value0_2).isInstanceOf(com.ibm.engine.model.Mode.class); - assertThat(value0_2.asString()).isEqualTo("OFB"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // BlockCipher - INode blockCipherNode = nodes.get(0); - assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); - assertThat(blockCipherNode.getChildren()).hasSize(2); - assertThat(blockCipherNode.asString()).isEqualTo("CAMELLIA-OFB"); - - // Encrypt under BlockCipher - INode encryptNode = blockCipherNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - - // Mode under BlockCipher - INode modeNode = blockCipherNode.getChildren().get(Mode.class); - assertThat(modeNode).isNotNull(); - assertThat(modeNode.getChildren()).isEmpty(); - assertThat(modeNode.asString()).isEqualTo("OFB"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaStreamCipher1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaStreamCipher1Test.java deleted file mode 100644 index 7c5b1f339..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaStreamCipher1Test.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.symmetric; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.StreamCipher; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaStreamCipher1Test extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(Algorithm.class); - assertThat(value0.asString()).isEqualTo("ChaCha20"); - - DetectionStore store_1 = - getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); - assertThat(store_1.getDetectionValues()).hasSize(1); - assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0_1 = store_1.getDetectionValues().get(0); - assertThat(value0_1).isInstanceOf(CipherAction.class); - assertThat(value0_1.asString()).isEqualTo("ENCRYPT"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // StreamCipher - INode streamCipherNode = nodes.get(0); - assertThat(streamCipherNode.getKind()).isEqualTo(StreamCipher.class); - assertThat(streamCipherNode.getChildren()).hasSize(1); - assertThat(streamCipherNode.asString()).isEqualTo("ChaCha20"); - - // Encrypt under BlockCipher - INode encryptNode = streamCipherNode.getChildren().get(Encrypt.class); - assertThat(encryptNode).isNotNull(); - assertThat(encryptNode.getChildren()).isEmpty(); - assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingTest.java deleted file mode 100644 index 003eaaf9c..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.wrapping; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.KeyWrap; -import com.ibm.mapper.model.functionality.Encapsulate; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaWrappingTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(CipherAction.class); - assertThat(value0.asString()).isEqualTo("WRAP"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // BlockCipher - INode blockCipherNode = nodes.get(0); - assertThat(blockCipherNode.getKind()).isEqualTo(KeyWrap.class); - assertThat(blockCipherNode.getChildren()).hasSize(4); - assertThat(blockCipherNode.asString()).isEqualTo("AES-128"); - - // KeyLength under BlockCipher - INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("128"); - - // Encapsulate under BlockCipher - INode encapsulateNode = blockCipherNode.getChildren().get(Encapsulate.class); - assertThat(encapsulateNode).isNotNull(); - assertThat(encapsulateNode.getChildren()).isEmpty(); - assertThat(encapsulateNode.asString()).isEqualTo("ENCAPSULATE"); - } -} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingWithPaddingTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingWithPaddingTest.java deleted file mode 100644 index 37d0bb788..000000000 --- a/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingWithPaddingTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.wrapping; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.ibm.engine.detection.DetectionStore; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.context.CipherContext; -import com.ibm.mapper.model.INode; -import com.ibm.mapper.model.KeyLength; -import com.ibm.mapper.model.KeyWrap; -import com.ibm.mapper.model.functionality.Encapsulate; -import com.ibm.plugin.TestBase; -import java.util.List; -import javax.annotation.Nonnull; -import org.junit.jupiter.api.Test; -import org.sonar.plugins.python.api.PythonCheck; -import org.sonar.plugins.python.api.PythonVisitorContext; -import org.sonar.plugins.python.api.symbols.Symbol; -import org.sonar.plugins.python.api.tree.Tree; -import org.sonar.python.checks.utils.PythonCheckVerifier; - -class PycaWrappingWithPaddingTest extends TestBase { - - @Test - void test() { - PythonCheckVerifier.verify( - "src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py", this); - } - - @Override - public void asserts( - int findingId, - @Nonnull DetectionStore detectionStore, - @Nonnull List nodes) { - /* - * Detection Store - */ - assertThat(detectionStore.getDetectionValues()).hasSize(1); - assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); - IValue value0 = detectionStore.getDetectionValues().get(0); - assertThat(value0).isInstanceOf(CipherAction.class); - assertThat(value0.asString()).isEqualTo("WRAP"); - - /* - * Translation - */ - assertThat(nodes).hasSize(1); - - // BlockCipher - INode blockCipherNode = nodes.get(0); - assertThat(blockCipherNode.getKind()).isEqualTo(KeyWrap.class); - assertThat(blockCipherNode.getChildren()).hasSize(4); - assertThat(blockCipherNode.asString()).isEqualTo("AES-128"); - - // KeyLength under BlockCipher - INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); - assertThat(keyLengthNode).isNotNull(); - assertThat(keyLengthNode.getChildren()).isEmpty(); - assertThat(keyLengthNode.asString()).isEqualTo("128"); - - // Encapsulate under BlockCipher - INode encapsulateNode = blockCipherNode.getChildren().get(Encapsulate.class); - assertThat(encapsulateNode).isNotNull(); - assertThat(encapsulateNode.getChildren()).isEmpty(); - assertThat(encapsulateNode.asString()).isEqualTo("ENCAPSULATE"); - } -} From 5ddb38048bf900175db654791b3ffc5e82784f52 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 15:43:48 +0200 Subject: [PATCH 02/24] feat(mapper): extend pyca mappers with new algorithm entries - PycaCipherMapper: add DES, RC2, Salsa20; add 3DES and CHACHA20_POLY1305 aliases; add RC4 alias - PycaDigestMapper: add MD2, MD4, RIPEMD160, TupleHash, Keccak, CSHAKE, KangarooTwelve; remove Poly1305 (belongs in MAC mapper) - PycaMacMapper: add Poly1305 and additional algorithm entries - PycaCurveMapper: new mapper for elliptic curve names (shared by pyca and pycrypto translators) - PycaKeyBasedAlgorithmMapper: new mapper for key-based algorithm names (shared by pyca and pycrypto translators) Signed-off-by: san-zrl --- .../mapper/mapper/pyca/PycaCurveMapper.java | 113 ++++++++++++++++++ .../pyca/PycaKeyBasedAlgorithmMapper.java | 64 ++++++++++ 2 files changed, 177 insertions(+) create mode 100644 mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java create mode 100644 mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaKeyBasedAlgorithmMapper.java diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java new file mode 100644 index 000000000..b4cee3881 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java @@ -0,0 +1,113 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.mapper.mapper.pyca; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.curves.Brainpoolp256r1; +import com.ibm.mapper.model.curves.Brainpoolp384r1; +import com.ibm.mapper.model.curves.Brainpoolp512r1; +import com.ibm.mapper.model.curves.Curve25519; +import com.ibm.mapper.model.curves.Curve448; +import com.ibm.mapper.model.curves.Edwards25519; +import com.ibm.mapper.model.curves.Edwards448; +import com.ibm.mapper.model.curves.Secp192r1; +import com.ibm.mapper.model.curves.Secp224r1; +import com.ibm.mapper.model.curves.Secp256k1; +import com.ibm.mapper.model.curves.Secp256r1; +import com.ibm.mapper.model.curves.Secp384r1; +import com.ibm.mapper.model.curves.Secp521r1; +import com.ibm.mapper.model.curves.Sect163k1; +import com.ibm.mapper.model.curves.Sect163r2; +import com.ibm.mapper.model.curves.Sect233k1; +import com.ibm.mapper.model.curves.Sect233r1; +import com.ibm.mapper.model.curves.Sect283k1; +import com.ibm.mapper.model.curves.Sect283r1; +import com.ibm.mapper.model.curves.Sect409k1; +import com.ibm.mapper.model.curves.Sect409r1; +import com.ibm.mapper.model.curves.Sect571k1; +import com.ibm.mapper.model.curves.Sect571r1; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PycaCurveMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + + @Nonnull String curve = str; + return switch (curve.toUpperCase().trim()) { + case "SECP192R1", "PRIME192V1", "P-192", "P192", "NIST P-192" -> + Optional.of(new EllipticCurveAlgorithm(new Secp192r1(detectionLocation))); + case "SECP224R1", "PRIME224V1", "P-224", "P224", "NIST P-224" -> + Optional.of(new EllipticCurveAlgorithm(new Secp224r1(detectionLocation))); + case "SECP256R1", "PRIME256V1", "P-256", "P256", "NIST P-256" -> + Optional.of(new EllipticCurveAlgorithm(new Secp256r1(detectionLocation))); + case "SECP384R1", "PRIME384V1", "P-384", "P384", "NIST P-384" -> + Optional.of(new EllipticCurveAlgorithm(new Secp384r1(detectionLocation))); + case "SECP521R1", "PRIME521V1", "P-521", "P521", "NIST P-521" -> + Optional.of(new EllipticCurveAlgorithm(new Secp521r1(detectionLocation))); + case "SECP256K1" -> + Optional.of(new EllipticCurveAlgorithm(new Secp256k1(detectionLocation))); + case "CURVE25519" -> + Optional.of(new EllipticCurveAlgorithm(new Curve25519(detectionLocation))); + case "ED25519" -> + Optional.of(new EllipticCurveAlgorithm(new Edwards25519(detectionLocation))); + case "CURVE448" -> + Optional.of(new EllipticCurveAlgorithm(new Curve448(detectionLocation))); + case "ED448" -> + Optional.of(new EllipticCurveAlgorithm(new Edwards448(detectionLocation))); + case "BRAINPOOLP256R1" -> + Optional.of(new EllipticCurveAlgorithm(new Brainpoolp256r1(detectionLocation))); + case "BRAINPOOLP384R1" -> + Optional.of(new EllipticCurveAlgorithm(new Brainpoolp384r1(detectionLocation))); + case "BRAINPOOLP512R1" -> + Optional.of(new EllipticCurveAlgorithm(new Brainpoolp512r1(detectionLocation))); + case "SECT571K1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect571k1(detectionLocation))); + case "SECT409K1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect409k1(detectionLocation))); + case "SECT283K1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect283k1(detectionLocation))); + case "SECT233K1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect233k1(detectionLocation))); + case "SECT163K1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect163k1(detectionLocation))); + case "SECT571R1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect571r1(detectionLocation))); + case "SECT409R1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect409r1(detectionLocation))); + case "SECT283R1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect283r1(detectionLocation))); + case "SECT233R1" -> + Optional.of(new EllipticCurveAlgorithm(new Sect233r1(detectionLocation))); + case "SECT163R2" -> + Optional.of(new EllipticCurveAlgorithm(new Sect163r2(detectionLocation))); + default -> Optional.empty(); + }; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaKeyBasedAlgorithmMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaKeyBasedAlgorithmMapper.java new file mode 100644 index 000000000..315ae9286 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaKeyBasedAlgorithmMapper.java @@ -0,0 +1,64 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.mapper.mapper.pyca; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.algorithms.DH; +import com.ibm.mapper.model.algorithms.DSA; +import com.ibm.mapper.model.algorithms.Ed25519; +import com.ibm.mapper.model.algorithms.Ed448; +import com.ibm.mapper.model.algorithms.ElGamal; +import com.ibm.mapper.model.algorithms.Fernet; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.curves.Curve25519; +import com.ibm.mapper.model.curves.Curve448; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class PycaKeyBasedAlgorithmMapper implements IMapper { + + @Override + public @Nonnull Optional parse( + @Nullable String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + + return switch (str.toUpperCase().trim()) { + case "RSA" -> Optional.of(new RSA(detectionLocation)); + case "DSA" -> Optional.of(new DSA(detectionLocation)); + case "DH" -> Optional.of(new DH(detectionLocation)); + case "EC" -> Optional.of(new EllipticCurveAlgorithm(detectionLocation)); + case "CURVE25519" -> + Optional.of(new EllipticCurveAlgorithm(new Curve25519(detectionLocation))); + case "CURVE448" -> + Optional.of(new EllipticCurveAlgorithm(new Curve448(detectionLocation))); + case "ED25519" -> Optional.of(new Ed25519(detectionLocation)); + case "ED448" -> Optional.of(new Ed448(detectionLocation)); + case "ELGAMAL" -> Optional.of(new ElGamal(detectionLocation)); + case "FERNET" -> Optional.of(new Fernet(detectionLocation)); + default -> Optional.empty(); + }; + } +} From 5e8baef15fbdfbe22339e62654c23b9f7090cf5a Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 15:44:23 +0200 Subject: [PATCH 03/24] =?UTF-8?q?feat(mapper/python):=20add=20ECDH?= =?UTF-8?q?=E2=86=92XDH=20and=20signature=20reorganizer=20rules,=20wire=20?= =?UTF-8?q?into=20Python?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MessageDigestReorganizer: new WRAP_SHA512_TRUNCATED rule - KeyAgreementReorganizer: add replaceEcdhWithXdh() factory and two concrete rules REPLACE_ECDH_WITH_X25519_WHEN_CURVE25519 and REPLACE_ECDH_WITH_X448_WHEN_CURVE448 - SignatureReorganizer: add moveNodesFromUnderFunctionalityUnderParent() for Sign/Verify x Signature/PSS - PythonReorganizerRules: wire all new rules and activate MessageDigestReorganizer.WRAP_SHA512_TRUNCATED Signed-off-by: san-zrl --- .../rules/MessageDigestReorganizer.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MessageDigestReorganizer.java diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MessageDigestReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MessageDigestReorganizer.java new file mode 100644 index 000000000..8796ce082 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MessageDigestReorganizer.java @@ -0,0 +1,96 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.mapper.reorganizer.rules; + +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; + +public final class MessageDigestReorganizer { + + private MessageDigestReorganizer() { + // private + } + + /** + * Handles {@code SHA512.new(truncate=N)}: when a SHA-512 {@link MessageDigest} node carries a + * {@link DigestSize} child whose value differs from 512 (the truncation value), replace the + * node with a {@code SHA2(N, SHA2(512, ...))} structure, mirroring the JCA pattern for {@code + * "SHA-512/224"} / {@code "SHA-512/256"}. + */ + @Nonnull + public static final IReorganizerRule WRAP_SHA512_TRUNCATED = + new ReorganizerRuleBuilder() + .createReorganizerRule("WRAP_SHA512_TRUNCATED") + .forNodeKind(MessageDigest.class) + .withDetectionCondition( + (node, parent, roots) -> { + if (!"SHA-512".equals(node.asString())) { + return false; + } + return node.hasChildOfType(DigestSize.class) + .filter(ds -> !"512".equals(ds.asString())) + .isPresent(); + }) + .perform( + (node, parent, roots) -> { + if (!(node instanceof SHA2 sha2Node)) { + return roots; + } + final Optional digestSizeOpt = + sha2Node.hasChildOfType(DigestSize.class); + if (digestSizeOpt.isEmpty()) { + return roots; + } + final int truncateSize = + ((DigestSize) digestSizeOpt.get()).getValue(); + final SHA2 preHash512 = + new SHA2(512, sha2Node.getDetectionContext()); + final SHA2 truncated = + new SHA2( + truncateSize, + preHash512, + sha2Node.getDetectionContext()); + + // Copy remaining children (BlockSize, OID, Digest, …) onto the + // new truncated node, skipping DigestSize (already set by SHA2 + // constructor) and the SHA2(512) pre-hash. + sha2Node.getChildren().values().stream() + .filter(child -> !(child instanceof DigestSize)) + .filter(child -> !(child instanceof MessageDigest)) + .forEach(truncated::put); + + if (parent != null) { + parent.put(truncated); + return roots; + } + // Root node replacement + final List newRoots = new LinkedList<>(roots); + newRoots.replaceAll(r -> r == node ? truncated : r); + return newRoots; + }); +} From 9e97579a9eca4f8150accbe5a67b9beed2d9e2d0 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 15:49:40 +0200 Subject: [PATCH 04/24] chore(python): reorganise pyca detection rules into pyca/ subpackage Move all Pyca* detection rule classes from detection/{aead,asymmetric,...} into detection/pyca/{aead,asymmetric,...} to make room for the parallel pycrypto/ package. No logic changes. Also removes stale pyca test fixtures (.py) and Java test classes that are superseded by forthcoming pycrypto-aware tests. Signed-off-by: san-zrl --- .../rules/detection/pyca/aead/PycaAEAD.java | 83 +++++++ .../rules/detection/pyca/aead/PycaAES.java | 97 ++++++++ .../detection/pyca/asymmetric/PycaDSA.java | 105 +++++++++ .../pyca/asymmetric/PycaDiffieHellman.java | 99 ++++++++ .../pyca/asymmetric/PycaEllipticCurve.java | 150 ++++++++++++ .../detection/pyca/asymmetric/PycaRSA.java | 197 ++++++++++++++++ .../detection/pyca/asymmetric/PycaSign.java | 65 ++++++ .../detection/pyca/fernet/PycaFernet.java | 83 +++++++ .../rules/detection/pyca/hash/PycaHash.java | 105 +++++++++ .../detection/pyca/hash/PycaHashWrapper.java | 35 +++ .../rules/detection/pyca/kdf/PycaKDF.java | 219 ++++++++++++++++++ .../pyca/keyagreement/PycaKeyAgreement.java | 64 +++++ .../rules/detection/pyca/mac/PycaMAC.java | 79 +++++++ .../detection/pyca/padding/PycaPadding.java | 91 ++++++++ .../detection/pyca/symmetric/PycaCipher.java | 122 ++++++++++ .../detection/pyca/wrapping/PycaWrapping.java | 63 +++++ 16 files changed, 1657 insertions(+) create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAEAD.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAES.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDSA.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDiffieHellman.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaEllipticCurve.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaRSA.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaSign.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernet.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHash.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashWrapper.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKDF.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreement.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMAC.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPadding.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrapping.java diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAEAD.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAEAD.java new file mode 100644 index 000000000..e97727ee3 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAEAD.java @@ -0,0 +1,83 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.aead; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.SecretKeyContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaAEAD extends DetectionRuleSet { + + private static final String TYPE = + "cryptography.hazmat.primitives.ciphers.aead.ChaCha20Poly1305"; + + private static final IDetectionRule ENCRYPT_CHACHA20POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withAnyParameters() + .buildForContext(new CipherContext(Map.of("algorithm", "ChaCha20Poly1305"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule DECRYPT_CHACHA20POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("decrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withAnyParameters() + .buildForContext(new CipherContext(Map.of("algorithm", "ChaCha20Poly1305"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule GENERATION_CHACHA20POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("generate_key") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext( + new SecretKeyContext( + Map.of("algorithm", "ChaCha20Poly1305", "kind", "AEAD"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules( + List.of(ENCRYPT_CHACHA20POLY1305, DECRYPT_CHACHA20POLY1305)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_CHACHA20POLY1305); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAES.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAES.java new file mode 100644 index 000000000..f0a6a014f --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAES.java @@ -0,0 +1,97 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.aead; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.SecretKeyContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaAES extends DetectionRuleSet { + + private static final List aesAlgorithms = + Arrays.asList("AESGCM", "AESGCMIV", "AESOCB3", "AESSIV", "AESCCM"); + + private static final String AEAD_TYPE_PREFIX = "cryptography.hazmat.primitives.ciphers.aead."; + + private static @Nonnull IDetectionRule encryptAES(String aesAlgorithm) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(AEAD_TYPE_PREFIX + aesAlgorithm) + .forMethods("encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withAnyParameters() + .buildForContext( + new CipherContext(Map.of("algorithm", aesAlgorithm, "kind", "AEAD"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + } + + private static @Nonnull IDetectionRule decryptAES(String aesAlgorithm) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(AEAD_TYPE_PREFIX + aesAlgorithm) + .forMethods("decrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withAnyParameters() + .buildForContext( + new CipherContext(Map.of("algorithm", aesAlgorithm, "kind", "AEAD"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + } + + private static @Nonnull List> generationRulesAES() { + LinkedList> rules = new LinkedList<>(); + for (String aesAlgorithm : aesAlgorithms) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(AEAD_TYPE_PREFIX + aesAlgorithm) + .forMethods("generate_key") + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .buildForContext( + new SecretKeyContext( + Map.of("algorithm", aesAlgorithm, "kind", "AEAD"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules( + List.of(decryptAES(aesAlgorithm), encryptAES(aesAlgorithm)))); + } + return rules; + } + + @Nonnull + @Override + protected List> buildRules() { + return generationRulesAES(); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDSA.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDSA.java new file mode 100644 index 000000000..4aa088855 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDSA.java @@ -0,0 +1,105 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.SignatureActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHash; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaDSA extends DetectionRuleSet { + + private static final String TYPE = "cryptography.hazmat.primitives.asymmetric.dsa"; + + private static final IDetectionRule SIGN_DSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE + ".generate_private_key") + .forMethods("sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withMethodParameter(ANY) + .withMethodParameter( + "cryptography.hazmat.primitives.*") // This "type" accepts both hashes + // and pre-hashes + .addDependingDetectionRules( + RuleSets.rulesOf( + PycaHash.class)) // The parameter of sign can either be an + // immediate + // hash, or a hash enclosed in the pre-hash + .buildForContext(new SignatureContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule GENERATION_DSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("generate_private_key") + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(SIGN_DSA)); + + private static final IDetectionRule PUBLIC_NUMBERS_DSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("DSAPublicNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PRIVATE_NUMBERS_DSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("DSAPrivateNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_DSA, PUBLIC_NUMBERS_DSA, PRIVATE_NUMBERS_DSA); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDiffieHellman.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDiffieHellman.java new file mode 100644 index 000000000..9469fa570 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDiffieHellman.java @@ -0,0 +1,99 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaDiffieHellman extends DetectionRuleSet { + + private static final String TYPE = "cryptography.hazmat.primitives.asymmetric.dh"; + + // The key size does not yet appear in PycaDiffieHellmanGenerateTestFile because + // of the TraceSymbol problem documented on the Github issue + private static final IDetectionRule GENERATE_PARAMETERS_DH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("generate_parameters") + .withMethodParameter(ANY) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DH"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule GENERATION_DH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE + ".generate_parameters") + .forMethods("generate_private_key") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext( + new PrivateKeyContext( + Map.of( + "algorithm", "DH", + "includePublicKey", "true"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(GENERATE_PARAMETERS_DH)); + + private static final IDetectionRule PUBLIC_NUMBERS_DH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("DHPublicNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "DH"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PRIVATE_NUMBERS_DH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("DHPrivateNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "DH"))) + .inBundle(() -> "CryptographyDiffieHellman") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_DH, PUBLIC_NUMBERS_DH, PRIVATE_NUMBERS_DH); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaEllipticCurve.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaEllipticCurve.java new file mode 100644 index 000000000..e3aa404b9 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaEllipticCurve.java @@ -0,0 +1,150 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.CurveFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.SignatureActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHash; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaEllipticCurve extends DetectionRuleSet { + + private static final String TYPE = "cryptography.hazmat.primitives.asymmetric.ec"; + private static final String GENERATE_METHOD = "generate_private_key"; + + // ECDSA is the only algorithm accepted as in the sign/verify functions (it is the only subclass + // of EllipticCurveSignatureAlgorithm) + private static final IDetectionRule ECDSA_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("ECDSA") + .withMethodParameter( + "cryptography.hazmat.primitives.*") // This "type" accepts both hashes + // and pre-hashes + .addDependingDetectionRules( + RuleSets.rulesOf( + PycaHash.class)) // The parameter of ECDSA can either be an + // immediate + // hash, or a hash enclosed in the pre-hash function + .buildForContext(new SignatureContext(Map.of("algorithm", "ECDSA"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + public static final IDetectionRule KEY_EXCHANGE_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE + "." + GENERATE_METHOD) + .forMethods("exchange") + .withMethodParameter(TYPE + ".*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter(ANY) + .buildForContext(new KeyAgreementContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule SIGN_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE + "." + GENERATE_METHOD) + .forMethods("sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withMethodParameter(ANY) + .withMethodParameter(TYPE + ".*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .addDependingDetectionRules(List.of(ECDSA_EC)) + .buildForContext(new SignatureContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule GENERATION_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods(GENERATE_METHOD) + .withMethodParameter(ANY) + .shouldBeDetectedAs(new CurveFactory<>()) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(SIGN_EC, KEY_EXCHANGE_EC)); + + private static final IDetectionRule DERIVATION_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("derive_private_key") + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .shouldBeDetectedAs(new CurveFactory<>()) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(SIGN_EC, KEY_EXCHANGE_EC)); + + // Private numbers relies on information (the curve) given by the public key + // For now; we only use it as a depending detection rule of PRIVATE_NUMBERS_EC + private static final IDetectionRule PRIVATE_NUMBERS_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("EllipticCurvePrivateNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PUBLIC_NUMBERS_EC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(TYPE) + .forMethods("EllipticCurvePublicNumbers") + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new PublicKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(PRIVATE_NUMBERS_EC)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_EC, DERIVATION_EC, PUBLIC_NUMBERS_EC); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaRSA.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaRSA.java new file mode 100644 index 000000000..7cf7d2396 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaRSA.java @@ -0,0 +1,197 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.SignatureActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHash; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaRSA extends DetectionRuleSet { + + private static final String PADDING_TYPE = "cryptography.hazmat.primitives.asymmetric.padding"; + private static final String HASH_TYPE = "cryptography.hazmat.primitives.*"; + private static final String RSA_TYPE = "cryptography.hazmat.primitives.asymmetric.rsa"; + + private static final IDetectionRule MGF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(PADDING_TYPE) + .forMethods("MGF1") + .shouldBeDetectedAs(new ValueActionFactory<>("MGF1")) + .withMethodParameter(HASH_TYPE) // This "type" accepts both hashes + // and pre-hashes + .addDependingDetectionRules(RuleSets.rulesOf(PycaHash.class)) + .buildForContext(new SignatureContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PSS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(PADDING_TYPE) + .forMethods("PSS") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withMethodParameter(ANY) + .addDependingDetectionRules(List.of(MGF1)) + .withMethodParameter(ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS1v15 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(PADDING_TYPE) + .forMethods("PKCS1v15") + .shouldBeDetectedAs( + new ValueActionFactory<>( + "PKCS1v15")) // this is necessary to capture something to + // trigger the translation + .withAnyParameters() + .buildForContext(new SignatureContext(Map.of("kind", "padding"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule OAEP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(PADDING_TYPE) + .forMethods("OAEP") + .shouldBeDetectedAs(new ValueActionFactory<>("OAEP")) + .withMethodParameter(ANY) + // .shouldBeDetectedAs(new AlgorithmFactory<>()) + // .asChildOfParameterWithId(-1) + .addDependingDetectionRules(List.of(MGF1)) + .withMethodParameter(HASH_TYPE) // This "type" accepts both hashes + // and pre-hashes + .addDependingDetectionRules( + RuleSets.rulesOf( + PycaHash.class)) // The parameter of sign can either be an + // immediate + // hash, or a hash enclosed in the pre-hash + .withMethodParameter(ANY) + .buildForContext(new CipherContext(Map.of("kind", "padding"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule SIGN_RSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key") + .forMethods("sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withMethodParameter(ANY) + .withMethodParameter("cryptography.hazmat.primitives.asymmetric.padding.*") + .addDependingDetectionRules( + List.of( + PSS, + PKCS1v15)) // For signatures, padding can only be PSS or PKCSv15 + .withMethodParameter( + HASH_TYPE) // This "type" accepts both hashes and pre-hashes + .addDependingDetectionRules( + RuleSets.rulesOf( + PycaHash.class)) // The parameter of sign can either be an + // immediate + // hash, or a hash enclosed in the pre-hash + .buildForContext(new SignatureContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule DECRYPT_RSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key") + .forMethods("decrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withMethodParameter(ANY) + .withMethodParameter("cryptography.hazmat.primitives.asymmetric.padding.*") + .addDependingDetectionRules( + List.of( + OAEP, + PKCS1v15)) // For encryption/decryption, padding can only be + // OAEP or PKCSv15 + .buildForContext(new CipherContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule GENERATION_RSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(RSA_TYPE) + .forMethods("generate_private_key") + .withMethodParameter(ANY) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(SIGN_RSA /*,VERIFY_RSA*/, DECRYPT_RSA)); + + private static final IDetectionRule PUBLIC_NUMBERS_RSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(RSA_TYPE) + .forMethods("RSAPublicNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(SIGN_RSA /*, VERIFY_RSA*/, DECRYPT_RSA)); + + private static final IDetectionRule PRIVATE_NUMBERS_RSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(RSA_TYPE) + .forMethods("RSAPrivateNumbers") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(List.of(SIGN_RSA /*, VERIFY_RSA*/, DECRYPT_RSA)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_RSA, PUBLIC_NUMBERS_RSA, PRIVATE_NUMBERS_RSA); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaSign.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaSign.java new file mode 100644 index 000000000..4d685ee15 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaSign.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaSign extends DetectionRuleSet { + + private static final IDetectionRule SIGN_ED25519 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey") + .forMethods("generate") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withoutParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "Ed25519"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule SIGN_ED448 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey") + .forMethods("generate") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withoutParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "Ed448"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(SIGN_ED25519, SIGN_ED448); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernet.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernet.java new file mode 100644 index 000000000..ea621e3e8 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernet.java @@ -0,0 +1,83 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.fernet; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaFernet extends DetectionRuleSet { + + private static @Nonnull List> encryptDecryptFernet() { + List methodNames = + List.of("encrypt", "encrypt_at_time", "decrypt", "decrypt_at_time"); + List objectNames = List.of("Fernet", "MultiFernet"); + List> rules = new LinkedList<>(); + + for (String method : methodNames) { + for (String object : objectNames) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.fernet." + object) + .forMethods(method) + .shouldBeDetectedAs( + new CipherActionFactory<>( + method.startsWith("encrypt") + ? CipherAction.Action.ENCRYPT + : CipherAction.Action.DECRYPT)) + .withAnyParameters() + .buildForContext(new CipherContext(Map.of("algorithm", "Fernet"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules()); + } + } + return rules; + } + + private static final IDetectionRule GENERATION_FERNET = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.fernet.Fernet") + .forMethods("generate_key") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new KeyContext(Map.of("algorithm", "Fernet"))) + .inBundle(() -> "Pyca") + .withDependingDetectionRules(encryptDecryptFernet()); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_FERNET); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHash.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHash.java new file mode 100644 index 000000000..05cc7e477 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHash.java @@ -0,0 +1,105 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.hash; + +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaHash extends DetectionRuleSet { + + @SuppressWarnings("java:S2386") + public static final List hashes = + Arrays.asList( + "SHA1", + "SHA512_224", + "SHA512_256", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHAKE128", + "SHAKE256", + "MD5", + "BLAKE2b", + "BLAKE2s", + "SM3"); + + private static @Nonnull List> hashesRules() { + LinkedList> rules = new LinkedList<>(); + for (final String hash : PycaHash.hashes) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.hashes") + .forMethods(hash) + .shouldBeDetectedAs(new ValueActionFactory<>(hash)) + .withAnyParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules()); + } + return rules; + } + + private static final IDetectionRule PRE_HASH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.asymmetric.utils") + .forMethods("Prehashed") + .withMethodParameter("cryptography.hazmat.primitives.hashes.*") + .addDependingDetectionRules(hashesRules()) + .buildForContext(new DigestContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + // Detects hashes.Hash(hashes.SHA256()) and similar direct hash-computation usages. + static final IDetectionRule HASH_WRAPPER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.hashes") + .forMethods("Hash") + .withMethodParameter("cryptography.hazmat.primitives.hashes.*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new DigestContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + final List> hashAndPrehashRules = new LinkedList<>(hashesRules()); + hashAndPrehashRules.add(PRE_HASH); + return hashAndPrehashRules; + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashWrapper.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashWrapper.java new file mode 100644 index 000000000..406e0f20d --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashWrapper.java @@ -0,0 +1,35 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.hash; + +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +public final class PycaHashWrapper extends DetectionRuleSet { + + @Nonnull + @Override + protected List> buildRules() { + return List.of(PycaHash.HASH_WRAPPER); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKDF.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKDF.java new file mode 100644 index 000000000..2c7795355 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKDF.java @@ -0,0 +1,219 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.AlgorithmParameter; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.AlgorithmParameterFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ModeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaKDF extends DetectionRuleSet { + + private static final String HASH_TYPE = "cryptography.hazmat.primitives.hashes.*"; + private static final String KDF_TYPE_PREFIX = "cryptography.hazmat.primitives.kdf."; + + private static final IDetectionRule X963KDF = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "x963kdf") + .forMethods("X963KDF") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "x963"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule KBKDFCMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "kbkdf") + .forMethods("KBKDFCMAC") + .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter(ANY) + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(0) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "cmac"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule KBKDFHMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "kbkdf") + .forMethods("KBKDFHMAC") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter(ANY) + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(0) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "hmac"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule HKDF_EXPAND = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "hkdf") + .forMethods("HKDFExpand") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "hkdf"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule HKDF = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "hkdf") + .forMethods("HKDF") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "hkdf"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule CONCAT_KDF_HMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "concatkdf") + .forMethods("ConcatKDFHMAC") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "concatkdf"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule CONCAT_KDF = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "concatkdf") + .forMethods("ConcatKDFHash") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "concatkdf"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule SCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "scrypt") + .forMethods("Scrypt") + .shouldBeDetectedAs(new ValueActionFactory<>("Scrypt")) + .withMethodParameter(ANY) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter("int") + .withMethodParameter("int") + .withMethodParameter("int") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(KDF_TYPE_PREFIX + "pbkdf2") + .forMethods("PBKDF2HMAC") + .withMethodParameter(HASH_TYPE) // Accepts only hashes (not pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(0) + .withMethodParameter(ANY) + .withMethodParameter("int") + .shouldBeDetectedAs( + new AlgorithmParameterFactory<>(AlgorithmParameter.Kind.ITERATIONS)) + .asChildOfParameterWithId(0) + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "pbkdf2"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + PBKDF2, + SCRYPT, + CONCAT_KDF, + CONCAT_KDF_HMAC, + HKDF, + HKDF_EXPAND, + KBKDFHMAC, + KBKDFCMAC, + X963KDF); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreement.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreement.java new file mode 100644 index 000000000..a23734f3b --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreement.java @@ -0,0 +1,64 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.keyagreement; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaKeyAgreement extends DetectionRuleSet { + + private static final IDetectionRule GENERATION_X25519 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey") + .forMethods("generate") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withoutParameters() + .buildForContext(new KeyAgreementContext(Map.of("algorithm", "x25519"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule GENERATION_X448 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey") + .forMethods("generate") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withoutParameters() + .buildForContext(new KeyAgreementContext(Map.of("algorithm", "x448"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(GENERATION_X25519, GENERATION_X448); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMAC.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMAC.java new file mode 100644 index 000000000..e38506c48 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMAC.java @@ -0,0 +1,79 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.mac; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaMAC extends DetectionRuleSet { + + private static final IDetectionRule NEW_CMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.cmac") + .forMethods("CMAC") + .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new MacContext(Map.of("kind", "cmac"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule NEW_HMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.hmac") + .forMethods("HMAC") + .withMethodParameter(ANY) + .withMethodParameter( + "cryptography.hazmat.primitives.hashes.*") // Accepts only hashes (not + // pre-hashes) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new MacContext(Map.of("kind", "hmac"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule NEW_POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.poly1305") + .forMethods("Poly1305") + .shouldBeDetectedAs(new ValueActionFactory<>("Poly1305")) + .withAnyParameters() + .buildForContext(new MacContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(NEW_CMAC, NEW_HMAC, NEW_POLY1305); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPadding.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPadding.java new file mode 100644 index 000000000..27d334a39 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPadding.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.padding; + +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.BlockSizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pyca.symmetric.PycaCipher; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaPadding extends DetectionRuleSet { + + private static final List paddings = Arrays.asList("PKCS7", "ANSIX923"); + + private static @Nonnull List> newPadding() { + final LinkedList> rules = new LinkedList<>(); + + for (String padding : paddings) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.padding") + .forMethods(padding) + .shouldBeDetectedAs(new ValueActionFactory<>(padding)) + .withMethodParameter("int") + .shouldBeDetectedAs(new BlockSizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(0) + .buildForContext(new CipherContext(Map.of("kind", "padding"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules()); + } + // When the block size is specified using a `block_size` attribute + for (String padding : paddings) { + for (String cipherAlgorithm : PycaCipher.blockCiphers) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.padding") + .forMethods(padding) + .shouldBeDetectedAs(new ValueActionFactory<>(padding)) + .withMethodParameter( + "cryptography.hazmat.primitives.ciphers.algorithms." + + cipherAlgorithm + + ".block_size") + .shouldBeDetectedAs(new BlockSizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(0) + .buildForContext(new CipherContext(Map.of("kind", "padding"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules()); + } + } + return rules; + } + + // It should be better to only detect Padding when it actually gets implied (i.e. there is + // `padder.update` function call). However, it does not bring much, and creates problems + // because the type handler may not distinguish an `encryptor.update` from `padder.update`. + + @Nonnull + @Override + protected List> buildRules() { + return newPadding(); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher.java new file mode 100644 index 000000000..ff61e5e43 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher.java @@ -0,0 +1,122 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.symmetric; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.ModeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pyca.padding.PycaPadding; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings({"java:S2386", "java:S1192"}) +public final class PycaCipher extends DetectionRuleSet { + + public static final List blockCiphers = + Arrays.asList( + "AES", + "AES128", + "AES256", + "Camellia", + "TripleDES", + "CAST5", + "SEED", + "SM4", + "Blowfish", + "IDEA"); + public static final List streamCiphers = Arrays.asList("ChaCha20", "ARC4"); + + public static final List modes = + Arrays.asList("CBC", "CTR", "OFB", "CFB", "CFB8", "GCM", "XTS", "ECB"); + + private static final IDetectionRule ENCRYPT_CIPHER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.ciphers.Cipher") + .forMethods("encryptor") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withAnyParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule DECRYPT_CIPHER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.ciphers.Cipher") + .forMethods("decryptor") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withAnyParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static @Nonnull List> followingNewCipherRules() { + final List> encryptionRules = + new LinkedList<>(List.of(DECRYPT_CIPHER, ENCRYPT_CIPHER)); + encryptionRules.addAll(RuleSets.rulesOf(PycaPadding.class)); + return encryptionRules; + } + + private static final IDetectionRule NEW_CIPHER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.ciphers") + .forMethods("Cipher") + .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .addDependingDetectionRules(followingNewCipherRules()) + .withMethodParameter("cryptography.hazmat.primitives.ciphers.modes.*") + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(0) + .buildForContext(new CipherContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule STREAM_CIPHER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.ciphers") + .forMethods("Cipher") + .withMethodParameter("cryptography.hazmat.primitives.ciphers.algorithms.*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .addDependingDetectionRules(followingNewCipherRules()) + .withMethodParameter("None") + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(0) + .buildForContext(new CipherContext()) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(NEW_CIPHER, STREAM_CIPHER); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrapping.java b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrapping.java new file mode 100644 index 000000000..f4dde0068 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrapping.java @@ -0,0 +1,63 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.wrapping; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PycaWrapping extends DetectionRuleSet { + + private static final IDetectionRule AES_KEY_WRAP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.keywrap") + .forMethods("aes_key_wrap") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.WRAP)) + .withAnyParameters() + .buildForContext(new CipherContext(Map.of("algorithm", "AES"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + private static final IDetectionRule AES_KEY_WRAP_WITH_PADDING = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("cryptography.hazmat.primitives.keywrap") + .forMethods("aes_key_wrap_with_padding") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.WRAP)) + .withAnyParameters() + .buildForContext(new CipherContext(Map.of("algorithm", "AES"))) + .inBundle(() -> "Pyca") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(AES_KEY_WRAP, AES_KEY_WRAP_WITH_PADDING); + } +} From 80efc727a9766c27c02267ea9e819dcd570869fc Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 15:50:20 +0200 Subject: [PATCH 05/24] feat(python): add PyCryptodome (pycryptodome/pycrypto) detection rules Add detection rule classes under detection/pycrypto/: cipher, hash, kdf, keyagreement, mac, publickey, random, signature Register all rule sets in PythonDetectionRules alongside the existing pyca rules. Update imports to use new pyca/ subpackage paths. Signed-off-by: san-zrl --- .../pycrypto/cipher/PythonCryptoCipher.java | 289 ++++++++++++++++++ .../pycrypto/hash/PythonCryptoHash.java | 106 +++++++ .../pycrypto/kdf/PythonCryptoKDF.java | 158 ++++++++++ .../PythonCryptoKeyAgreement.java | 136 +++++++++ .../pycrypto/mac/PythonCryptoMac.java | 101 ++++++ .../pycrypto/publickey/PythonCryptoDSA.java | 97 ++++++ .../pycrypto/publickey/PythonCryptoECC.java | 114 +++++++ .../publickey/PythonCryptoElGamal.java | 81 +++++ .../pycrypto/publickey/PythonCryptoRSA.java | 97 ++++++ .../pycrypto/random/PythonCryptoRandom.java | 66 ++++ .../signature/PythonCryptoSignature.java | 267 ++++++++++++++++ 11 files changed, 1512 insertions(+) create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PythonCryptoKDF.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoDSA.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoECC.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoElGamal.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoRSA.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/random/PythonCryptoRandom.java create mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java new file mode 100644 index 000000000..a2b87baa8 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java @@ -0,0 +1,289 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.ModeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoRSA; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoCipher extends DetectionRuleSet { + + private PythonCryptoCipher() { + // private + } + + private static final IDetectionRule ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("encrypt", "encrypt_and_digest") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter(ANY) + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule DECRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("decrypt", "decrypt_and_verify") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withMethodParameter(ANY) + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule AES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.AES", "Cryptodome.Cipher.AES") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("AES")) + .withMethodParameter(ANY) + .withMethodParameter(ANY) // Crypto.Cipher.AES.* or Cryptodome.Cipher.AES.* + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("iv", ANY) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("segment_size", "int") + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("msg_len", "int") + .withOptionalNamedMethodParameter("assoc_len", "int") + .withOptionalNamedMethodParameter("initial_value", ANY) + .withOptionalNamedMethodParameter("counter", ANY) + .withOptionalNamedMethodParameter("use_aesni", ANY) + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule DES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.DES", "Cryptodome.Cipher.DES") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("DES")) + .withMethodParameter(ANY) + .withMethodParameter(ANY) // Crypto.Cipher.DES.* or Cryptodome.Cipher.DES.* + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("iv", ANY) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("segment_size", "int") + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("initial_value", "int") + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule DES3 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.DES3", "Cryptodome.Cipher.DES3") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("3DES")) + .withMethodParameter(ANY) + .withMethodParameter(ANY) // Crypto.Cipher.DES3.* or Cryptodome.Cipher.DES3.* + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("iv", ANY) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("segment_size", "int") + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("initial_value", "int") + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule BLOWFISH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.Blowfish", "Cryptodome.Cipher.Blowfish") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("Blowfish")) + .withMethodParameter(ANY) + .withMethodParameter( + ANY) // Crypto.Cipher.Blowfish.* or Cryptodome.Cipher.Blowfish.* + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("iv", ANY) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("segment_size", "int") + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("initial_value", "int") + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule CAST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.CAST", "Cryptodome.Cipher.CAST") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("CAST5")) + .withMethodParameter(ANY) + .withMethodParameter(ANY) // Crypto.Cipher.CAST.* or Cryptodome.Cipher.CAST.* + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("iv", ANY) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("segment_size", "int") + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("initial_value", "int") + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule ARC2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.ARC2", "Cryptodome.Cipher.ARC2") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2")) + .withMethodParameter(ANY) + .withMethodParameter(ANY) // Crypto.Cipher.ARC2.* or Cryptodome.Cipher.ARC2.* + .shouldBeDetectedAs(new ModeFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("iv", ANY) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("effective_keylen", "int") + .withOptionalNamedMethodParameter("segment_size", "int") + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("initial_value", "int") + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule ARC4 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.ARC4", "Cryptodome.Cipher.ARC4") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("RC4")) + .withAnyParameters() + .buildForContext(new CipherContext(Map.of("algorithm", "RC4"))) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule CHACHA20 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.ChaCha20", "Cryptodome.Cipher.ChaCha20") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("ChaCha20")) + .withAnyParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule CHACHA20_POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "Crypto.Cipher.ChaCha20_Poly1305", + "Cryptodome.Cipher.ChaCha20_Poly1305") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("ChaCha20Poly1305")) + .withAnyParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule SALSA20 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Cipher.Salsa20", "Cryptodome.Cipher.Salsa20") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("Salsa20")) + .withAnyParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule PKCS1_OAEP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectExactTypes("Crypto.Cipher.PKCS1_OAEP", "Cryptodome.Cipher.PKCS1_OAEP") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS1_OAEP")) + .withMethodParameter( + ANY) // Crypto.PublicKey.RSAkey or Cryptodome.PublicKey.RSAkey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoRSA.class)) + .withOptionalNamedMethodParameter("hashAlgo", ANY) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("msgfunc", ANY) + .withOptionalNamedMethodParameter("label", ANY) + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + private static final IDetectionRule PKCS1_V1_5 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectExactTypes("Crypto.Cipher.PKCS1_v1_5", "Cryptodome.Cipher.PKCS1_v1_5") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS1_v1_5")) + .withMethodParameter( + ANY) // Crypto.PublicKey.RSAkey or Cryptodome.PublicKey.RSAkey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoRSA.class)) + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + AES, + DES, + DES3, + BLOWFISH, + CAST, + ARC2, + ARC4, + CHACHA20, + CHACHA20_POLY1305, + SALSA20, + PKCS1_OAEP, + PKCS1_V1_5); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java new file mode 100644 index 000000000..b51be775c --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java @@ -0,0 +1,106 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.Size.UnitType; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.DigestSizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoHash extends DetectionRuleSet { + + private PythonCryptoHash() { + // private + } + + public static final List hashes = + Arrays.asList( + "MD2", + "MD4", + "MD5", + "SHA1", + "SHA224", + "SHA256", + "SHA384", + // "SHA512", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "RIPEMD160", + "keccak", + "TupleHash128", + "TupleHash256", + "SHAKE128", + "SHAKE256", + "cSHAKE128", + "cSHAKE256", + "KangarooTwelve", + "BLAKE2b", + "BLAKE2s"); + + private static final IDetectionRule SHA512 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.SHA512", "Cryptodome.Hash.SHA512") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA512")) + .withOptionalNamedMethodParameter("data", ANY) + .withOptionalNamedMethodParameter("truncate", "int") + .shouldBeDetectedAs(new DigestSizeFactory<>(UnitType.BIT)) + .asChildOfParameterWithId(-1) + .buildForContext(new DigestContext(Map.of("preHash", "SHA512"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + LinkedList> rules = new LinkedList<>(); + + rules.add(SHA512); + + for (final String hash : PythonCryptoHash.hashes) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash." + hash, "Cryptodome.Hash." + hash) + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>(hash)) + .withAnyParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules()); + } + return rules; + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PythonCryptoKDF.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PythonCryptoKDF.java new file mode 100644 index 000000000..010da7ce2 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PythonCryptoKDF.java @@ -0,0 +1,158 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.kdf; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.Size; +import com.ibm.engine.model.Size.UnitType; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.IterationCountFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoKDF extends DetectionRuleSet { + + private PythonCryptoKDF() { + // private + } + + // PBKDF1 - module function call + private static final IDetectionRule PBKDF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF1") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF1")) + .withNamedMethodParameter("password", ANY) + .withNamedMethodParameter("salt", ANY) + .withNamedMethodParameter("dkLen", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("count", "int") + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("hashAlgo", ANY) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf1"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // PBKDF2 - module function call + private static final IDetectionRule PBKDF2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF2") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2")) + .withNamedMethodParameter("password", ANY) + .withNamedMethodParameter("salt", ANY) + .withOptionalNamedMethodParameter("dkLen", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("count", "int") + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("hmac_hash_module", ANY) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf2"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // scrypt - module function call + private static final IDetectionRule SCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("scrypt") + .shouldBeDetectedAs(new ValueActionFactory<>("scrypt")) + .withNamedMethodParameter("password", ANY) + .withNamedMethodParameter("salt", ANY) + .withNamedMethodParameter("key_len", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withNamedMethodParameter("N", "int") + .withNamedMethodParameter("r", "int") + .withNamedMethodParameter("p", "int") + .withOptionalNamedMethodParameter("num_keys", "int") + .buildForContext(new KeyDerivationFunctionContext(Map.of("kind", "scrypt"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule HKDF = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("HKDF") + .shouldBeDetectedAs(new ValueActionFactory<>("HKDF")) + .withNamedMethodParameter("master", ANY) + .withNamedMethodParameter("key_len", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withNamedMethodParameter("salt", ANY) + .withNamedMethodParameter( + "hashmod", ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hash_mod) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("num_keys", "int") + .withOptionalNamedMethodParameter("context", ANY) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-hkdf"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // scrypt - module function call + private static final IDetectionRule SP800_108_COUNTER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("SP800_108_Counter") + .shouldBeDetectedAs(new ValueActionFactory<>("SP800_108_Counter")) + .withNamedMethodParameter("master", ANY) // master + .withNamedMethodParameter("key_len", "int") // key_len + .shouldBeDetectedAs(new KeySizeFactory<>(UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withNamedMethodParameter("prf", ANY) + .withOptionalNamedMethodParameter("num_keys", "int") + .withOptionalNamedMethodParameter("label", ANY) + .withOptionalNamedMethodParameter("context", ANY) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(PBKDF1, PBKDF2, SCRYPT, HKDF, SP800_108_COUNTER); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java new file mode 100644 index 000000000..7f35538c2 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java @@ -0,0 +1,136 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.keyagreement; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoECC; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoKeyAgreement extends DetectionRuleSet { + + private PythonCryptoKeyAgreement() { + // private + } + + private static final IDetectionRule IMPORT_X25519_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.DH", "Cryptodome.Protocol.DH") + .forMethods("import_x25519_public_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PUBLIC_KEY_GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "Curve25519"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule IMPORT_X25519_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.DH", "Cryptodome.Protocol.DH") + .forMethods("import_x25519_private_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withAnyParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "Curve25519"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule IMPORT_X448_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.DH", "Cryptodome.Protocol.DH") + .forMethods("import_x448_public_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PUBLIC_KEY_GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "Curve448"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule IMPORT_X448_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.DH", "Cryptodome.Protocol.DH") + .forMethods("import_x448_private_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withAnyParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "Curve448"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // DH.key_agreement - module function call + private static final IDetectionRule DH_KEY_AGREEMENT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.DH", "Cryptodome.Protocol.DH") + .forMethods("key_agreement") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDH")) + .withMethodParameter(ANY) // kdf + .withMethodParameter( + ANY) // Crypto.PublicKey.ECC.ECCKey or Cryptodome.PublicKey.ECC.ECCKey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules( + Stream.concat( + RuleSets.rulesOf(PythonCryptoECC.class).stream(), + Stream.of( + IMPORT_X25519_PRIVATE_KEY, + IMPORT_X448_PRIVATE_KEY)) + .toList()) + .withMethodParameter( + ANY) // Crypto.PublicKey.ECC.ECCKey or Cryptodome.PublicKey.ECC.ECCKey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules( + Stream.concat( + RuleSets.rulesOf(PythonCryptoECC.class).stream(), + Stream.of( + IMPORT_X25519_PUBLIC_KEY, + IMPORT_X448_PUBLIC_KEY)) + .toList()) + .buildForContext(new KeyAgreementContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(DH_KEY_AGREEMENT); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java new file mode 100644 index 000000000..33bfa7b4f --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java @@ -0,0 +1,101 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.mac; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.Size.UnitType; +import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.DigestSizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoMac extends DetectionRuleSet { + + private PythonCryptoMac() { + // private + } + + private static final IDetectionRule CMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.CMAC", "Cryptodome.Hash.CMAC") + .forMethods("new") + .withNamedMethodParameter("key", ANY) + .withOptionalNamedMethodParameter("msg", ANY) + .withOptionalNamedMethodParameter( + "ciphermod", ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withOptionalNamedMethodParameter("cipher_params", ANY) + .withOptionalNamedMethodParameter("mac_len", "int") + .shouldBeDetectedAs(new DigestSizeFactory<>(UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("update_after_digest", ANY) + .buildForContext(new MacContext(Map.of("kind", "cmac"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule HMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.HMAC", "Cryptodome.Hash.HMAC") + .forMethods("new") + .withNamedMethodParameter("key", ANY) + .withOptionalNamedMethodParameter("msg", ANY) + .withOptionalNamedMethodParameter( + "digestmod", ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext(new MacContext(Map.of("kind", "hmac"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + List> rules = new ArrayList<>(); + // add CMAC + HMAC + rules.addAll(List.of(CMAC, HMAC)); + + // add "simple" MACs + for (final String mac : List.of("KMAC128", "KMAC256", "Poly1305")) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash." + mac, "Cryptodome.Hash." + mac) + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>(mac)) + .withAnyParameters() + .buildForContext(new MacContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules()); + } + return rules; + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoDSA.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoDSA.java new file mode 100644 index 000000000..50c7c9812 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoDSA.java @@ -0,0 +1,97 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +// For RSA, DSA, and ECC keys there are corresponding signature or encryption schemes +// that use them as a parameter. In order the detect the key object in the context of +// these higher-level methods the corresponding rules are used as dependent rules. +@SuppressWarnings("java:S1192") +public final class PythonCryptoDSA extends DetectionRuleSet { + + private PythonCryptoDSA() { + // private + } + + // DSA generate -> private key + private static final IDetectionRule DSA_GENERATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.DSA", "Cryptodome.PublicKey.DSA") + .forMethods("generate") + // .shouldBeDetectedAs( + // new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withNamedMethodParameter("bits", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("randfunc", ANY) + .withOptionalNamedMethodParameter("domain", ANY) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // DSA construct and import_key -> public or private key + private static final IDetectionRule DSA_CONSTRUCT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.DSA", "Cryptodome.PublicKey.DSA") + .forMethods("construct", "import_key") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new KeyContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // DsaKey public_key -> public key + private static final IDetectionRule DSA_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "Crypto.PublicKey.DSA.DsaKey", "Cryptodome.PublicKey.DSA.DsaKey") + .forMethods("public_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PUBLIC_KEY_GENERATION)) + .withoutParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "DSA"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(DSA_CONSTRUCT, DSA_GENERATE, DSA_PUBLIC_KEY); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoECC.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoECC.java new file mode 100644 index 000000000..1c74ca04f --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoECC.java @@ -0,0 +1,114 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.CurveFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +// For RSA, DSA, and ECC keys there are corresponding signature or encryption schemes +// that use them as a parameter. In order the detect the key object in the context of +// these higher-level methods the corresponding rules are used as dependent rules. +@SuppressWarnings("java:S1192") +public final class PythonCryptoECC extends DetectionRuleSet { + + private PythonCryptoECC() { + // private + } + + // ECC key generation + private static final IDetectionRule ECC_GENERATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.ECC", "Cryptodome.PublicKey.ECC") + .forMethods("generate") + // .shouldBeDetectedAs(new + // KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withNamedMethodParameter("curve", "str") + .shouldBeDetectedAs(new CurveFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // ECC key construction + private static final IDetectionRule ECC_CONSTRUCT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.ECC", "Cryptodome.PublicKey.ECC") + .forMethods("construct") + // .shouldBeDetectedAs( + // new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withNamedMethodParameter("curve", "str") + .shouldBeDetectedAs(new CurveFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("d", "int") + .withOptionalNamedMethodParameter("seed", ANY) + .withOptionalNamedMethodParameter("point_x", "int") + .withOptionalNamedMethodParameter("point_y", "int") + .buildForContext(new KeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // ECC import + private static final IDetectionRule ECC_IMPORT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.ECC", "Cryptodome.PublicKey.ECC") + .forMethods("import_key") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new KeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // EccKey public_key -> public key + private static final IDetectionRule ECC_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "Crypto.PublicKey.ECC.EccKey", "Cryptodome.PublicKey.ECC.EccKey") + .forMethods("public_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PUBLIC_KEY_GENERATION)) + .withoutParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(ECC_CONSTRUCT, ECC_GENERATE, ECC_IMPORT, ECC_PUBLIC_KEY); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoElGamal.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoElGamal.java new file mode 100644 index 000000000..26e3d9696 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoElGamal.java @@ -0,0 +1,81 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +// Only the ElGamal rules are registered as top-level detection rules. The documentation +// describes them as obsolete keys. Pycryptodome does not provide a higher-level method +// (encryption, signature) based on ElGamal keys. +@SuppressWarnings("java:S1192") +public final class PythonCryptoElGamal extends DetectionRuleSet { + + private PythonCryptoElGamal() { + // private + } + + // ElGamal + private static final IDetectionRule ELGAMAL_GENERATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.ElGamal", "Cryptodome.PublicKey.ElGamal") + .forMethods("generate") + // .shouldBeDetectedAs( + // new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withNamedMethodParameter("bits", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withNamedMethodParameter("randfunc", ANY) + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "ElGamal"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // ElGamal + private static final IDetectionRule ELGAMAL_CONSTRUCT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.ElGamal", "Cryptodome.PublicKey.ElGamal") + .forMethods("construct") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new KeyContext(Map.of("algorithm", "ElGamal"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(ELGAMAL_CONSTRUCT, ELGAMAL_GENERATE); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoRSA.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoRSA.java new file mode 100644 index 000000000..d893e2830 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoRSA.java @@ -0,0 +1,97 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +// For RSA, DSA, and ECC keys there are corresponding signature or encryption schemes +// that use them as a parameter. In order the detect the key object in the context of +// these higher-level methods the corresponding rules are used as dependent rules. +@SuppressWarnings("java:S1192") +public final class PythonCryptoRSA extends DetectionRuleSet { + + private PythonCryptoRSA() { + // private + } + + // RSA generate -> private key + private static final IDetectionRule RSA_GENERATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.RSA", "Cryptodome.PublicKey.RSA") + .forMethods("generate") + // .shouldBeDetectedAs( + // new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withNamedMethodParameter("bits", "int") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("randfunc", ANY) + .withOptionalNamedMethodParameter("e", "int") + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // RSA construct and import_key -> public or private key + private static final IDetectionRule RSA_CONSTRUCT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.PublicKey.RSA", "Cryptodome.PublicKey.RSA") + .forMethods("construct", "import_key") + .shouldBeDetectedAs(new KeyActionFactory<>(KeyAction.Action.GENERATION)) + .withAnyParameters() + .buildForContext(new KeyContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // RsaKey public_key -> public key + private static final IDetectionRule RSA_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes( + "Crypto.PublicKey.RSA.RsaKey", "Cryptodome.PublicKey.RSA.RsaKey") + .forMethods("public_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PUBLIC_KEY_GENERATION)) + .withoutParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "RSA"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(RSA_CONSTRUCT, RSA_GENERATE, RSA_PUBLIC_KEY); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/random/PythonCryptoRandom.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/random/PythonCryptoRandom.java new file mode 100644 index 000000000..bb5c1cb72 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/random/PythonCryptoRandom.java @@ -0,0 +1,66 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.random; + +import com.ibm.engine.model.context.PRNGContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoRandom extends DetectionRuleSet { + + private PythonCryptoRandom() { + // private + } + + private static final IDetectionRule RANDOM_GET_BYTES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Random", "Cryptodome.Random") + .forMethods("get_random_bytes") + .shouldBeDetectedAs(new ValueActionFactory<>("PRNG")) + .withNamedMethodParameter("N", "int") + .buildForContext(new PRNGContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule RANDOM_FUNC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Random.random", "Cryptodome.Random.random") + .forMethods( + "getrandbits", "randrange", "randint", "choice", "shuffle", "sample") + .shouldBeDetectedAs(new ValueActionFactory<>("PRNG")) + .withAnyParameters() + .buildForContext(new PRNGContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(RANDOM_GET_BYTES, RANDOM_FUNC); + } +} diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java new file mode 100644 index 000000000..090769d0e --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java @@ -0,0 +1,267 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.Size.UnitType; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.SaltSizeFactory; +import com.ibm.engine.model.factory.SignatureActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.pycrypto.hash.PythonCryptoHash; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoDSA; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoECC; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoRSA; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoSignature extends DetectionRuleSet { + + private PythonCryptoSignature() { + // private + } + + private static final IDetectionRule SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withNamedMethodParameter("msg_hash", ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoHash.class)) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule VERIFY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("verify") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.VERIFY)) + .withNamedMethodParameter("msg_hash", ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoHash.class)) + .withNamedMethodParameter("signature", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // PSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule PKCS1V15 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.pkcs1_15", "Cryptodome.Signature.pkcs1_15") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PKCS1V15")) + .withNamedMethodParameter( + "rsa_key", ANY) // Crypto.PublicKey.RSA or Cryptodome.PublicKey.RSA + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoRSA.class)) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // PSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule PSS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.pss", "Cryptodome.Signature.pss") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withNamedMethodParameter( + "rsa_key", ANY) // Crypto.PublicKey.RSA or Cryptodome.PublicKey.RSA + // .shouldBeDetectedAs(new AlgorithmFactory<>()) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoRSA.class)) + .withOptionalNamedMethodParameter("mask_func", ANY) + .withOptionalNamedMethodParameter("salt", "int") + .shouldBeDetectedAs(new SaltSizeFactory<>(UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("rand_func", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // PSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule PSS_MGF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.pss", "Cryptodome.Signature.pss") + .forMethods("MGF1") + .shouldBeDetectedAs(new ValueActionFactory<>("MGF1")) + .withNamedMethodParameter("msgSeed", ANY) + .withNamedMethodParameter("maskLen", "int") + .withNamedMethodParameter("hash_gen", ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // DSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule DSS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.DSS") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("DSS")) + .withNamedMethodParameter("key", "Crypto.PublicKey.DSA") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoDSA.class)) + .withNamedMethodParameter("mode", "str") + .withOptionalNamedMethodParameter("encoding", "str") + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // DSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule DSS_CRYPTODOME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Cryptodome.Signature.DSS") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("DSS")) + .withNamedMethodParameter("key", "Cryptodome.PublicKey.DSA") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoDSA.class)) + .withNamedMethodParameter("mode", "str") + .withOptionalNamedMethodParameter("encoding", "str") + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // DSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule ECDSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.DSS") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDSA")) + .withNamedMethodParameter("key", "Crypto.PublicKey.ECC") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoECC.class)) + .withNamedMethodParameter("mode", "str") + .withOptionalNamedMethodParameter("encoding", "str") + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // DSS signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule ECDSA_CRYPTODOME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Cryptodome.Signature.DSS") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDSA")) + .withNamedMethodParameter("key", "Cryptodome.PublicKey.ECC") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoECC.class)) + .withNamedMethodParameter("mode", "str") + .withOptionalNamedMethodParameter("encoding", "str") + .withOptionalNamedMethodParameter("randfunc", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + // EdDSA import private key + private static final IDetectionRule EDDSA_IMPORT_PRIVATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.eddsa", "Cryptodome.Signature.eddsa") + .forMethods("import_private_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withAnyParameters() + .buildForContext(new PrivateKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // EdDSA import public key + private static final IDetectionRule EDDSA_IMPORT_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.eddsa", "Cryptodome.Signature.eddsa") + .forMethods("import_public_key") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PUBLIC_KEY_GENERATION)) + .withAnyParameters() + .buildForContext(new PublicKeyContext(Map.of("algorithm", "EC"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + // EdDSA signature scheme - sign and verify methods (called on the result of .new()) + private static final IDetectionRule EDDSA = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Signature.eddsa", "Cryptodome.Signature.eddsa") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("EDDSA")) + .withNamedMethodParameter( + "key", ANY) // Crypto.PublicKey.ECCkey or Cryptodome.PublicKey.ECCkey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules( + Stream.concat( + RuleSets.rulesOf(PythonCryptoECC.class).stream(), + Stream.of( + EDDSA_IMPORT_PRIVATE_KEY, + EDDSA_IMPORT_PUBLIC_KEY)) + .toList()) + .withNamedMethodParameter("mode", "str") + .withOptionalNamedMethodParameter("context", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + PKCS1V15, PSS, PSS_MGF1, DSS, DSS_CRYPTODOME, ECDSA, ECDSA_CRYPTODOME, EDDSA); + } +} From e5a70b06eec352eb9ce250fe067e2230745bcaff Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 15:52:26 +0200 Subject: [PATCH 06/24] feat(python): wire new translator contexts for key and random - Add PycaKeyContextTranslator (handles KeyContext) and PycaRandomContextTranslator (handles PRNGContext) - Remove PycaSecretContextTranslator (dead code, superseded by PycaKeyContextTranslator) - In PythonTranslator: route KeyContext after PrivateKey/PublicKey to avoid premature matching; add PRNGContext branch Signed-off-by: san-zrl --- .../contexts/PycaKeyContextTranslator.java | 76 +++++++++++++++++++ .../contexts/PycaRandomContextTranslator.java | 57 ++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java create mode 100644 python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaRandomContextTranslator.java diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java new file mode 100644 index 000000000..df9bdde20 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java @@ -0,0 +1,76 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.pyca.PycaCurveMapper; +import com.ibm.mapper.mapper.pyca.PycaKeyBasedAlgorithmMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +public final class PycaKeyContextTranslator implements IContextTranslation { + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + if (value instanceof KeyAction) { + // action is always "generate" + final PycaKeyBasedAlgorithmMapper mapper = new PycaKeyBasedAlgorithmMapper(); + return detectionContext + .get("algorithm") + .flatMap(str -> mapper.parse(str, detectionLocation)) + .map( + algo -> { + final Key key = new Key(algo); + key.put(new KeyGeneration(detectionLocation)); + return key; + }); + } else if (value instanceof Curve curve + && detectionContext + .get("algorithm") + .map(a -> a.equalsIgnoreCase("EC")) + .orElse(false)) { + final PycaCurveMapper mapper = new PycaCurveMapper(); + return mapper.parse(curve.asString(), detectionLocation) + .map( + ec -> { + Key key = new Key(ec); + key.put(new KeyGeneration(detectionLocation)); + // currently only GENERATE is + // used as key action is this + // context + return key; + }); + } + return Optional.empty(); + } +} diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaRandomContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaRandomContextTranslator.java new file mode 100644 index 000000000..9e6458896 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaRandomContextTranslator.java @@ -0,0 +1,57 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1301") +public final class PycaRandomContextTranslator implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + if (value instanceof ValueAction) { + return switch (value.asString().toUpperCase().trim()) { + case "PRNG" -> + Optional.of( + new Algorithm( + "PRNG", + PseudorandomNumberGenerator.class, + detectionLocation)); + default -> Optional.empty(); + }; + } + return Optional.empty(); + } +} From b7bc7c449af4777dd9b1e46b106148d4e6716dfa Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 16:02:16 +0200 Subject: [PATCH 07/24] feat(python): update existing context translators for PyCryptodome - CipherContextTranslator: add MODE_* aliases, PKCS1_OAEP, PKCS1_V1_5, HPKE, EAX, KW, KWP; handle ValueAction for algorithm names - DigestContextTranslator: handle DigestSize values (SHA-512/t truncation) - KeyAgreementContextTranslator: accept ValueAction, add X25519/X448 - KeyDerivationContextTranslator: add PBKDF1, KDFCounter, IterationCount, SaltSize; add pycrypto-specific KDF bundle key handling - MacContextTranslator: delegate ValueAction to PycaMacMapper - PrivateKeyContextTranslator: replace inline curve/algorithm switches with PycaCurveMapper and PycaKeyBasedAlgorithmMapper - PublicKeyContextTranslator: same mapper refactor; add Curve handling - SignatureContextTranslator: add RSA, DSS, ECDSA, EdDSA, RSA-PKCS1V15 Signed-off-by: san-zrl --- .../rules/detection/hash/PycaHashWrapper.java | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHashWrapper.java diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHashWrapper.java b/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHashWrapper.java deleted file mode 100644 index f66828b50..000000000 --- a/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHashWrapper.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA - * - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you 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 - * - * http://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. - */ -package com.ibm.plugin.rules.detection.hash; - -import com.ibm.engine.rule.DetectionRuleSet; -import com.ibm.engine.rule.IDetectionRule; -import java.util.List; -import javax.annotation.Nonnull; -import org.sonar.plugins.python.api.tree.Tree; - -public final class PycaHashWrapper extends DetectionRuleSet { - - @Nonnull - @Override - protected List> buildRules() { - return List.of(PycaHash.HASH_WRAPPER); - } -} From e99c3c947f11c80bc66a4a78f1ef64c9491ed16d Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 16:04:50 +0200 Subject: [PATCH 08/24] chore(python): move pyca test fixtures and test classes into pyca/ subpackage Mirrors the detection rule reorganisation: test fixtures and Java test classes for pyca rules move from test/detection/{aead,...} into test/detection/pyca/{aead,...}. Also renames named_parameters/ test package (was at detection root). Signed-off-by: san-zrl --- .../detection/pyca/aead/PycaAESGCMTestFile.py | 10 + .../pyca/aead/PycaChaCha20Poly1305TestFile.py | 11 + .../asymmetric/DSA/PycaDSANumbersTestFile.py | 13 + .../asymmetric/DSA/PycaDSASignTestFile.py | 11 + .../PycaDiffieHellmanGenerateTestFile.py | 6 + .../PycaDiffieHellmanNumbersTestFile.py | 13 + .../PycaEllipticCurveDeriveTestFile.py | 12 + .../PycaEllipticCurveKeyExchangeTestFile.py | 21 ++ .../PycaEllipticCurveNumbersTestFile.py | 53 ++++ .../PycaEllipticCurveSign2TestFile.py | 48 ++++ .../PycaEllipticCurveSignTestFile.py | 37 +++ .../PycaEllipticCurveVerifyTestFile.py | 8 + .../asymmetric/RSA/PycaRSADecryptTestFile.py | 19 ++ .../asymmetric/RSA/PycaRSANumbersTestFile.py | 13 + .../asymmetric/RSA/PycaRSASign1TestFile.py | 19 ++ .../asymmetric/RSA/PycaRSASign2TestFile.py | 16 ++ .../pyca/fernet/PycaFernetDecryptTestFile.py | 15 ++ .../pyca/fernet/PycaFernetEncryptTestFile.py | 16 ++ .../pyca/fernet/PycaMultiFernetTestFile.py | 19 ++ .../detection/pyca/hash/PycaHashDirectTest.py | 5 + .../pyca/kdf/PycaConcatKDFHMACTestFile.py | 15 ++ .../pyca/kdf/PycaConcatKDFHashTestFile.py | 13 + .../pyca/kdf/PycaHKDFExpandTestFile.py | 14 + .../detection/pyca/kdf/PycaHKDFTestFile.py | 15 ++ .../pyca/kdf/PycaKBKDFCMACTestFile.py | 21 ++ .../pyca/kdf/PycaKBKDFHMACTestFile.py | 22 ++ .../detection/pyca/kdf/PycaPBKDF2TestFile.py | 16 ++ .../detection/pyca/kdf/PycaScryptTestFile.py | 15 ++ .../detection/pyca/kdf/PycaX963KDFTestFile.py | 13 + .../keyagreement/PycaKeyAgreementTestFile.py | 38 +++ .../detection/pyca/mac/PycaCMACTestFile.py | 25 ++ .../detection/pyca/mac/PycaHMACTestFile.py | 25 ++ ...ycaMacDetectionInCustomFunctionTestFile.py | 27 ++ .../pyca/mac/PycaPoly1305TestFile.py | 21 ++ .../pyca/padding/PycaPaddingTestFile.py | 16 ++ .../pyca/symmetric/PycaCipher1TestFile.py | 21 ++ .../pyca/symmetric/PycaCipher2TestFile.py | 12 + .../symmetric/PycaStreamCipher1TestFile.py | 11 + .../pyca/wrapping/PycaWrappingTestFile.py | 25 ++ .../PycaWrappingWithPaddingTestFile.py | 25 ++ .../NamedAllKeywordParametersTest.java | 92 +++++++ .../NamedParameterWildcardTest.java | 71 +++++ .../named_parameters/NamedParametersTest.java | 206 ++++++++++++++ .../detection/pyca/aead/PycaAESGCMTest.java | 148 ++++++++++ .../pyca/aead/PycaChaCha20Poly1305Test.java | 135 ++++++++++ .../asymmetric/DSA/PycaDSANumbersTest.java | 140 ++++++++++ .../pyca/asymmetric/DSA/PycaDSASignTest.java | 156 +++++++++++ .../PycaDiffieHellmanGenerateTest.java | 99 +++++++ .../PycaDiffieHellmanNumbersTest.java | 96 +++++++ .../PycaEllipticCurveDeriveTest.java | 103 +++++++ .../PycaEllipticCurveKeyExchangeTest.java | 206 ++++++++++++++ .../PycaEllipticCurveNumbersTest.java | 52 ++++ .../PycaEllipticCurveSign2Test.java | 185 +++++++++++++ .../PycaEllipticCurveSignTest.java | 158 +++++++++++ .../PycaEllipticCurveVerifyTest.java | 55 ++++ .../asymmetric/RSA/PycaRSADecryptTest.java | 253 ++++++++++++++++++ .../asymmetric/RSA/PycaRSANumbersTest.java | 142 ++++++++++ .../pyca/asymmetric/RSA/PycaRSASign1Test.java | 231 ++++++++++++++++ .../pyca/asymmetric/RSA/PycaRSASign2Test.java | 170 ++++++++++++ .../pyca/fernet/PycaFernetDecryptTest.java | 200 ++++++++++++++ .../pyca/fernet/PycaFernetEncryptTest.java | 200 ++++++++++++++ .../pyca/fernet/PycaMultiFernetTest.java | 211 +++++++++++++++ .../pyca/hash/PycaHashDirectTest.java | 102 +++++++ .../pyca/kdf/PycaConcatKDFHMACTest.java | 133 +++++++++ .../pyca/kdf/PycaConcatKDFHashTest.java | 131 +++++++++ .../pyca/kdf/PycaHKDFExpandTest.java | 133 +++++++++ .../detection/pyca/kdf/PycaHKDFTest.java | 133 +++++++++ .../detection/pyca/kdf/PycaKBKDFCMACTest.java | 142 ++++++++++ .../detection/pyca/kdf/PycaKBKDFHMACTest.java | 163 +++++++++++ .../detection/pyca/kdf/PycaPBKDF2Test.java | 147 ++++++++++ .../detection/pyca/kdf/PycaScryptTest.java | 101 +++++++ .../detection/pyca/kdf/PycaX963KDFTest.java | 133 +++++++++ .../keyagreement/PycaKeyAgreementTest.java | 179 +++++++++++++ .../detection/pyca/mac/PycaCMACTest.java | 101 +++++++ .../detection/pyca/mac/PycaHMACTest.java | 122 +++++++++ .../PycaMacDetectionInCustomFunctionTest.java | 131 +++++++++ .../detection/pyca/mac/PycaPoly1305Test.java | 80 ++++++ .../pyca/padding/PycaPaddingTest.java | 125 +++++++++ .../pyca/symmetric/PycaCipher1Test.java | 161 +++++++++++ .../pyca/symmetric/PycaCipher2Test.java | 104 +++++++ .../pyca/symmetric/PycaStreamCipher1Test.java | 89 ++++++ .../pyca/wrapping/PycaWrappingTest.java | 87 ++++++ .../wrapping/PycaWrappingWithPaddingTest.java | 88 ++++++ 83 files changed, 6649 insertions(+) create mode 100644 python/src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py create mode 100644 python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedAllKeywordParametersTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParameterWildcardTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParametersTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaChaCha20Poly1305Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSASignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign1Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetDecryptTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetEncryptTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaMultiFernetTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashDirectTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHashTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFExpandTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFCMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFHMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaPBKDF2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaScryptTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaX963KDFTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreementTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaCMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaHMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaPoly1305Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPaddingTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher1Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTest.java diff --git a/python/src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py b/python/src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py new file mode 100644 index 000000000..84de0996e --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py @@ -0,0 +1,10 @@ +import os +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +data = b"a secret message" +aad = b"authenticated but unencrypted data" +key = AESGCM.generate_key(bit_length=128) # Noncompliant {{(SecretKey) AES}} +aesgcm = AESGCM(key) +nonce = os.urandom(12) +ct = aesgcm.encrypt(nonce, data, aad) +aesgcm.decrypt(nonce, ct, aad) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py b/python/src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py new file mode 100644 index 000000000..135304288 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py @@ -0,0 +1,11 @@ +import os +from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 + +data = b"a secret message" +aad = b"authenticated but unencrypted data" +key = ChaCha20Poly1305.generate_key() # Noncompliant {{(SecretKey) ChaCha20}} +chacha = ChaCha20Poly1305(key) +nonce = os.urandom(12) +ct = chacha.encrypt(nonce, data, aad) +nonce2 = os.urandom(12) +chacha.decrypt(nonce2, ct, aad) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py new file mode 100644 index 000000000..a2205d08d --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py @@ -0,0 +1,13 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import dsa +from cryptography.hazmat.primitives.asymmetric.dsa import DSAPrivateNumbers + +def generate_dsa_key_from_parameters( + p, q, g, x, y +) -> dsa.DSAPrivateKey: + """ + Generates a DSA private key from parameters p, q, g, x, and y. + """ + public_numbers = dsa.DSAPublicNumbers(y, dsa.DSAParameterNumbers(p, q, g)) # Noncompliant {{(PublicKey) DSA}} + private_numbers = DSAPrivateNumbers(x, public_numbers) # Noncompliant {{(PrivateKey) DSA}} + return private_numbers.private_key(default_backend()) diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py new file mode 100644 index 000000000..4bed251fe --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py @@ -0,0 +1,11 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import dsa + +private_key = dsa.generate_private_key( # Noncompliant {{(PrivateKey) DSA}} + key_size=1024, +) +data = b"this is some data I'd like to sign" +signature = private_key.sign( + data, + hashes.SHA256() +) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py new file mode 100644 index 000000000..ea45be61b --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py @@ -0,0 +1,6 @@ +from cryptography.hazmat.primitives.asymmetric import dh + +# Generate some parameters. These can be reused. +parameters = dh.generate_parameters(generator=2, key_size=2048) +# Generate a private key for use in the exchange. +server_private_key = parameters.generate_private_key() # Noncompliant {{(PrivateKey) FFDH}} diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py new file mode 100644 index 000000000..06a8ae83b --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py @@ -0,0 +1,13 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.hazmat.primitives.asymmetric.dh import DHPrivateNumbers + +def generate_dh_key_from_parameters( + p, g, x, y +) -> dh.DHPrivateKey: + """ + Generates a DH private key from parameters p, g, x, and y. + """ + public_numbers = dh.DHPublicNumbers(y, p, g) # Noncompliant {{(PublicKey) FFDH}} + private_numbers = DHPrivateNumbers(x, public_numbers) # Noncompliant {{(PublicKey) FFDH}} + return private_numbers.private_key(default_backend()) diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py new file mode 100644 index 000000000..e5709f211 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py @@ -0,0 +1,12 @@ +# Code inspired by https://github.com/dimaqq/minioidc/blob/main/tests/test_minioidc.py + +import cryptography.hazmat.primitives.asymmetric.ec +import base64 + +TEST_PRIVATE_KEY = cryptography.hazmat.primitives.asymmetric.ec.derive_private_key( # Noncompliant {{(PrivateKey) EC-secp256r1}} + int.from_bytes( + base64.urlsafe_b64decode("870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE" + "==="), + "big", + ), + cryptography.hazmat.primitives.asymmetric.ec.SECP256R1(), +) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py new file mode 100644 index 000000000..b1d727b32 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py @@ -0,0 +1,21 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + +# Generate a private key for use in the exchange. +server_private_key = ec.generate_private_key( # Noncompliant {{(PrivateKey) EC-secp384r1}} + ec.SECP384R1() +) + +def exchange(public_key): + shared_key = server_private_key.exchange( + ec.ECDH(), public_key) + + # Perform key derivation. // TODO: How should this key derivation be linked to the private key? + derived_key = HKDF( # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + algorithm=hashes.SHA256(), + length=32, + salt=None, + info=b'handshake data', + ).derive(shared_key) + return derived_key \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py new file mode 100644 index 000000000..38c65d80d --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py @@ -0,0 +1,53 @@ +# Code inspired by https://github.com/ydb-platform/ydb/blob/284b7efb67edcdade0b12c849b7fad40739ad62b/contrib/python/Twisted/py2/twisted/conch/ssh/keys.py#L799 + +from cryptography.hazmat.primitives.asymmetric import dsa, rsa, padding, ec + +_curveTable = { + b'ecdsa-sha2-nistp256': ec.SECP256R1(), + b'ecdsa-sha2-nistp384': ec.SECP384R1(), + b'ecdsa-sha2-nistp521': ec.SECP521R1(), +} + +def default_backend(): + global _default_backend + + if _default_backend is None: + from cryptography.hazmat.backends.openssl.backend import backend + + _default_backend = backend + + return _default_backend + +class Key(object): + @classmethod + def _fromECComponents(cls, x, y, curve, privateValue=None): + """ + Build a key from EC components. + + @param x: The affine x component of the public point used for verifying. + @type x: L{int} + + @param y: The affine y component of the public point used for verifying. + @type y: L{int} + + @param curve: NIST name of elliptic curve. + @type curve: L{bytes} + + @param privateValue: The private value. + @type privateValue: L{int} + """ + + publicNumbers = ec.EllipticCurvePublicNumbers( + x=x, y=y, curve=_curveTable[curve]) + if privateValue is None: + # We have public components. + keyObject = publicNumbers.public_key(default_backend()) + else: + privateNumbers = ec.EllipticCurvePrivateNumbers( + private_value=privateValue, public_numbers=publicNumbers) + keyObject = privateNumbers.private_key(default_backend()) + + return cls(keyObject) + +some_var = b'ecdsa-sha2-nistp256' +Key._fromECComponents(None, None, None, some_var, None) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py new file mode 100644 index 000000000..56aab2093 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py @@ -0,0 +1,48 @@ +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey + +private_key = Ed25519PrivateKey.generate() # Noncompliant {{(PrivateKey) Ed25519}} +signature = private_key.sign(b"my authenticated message") +public_key = private_key.public_key() +# Raises InvalidSignature if verification fails +public_key.verify(signature, b"my authenticated message") + +private_key = Ed448PrivateKey.generate() # Noncompliant {{(PrivateKey) Ed448}} +signature = private_key.sign(b"my authenticated message") +public_key = private_key.public_key() +# Raises InvalidSignature if verification fails +public_key.verify(signature, b"my authenticated message") + +# False positives that should NOT be detected (PR-429 fix) +# These are unrelated generate() methods with parameters +class VLMModel: + def generate(self, **gen_kwargs): + return [1, 2, 3] + +class TextModel: + def generate(self, *prompts): + return "generated text" + +vlm_model = VLMModel() +text_model = TextModel() + +# These should NOT trigger detection (not cryptography-related) +generated_ids = vlm_model.generate(**{"max_length": 100}) +generated_text = text_model.generate("prompt1", "prompt2") + +# GROUND TRUTH (translation of the 1st finding) +# +# PrivateKey EC +# Signature EdDSA +# MessageDigest SHA-512 +# EllipticCurveAlgorithm EC +# EllipticCurve Curve25519 +# Sign SIGN +# EllipticCurveAlgorithm EC +# EllipticCurve Curve25519 +# KeyGeneration KEYGENERATION +# PublicKey EC +# EllipticCurveAlgorithm EC +# EllipticCurve Curve25519 +# KeyGeneration KEYGENERATION +# \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py new file mode 100644 index 000000000..56fdeec3d --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py @@ -0,0 +1,37 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import utils + +# param = ec.SECP192R1() +param = ec.SECP384R1() +# private_key, other_var = ec.generate_private_key(param), 42 # TODO: because of TraceSeymbols not yet supporting multi-var assignments, this does not work +private_key = ec.generate_private_key(param) # Noncompliant {{(PrivateKey) EC-secp384r1}} + +# Ploys that should not be detected +b = ec.ECDSA(utils.Prehashed(hashes.SHA3_224())) # TODO: The test should pass also when removing "b =" +utils.Prehashed(hashes.SHA3_224()) +hashes.SHA3_224() + +digest = b"\x00" * 64 +sig = private_key.sign(digest, ec.ECDSA(utils.Prehashed(hashes.SHA3_512()))) + +# TODO: Make it work when uncommented +# pk = private_key.public_key() +# pk.verify(sig, digest, ec.ECDSA(hashes.SHA3_512())) + +# GROUND TRUTH (translation) +# +# PrivateKey EC +# Signature ECDSA +# MessageDigest SHA3-512 +# EllipticCurveAlgorithm EC +# EllipticCurve SECP384R1 +# Sign SIGN +# EllipticCurveAlgorithm EC +# EllipticCurve SECP384R1 +# KeyGeneration KEYGENERATION +# PublicKey EC +# EllipticCurveAlgorithm EC +# EllipticCurve SECP384R1 +# KeyGeneration KEYGENERATION +# diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py new file mode 100644 index 000000000..c357dea0f --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py @@ -0,0 +1,8 @@ +# Code inspired by https://github.com/redis/redis-py/blob/master/redis/ocsp.py + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec + +def verify(pubkey, signature, digest): + if isinstance(pubkey, ec.EllipticCurvePublicKey): + pubkey.verify(signature, digest, ec.ECDSA(hashes.SHA3_512())) diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py new file mode 100644 index 000000000..a63b81590 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py @@ -0,0 +1,19 @@ +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding + +private_key = rsa.generate_private_key( # Noncompliant {{(PrivateKey) RSA}} + public_exponent=65537, + key_size=1024, +) + +def decrypt(ciphertext): + plaintext = private_key.decrypt( + ciphertext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA384()), + algorithm=hashes.SHA256(), + label=None + ) + ) + return plaintext \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py new file mode 100644 index 000000000..f84461215 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py @@ -0,0 +1,13 @@ +from cryptography.hazmat.primitives.asymmetric.rsa import * +from cryptography.hazmat.backends import default_backend + +def generate_rsa_key_from_parameters( + p, q, d, dmp1, dmq1, iqmp, e, n +) -> RSAPrivateKey: + """ + Note: from certbot dp is dmp1, dq is dmq1 and qi is iqmp + """ + public_numbers = RSAPublicNumbers(e, n) # Noncompliant {{(PublicKey) RSA}} + return RSAPrivateNumbers( # Noncompliant {{(PrivateKey) RSA}} + p, q, d, dmp1, dmq1, iqmp, public_numbers + ).private_key(default_backend()) diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py new file mode 100644 index 000000000..3d25ad2dd --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py @@ -0,0 +1,19 @@ +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import utils + +private_key = rsa.generate_private_key( # Noncompliant {{(PrivateKey) RSA}} + public_exponent=65537, + key_size=2048, +) + +message = b"A message I want to sign" +signature = private_key.sign( + message, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + utils.Prehashed(hashes.SHA384()) +) diff --git a/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py new file mode 100644 index 000000000..43fd0ae39 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py @@ -0,0 +1,16 @@ +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric import utils + +private_key = rsa.generate_private_key( # Noncompliant {{(PrivateKey) RSA}} + public_exponent=65537, + key_size=2048, +) + +message = b"A message I want to sign" +signature = private_key.sign( + message, + padding.PKCS1v15(), + hashes.SHA3_384() +) diff --git a/python/src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py b/python/src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py new file mode 100644 index 000000000..0ba848d20 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py @@ -0,0 +1,15 @@ +from cryptography.fernet import Fernet + +def test1(): + key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} + + def dec(ciphertext): + f = Fernet(key) + return f.decrypt(ciphertext) + +def test2(): + key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} + + def dec(ciphertext, time): + f = Fernet(key) + return f.decrypt_at_time(ciphertext, time) diff --git a/python/src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py b/python/src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py new file mode 100644 index 000000000..e7b3eeb58 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py @@ -0,0 +1,16 @@ +from cryptography.fernet import Fernet + +def test1(): + key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} + + def enc(data): + f = Fernet(key) + return f.encrypt(data) + +def test2(): + key = Fernet.generate_key() # Noncompliant {{(SecretKey) Fernet}} + + def enc(data, time): + f = Fernet(key) + return f.encrypt_at_time(data, time) + diff --git a/python/src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py b/python/src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py new file mode 100644 index 000000000..669b8f0d4 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py @@ -0,0 +1,19 @@ +from cryptography.fernet import Fernet, MultiFernet + +key1 = Fernet(Fernet.generate_key()) # Noncompliant {{(SecretKey) Fernet}} +key2 = Fernet(Fernet.generate_key()) # Noncompliant {{(SecretKey) Fernet}} + +def enc(data): + return MultiFernet([key1, key2]).encrypt(data) + +def dec(data): + return MultiFernet([key1, key2]).decrypt(data) + +# TODO: When using the following code instead, the depending `encrypt` and `decrypt` are detected twice because the type resolution of `f` does not succeed (so it could be Fernet as well as MultiFernet) + +# f = MultiFernet([key1, key2]) +# def enc(data): +# return f.encrypt(data) + +# def dec(data): +# return f.decrypt(data) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py b/python/src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py new file mode 100644 index 000000000..3b6afb9c1 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py @@ -0,0 +1,5 @@ +from cryptography.hazmat.primitives import hashes + +sha256_obj = hashes.Hash(hashes.SHA256()) # Noncompliant {{(MessageDigest) SHA-256}} +sha256_obj.update(b"data") +digest = sha256_obj.finalize() diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py new file mode 100644 index 000000000..bdd95889d --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py @@ -0,0 +1,15 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC + +salt = os.urandom(16) +otherinfo = b"concatkdf-example" + +ckdf = ConcatKDFHMAC( # Noncompliant {{(KeyDerivationFunction) ConcatenationKDF}} + algorithm=hashes.SHA256(), + length=32, + salt=salt, + otherinfo=otherinfo, +) + +key = ckdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py new file mode 100644 index 000000000..f4be827c0 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py @@ -0,0 +1,13 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash + +otherinfo = b"concatkdf-example" + +ckdf = ConcatKDFHash( # Noncompliant {{(KeyDerivationFunction) ConcatenationKDF}} + algorithm=hashes.SHA256(), + length=64, + otherinfo=otherinfo, +) + +key = ckdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py new file mode 100644 index 000000000..081088184 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py @@ -0,0 +1,14 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand + +info = b"hkdf-example" +key_material = os.urandom(16) + +hkdf = HKDFExpand( # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + algorithm=hashes.SHA256(), + length=32, + info=info, +) + +key = hkdf.derive(key_material) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py new file mode 100644 index 000000000..eb0a673b0 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py @@ -0,0 +1,15 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + +salt = os.urandom(16) +info = b"hkdf-example" + +hkdf = HKDF( # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + algorithm=hashes.SHA256(), + length=32, + salt=salt, + info=info, +) + +key = hkdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py new file mode 100644 index 000000000..abd8f7afb --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py @@ -0,0 +1,21 @@ +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFCMAC, Mode +) + +label = b"KBKDF CMAC Label" +context = b"KBKDF CMAC Context" + +kdf = KBKDFCMAC( # Noncompliant {{(Mac) CMAC-AES}} + algorithm=algorithms.AES, + mode=Mode.CounterMode, + length=32, + rlen=4, + llen=4, + location=CounterLocation.BeforeFixed, + label=label, + context=context, + fixed=None, +) + +key = kdf.derive(b"32 bytes long input key material") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py new file mode 100644 index 000000000..f29af21d6 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py @@ -0,0 +1,22 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) + +label = b"KBKDF HMAC Label" +context = b"KBKDF HMAC Context" + +kdf = KBKDFHMAC( # Noncompliant {{(Mac) HMAC-SHA-256}} + algorithm=hashes.SHA256(), + mode=Mode.CounterMode, + length=32, + rlen=4, + llen=4, + location=CounterLocation.BeforeFixed, + label=label, + context=context, + fixed=None, +) + +key = kdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py new file mode 100644 index 000000000..6baeb886c --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py @@ -0,0 +1,16 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +# Salts should be randomly generated +salt = os.urandom(16) + +# derive +kdf = PBKDF2HMAC( # Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-256}} + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=480000, +) + +key = kdf.derive(b"my great password") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py new file mode 100644 index 000000000..42c5ad086 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py @@ -0,0 +1,15 @@ +import os +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + +salt = os.urandom(16) + +# derive +kdf = Scrypt( # Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} + salt=salt, + length=32, + n=2**14, + r=8, + p=1, +) + +key = kdf.derive(b"my great password") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py new file mode 100644 index 000000000..10c0f51c7 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py @@ -0,0 +1,13 @@ +import os +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +sharedinfo = b"ANSI-KDF-X9.63 Example" + +xkdf = X963KDF( # Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.63}} + algorithm=hashes.SHA256(), + length=32, + sharedinfo=sharedinfo, +) + +key = xkdf.derive(b"input key") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py b/python/src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py new file mode 100644 index 000000000..f3212f37a --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py @@ -0,0 +1,38 @@ +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey +from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey + +# Generate a private key for use in the exchange. +private_key = X25519PrivateKey.generate() # Noncompliant {{(KeyAgreement) x25519}} +# In a real handshake the peer_public_key will be received from the +# other party. For this example we'll generate another private key and +# get a public key from that. Note that in a DH handshake both peers +# must agree on a common set of parameters. +peer_public_key = X25519PrivateKey.generate().public_key() # Noncompliant {{(KeyAgreement) x25519}} +shared_key = private_key.exchange(peer_public_key) + +# Generate a private key for use in the exchange. +private_key = X448PrivateKey.generate() # Noncompliant {{(KeyAgreement) x448}} +# In a real handshake the peer_public_key will be received from the +# other party. For this example we'll generate another private key and +# get a public key from that. Note that in a DH handshake both peers +# must agree on a common set of parameters. +peer_public_key = X448PrivateKey.generate().public_key() # Noncompliant {{(KeyAgreement) x448}} +shared_key = private_key.exchange(peer_public_key) + +# False positives that should NOT be detected (PR-429 fix) +# These are unrelated generate() methods with parameters +class DataGenerator: + def generate(self, **kwargs): + return [1, 2, 3] + +class ModelGenerator: + def generate(self, *args): + return "generated" + +data_gen = DataGenerator() +model_gen = ModelGenerator() + +# These should NOT trigger detection (not cryptography-related) +result1 = data_gen.generate(**{"size": 100}) +result2 = model_gen.generate("prompt1", "prompt2") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py new file mode 100644 index 000000000..1d86fa264 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py @@ -0,0 +1,25 @@ +from cryptography.hazmat.primitives import cmac +from cryptography.hazmat.primitives.ciphers import algorithms + +def generate_cmac(key, data): + # Selecting the desired algorithm (e.g., CMAC-AES) + algorithm = algorithms.AES(key) + + # Creating the CMAC context + cmac_ctx = cmac.CMAC(algorithm) # Noncompliant {{(Mac) CMAC-AES}} + + # Updating the context with the data + cmac_ctx.update(data) + + # Finalizing the CMAC computation and getting the CMAC value + cmac_value = cmac_ctx.finalize() + + return cmac_value + +# Example usage +if __name__ == "__main__": + key = b'Sixteen byte key' # 16-byte key for AES + data = b'This is some data' # Data to generate CMAC for + + cmac_value = generate_cmac(key, data) + print("Generated CMAC:", cmac_value.hex()) diff --git a/python/src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py new file mode 100644 index 000000000..70490920a --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py @@ -0,0 +1,25 @@ +from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import hashes + +def generate_hmac(key, data): + # Selecting the desired hash algorithm (e.g., SHA-256) + algorithm = hashes.SHA256() + + # Creating the HMAC context + hmac_ctx = hmac.HMAC(key, algorithm) # Noncompliant {{(Mac) HMAC-SHA-256}} + + # Updating the context with the data + hmac_ctx.update(data) + + # Finalizing the HMAC computation and getting the HMAC value + hmac_value = hmac_ctx.finalize() + + return hmac_value + +# Example usage +if __name__ == "__main__": + key = b'SecretKey123' # Key for HMAC + data = b'This is some data' # Data to generate HMAC for + + hmac_value = generate_hmac(key, data) + print("Generated HMAC:", hmac_value.hex()) diff --git a/python/src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py new file mode 100644 index 000000000..ec0a20fc1 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py @@ -0,0 +1,27 @@ +from cryptography.hazmat.primitives import hmac +from cryptography.hazmat.primitives import hashes + +def custom_sign(key, data): + # Custom function with cryptographic operation + algorithm = hashes.SHA256() + hmac_obj = hmac.HMAC(key, algorithm) # Noncompliant {{(Mac) HMAC-SHA-256}} + hmac_obj.update(data) + return hmac_obj.finalize() + +def non_crypto_function(text): + # Non-cryptographic function - should not trigger detection + result = "not crypto: " + text + return result.upper() + +# Example usage +if __name__ == "__main__": + key = b'SecretKey123' + data = b'This is some data' + + # Cryptographic operation in custom function is detected + result = custom_sign(key, data) + print("HMAC Result:", result.hex()) + + # Non-cryptographic function call does not trigger detection + text_result = non_crypto_function("hello") + print("Text Result:", text_result) diff --git a/python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py new file mode 100644 index 000000000..2fbaec802 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py @@ -0,0 +1,21 @@ +from cryptography.hazmat.primitives.poly1305 import Poly1305 + +def generate_poly1305(key, data): + # Create a Poly1305 context with the given key + poly1305_ctx = Poly1305(key) # Noncompliant {{(Mac) Poly1305}} + + # Update the context with the data + poly1305_ctx.update(data) + + # Finalize the Poly1305 computation and get the authentication tag + poly1305_tag = poly1305_ctx.finalize() + + return poly1305_tag + +# Example usage +if __name__ == "__main__": + key = b'Sixteen byte key' # 16-byte key for Poly1305 + data = b'This is some data' # Data to generate Poly1305 tag for + + poly1305_tag = generate_poly1305(key, data) + print("Generated Poly1305 Tag:", poly1305_tag.hex()) diff --git a/python/src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py b/python/src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py new file mode 100644 index 000000000..7485f8b52 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py @@ -0,0 +1,16 @@ +import os +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives import padding + +key = os.urandom(32) +iv = os.urandom(16) +# Create a cipher object +cipher = Cipher(algorithms.CAST5(key), modes.CFB(iv)) # Noncompliant {{(BlockCipher) CAST5-CFB}} + +padder = padding.ANSIX923(128).padder() +padded_data = padder.update(b"a secret message") +print(padded_data) +padded_data += padder.finalize() +print(padded_data) + +# Then, one could use the cipher to encrypt the padded data \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py new file mode 100644 index 000000000..b95d70dac --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py @@ -0,0 +1,21 @@ +import os +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.padding import PKCS7 + +key = os.urandom(32) +iv = os.urandom(16) +# Create a cipher object +cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) # Noncompliant {{(BlockCipher) AES-CBC-PKCS7}} + +# Specify padding (PKCS7 in this case) +padder = PKCS7(algorithms.AES.block_size).padder() + +# Encrypt +encryptor = cipher.encryptor() +padded_data = padder.update(b"a secret message") + padder.finalize() +ct = encryptor.update(padded_data) + encryptor.finalize() + +# Decrypt +decryptor = cipher.decryptor() +padded_res = decryptor.update(ct) + decryptor.finalize() +unpadded_res = padder.update(padded_res) + padder.finalize() \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py new file mode 100644 index 000000000..3372719a6 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py @@ -0,0 +1,12 @@ +import os +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.padding import PKCS7 + +key = os.urandom(32) +iv = os.urandom(16) +# Create a cipher object +cipher = Cipher(algorithms.Camellia(key), modes.OFB(iv)) # Noncompliant {{(BlockCipher) CAMELLIA-OFB}} + +# Encrypt +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py new file mode 100644 index 000000000..a1ff78550 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py @@ -0,0 +1,11 @@ +import os +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +key = os.urandom(32) +iv = os.urandom(16) +# Create a cipher object +cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None) # Noncompliant {{(StreamCipher) ChaCha20}} + +# Encrypt +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py b/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py new file mode 100644 index 000000000..f4b3093e4 --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py @@ -0,0 +1,25 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.keywrap import aes_key_wrap, aes_key_unwrap + +def aes_key_wrap_example(): + # Generate a key to wrap + key_to_wrap = b'Sixteen byte key' + + # Generate wrapping key (must be 128, 192, or 256 bits long) + wrapping_key = b'ABCDEFGHIJKLMNOP' + + # Wrap the key + wrapped_key = aes_key_wrap(wrapping_key, key_to_wrap, default_backend()) # Noncompliant {{(KeyWrap) AES-128}} + + print("Wrapped Key:", wrapped_key.hex()) + + # Unwrap the key + unwrapped_key = aes_key_unwrap(wrapping_key, wrapped_key, default_backend()) + + print("Unwrapped Key:", unwrapped_key.hex()) + + # Ensure that the unwrapped key matches the original key + assert unwrapped_key == key_to_wrap + +if __name__ == "__main__": + aes_key_wrap_example() diff --git a/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py b/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py new file mode 100644 index 000000000..62580615e --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py @@ -0,0 +1,25 @@ +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.keywrap import aes_key_wrap_with_padding, aes_key_unwrap_with_padding + +def aes_key_wrap_example(): + # Generate a key to wrap + key_to_wrap = b'Sixteen byte key' + + # Generate wrapping key (must be 128, 192, or 256 bits long) + wrapping_key = b'ABCDEFGHIJKLMNOP' + + # Wrap the key + wrapped_key = aes_key_wrap_with_padding(wrapping_key, key_to_wrap, default_backend()) # Noncompliant {{(KeyWrap) AES-128}} + + print("Wrapped Key:", wrapped_key.hex()) + + # Unwrap the key + unwrapped_key = aes_key_unwrap_with_padding(wrapping_key, wrapped_key, default_backend()) + + print("Unwrapped Key:", unwrapped_key.hex()) + + # Ensure that the unwrapped key matches the original key + assert unwrapped_key == key_to_wrap + +if __name__ == "__main__": + aes_key_wrap_example() diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedAllKeywordParametersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedAllKeywordParametersTest.java new file mode 100644 index 000000000..dc4b42d51 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedAllKeywordParametersTest.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.named_parameters; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.AlgorithmParameter; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.AlgorithmParameterFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class NamedAllKeywordParametersTest extends TestBase { + private int findingCount; + + NamedAllKeywordParametersTest() { + super( + List.of( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("test.module.Foo") + .forMethods("f") + .shouldBeDetectedAs(new ValueActionFactory<>("f")) + .withNamedMethodParameter("a", "str") + .withNamedMethodParameter("b", "int") + .withOptionalNamedMethodParameter("c", "str") + .shouldBeDetectedAs( + new AlgorithmParameterFactory<>( + AlgorithmParameter.Kind.ANY)) + .asChildOfParameterWithId(2) + .buildForContext(new DigestContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules())); + } + + @Test + void namedDeclarationsAcceptAllKeywords() { + PythonCheckVerifier.verifyNoIssue( + "src/test/files/rules/detection/named_parameters/FAllByKeywordTest.py", this); + assertThat(findingCount).isEqualTo(1); + } + + @Test + void namedDeclarationsStillAcceptAllPositionalArguments() { + PythonCheckVerifier.verifyNoIssue( + "src/test/files/rules/detection/named_parameters/FAllPositionalTest.py", this); + assertThat(findingCount).isEqualTo(1); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + findingCount++; + assertThat(detectionStore.getActionValue()).containsInstanceOf(ValueAction.class); + DetectionStore child = + getStoreOfValueType(AlgorithmParameter.class, detectionStore.getChildren()); + assertThat(child).isNotNull(); + assertThat(child.getDetectionValues()).extracting(IValue::asString).containsExactly("yes"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParameterWildcardTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParameterWildcardTest.java new file mode 100644 index 000000000..7eccd762b --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParameterWildcardTest.java @@ -0,0 +1,71 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.named_parameters; + +import static com.ibm.engine.detection.MethodMatcher.ANY; +import static org.assertj.core.api.Assertions.fail; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class NamedParameterWildcardTest extends TestBase { + + NamedParameterWildcardTest() { + super( + List.of( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("test.module.Foo") + .forMethods("f") + .shouldBeDetectedAs(new ValueActionFactory<>("f")) + .withMethodParameter(ANY) + .withNamedMethodParameter("b", "int") + .buildForContext(new DigestContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules())); + } + + @Test + void keywordForNamedParameterCannotFillPositionalSlot() { + PythonCheckVerifier.verifyNoIssue( + "src/test/files/rules/detection/named_parameters/FNegativePositionalSlotClaimedTest.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + fail("A missing positional argument must not produce a finding"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParametersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParametersTest.java new file mode 100644 index 000000000..63ebf024e --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/named_parameters/NamedParametersTest.java @@ -0,0 +1,206 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.named_parameters; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.AlgorithmParameter; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.AlgorithmParameterFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +/** + * Tests positional {@code a}, required named {@code b}, and optional named {@code c} in {@code + * Foo.f}. Parameter {@code a} must be passed positionally; the all-named variant is tested + * separately. The rule captures {@code c} as a child, and rejected calls must produce no finding, + * not just no Sonar issue. + */ +class NamedParametersTest extends TestBase { + + private static final String BASE_PATH = "src/test/files/rules/detection/named_parameters/"; + + private static final IDetectionRule RULE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("test.module.Foo") + .forMethods("f") + .shouldBeDetectedAs(new ValueActionFactory<>("f")) + .withMethodParameter("str") + .withNamedMethodParameter("b", "int") + .withOptionalNamedMethodParameter("c", "str") + .shouldBeDetectedAs( + new AlgorithmParameterFactory<>(AlgorithmParameter.Kind.ANY)) + .asChildOfParameterWithId(2) + .buildForContext(new DigestContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules(); + + private boolean expectCChild = false; + + private int findingCount = 0; + + NamedParametersTest() { + super(List.of(RULE)); + } + + @Test + void testAllPositional() { + expectCChild = true; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FAllPositionalTest.py", this); + } + + @Test + void testCByKeyword() { + expectCChild = true; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FCByKeywordTest.py", this); + } + + @Test + void testBCByKeywordCanonicalOrder() { + expectCChild = true; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FBCByKeywordCanonicalOrderTest.py", this); + } + + @Test + void testBCByKeywordReordered() { + expectCChild = true; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FBCByKeywordReorderedTest.py", this); + } + + @Test + void rejectsKeywordForPositionalParameter() { + verifyNoDetection("FAllByKeywordTest.py"); + } + + @Test + void testExtraUnknownKwarg() { + expectCChild = true; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FExtraUnknownKwargTest.py", this); + } + + @Test + void testCAbsentPositional() { + expectCChild = false; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FCAbsentPositionalTest.py", this); + } + + @Test + void testCAbsentBByKeyword() { + expectCChild = false; + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FCAbsentBByKeywordTest.py", this); + } + + @Test + void rejectsKeywordForPositionalParameterWhenOptionalIsAbsent() { + verifyNoDetection("FCAbsentAllKeywordTest.py"); + } + + @Test + void testOptionalWrongTypeDoesNotCaptureChild() { + PythonCheckVerifier.verifyNoIssue(BASE_PATH + "FOptionalWrongTypeTest.py", this); + assertThat(findingCount).isEqualTo(1); + } + + @Test + void testNegativeBAbsent() { + verifyNoDetection("FNegativeBAbsentTest.py"); + } + + @Test + void testNegativeWrongTypeA() { + verifyNoDetection("FNegativeWrongTypeATest.py"); + } + + @Test + void testNegativeWrongTypeB() { + verifyNoDetection("FNegativeWrongTypeBTest.py"); + } + + @Test + void testNegativeWrongTypeBByKeyword() { + verifyNoDetection("FNegativeWrongTypeBByKeywordTest.py"); + } + + @Test + void testNegativeAAbsent() { + verifyNoDetection("FNegativeAAbsentTest.py"); + } + + @Test + void testNegativeUnrelatedKeywordCannotFillPositionalSlot() { + verifyNoDetection("FNegativeUnrelatedKeywordTest.py"); + } + + @Test + void testNegativeDictUnpack() { + verifyNoDetection("FNegativeDictUnpackTest.py"); + } + + private void verifyNoDetection(String filename) { + PythonCheckVerifier.verifyNoIssue(BASE_PATH + filename, this); + assertThat(findingCount).as("findings for " + filename).isZero(); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + findingCount++; + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + + IValue rootValue = detectionStore.getDetectionValues().get(0); + assertThat(rootValue).isInstanceOf(ValueAction.class); + assertThat(rootValue.asString()).isEqualTo("f"); + + DetectionStore cStore = + getStoreOfValueType(AlgorithmParameter.class, detectionStore.getChildren()); + if (expectCChild) { + assertThat(cStore) + .as("expected a child detection store for c but found none") + .isNotNull(); + assertThat(cStore.getDetectionValues()).hasSize(1); + IValue cValue = cStore.getDetectionValues().get(0); + assertThat(cValue).isInstanceOf(AlgorithmParameter.class); + assertThat(cValue.asString()).isEqualTo("yes"); + } else { + assertThat(cStore) + .as("expected no child detection store for c but one was produced") + .isNull(); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java new file mode 100644 index 000000000..7811c3317 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java @@ -0,0 +1,148 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.aead; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.SecretKeyContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaAESGCMTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(SecretKeyContext.class); + assertThat(value).isInstanceOf(KeySize.class); + assertThat(value.asString()).isEqualTo("128"); + + assertThat(detectionStore.getChildren()).hasSize(2); + + DetectionStore store = + detectionStore.getChildren().get(0); + IValue decryptValue = store.getDetectionValues().get(0); + assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(decryptValue).isInstanceOf(CipherAction.class); + assertThat(decryptValue.asString()).isEqualTo("DECRYPT"); + + store = detectionStore.getChildren().get(1); + IValue encryptValue = store.getDetectionValues().get(0); + assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(encryptValue).isInstanceOf(CipherAction.class); + assertThat(encryptValue.asString()).isEqualTo("ENCRYPT"); + + /* + * Translation + */ + + assertThat(nodes).hasSize(1); + + // SecretKey + INode secretKeyNode = nodes.get(0); + assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); + assertThat(secretKeyNode.getChildren()).hasSize(4); + assertThat(secretKeyNode.asString()).isEqualTo("AES"); + + // AuthenticatedEncryption under SecretKey + INode authenticatedEncryptionNode = + secretKeyNode.getChildren().get(AuthenticatedEncryption.class); + assertThat(authenticatedEncryptionNode).isNotNull(); + assertThat(authenticatedEncryptionNode.getChildren()).hasSize(4); + assertThat(authenticatedEncryptionNode.asString()).isEqualTo("AES-128-GCM"); + + // Mode under AuthenticatedEncryption under SecretKey + INode modeNode = authenticatedEncryptionNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("GCM"); + + // BlockSize under AuthenticatedEncryption under SecretKey + INode blockSizeNode = authenticatedEncryptionNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("128"); + + // Oid under AuthenticatedEncryption under SecretKey + INode oidNode = authenticatedEncryptionNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1.6"); + + // KeyLength under AuthenticatedEncryption under SecretKey + INode keyLengthNode = authenticatedEncryptionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("128"); + + // Encrypt under SecretKey + INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + + // Decrypt under SecretKey + INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); + assertThat(decryptNode).isNotNull(); + assertThat(decryptNode.getChildren()).isEmpty(); + assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); + + // Generate under SecretKey + INode generateNode = secretKeyNode.getChildren().get(KeyGeneration.class); + assertThat(generateNode).isNotNull(); + assertThat(generateNode.getChildren()).isEmpty(); + assertThat(generateNode.asString()).isEqualTo("KEYGENERATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaChaCha20Poly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaChaCha20Poly1305Test.java new file mode 100644 index 000000000..88c1b01f4 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaChaCha20Poly1305Test.java @@ -0,0 +1,135 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.aead; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.SecretKeyContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaChaCha20Poly1305Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(SecretKeyContext.class); + assertThat(value).isInstanceOf(KeyAction.class); + assertThat(value.asString()).isEqualTo("GENERATION"); + + assertThat(detectionStore.getChildren()).hasSize(2); + + DetectionStore store = + detectionStore.getChildren().get(0); + IValue decryptValue = store.getDetectionValues().get(0); + assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(decryptValue).isInstanceOf(CipherAction.class); + assertThat(decryptValue.asString()).isEqualTo("ENCRYPT"); + + store = detectionStore.getChildren().get(1); + IValue encryptValue = store.getDetectionValues().get(0); + assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(encryptValue).isInstanceOf(CipherAction.class); + assertThat(encryptValue.asString()).isEqualTo("DECRYPT"); + + /* + * Translation + */ + + assertThat(nodes).hasSize(1); + + // SecretKey + INode secretKeyNode = nodes.get(0); + assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); + assertThat(secretKeyNode.getChildren()).hasSize(4); + assertThat(secretKeyNode.asString()).isEqualTo("ChaCha20"); + + // Encrypt under SecretKey + INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + + // Decrypt under SecretKey + INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); + assertThat(decryptNode).isNotNull(); + assertThat(decryptNode.getChildren()).isEmpty(); + assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); + + // KeyGeneration under SecretKey + INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // AuthenticatedEncryption under SecretKey + INode authenticatedEncryptionNode = + secretKeyNode.getChildren().get(AuthenticatedEncryption.class); + assertThat(authenticatedEncryptionNode).isNotNull(); + assertThat(authenticatedEncryptionNode.getChildren()).hasSize(1); + assertThat(authenticatedEncryptionNode.asString()).isEqualTo("ChaCha20-Poly1305"); + + // MessageDigest under AuthenticatedEncryption under SecretKey + INode messageDigestNode = + authenticatedEncryptionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(1); + assertThat(messageDigestNode.asString()).isEqualTo("Poly1305"); + + // Digest under MessageDigest under AuthenticatedEncryption under SecretKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTest.java new file mode 100644 index 000000000..beebbd7c6 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTest.java @@ -0,0 +1,140 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.DSA; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaDSANumbersTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + if (findingId == 0) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PublicKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PublicKey + INode publicKeyNode = nodes.get(0); + assertThat(publicKeyNode.getKind()).isEqualTo(PublicKey.class); + assertThat(publicKeyNode.getChildren()).hasSize(2); + assertThat(publicKeyNode.asString()).isEqualTo("DSA"); + + // Signature under PublicKey + INode signatureNode = publicKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(1); + assertThat(signatureNode.asString()).isEqualTo("DSA"); + + // Oid under Signature under PublicKey + INode oidNode = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.10040.4.1"); + + // KeyGeneration under PublicKey + INode keyGenerationNode = publicKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } else { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("DSA"); + + // Signature under PrivateKey + INode signatureNode = privateKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(1); + assertThat(signatureNode.asString()).isEqualTo("DSA"); + + // Oid under Signature under PrivateKey + INode oidNode = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.10040.4.1"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSASignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSASignTest.java new file mode 100644 index 000000000..1c4cfcafe --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSASignTest.java @@ -0,0 +1,156 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.DSA; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaDSASignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeySize.class); + assertThat(value0.asString()).isEqualTo("1024"); + + DetectionStore store_1 = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(SignatureAction.class); + assertThat(value0_1.asString()).isEqualTo("SIGN"); + + DetectionStore store_1_1 = + getStoreOfValueType(ValueAction.class, store_1.getChildren()); + assertThat(store_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(DigestContext.class); + IValue value0_1_1 = store_1_1.getDetectionValues().get(0); + assertThat(value0_1_1).isInstanceOf(ValueAction.class); + assertThat(value0_1_1.asString()).isEqualTo("SHA256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(4); + assertThat(privateKeyNode.asString()).isEqualTo("DSA"); + + // Signature under PrivateKey + INode signatureNode = privateKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(2); + assertThat(signatureNode.asString()).isEqualTo("DSA-SHA-256"); + + // Oid under Signature under PrivateKey + INode oidNode = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.3.2"); + + // MessageDigest under Signature under PrivateKey + INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // BlockSize under MessageDigest under Signature under PrivateKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Oid under MessageDigest under Signature under PrivateKey + INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under Signature under PrivateKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Digest under MessageDigest under Signature under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // Sign under PrivateKey + INode signNode = privateKeyNode.getChildren().get(Sign.class); + assertThat(signNode).isNotNull(); + assertThat(signNode.getChildren()).isEmpty(); + assertThat(signNode.asString()).isEqualTo("SIGN"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java new file mode 100644 index 000000000..4097dcf75 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java @@ -0,0 +1,99 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.DiffieHellman; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public final class PycaDiffieHellmanGenerateTest extends TestBase { + + // The key size does not yet appear because + // of the TraceSymbol problem documented on the Github issue + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("FFDH"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // PublicKeyEncryption under PrivateKey + INode publicKeyEncryptionNode = privateKeyNode.getChildren().get(PublicKeyEncryption.class); + assertThat(publicKeyEncryptionNode).isNotNull(); + assertThat(publicKeyEncryptionNode.getChildren()).hasSize(1); + assertThat(publicKeyEncryptionNode.asString()).isEqualTo("FFDH"); + + // Oid under PublicKeyEncryption under PrivateKey + INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.3.1"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java new file mode 100644 index 000000000..26099a4a9 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java @@ -0,0 +1,96 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.DiffieHellman; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaDiffieHellmanNumbersTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PublicKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PublicKey + INode publicKeyNode1 = nodes.get(0); + assertThat(publicKeyNode1.getKind()).isEqualTo(PublicKey.class); + assertThat(publicKeyNode1.getChildren()).hasSize(2); + assertThat(publicKeyNode1.asString()).isEqualTo("FFDH"); + + // PublicKeyEncryption under PublicKey + INode publicKeyEncryptionNode1 = + publicKeyNode1.getChildren().get(PublicKeyEncryption.class); + assertThat(publicKeyEncryptionNode1).isNotNull(); + assertThat(publicKeyEncryptionNode1.getChildren()).hasSize(1); + assertThat(publicKeyEncryptionNode1.asString()).isEqualTo("FFDH"); + + // Oid under PublicKeyEncryption under PublicKey + INode oidNode1 = publicKeyEncryptionNode1.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.1.3.1"); + + // KeyGeneration under PublicKey + INode keyGenerationNode1 = publicKeyNode1.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode1).isNotNull(); + assertThat(keyGenerationNode1.getChildren()).isEmpty(); + assertThat(keyGenerationNode1.asString()).isEqualTo("KEYGENERATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java new file mode 100644 index 000000000..d34d4fa91 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java @@ -0,0 +1,103 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaEllipticCurveDeriveTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); + assertThat(value).isInstanceOf(Curve.class); + assertThat(value.asString()).isEqualTo("SECP256R1"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("EC-secp256r1"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // PublicKeyEncryption under PrivateKey + INode publicKeyEncryptionNode = privateKeyNode.getChildren().get(PublicKeyEncryption.class); + assertThat(publicKeyEncryptionNode).isNotNull(); + assertThat(publicKeyEncryptionNode.getChildren()).hasSize(2); + assertThat(publicKeyEncryptionNode.asString()).isEqualTo("EC-secp256r1"); + + // EllipticCurve under PublicKeyEncryption under PrivateKey + INode ellipticCurveNode = publicKeyEncryptionNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("secp256r1"); + + // Oid under PublicKeyEncryption under PrivateKey + INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.10045.2.1"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java new file mode 100644 index 000000000..c073991dc --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java @@ -0,0 +1,206 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaEllipticCurveKeyExchangeTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 0) { + /* + * Detection Store + */ + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Curve.class); + assertThat(value0.asString()).isEqualTo("SECP384R1"); + + DetectionStore store_1 = + getStoreOfValueType(Algorithm.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(KeyAgreementContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(Algorithm.class); + assertThat(value0_1.asString()).isEqualTo("ECDH"); + + /* + * Translation + */ + + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("EC-secp384r1"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // KeyAgreement under PrivateKey + INode keyAgreementNode = privateKeyNode.getChildren().get(KeyAgreement.class); + assertThat(keyAgreementNode).isNotNull(); + assertThat(keyAgreementNode.getChildren()).hasSize(3); + assertThat(keyAgreementNode.asString()).isEqualTo("ECDH"); + + // EllipticCurve under KeyAgreement under PrivateKey + INode ellipticCurveNode = keyAgreementNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("secp384r1"); + + // Oid under KeyAgreement under PrivateKey + INode oidNode = keyAgreementNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.3.132.1.12"); + + // KeyGeneration under PrivateKey + keyGenerationNode = keyAgreementNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } else if (findingId == 1) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyDerivationFunction + INode keyDerivationFunctionNode = nodes.get(0); + assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); + assertThat(keyDerivationFunctionNode.asString()).isEqualTo("HKDF-SHA-256"); + + // KeyDerivation under KeyDerivationFunction + INode keyDerivationNode = + keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // KeyLength under KeyDerivationFunction + INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + + // MessageDigest under KeyDerivationFunction + INode messageDigestNode = + keyDerivationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Digest under MessageDigest under KeyDerivationFunction + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under KeyDerivationFunction + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Oid under MessageDigest under KeyDerivationFunction + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under KeyDerivationFunction + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java new file mode 100644 index 000000000..9c1f7d79c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java @@ -0,0 +1,52 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.Ignore; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaEllipticCurveNumbersTest extends TestBase { + + @Ignore("In this testcase the name of a var is resolved, but not teh actual value.") + @Test + void test() { + PythonCheckVerifier.verifyNoIssue( + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + // TODO: + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java new file mode 100644 index 000000000..84e4928eb --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java @@ -0,0 +1,185 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaEllipticCurveSign2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 0) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("Ed25519"); + + // Signature under PrivateKey + INode signatureNode = privateKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(3); + assertThat(signatureNode.asString()).isEqualTo("Ed25519"); + + // MessageDigest under Signature under PrivateKey + INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-512"); + + // Oid under MessageDigest under Signature under PrivateKey + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); + + // DigestSize under MessageDigest under Signature under PrivateKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("512"); + + // Digest under MessageDigest under Signature under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Signature under PrivateKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("1024"); + + // Oid under Signature under PrivateKey + INode oidNode1 = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.3.101.112"); + + // EllipticCurve under Signature under PrivateKey + INode ellipticCurveNode = signatureNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("Edwards25519"); + } else if (findingId == 1) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("Ed448"); + + // Signature under PrivateKey + INode signatureNode = privateKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(3); + assertThat(signatureNode.asString()).isEqualTo("Ed448"); + + // MessageDigest under Signature under PrivateKey + INode messageDigestNode = + signatureNode.getChildren().get(ExtendableOutputFunction.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(2); + assertThat(messageDigestNode.asString()).isEqualTo("SHAKE256"); + + // Digest under MessageDigest under Signature under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Oid under Signature under PrivateKey + INode oidNode = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.3.101.113"); + + // EllipticCurve under Signature under PrivateKey + INode ellipticCurveNode = signatureNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("Edwards448"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java new file mode 100644 index 000000000..e70a1c3df --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java @@ -0,0 +1,158 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaEllipticCurveSignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Curve.class); + assertThat(value0.asString()).isEqualTo("SECP384R1"); + + DetectionStore store_1 = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(2); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(SignatureAction.class); + assertThat(value0_1.asString()).isEqualTo("SIGN"); + + IValue value1_1 = store_1.getDetectionValues().get(1); + assertThat(value1_1).isInstanceOf(Algorithm.class); + assertThat(value1_1.asString()).isEqualTo("ECDSA"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(3); + assertThat(privateKeyNode.asString()).isEqualTo("EC-secp384r1"); + + // Signature under PrivateKey + INode signatureNode = privateKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(3); + assertThat(signatureNode.asString()).isEqualTo("ECDSA-secp384r1-SHA3-512"); + + // MessageDigest under Signature under PrivateKey + INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA3-512"); + + // BlockSize under MessageDigest under Signature under PrivateKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("576"); + + // Oid under MessageDigest under Signature under PrivateKey + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.10"); + + // DigestSize under MessageDigest under Signature under PrivateKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("512"); + + // Digest under MessageDigest under Signature under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Oid under Signature under PrivateKey + INode oidNode1 = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.3.12"); + + // EllipticCurve under Signature under PrivateKey + INode ellipticCurveNode = signatureNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("secp384r1"); + + // Sign under PrivateKey + INode signNode = privateKeyNode.getChildren().get(Sign.class); + assertThat(signNode).isNotNull(); + assertThat(signNode.getChildren()).isEmpty(); + assertThat(signNode.asString()).isEqualTo("SIGN"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java new file mode 100644 index 000000000..2941263c2 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java @@ -0,0 +1,55 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaEllipticCurveVerifyTest extends TestBase { + + // junit4 + @Disabled( + "Reenable once we have an approach to detect `verify` (either make it an entry\n" + + "point, or better handle file imports for depending detection rule)") + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + // TODO + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTest.java new file mode 100644 index 000000000..274f3bcd8 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTest.java @@ -0,0 +1,253 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MaskGenerationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaRSADecryptTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 0) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeySize.class); + assertThat(value0.asString()).isEqualTo("1024"); + + DetectionStore store_1 = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + assertThat(store_1).isNotNull(); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(CipherAction.class); + assertThat(value0_1.asString()).isEqualTo("DECRYPT"); + + DetectionStore store_1_1 = + getStoreOfValueType(ValueAction.class, store_1.getChildren()); + assertThat(store_1_1).isNotNull(); + assertThat(store_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1_1 = store_1_1.getDetectionValues().get(0); + assertThat(value0_1_1).isInstanceOf(ValueAction.class); + assertThat(value0_1_1.asString()).isEqualTo("OAEP"); + + List> stores = + getStoresOfValueType(ValueAction.class, store_1_1.getChildren()); + assertThat(stores).isNotNull(); + for (DetectionStore s : stores) { + assertThat(s.getDetectionValues()).hasSize(1); + assertThat(s.getDetectionValueContext()) + .isInstanceOfAny(SignatureContext.class, DigestContext.class); + IValue v = s.getDetectionValues().get(0); + assertThat(v).isInstanceOf(ValueAction.class); + assertThat(v.asString()) + .satisfiesAnyOf( + str -> assertThat(str).isEqualTo("MGF1"), + str -> assertThat(str).isEqualTo("SHA256")); + + if (s.getDetectionValueContext().is(SignatureContext.class)) { + DetectionStore store_1_1_1_1 = + getStoreOfValueType(ValueAction.class, s.getChildren()); + assertThat(store_1_1_1_1).isNotNull(); + assertThat(store_1_1_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1_1_1.getDetectionValueContext()) + .isInstanceOf(DigestContext.class); + IValue value0_1_1_1_1 = store_1_1_1_1.getDetectionValues().get(0); + assertThat(value0_1_1_1_1).isInstanceOf(ValueAction.class); + assertThat(value0_1_1_1_1.asString()).isEqualTo("SHA384"); + } + } + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(4); + assertThat(privateKeyNode.asString()).isEqualTo("RSA"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // PublicKeyEncryption under PrivateKey + INode pke = privateKeyNode.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren()).hasSize(2); + assertThat(pke.asString()).isEqualTo("RSA-OAEP"); + + // Oid under Signature under PrivateKey + INode oidNode = pke.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.1.7"); + + // Padding under Signature under PrivateKey + INode paddingNode = pke.getChildren().get(Padding.class); + assertThat(paddingNode).isNotNull(); + assertThat(paddingNode.getChildren()).hasSize(2); + assertThat(paddingNode.asString()).isEqualTo("OAEP"); + + // MessageDigest under Padding under Signature under PrivateKey + INode messageDigestNode = paddingNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Oid under MessageDigest under Padding under Signature under PrivateKey + INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // Digest under MessageDigest under Padding under Signature under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Padding under Signature under PrivateKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // DigestSize under MessageDigest under Padding under Signature under PrivateKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // MaskGenerationFunction under Padding under Signature under PrivateKey + INode maskGenerationFunctionNode = + paddingNode.getChildren().get(MaskGenerationFunction.class); + assertThat(maskGenerationFunctionNode).isNotNull(); + assertThat(maskGenerationFunctionNode.getChildren()).hasSize(2); + assertThat(maskGenerationFunctionNode.asString()).isEqualTo("MGF1"); + + // Oid under MaskGenerationFunction under Padding under Signature under PrivateKey + INode oidNode2 = maskGenerationFunctionNode.getChildren().get(Oid.class); + assertThat(oidNode2).isNotNull(); + assertThat(oidNode2.getChildren()).isEmpty(); + assertThat(oidNode2.asString()).isEqualTo("1.2.840.113549.1.1.8"); + + // MessageDigest under MaskGenerationFunction under Padding under Signature under + // PrivateKey + INode messageDigestNode1 = + maskGenerationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode1).isNotNull(); + assertThat(messageDigestNode1.getChildren()).hasSize(4); + assertThat(messageDigestNode1.asString()).isEqualTo("SHA-384"); + + // Oid under MessageDigest under MaskGenerationFunction under Padding under Signature + // under PrivateKey + INode oidNode3 = messageDigestNode1.getChildren().get(Oid.class); + assertThat(oidNode3).isNotNull(); + assertThat(oidNode3.getChildren()).isEmpty(); + assertThat(oidNode3.asString()).isEqualTo("2.16.840.1.101.3.4.2.2"); + + // Digest under MessageDigest under MaskGenerationFunction under Padding under Signature + // under PrivateKey + INode digestNode1 = messageDigestNode1.getChildren().get(Digest.class); + assertThat(digestNode1).isNotNull(); + assertThat(digestNode1.getChildren()).isEmpty(); + assertThat(digestNode1.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under MaskGenerationFunction under Padding under + // Signature under PrivateKey + INode blockSizeNode1 = messageDigestNode1.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("1024"); + + // DigestSize under MessageDigest under MaskGenerationFunction under Padding under + // Signature under PrivateKey + INode digestSizeNode1 = messageDigestNode1.getChildren().get(DigestSize.class); + assertThat(digestSizeNode1).isNotNull(); + assertThat(digestSizeNode1.getChildren()).isEmpty(); + assertThat(digestSizeNode1.asString()).isEqualTo("384"); + + // Decrypt under PrivateKey + INode decryptNode = privateKeyNode.getChildren().get(Decrypt.class); + assertThat(decryptNode).isNotNull(); + assertThat(decryptNode.getChildren()).isEmpty(); + assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); + + // KeyLength under PrivateKey + INode keyLengthNode = privateKeyNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("1024"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTest.java new file mode 100644 index 000000000..5321621ee --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTest.java @@ -0,0 +1,142 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaRSANumbersTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + if (findingId == 0) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PublicKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PublicKey + INode publicKeyNode = nodes.get(0); + assertThat(publicKeyNode.getKind()).isEqualTo(PublicKey.class); + assertThat(publicKeyNode.getChildren()).hasSize(2); + assertThat(publicKeyNode.asString()).isEqualTo("RSA"); + + // PublicKeyEncryption under PublicKey + INode publicKeyEncryptionNode = + publicKeyNode.getChildren().get(PublicKeyEncryption.class); + assertThat(publicKeyEncryptionNode).isNotNull(); + assertThat(publicKeyEncryptionNode.getChildren()).hasSize(1); + assertThat(publicKeyEncryptionNode.asString()).isEqualTo("RSA"); + + // Oid under PublicKeyEncryption under PublicKey + INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + // KeyGeneration under PublicKey + INode keyGenerationNode = publicKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } else if (findingId == 1) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(2); + assertThat(privateKeyNode.asString()).isEqualTo("RSA"); + + // PublicKeyEncryption under PrivateKey + INode publicKeyEncryptionNode = + privateKeyNode.getChildren().get(PublicKeyEncryption.class); + assertThat(publicKeyEncryptionNode).isNotNull(); + assertThat(publicKeyEncryptionNode.getChildren()).hasSize(1); + assertThat(publicKeyEncryptionNode.asString()).isEqualTo("RSA"); + + // Oid under PublicKeyEncryption under PrivateKey + INode oidNode = publicKeyEncryptionNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign1Test.java new file mode 100644 index 000000000..25c86fe9c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign1Test.java @@ -0,0 +1,231 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MaskGenerationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaRSASign1Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeySize.class); + assertThat(value0.asString()).isEqualTo("2048"); + + DetectionStore store_1 = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(SignatureAction.class); + assertThat(value0_1.asString()).isEqualTo("SIGN"); + + DetectionStore store_1_1 = + getStoreOfValueType(ValueAction.class, store_1.getChildren()); + assertThat(store_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue value0_1_1 = store_1_1.getDetectionValues().get(0); + assertThat(value0_1_1).isInstanceOf(ValueAction.class); + assertThat(value0_1_1.asString()).isEqualTo("RSA-PSS"); + + DetectionStore store_1_1_1 = + getStoreOfValueType(ValueAction.class, store_1_1.getChildren()); + assertThat(store_1_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue value0_1_1_1 = store_1_1_1.getDetectionValues().get(0); + assertThat(value0_1_1_1).isInstanceOf(ValueAction.class); + assertThat(value0_1_1_1.asString()).isEqualTo("MGF1"); + + DetectionStore store_1_1_1_1 = + getStoreOfValueType(ValueAction.class, store_1_1_1.getChildren()); + assertThat(store_1_1_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1_1_1.getDetectionValueContext()).isInstanceOf(DigestContext.class); + IValue value0_1_1_1_1 = store_1_1_1_1.getDetectionValues().get(0); + assertThat(value0_1_1_1_1).isInstanceOf(ValueAction.class); + assertThat(value0_1_1_1_1.asString()).isEqualTo("SHA256"); + + /* + * Translation + */ + + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(4); + assertThat(privateKeyNode.asString()).isEqualTo("RSA"); + + // Sign under PrivateKey + INode signNode = privateKeyNode.getChildren().get(Sign.class); + assertThat(signNode).isNotNull(); + assertThat(signNode.getChildren()).isEmpty(); + assertThat(signNode.asString()).isEqualTo("SIGN"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // ProbabilisticSignatureScheme under PrivateKey + INode probabilisticSignatureSchemeNode = + privateKeyNode.getChildren().get(ProbabilisticSignatureScheme.class); + assertThat(probabilisticSignatureSchemeNode).isNotNull(); + assertThat(probabilisticSignatureSchemeNode.getChildren()).hasSize(3); + assertThat(probabilisticSignatureSchemeNode.asString()).isEqualTo("RSA-PSS"); + + // MaskGenerationFunction under ProbabilisticSignatureScheme under PrivateKey + INode maskGenerationFunctionNode = + probabilisticSignatureSchemeNode.getChildren().get(MaskGenerationFunction.class); + assertThat(maskGenerationFunctionNode).isNotNull(); + assertThat(maskGenerationFunctionNode.getChildren()).hasSize(2); + assertThat(maskGenerationFunctionNode.asString()).isEqualTo("MGF1"); + + // MessageDigest under MaskGenerationFunction under ProbabilisticSignatureScheme under + // PrivateKey + INode messageDigestNode = maskGenerationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // BlockSize under MessageDigest under MaskGenerationFunction under + // ProbabilisticSignatureScheme under PrivateKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // DigestSize under MessageDigest under MaskGenerationFunction under + // ProbabilisticSignatureScheme under PrivateKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under MaskGenerationFunction under ProbabilisticSignatureScheme + // under PrivateKey + INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // Digest under MessageDigest under MaskGenerationFunction under + // ProbabilisticSignatureScheme under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Oid under MaskGenerationFunction under ProbabilisticSignatureScheme under PrivateKey + INode oidNode2 = maskGenerationFunctionNode.getChildren().get(Oid.class); + assertThat(oidNode2).isNotNull(); + assertThat(oidNode2.getChildren()).isEmpty(); + assertThat(oidNode2.asString()).isEqualTo("1.2.840.113549.1.1.8"); + + // MessageDigest under ProbabilisticSignatureScheme under PrivateKey + INode messageDigestNode1 = + probabilisticSignatureSchemeNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode1).isNotNull(); + assertThat(messageDigestNode1.getChildren()).hasSize(4); + assertThat(messageDigestNode1.asString()).isEqualTo("SHA-384"); + + // BlockSize under MessageDigest under ProbabilisticSignatureScheme under PrivateKey + INode blockSizeNode1 = messageDigestNode1.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("1024"); + + // DigestSize under MessageDigest under ProbabilisticSignatureScheme under PrivateKey + INode digestSizeNode1 = messageDigestNode1.getChildren().get(DigestSize.class); + assertThat(digestSizeNode1).isNotNull(); + assertThat(digestSizeNode1.getChildren()).isEmpty(); + assertThat(digestSizeNode1.asString()).isEqualTo("384"); + + // Oid under MessageDigest under ProbabilisticSignatureScheme under PrivateKey + INode oidNode3 = messageDigestNode1.getChildren().get(Oid.class); + assertThat(oidNode3).isNotNull(); + assertThat(oidNode3.getChildren()).isEmpty(); + assertThat(oidNode3.asString()).isEqualTo("2.16.840.1.101.3.4.2.2"); + + // Digest under MessageDigest under ProbabilisticSignatureScheme under PrivateKey + INode digestNode1 = messageDigestNode1.getChildren().get(Digest.class); + assertThat(digestNode1).isNotNull(); + assertThat(digestNode1.getChildren()).isEmpty(); + assertThat(digestNode1.asString()).isEqualTo("DIGEST"); + + // Oid under ProbabilisticSignatureScheme under PrivateKey + INode oidNode4 = probabilisticSignatureSchemeNode.getChildren().get(Oid.class); + assertThat(oidNode4).isNotNull(); + assertThat(oidNode4.getChildren()).isEmpty(); + assertThat(oidNode4.asString()).isEqualTo("1.2.840.113549.1.1.10"); + + // KeyLength under PrivateKey + INode keyLengthNode = privateKeyNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("2048"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign2Test.java new file mode 100644 index 000000000..c39328fa6 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign2Test.java @@ -0,0 +1,170 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaRSASign2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(PrivateKeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeySize.class); + assertThat(value0.asString()).isEqualTo("2048"); + + DetectionStore store_1 = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(SignatureAction.class); + assertThat(value0_1.asString()).isEqualTo("SIGN"); + + List> stores = + getStoresOfValueType(ValueAction.class, store_1.getChildren()); + assertThat(stores).isNotNull(); + for (DetectionStore store : stores) { + assertThat(store.getDetectionValues()).hasSize(1); + assertThat(store.getDetectionValueContext()) + .isInstanceOfAny(SignatureContext.class, DigestContext.class); + IValue v = store.getDetectionValues().get(0); + assertThat(v).isInstanceOf(ValueAction.class); + assertThat(v.asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("PKCS1v15"), + s -> assertThat(s).isEqualTo("SHA3_384")); + } + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PrivateKey + INode privateKeyNode = nodes.get(0); + assertThat(privateKeyNode.getKind()).isEqualTo(PrivateKey.class); + assertThat(privateKeyNode.getChildren()).hasSize(4); + assertThat(privateKeyNode.asString()).isEqualTo("RSA"); + + // KeyGeneration under PrivateKey + INode keyGenerationNode = privateKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // Sign under PrivateKey + INode signNode = privateKeyNode.getChildren().get(Sign.class); + assertThat(signNode).isNotNull(); + assertThat(signNode.getChildren()).isEmpty(); + assertThat(signNode.asString()).isEqualTo("SIGN"); + + // KeyLength under PrivateKey + INode keyLengthNode = privateKeyNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("2048"); + + // Signature under PrivateKey + INode signatureNode = privateKeyNode.getChildren().get(Signature.class); + assertThat(signatureNode).isNotNull(); + assertThat(signatureNode.getChildren()).hasSize(2); + assertThat(signatureNode.asString()).isEqualTo("RSA-PKCS1-1.5-SHA3-384"); + + // MessageDigest under Signature under PrivateKey + INode messageDigestNode = signatureNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA3-384"); + + // DigestSize under MessageDigest under Signature under PrivateKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("384"); + + // Oid under MessageDigest under Signature under PrivateKey + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.9"); + + // BlockSize under MessageDigest under Signature under PrivateKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("832"); + + // Digest under MessageDigest under Signature under PrivateKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Oid under Signature under PrivateKey + INode oidNode1 = signatureNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.3.15"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetDecryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetDecryptTest.java new file mode 100644 index 000000000..19f7d28cd --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetDecryptTest.java @@ -0,0 +1,200 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.fernet; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaFernetDecryptTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(KeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + DetectionStore store_1 = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(CipherAction.class); + assertThat(value0_1.asString()).isEqualTo("DECRYPT"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // SecretKey + INode secretKeyNode = nodes.get(0); + assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); + assertThat(secretKeyNode.getChildren()).hasSize(3); + assertThat(secretKeyNode.asString()).isEqualTo("Fernet"); + + // KeyGeneration under SecretKey + INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // Decrypt under SecretKey + INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); + assertThat(decryptNode).isNotNull(); + assertThat(decryptNode.getChildren()).isEmpty(); + assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); + + // AuthenticatedEncryption under SecretKey + INode authenticatedEncryptionNode = + secretKeyNode.getChildren().get(AuthenticatedEncryption.class); + assertThat(authenticatedEncryptionNode).isNotNull(); + assertThat(authenticatedEncryptionNode.getChildren()).hasSize(2); + assertThat(authenticatedEncryptionNode.asString()).isEqualTo("Fernet"); + + // Mac under AuthenticatedEncryption under SecretKey + INode macNode = authenticatedEncryptionNode.getChildren().get(Mac.class); + assertThat(macNode).isNotNull(); + assertThat(macNode.getChildren()).hasSize(3); + assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); + + // MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Digest under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // DigestSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // Tag under Mac under AuthenticatedEncryption under SecretKey + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // Oid under Mac under AuthenticatedEncryption under SecretKey + INode oidNode1 = macNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); + + // BlockCipher under AuthenticatedEncryption under SecretKey + INode blockCipherNode = authenticatedEncryptionNode.getChildren().get(BlockCipher.class); + assertThat(blockCipherNode).isNotNull(); + assertThat(blockCipherNode.getChildren()).hasSize(5); + assertThat(blockCipherNode.asString()).isEqualTo("AES-128-CBC-PKCS7"); + + // Mode under BlockCipher under AuthenticatedEncryption under SecretKey + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CBC"); + + // BlockSize under BlockCipher under AuthenticatedEncryption under SecretKey + INode blockSizeNode1 = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("128"); + + // Padding under BlockCipher under AuthenticatedEncryption under SecretKey + INode paddingNode = blockCipherNode.getChildren().get(Padding.class); + assertThat(paddingNode).isNotNull(); + assertThat(paddingNode.getChildren()).isEmpty(); + assertThat(paddingNode.asString()).isEqualTo("PKCS7"); + + // KeyLength under BlockCipher under AuthenticatedEncryption under SecretKey + INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("128"); + + // Oid under BlockCipher under AuthenticatedEncryption under SecretKey + INode oidNode2 = blockCipherNode.getChildren().get(Oid.class); + assertThat(oidNode2).isNotNull(); + assertThat(oidNode2.getChildren()).isEmpty(); + assertThat(oidNode2.asString()).isEqualTo("2.16.840.1.101.3.4.1.2"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetEncryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetEncryptTest.java new file mode 100644 index 000000000..5ad5aa112 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetEncryptTest.java @@ -0,0 +1,200 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.fernet; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaFernetEncryptTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(KeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + DetectionStore store_1 = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(CipherAction.class); + assertThat(value0_1.asString()).isEqualTo("ENCRYPT"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // SecretKey + INode secretKeyNode = nodes.get(0); + assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); + assertThat(secretKeyNode.getChildren()).hasSize(3); + assertThat(secretKeyNode.asString()).isEqualTo("Fernet"); + + // Encrypt under SecretKey + INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + + // KeyGeneration under SecretKey + INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // AuthenticatedEncryption under SecretKey + INode authenticatedEncryptionNode = + secretKeyNode.getChildren().get(AuthenticatedEncryption.class); + assertThat(authenticatedEncryptionNode).isNotNull(); + assertThat(authenticatedEncryptionNode.getChildren()).hasSize(2); + assertThat(authenticatedEncryptionNode.asString()).isEqualTo("Fernet"); + + // Mac under AuthenticatedEncryption under SecretKey + INode macNode = authenticatedEncryptionNode.getChildren().get(Mac.class); + assertThat(macNode).isNotNull(); + assertThat(macNode.getChildren()).hasSize(3); + assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); + + // MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Digest under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Oid under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // BlockSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // DigestSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Tag under Mac under AuthenticatedEncryption under SecretKey + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // Oid under Mac under AuthenticatedEncryption under SecretKey + INode oidNode1 = macNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); + + // BlockCipher under AuthenticatedEncryption under SecretKey + INode blockCipherNode = authenticatedEncryptionNode.getChildren().get(BlockCipher.class); + assertThat(blockCipherNode).isNotNull(); + assertThat(blockCipherNode.getChildren()).hasSize(5); + assertThat(blockCipherNode.asString()).isEqualTo("AES-128-CBC-PKCS7"); + + // Mode under BlockCipher under AuthenticatedEncryption under SecretKey + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CBC"); + + // Padding under BlockCipher under AuthenticatedEncryption under SecretKey + INode paddingNode = blockCipherNode.getChildren().get(Padding.class); + assertThat(paddingNode).isNotNull(); + assertThat(paddingNode.getChildren()).isEmpty(); + assertThat(paddingNode.asString()).isEqualTo("PKCS7"); + + // KeyLength under BlockCipher under AuthenticatedEncryption under SecretKey + INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("128"); + + // Oid under BlockCipher under AuthenticatedEncryption under SecretKey + INode oidNode2 = blockCipherNode.getChildren().get(Oid.class); + assertThat(oidNode2).isNotNull(); + assertThat(oidNode2.getChildren()).isEmpty(); + assertThat(oidNode2.asString()).isEqualTo("2.16.840.1.101.3.4.1.2"); + + // BlockSize under BlockCipher under AuthenticatedEncryption under SecretKey + INode blockSizeNode1 = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("128"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaMultiFernetTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaMultiFernetTest.java new file mode 100644 index 000000000..ac90ea5a5 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaMultiFernetTest.java @@ -0,0 +1,211 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.fernet; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaMultiFernetTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(KeyContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + DetectionStore store_2 = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + assertThat(store_2.getDetectionValues()).hasSize(1); + assertThat(store_2.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_2 = store_2.getDetectionValues().get(0); + assertThat(value0_2).isInstanceOf(CipherAction.class); + assertThat(value0_2.asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // SecretKey + INode secretKeyNode = nodes.get(0); + assertThat(secretKeyNode.getKind()).isEqualTo(SecretKey.class); + assertThat(secretKeyNode.getChildren()).hasSize(4); + assertThat(secretKeyNode.asString()).isEqualTo("Fernet"); + + // Decrypt under SecretKey + INode decryptNode = secretKeyNode.getChildren().get(Decrypt.class); + assertThat(decryptNode).isNotNull(); + assertThat(decryptNode.getChildren()).isEmpty(); + assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); + + // AuthenticatedEncryption under SecretKey + INode authenticatedEncryptionNode = + secretKeyNode.getChildren().get(AuthenticatedEncryption.class); + assertThat(authenticatedEncryptionNode).isNotNull(); + assertThat(authenticatedEncryptionNode.getChildren()).hasSize(2); + assertThat(authenticatedEncryptionNode.asString()).isEqualTo("Fernet"); + + // BlockCipher under AuthenticatedEncryption under SecretKey + INode blockCipherNode = authenticatedEncryptionNode.getChildren().get(BlockCipher.class); + assertThat(blockCipherNode).isNotNull(); + assertThat(blockCipherNode.getChildren()).hasSize(5); + assertThat(blockCipherNode.asString()).isEqualTo("AES-128-CBC-PKCS7"); + + // BlockSize under BlockCipher under AuthenticatedEncryption under SecretKey + INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("128"); + + // KeyLength under BlockCipher under AuthenticatedEncryption under SecretKey + INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("128"); + + // Oid under BlockCipher under AuthenticatedEncryption under SecretKey + INode oidNode = blockCipherNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1.2"); + + // Mode under BlockCipher under AuthenticatedEncryption under SecretKey + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CBC"); + + // Padding under BlockCipher under AuthenticatedEncryption under SecretKey + INode paddingNode = blockCipherNode.getChildren().get(Padding.class); + assertThat(paddingNode).isNotNull(); + assertThat(paddingNode.getChildren()).isEmpty(); + assertThat(paddingNode.asString()).isEqualTo("PKCS7"); + + // Mac under AuthenticatedEncryption under SecretKey + INode macNode = authenticatedEncryptionNode.getChildren().get(Mac.class); + assertThat(macNode).isNotNull(); + assertThat(macNode.getChildren()).hasSize(3); + assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); + + // Tag under Mac under AuthenticatedEncryption under SecretKey + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // Oid under Mac under AuthenticatedEncryption under SecretKey + oidNode = macNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.2.9"); + + // MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // BlockSize under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode blockSizeNode1 = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("512"); + + // Oid under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under Mac under AuthenticatedEncryption under + // SecretKey + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Digest under MessageDigest under Mac under AuthenticatedEncryption under SecretKey + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Encrypt under SecretKey + INode encryptNode = secretKeyNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + + // KeyGeneration under SecretKey + INode keyGenerationNode = secretKeyNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashDirectTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashDirectTest.java new file mode 100644 index 000000000..0e57b8a00 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashDirectTest.java @@ -0,0 +1,102 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaHashDirectTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // MessageDigest (SHA256) + INode messageDigestNode = nodes.get(0); + assertThat(messageDigestNode.getKind()).isEqualTo(MessageDigest.class); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // DigestSize under MessageDigest + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // BlockSize under MessageDigest + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Oid under MessageDigest + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // Digest functionality under MessageDigest + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHMACTest.java new file mode 100644 index 000000000..2e7bd5711 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHMACTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaConcatKDFHMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(macNode.getChildren()).hasSize(3); + assertThat(macNode.asString()).isEqualTo("ConcatenationKDF"); + + // MessageDigest under Mac + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // DigestSize under MessageDigest under Mac + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under Mac + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // Digest under MessageDigest under Mac + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Mac + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // KeyDerivation under Mac + INode keyDerivationNode = macNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // KeyLength under Mac + INode keyLengthNode = macNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHashTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHashTest.java new file mode 100644 index 000000000..e10a0c5f2 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHashTest.java @@ -0,0 +1,131 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaConcatKDFHashTest extends TestBase { + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("512"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + // KeyDerivationFunction + INode keyDerivationFunctionNode = nodes.get(0); + assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); + assertThat(keyDerivationFunctionNode.asString()).isEqualTo("ConcatenationKDF"); + + // MessageDigest under KeyDerivationFunction + INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // BlockSize under MessageDigest under KeyDerivationFunction + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Digest under MessageDigest under KeyDerivationFunction + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // Oid under MessageDigest under KeyDerivationFunction + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under KeyDerivationFunction + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // KeyLength under KeyDerivationFunction + INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("512"); + + // KeyDerivation under KeyDerivationFunction + INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFExpandTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFExpandTest.java new file mode 100644 index 000000000..585f7448b --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFExpandTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaHKDFExpandTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyDerivationFunction + INode keyDerivationFunctionNode = nodes.get(0); + assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); + assertThat(keyDerivationFunctionNode.asString()).isEqualTo("HKDF-SHA-256"); + + // KeyDerivation under KeyDerivationFunction + INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // KeyLength under KeyDerivationFunction + INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + + // MessageDigest under KeyDerivationFunction + INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // BlockSize under MessageDigest under KeyDerivationFunction + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Digest under MessageDigest under KeyDerivationFunction + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // DigestSize under MessageDigest under KeyDerivationFunction + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under KeyDerivationFunction + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFTest.java new file mode 100644 index 000000000..1db1ba235 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaHKDFTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyDerivationFunction + INode keyDerivationFunctionNode = nodes.get(0); + assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); + assertThat(keyDerivationFunctionNode.asString()).isEqualTo("HKDF-SHA-256"); + + // MessageDigest under KeyDerivationFunction + INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // DigestSize under MessageDigest under KeyDerivationFunction + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under KeyDerivationFunction + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // BlockSize under MessageDigest under KeyDerivationFunction + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Digest under MessageDigest under KeyDerivationFunction + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // KeyLength under KeyDerivationFunction + INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + + // KeyDerivation under KeyDerivationFunction + INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFCMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFCMACTest.java new file mode 100644 index 000000000..16e7df570 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFCMACTest.java @@ -0,0 +1,142 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaKBKDFCMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("AES"); + + DetectionStore store_1 = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(com.ibm.engine.model.Mode.class); + assertThat(value0_1.asString()).isEqualTo("CounterMode"); + + DetectionStore store_2 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_2.getDetectionValues()).hasSize(1); + assertThat(store_2.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_2 = store_2.getDetectionValues().get(0); + assertThat(value0_2).isInstanceOf(KeySize.class); + assertThat(value0_2.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(Mac.class); + assertThat(macNode.getChildren()).hasSize(4); + assertThat(macNode.asString()).isEqualTo("CMAC-AES"); + + // Tag under Mac + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // KeyDerivation under Mac + INode keyDerivationNode = macNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // BlockCipher under Mac + INode blockCipherNode = macNode.getChildren().get(BlockCipher.class); + assertThat(blockCipherNode).isNotNull(); + assertThat(blockCipherNode.getChildren()).hasSize(3); + assertThat(blockCipherNode.asString()).isEqualTo("AES-CTR"); + + // BlockSize under BlockCipher under Mac + INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("128"); + + // Mode under BlockCipher under Mac + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CTR"); + + // Oid under BlockCipher under Mac + INode oidNode = blockCipherNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + + // KeyLength under Mac + INode keyLengthNode = macNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFHMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFHMACTest.java new file mode 100644 index 000000000..828cbf21d --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFHMACTest.java @@ -0,0 +1,163 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaKBKDFHMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(com.ibm.engine.model.Mode.class); + assertThat(value0_1.asString()).isEqualTo("CounterMode"); + + DetectionStore store_2 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_2.getDetectionValues()).hasSize(1); + assertThat(store_2.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_2 = store_2.getDetectionValues().get(0); + assertThat(value0_2).isInstanceOf(KeySize.class); + assertThat(value0_2.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(Mac.class); + assertThat(macNode.getChildren()).hasSize(5); + assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); + + // Tag under Mac + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // Oid under Mac + INode oidNode = macNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("1.2.840.113549.2.9"); + + // KeyLength under Mac + INode keyLengthNode = macNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + + // KeyDerivation under Mac + INode keyDerivationNode = macNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // MessageDigest under Mac + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(5); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Mode under MessageDigest under Mac + INode modeNode = messageDigestNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CTR"); + + // DigestSize under MessageDigest under Mac + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under Mac + INode oidNode1 = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // Digest under MessageDigest under Mac + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Mac + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaPBKDF2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaPBKDF2Test.java new file mode 100644 index 000000000..a714802e5 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaPBKDF2Test.java @@ -0,0 +1,147 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.AlgorithmParameter; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaPBKDF2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + DetectionStore store_2 = + getStoreOfValueType(AlgorithmParameter.class, detectionStore.getChildren()); + assertThat(store_2.getDetectionValues()).hasSize(1); + assertThat(store_2.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_2 = store_2.getDetectionValues().get(0); + assertThat(value0_2).isInstanceOf(AlgorithmParameter.class); + assertThat(value0_2.asString()).isEqualTo("480000"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PasswordBasedKeyDerivationFunction + INode passwordBasedKeyDerivationFunctionNode = nodes.get(0); + assertThat(passwordBasedKeyDerivationFunctionNode.getKind()) + .isEqualTo(PasswordBasedKeyDerivationFunction.class); + assertThat(passwordBasedKeyDerivationFunctionNode.getChildren()).hasSize(3); + assertThat(passwordBasedKeyDerivationFunctionNode.asString()).isEqualTo("PBKDF2-SHA-256"); + + // KeyDerivation under PasswordBasedKeyDerivationFunction + INode keyDerivationNode = + passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // MessageDigest under PasswordBasedKeyDerivationFunction + INode messageDigestNode = + passwordBasedKeyDerivationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Digest under MessageDigest under PasswordBasedKeyDerivationFunction + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under PasswordBasedKeyDerivationFunction + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // DigestSize under MessageDigest under PasswordBasedKeyDerivationFunction + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Oid under MessageDigest under PasswordBasedKeyDerivationFunction + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // KeyLength under PasswordBasedKeyDerivationFunction + INode keyLengthNode = + passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaScryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaScryptTest.java new file mode 100644 index 000000000..f1a85a3fb --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaScryptTest.java @@ -0,0 +1,101 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaScryptTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(ValueAction.class); + assertThat(value0.asString()).isEqualTo("Scrypt"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // PasswordBasedKeyDerivationFunction + INode passwordBasedKeyDerivationFunctionNode = nodes.get(0); + assertThat(passwordBasedKeyDerivationFunctionNode.getKind()) + .isEqualTo(PasswordBasedKeyDerivationFunction.class); + assertThat(passwordBasedKeyDerivationFunctionNode.getChildren()).hasSize(2); + assertThat(passwordBasedKeyDerivationFunctionNode.asString()).isEqualTo("scrypt"); + + // KeyDerivation under PasswordBasedKeyDerivationFunction + INode keyDerivationNode = + passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // KeyLength under PasswordBasedKeyDerivationFunction + INode keyLengthNode = + passwordBasedKeyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaX963KDFTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaX963KDFTest.java new file mode 100644 index 000000000..9310d6380 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaX963KDFTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaX963KDFTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + DetectionStore store_1 = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(KeySize.class); + assertThat(value0_1.asString()).isEqualTo("256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyDerivationFunction + INode keyDerivationFunctionNode = nodes.get(0); + assertThat(keyDerivationFunctionNode.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(keyDerivationFunctionNode.getChildren()).hasSize(3); + assertThat(keyDerivationFunctionNode.asString()).isEqualTo("ANSI-KDF-X9.63"); + + // KeyDerivation under KeyDerivationFunction + INode keyDerivationNode = keyDerivationFunctionNode.getChildren().get(KeyDerivation.class); + assertThat(keyDerivationNode).isNotNull(); + assertThat(keyDerivationNode.getChildren()).isEmpty(); + assertThat(keyDerivationNode.asString()).isEqualTo("KEYDERIVATION"); + + // KeyLength under KeyDerivationFunction + INode keyLengthNode = keyDerivationFunctionNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("256"); + + // MessageDigest under KeyDerivationFunction + INode messageDigestNode = keyDerivationFunctionNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // BlockSize under MessageDigest under KeyDerivationFunction + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Oid under MessageDigest under KeyDerivationFunction + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under KeyDerivationFunction + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Digest under MessageDigest under KeyDerivationFunction + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreementTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreementTest.java new file mode 100644 index 000000000..9327a162f --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreementTest.java @@ -0,0 +1,179 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.keyagreement; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaKeyAgreementTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + if (findingId == 0) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyAgreementContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyAgreement + INode keyAgreementNode = nodes.get(0); + assertThat(keyAgreementNode.getKind()).isEqualTo(KeyAgreement.class); + assertThat(keyAgreementNode.getChildren()).hasSize(3); + assertThat(keyAgreementNode.asString()).isEqualTo("x25519"); + + // EllipticCurve under KeyAgreement + INode ellipticCurveNode = keyAgreementNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("Curve25519"); + + // KeyGeneration under KeyAgreement + INode keyGenerationNode = keyAgreementNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // Oid under KeyAgreement + INode oidNode1 = keyAgreementNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.3.101.110"); + + } else if (findingId == 1) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyAgreementContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyAgreement + INode keyAgreementNode = nodes.get(0); + assertThat(keyAgreementNode.getKind()).isEqualTo(KeyAgreement.class); + assertThat(keyAgreementNode.getChildren()).hasSize(3); + assertThat(keyAgreementNode.asString()).isEqualTo("x25519"); + + // EllipticCurve under KeyAgreement + INode ellipticCurveNode = keyAgreementNode.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode).isNotNull(); + assertThat(ellipticCurveNode.getChildren()).isEmpty(); + assertThat(ellipticCurveNode.asString()).isEqualTo("Curve25519"); + + // KeyGeneration under KeyAgreement + INode keyGenerationNode = keyAgreementNode.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode).isNotNull(); + assertThat(keyGenerationNode.getChildren()).isEmpty(); + assertThat(keyGenerationNode.asString()).isEqualTo("KEYGENERATION"); + + // Oid under KeyAgreement + INode oidNode1 = keyAgreementNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.3.101.110"); + + } else { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyAgreementContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(KeyAction.class); + assertThat(value0.asString()).isEqualTo("GENERATION"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // KeyAgreement + INode keyAgreementNode1 = nodes.get(0); + assertThat(keyAgreementNode1.getKind()).isEqualTo(KeyAgreement.class); + assertThat(keyAgreementNode1.getChildren()).hasSize(3); + assertThat(keyAgreementNode1.asString()).isEqualTo("x448"); + + // EllipticCurve under KeyAgreement + INode ellipticCurveNode1 = keyAgreementNode1.getChildren().get(EllipticCurve.class); + assertThat(ellipticCurveNode1).isNotNull(); + assertThat(ellipticCurveNode1.getChildren()).isEmpty(); + assertThat(ellipticCurveNode1.asString()).isEqualTo("Curve448"); + + // KeyGeneration under KeyAgreement + INode keyGenerationNode1 = keyAgreementNode1.getChildren().get(KeyGeneration.class); + assertThat(keyGenerationNode1).isNotNull(); + assertThat(keyGenerationNode1.getChildren()).isEmpty(); + assertThat(keyGenerationNode1.asString()).isEqualTo("KEYGENERATION"); + + // Oid under KeyAgreement + INode oidNode3 = keyAgreementNode1.getChildren().get(Oid.class); + assertThat(oidNode3).isNotNull(); + assertThat(oidNode3.getChildren()).isEmpty(); + assertThat(oidNode3.asString()).isEqualTo("1.3.101.111"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaCMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaCMACTest.java new file mode 100644 index 000000000..afeab14a3 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaCMACTest.java @@ -0,0 +1,101 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaCMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("AES"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(Mac.class); + assertThat(macNode.getChildren()).hasSize(2); + assertThat(macNode.asString()).isEqualTo("CMAC-AES"); + + // BlockCipher under Mac + INode blockCipherNode = macNode.getChildren().get(BlockCipher.class); + assertThat(blockCipherNode).isNotNull(); + assertThat(blockCipherNode.getChildren()).hasSize(2); + assertThat(blockCipherNode.asString()).isEqualTo("AES"); + + // BlockSize under BlockCipher under Mac + INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("128"); + + // Oid under BlockCipher under Mac + INode oidNode = blockCipherNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + + // Tag under Mac + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaHMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaHMACTest.java new file mode 100644 index 000000000..374ee293d --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaHMACTest.java @@ -0,0 +1,122 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaHMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(Mac.class); + assertThat(macNode.getChildren()).hasSize(3); + assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); + + // MessageDigest under Mac + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Digest under MessageDigest under Mac + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Mac + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Oid under MessageDigest under Mac + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under Mac + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Tag under Mac + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // Oid under Mac + INode oidNode1 = macNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTest.java new file mode 100644 index 000000000..e7806b573 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTest.java @@ -0,0 +1,131 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +/** + * Verifies that cryptographic operations inside user-defined functions are detected during standard + * AST traversal. This test covers both positive cases (cryptographic operations) and negative cases + * (non-cryptographic functions). + */ +class PycaMacDetectionInCustomFunctionTest extends TestBase { + + @Test + void testCryptographicOperationInCustomFunction() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + // Verifies that cryptographic operations inside user-defined functions are detected + // during standard AST traversal. + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("SHA256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(Mac.class); + assertThat(macNode.getChildren()).hasSize(3); + assertThat(macNode.asString()).isEqualTo("HMAC-SHA-256"); + + // MessageDigest under Mac + INode messageDigestNode = macNode.getChildren().get(MessageDigest.class); + assertThat(messageDigestNode).isNotNull(); + assertThat(messageDigestNode.getChildren()).hasSize(4); + assertThat(messageDigestNode.asString()).isEqualTo("SHA-256"); + + // Digest under MessageDigest under Mac + INode digestNode = messageDigestNode.getChildren().get(Digest.class); + assertThat(digestNode).isNotNull(); + assertThat(digestNode.getChildren()).isEmpty(); + assertThat(digestNode.asString()).isEqualTo("DIGEST"); + + // BlockSize under MessageDigest under Mac + INode blockSizeNode = messageDigestNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("512"); + + // Oid under MessageDigest under Mac + INode oidNode = messageDigestNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + // DigestSize under MessageDigest under Mac + INode digestSizeNode = messageDigestNode.getChildren().get(DigestSize.class); + assertThat(digestSizeNode).isNotNull(); + assertThat(digestSizeNode.getChildren()).isEmpty(); + assertThat(digestSizeNode.asString()).isEqualTo("256"); + + // Tag under Mac + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + + // Oid under Mac + INode oidNode1 = macNode.getChildren().get(Oid.class); + assertThat(oidNode1).isNotNull(); + assertThat(oidNode1.getChildren()).isEmpty(); + assertThat(oidNode1.asString()).isEqualTo("1.2.840.113549.2.9"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaPoly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaPoly1305Test.java new file mode 100644 index 000000000..b6e9d0d96 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaPoly1305Test.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaPoly1305Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(ValueAction.class); + assertThat(value0.asString()).isEqualTo("Poly1305"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // Mac + INode macNode = nodes.get(0); + assertThat(macNode.getKind()).isEqualTo(Mac.class); + assertThat(macNode.getChildren()).hasSize(1); + assertThat(macNode.asString()).isEqualTo("Poly1305"); + + // Tag under Mac + INode tagNode = macNode.getChildren().get(Tag.class); + assertThat(tagNode).isNotNull(); + assertThat(tagNode.getChildren()).isEmpty(); + assertThat(tagNode.asString()).isEqualTo("TAG"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPaddingTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPaddingTest.java new file mode 100644 index 000000000..7d3a43556 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPaddingTest.java @@ -0,0 +1,125 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.padding; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Padding; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaPaddingTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("CAST5"); + + DetectionStore store_1 = + getStoreOfValueType(ValueAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(ValueAction.class); + assertThat(value0_1.asString()).isEqualTo("ANSIX923"); + + DetectionStore store_1_1 = + getStoreOfValueType(com.ibm.engine.model.BlockSize.class, store_1.getChildren()); + assertThat(store_1_1.getDetectionValues()).hasSize(1); + assertThat(store_1_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1_1 = store_1_1.getDetectionValues().get(0); + assertThat(value0_1_1).isInstanceOf(com.ibm.engine.model.BlockSize.class); + assertThat(value0_1_1.asString()).isEqualTo("128"); + + DetectionStore store_2 = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(store_2.getDetectionValues()).hasSize(1); + assertThat(store_2.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_2 = store_2.getDetectionValues().get(0); + assertThat(value0_2).isInstanceOf(com.ibm.engine.model.Mode.class); + assertThat(value0_2.asString()).isEqualTo("CFB"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // BlockCipher + INode blockCipherNode = nodes.get(0); + assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); + assertThat(blockCipherNode.getChildren()).hasSize(3); + assertThat(blockCipherNode.asString()).isEqualTo("CAST5-CFB"); + + // BlockSize under BlockCipher + INode blockSizeNode = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("64"); + + // Mode under BlockCipher + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CFB"); + + // Padding under BlockCipher + INode paddingNode = blockCipherNode.getChildren().get(Padding.class); + assertThat(paddingNode).isNotNull(); + assertThat(paddingNode.getChildren()).hasSize(1); + assertThat(paddingNode.asString()).isEqualTo("ANSI X9.23"); + + // BlockSize under Padding under BlockCipher + INode blockSizeNode1 = paddingNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("128"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher1Test.java new file mode 100644 index 000000000..36834055c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher1Test.java @@ -0,0 +1,161 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.symmetric; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaCipher1Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("AES"); + + List> store_1 = + getStoresOfValueType(CipherAction.class, detectionStore.getChildren()); + for (DetectionStore store : store_1) { + assertThat(store.getDetectionValues()).hasSize(1); + assertThat(store.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(CipherAction.class); + assertThat(value0_1.asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("DECRYPT"), + s -> assertThat(s).isEqualTo("ENCRYPT")); + } + + DetectionStore store_3 = + getStoreOfValueType(ValueAction.class, detectionStore.getChildren()); + assertThat(store_3.getDetectionValues()).hasSize(1); + assertThat(store_3.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_3 = store_3.getDetectionValues().get(0); + assertThat(value0_3).isInstanceOf(ValueAction.class); + assertThat(value0_3.asString()).isEqualTo("PKCS7"); + + DetectionStore store_3_1 = + getStoreOfValueType(com.ibm.engine.model.BlockSize.class, store_3.getChildren()); + assertThat(store_3_1.getDetectionValues()).hasSize(1); + assertThat(store_3_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_3_1 = store_3_1.getDetectionValues().get(0); + assertThat(value0_3_1).isInstanceOf(com.ibm.engine.model.BlockSize.class); + assertThat(value0_3_1.asString()).isEqualTo("80"); + + DetectionStore store_4 = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(store_4.getDetectionValues()).hasSize(1); + assertThat(store_4.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_4 = store_4.getDetectionValues().get(0); + assertThat(value0_4).isInstanceOf(com.ibm.engine.model.Mode.class); + assertThat(value0_4.asString()).isEqualTo("CBC"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // BlockCipher + INode blockCipherNode = nodes.get(0); + assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); + assertThat(blockCipherNode.getChildren()).hasSize(6); + assertThat(blockCipherNode.asString()).isEqualTo("AES-CBC-PKCS7"); + + // Mode under BlockCipher + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CBC"); + + // Decrypt under BlockCipher + INode decryptNode = blockCipherNode.getChildren().get(Decrypt.class); + assertThat(decryptNode).isNotNull(); + assertThat(decryptNode.getChildren()).isEmpty(); + assertThat(decryptNode.asString()).isEqualTo("DECRYPT"); + + // Oid under BlockCipher + INode oidNode = blockCipherNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + + // Encrypt under BlockCipher + INode encryptNode = blockCipherNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + + // Padding under BlockCipher + INode paddingNode = blockCipherNode.getChildren().get(Padding.class); + assertThat(paddingNode).isNotNull(); + assertThat(paddingNode.getChildren()).hasSize(1); + assertThat(paddingNode.asString()).isEqualTo("PKCS7"); + + // BlockSize under Padding under BlockCipher + INode blockSizeNode = paddingNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode).isNotNull(); + assertThat(blockSizeNode.getChildren()).isEmpty(); + assertThat(blockSizeNode.asString()).isEqualTo("80"); + + // BlockSize under BlockCipher + INode blockSizeNode1 = blockCipherNode.getChildren().get(BlockSize.class); + assertThat(blockSizeNode1).isNotNull(); + assertThat(blockSizeNode1.getChildren()).isEmpty(); + assertThat(blockSizeNode1.asString()).isEqualTo("128"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher2Test.java new file mode 100644 index 000000000..a69bca299 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher2Test.java @@ -0,0 +1,104 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.symmetric; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaCipher2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("Camellia"); + + DetectionStore store_1 = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(CipherAction.class); + assertThat(value0_1.asString()).isEqualTo("ENCRYPT"); + + DetectionStore store_2 = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(store_2.getDetectionValues()).hasSize(1); + assertThat(store_2.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_2 = store_2.getDetectionValues().get(0); + assertThat(value0_2).isInstanceOf(com.ibm.engine.model.Mode.class); + assertThat(value0_2.asString()).isEqualTo("OFB"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // BlockCipher + INode blockCipherNode = nodes.get(0); + assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); + assertThat(blockCipherNode.getChildren()).hasSize(2); + assertThat(blockCipherNode.asString()).isEqualTo("CAMELLIA-OFB"); + + // Encrypt under BlockCipher + INode encryptNode = blockCipherNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + + // Mode under BlockCipher + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("OFB"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java new file mode 100644 index 000000000..98776e4f0 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java @@ -0,0 +1,89 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.symmetric; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.StreamCipher; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaStreamCipher1Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(Algorithm.class); + assertThat(value0.asString()).isEqualTo("ChaCha20"); + + DetectionStore store_1 = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + assertThat(store_1.getDetectionValues()).hasSize(1); + assertThat(store_1.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0_1 = store_1.getDetectionValues().get(0); + assertThat(value0_1).isInstanceOf(CipherAction.class); + assertThat(value0_1.asString()).isEqualTo("ENCRYPT"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // StreamCipher + INode streamCipherNode = nodes.get(0); + assertThat(streamCipherNode.getKind()).isEqualTo(StreamCipher.class); + assertThat(streamCipherNode.getChildren()).hasSize(1); + assertThat(streamCipherNode.asString()).isEqualTo("ChaCha20"); + + // Encrypt under BlockCipher + INode encryptNode = streamCipherNode.getChildren().get(Encrypt.class); + assertThat(encryptNode).isNotNull(); + assertThat(encryptNode.getChildren()).isEmpty(); + assertThat(encryptNode.asString()).isEqualTo("ENCRYPT"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingTest.java new file mode 100644 index 000000000..9c1a34897 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingTest.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.wrapping; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.KeyWrap; +import com.ibm.mapper.model.functionality.Encapsulate; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaWrappingTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(CipherAction.class); + assertThat(value0.asString()).isEqualTo("WRAP"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // BlockCipher + INode blockCipherNode = nodes.get(0); + assertThat(blockCipherNode.getKind()).isEqualTo(KeyWrap.class); + assertThat(blockCipherNode.getChildren()).hasSize(4); + assertThat(blockCipherNode.asString()).isEqualTo("AES-128"); + + // KeyLength under BlockCipher + INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("128"); + + // Encapsulate under BlockCipher + INode encapsulateNode = blockCipherNode.getChildren().get(Encapsulate.class); + assertThat(encapsulateNode).isNotNull(); + assertThat(encapsulateNode.getChildren()).isEmpty(); + assertThat(encapsulateNode.asString()).isEqualTo("ENCAPSULATE"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTest.java new file mode 100644 index 000000000..bfeeec87f --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTest.java @@ -0,0 +1,88 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pyca.wrapping; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.KeyWrap; +import com.ibm.mapper.model.functionality.Encapsulate; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +class PycaWrappingWithPaddingTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value0 = detectionStore.getDetectionValues().get(0); + assertThat(value0).isInstanceOf(CipherAction.class); + assertThat(value0.asString()).isEqualTo("WRAP"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + // BlockCipher + INode blockCipherNode = nodes.get(0); + assertThat(blockCipherNode.getKind()).isEqualTo(KeyWrap.class); + assertThat(blockCipherNode.getChildren()).hasSize(4); + assertThat(blockCipherNode.asString()).isEqualTo("AES-128"); + + // KeyLength under BlockCipher + INode keyLengthNode = blockCipherNode.getChildren().get(KeyLength.class); + assertThat(keyLengthNode).isNotNull(); + assertThat(keyLengthNode.getChildren()).isEmpty(); + assertThat(keyLengthNode.asString()).isEqualTo("128"); + + // Encapsulate under BlockCipher + INode encapsulateNode = blockCipherNode.getChildren().get(Encapsulate.class); + assertThat(encapsulateNode).isNotNull(); + assertThat(encapsulateNode.getChildren()).isEmpty(); + assertThat(encapsulateNode.asString()).isEqualTo("ENCAPSULATE"); + } +} From 71d1ff14d509585c414807189e59a8aeed27b043 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Thu, 1 Oct 2026 16:05:25 +0200 Subject: [PATCH 09/24] test(python): add PyCryptodome test fixtures and test classes Signed-off-by: san-zrl --- .../detection/pycrypto/cipher/AESTestFile.py | 5 + .../pycrypto/cipher/BlowfishTestFile.py | 4 + .../pycrypto/cipher/CAST5TestFile.py | 4 + .../cipher/ChaCha20Poly1305TestFile.py | 7 + .../pycrypto/cipher/ChaCha20TestFile.py | 5 + .../detection/pycrypto/cipher/DESTestFile.py | 5 + .../pycrypto/cipher/PKCS1OAEPTestFile.py | 6 + .../pycrypto/cipher/PKCS1v15TestFile.py | 5 + .../detection/pycrypto/cipher/RC2TestFile.py | 4 + .../detection/pycrypto/cipher/RC4TestFile.py | 4 + .../pycrypto/cipher/Salsa20TestFile.py | 5 + .../pycrypto/cipher/TripleDESTestFile.py | 4 + .../pycrypto/hash/BLAKE2bTestFile.py | 3 + .../pycrypto/hash/BLAKE2sTestFile.py | 3 + .../detection/pycrypto/hash/MD2TestFile.py | 3 + .../detection/pycrypto/hash/MD5TestFile.py | 3 + .../pycrypto/hash/RIPEMD160TestFile.py | 3 + .../detection/pycrypto/hash/SHA1TestFile.py | 3 + .../detection/pycrypto/hash/SHA224TestFile.py | 3 + .../detection/pycrypto/hash/SHA256TestFile.py | 3 + .../detection/pycrypto/hash/SHA384TestFile.py | 3 + .../pycrypto/hash/SHA3_224TestFile.py | 3 + .../pycrypto/hash/SHA3_256TestFile.py | 3 + .../pycrypto/hash/SHA3_384TestFile.py | 3 + .../pycrypto/hash/SHA3_512TestFile.py | 3 + .../detection/pycrypto/hash/SHA512TestFile.py | 3 + .../pycrypto/hash/SHA512_224TestFile.py | 3 + .../pycrypto/hash/SHA512_256TestFile.py | 3 + .../pycrypto/hash/TupleHash128TestFile.py | 3 + .../pycrypto/hash/cSHAKE256TestFile.py | 3 + .../detection/pycrypto/kdf/HKDFTestFile.py | 7 + .../detection/pycrypto/kdf/PBKDF1TestFile.py | 7 + .../detection/pycrypto/kdf/PBKDF2TestFile.py | 7 + .../pycrypto/kdf/SP800108CounterTestFile.py | 9 ++ .../detection/pycrypto/kdf/ScryptTestFile.py | 6 + .../pycrypto/keyagreement/ECDHTestFile.py | 14 ++ .../pycrypto/keyagreement/X25519TestFile.py | 14 ++ .../pycrypto/keyagreement/X448TestFile.py | 13 ++ .../detection/pycrypto/mac/CMACTestFile.py | 5 + .../detection/pycrypto/mac/HMACTestFile.py | 4 + .../detection/pycrypto/mac/KMAC128TestFile.py | 4 + .../detection/pycrypto/mac/KMAC256TestFile.py | 4 + .../pycrypto/mac/Poly1305TestFile.py | 4 + .../pycrypto/publickey/DSATestFile.py | 6 + .../pycrypto/publickey/ECCTestFile.py | 6 + .../pycrypto/publickey/ElGamalTestFile.py | 5 + .../pycrypto/publickey/RSATestFile.py | 6 + .../pycrypto/signature/DSSSignTestFile.py | 9 ++ .../pycrypto/signature/DSSVerifyTestFile.py | 15 ++ .../pycrypto/signature/ECDSASignTestFile.py | 9 ++ .../pycrypto/signature/ECDSAVerifyTestFile.py | 15 ++ .../pycrypto/signature/EdDSASignTestFile.py | 9 ++ .../pycrypto/signature/EdDSAVerifyTestFile.py | 14 ++ .../signature/PKCS1v15SignTestFile.py | 9 ++ .../signature/PKCS1v15VerifyTestFile.py | 15 ++ .../pycrypto/signature/PSSSignTestFile.py | 9 ++ .../pycrypto/signature/PSSVerifyTestFile.py | 16 ++ .../detection/pycrypto/cipher/AESTest.java | 106 +++++++++++++ .../pycrypto/cipher/BlowfishTest.java | 88 +++++++++++ .../detection/pycrypto/cipher/CAST5Test.java | 94 +++++++++++ .../pycrypto/cipher/ChaCha20Poly1305Test.java | 79 ++++++++++ .../pycrypto/cipher/ChaCha20Test.java | 67 ++++++++ .../detection/pycrypto/cipher/DESTest.java | 100 ++++++++++++ .../pycrypto/cipher/PKCS1OAEPTest.java | 117 ++++++++++++++ .../pycrypto/cipher/PKCS1v15Test.java | 89 +++++++++++ .../detection/pycrypto/cipher/RC2Test.java | 88 +++++++++++ .../detection/pycrypto/cipher/RC4Test.java | 77 +++++++++ .../pycrypto/cipher/Salsa20Test.java | 83 ++++++++++ .../pycrypto/cipher/TripleDESTest.java | 84 ++++++++++ .../detection/pycrypto/hash/BLAKE2bTest.java | 80 ++++++++++ .../detection/pycrypto/hash/BLAKE2sTest.java | 80 ++++++++++ .../detection/pycrypto/hash/MD2Test.java | 86 +++++++++++ .../detection/pycrypto/hash/MD5Test.java | 86 +++++++++++ .../pycrypto/hash/RIPEMD160Test.java | 80 ++++++++++ .../detection/pycrypto/hash/SHA1Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA224Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA256Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA384Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA3_224Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA3_256Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA3_384Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA3_512Test.java | 92 +++++++++++ .../detection/pycrypto/hash/SHA512Test.java | 92 +++++++++++ .../pycrypto/hash/SHA512_224Test.java | 95 ++++++++++++ .../pycrypto/hash/SHA512_256Test.java | 95 ++++++++++++ .../pycrypto/hash/TupleHash128Test.java | 80 ++++++++++ .../pycrypto/hash/cSHAKE256Test.java | 80 ++++++++++ .../detection/pycrypto/kdf/HKDFTest.java | 102 ++++++++++++ .../detection/pycrypto/kdf/PBKDF1Test.java | 117 ++++++++++++++ .../detection/pycrypto/kdf/PBKDF2Test.java | 111 +++++++++++++ .../pycrypto/kdf/SP800108CounterTest.java | 82 ++++++++++ .../detection/pycrypto/kdf/ScryptTest.java | 82 ++++++++++ .../pycrypto/keyagreement/ECDHTest.java | 82 ++++++++++ .../pycrypto/keyagreement/X25519Test.java | 87 +++++++++++ .../pycrypto/keyagreement/X448Test.java | 86 +++++++++++ .../detection/pycrypto/mac/CMACTest.java | 92 +++++++++++ .../detection/pycrypto/mac/HMACTest.java | 91 +++++++++++ .../detection/pycrypto/mac/KMAC128Test.java | 99 ++++++++++++ .../detection/pycrypto/mac/KMAC256Test.java | 99 ++++++++++++ .../detection/pycrypto/mac/Poly1305Test.java | 80 ++++++++++ .../detection/pycrypto/publickey/DSATest.java | 144 +++++++++++++++++ .../detection/pycrypto/publickey/ECCTest.java | 143 +++++++++++++++++ .../pycrypto/publickey/ElGamalTest.java | 100 ++++++++++++ .../detection/pycrypto/publickey/RSATest.java | 146 ++++++++++++++++++ .../pycrypto/signature/DSSSignTest.java | 128 +++++++++++++++ .../pycrypto/signature/DSSVerifyTest.java | 128 +++++++++++++++ .../pycrypto/signature/ECDSASignTest.java | 132 ++++++++++++++++ .../pycrypto/signature/ECDSAVerifyTest.java | 132 ++++++++++++++++ .../pycrypto/signature/EdDSASignTest.java | 127 +++++++++++++++ .../pycrypto/signature/EdDSAVerifyTest.java | 127 +++++++++++++++ .../pycrypto/signature/PKCS1v15SignTest.java | 141 +++++++++++++++++ .../signature/PKCS1v15VerifyTest.java | 142 +++++++++++++++++ .../pycrypto/signature/PSSSignTest.java | 135 ++++++++++++++++ .../pycrypto/signature/PSSVerifyTest.java | 135 ++++++++++++++++ 114 files changed, 6085 insertions(+) create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/AESTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/BlowfishTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/CAST5TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20Poly1305TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/DESTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/PKCS1OAEPTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/PKCS1v15TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/RC2TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/RC4TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/Salsa20TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/cipher/TripleDESTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/BLAKE2bTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/BLAKE2sTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/MD2TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/MD5TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/RIPEMD160TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA1TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA224TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA256TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA384TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA3_224TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA3_256TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA3_384TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA3_512TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA512TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/cSHAKE256TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/kdf/HKDFTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/kdf/PBKDF1TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/kdf/PBKDF2TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/kdf/SP800108CounterTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/kdf/ScryptTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/keyagreement/ECDHTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/mac/CMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/mac/HMACTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/mac/KMAC128TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/mac/KMAC256TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/publickey/DSATestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/publickey/ElGamalTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/publickey/RSATestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/DSSSignTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/DSSVerifyTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/ECDSASignTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/ECDSAVerifyTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/EdDSASignTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/EdDSAVerifyTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15SignTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15VerifyTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/PSSSignTestFile.py create mode 100644 python/src/test/files/rules/detection/pycrypto/signature/PSSVerifyTestFile.py create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/AESTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/BlowfishTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/CAST5Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Poly1305Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/DESTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1OAEPTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1v15Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC4Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/Salsa20Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/TripleDESTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2bTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2sTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD5Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/RIPEMD160Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA1Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA224Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA256Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA384Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_224Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_256Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_384Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_512Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_224Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_256Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/cSHAKE256Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/HKDFTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF1Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF2Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/SP800108CounterTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/ScryptTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/ECDHTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X25519Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X448Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/CMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/HMACTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/DSATest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ElGamalTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/RSATest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSSignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSVerifyTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSASignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSAVerifyTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSASignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSAVerifyTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15SignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15VerifyTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSSignTest.java create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSVerifyTest.java diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/AESTestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/AESTestFile.py new file mode 100644 index 000000000..ed60adc81 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/AESTestFile.py @@ -0,0 +1,5 @@ +from Crypto.Cipher import AES + +key_aes = b"0123456789abcdef" +cipher_aes = AES.new(key_aes, AES.MODE_CBC, iv=b'some init vector') # Noncompliant {{(BlockCipher) AES-CBC}} +cipher_aes.encrypt(b'some message') diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/BlowfishTestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/BlowfishTestFile.py new file mode 100644 index 000000000..acbb3603a --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/BlowfishTestFile.py @@ -0,0 +1,4 @@ +from Crypto.Cipher import Blowfish + +key_blowfish = b"0123456789abcdef" +cipher_blowfish = Blowfish.new(key_blowfish, Blowfish.MODE_CBC) # Noncompliant {{(BlockCipher) Blowfish-CBC}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/CAST5TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/CAST5TestFile.py new file mode 100644 index 000000000..dd654fcea --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/CAST5TestFile.py @@ -0,0 +1,4 @@ +from Crypto.Cipher import CAST + +key_cast = b"0123456789abcdef" +cipher_cast = CAST.new(key_cast, CAST.MODE_CBC) # Noncompliant {{(BlockCipher) CAST5-CBC}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20Poly1305TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20Poly1305TestFile.py new file mode 100644 index 000000000..2f69ea0be --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20Poly1305TestFile.py @@ -0,0 +1,7 @@ +from Crypto.Cipher import ChaCha20_Poly1305 + +key_chacha20_poly1305 = b"0123456789abcdef0123456789abcdef" +nonce_chacha20_poly1305 = b"01234567" +cipher_chacha20_poly1305 = ChaCha20_Poly1305.new( # Noncompliant {{(AuthenticatedEncryption) ChaCha20-Poly1305}} + key=key_chacha20_poly1305, + nonce=nonce_chacha20_poly1305) diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20TestFile.py new file mode 100644 index 000000000..be90c8ffb --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/ChaCha20TestFile.py @@ -0,0 +1,5 @@ +from Crypto.Cipher import ChaCha20 + +key_chacha20 = b"0123456789abcdef0123456789abcdef" +nonce_chacha20 = b"01234567" +cipher_chacha20 = ChaCha20.new(key=key_chacha20, nonce=nonce_chacha20) # Noncompliant {{(StreamCipher) ChaCha20}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/DESTestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/DESTestFile.py new file mode 100644 index 000000000..bb956ab8e --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/DESTestFile.py @@ -0,0 +1,5 @@ +from Crypto.Cipher import DES + +key_des = b"01234567" +cipher_des = DES.new(key_des, DES.MODE_ECB) # Noncompliant {{(BlockCipher) DES-56-ECB}} +cipher_des.decrypt(b'some blob') diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/PKCS1OAEPTestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/PKCS1OAEPTestFile.py new file mode 100644 index 000000000..d6b667f2a --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/PKCS1OAEPTestFile.py @@ -0,0 +1,6 @@ +from Crypto.Cipher import PKCS1_OAEP +from Crypto.PublicKey import RSA +from Crypto.Hash import SHA256 + +key = RSA.import_key(open('public.pem').read()) +cipher = PKCS1_OAEP.new(key, hashAlgo=SHA256) # Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/PKCS1v15TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/PKCS1v15TestFile.py new file mode 100644 index 000000000..adee56079 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/PKCS1v15TestFile.py @@ -0,0 +1,5 @@ +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.PublicKey import RSA + +key = RSA.importKey(open('public.pem').read()) +cipher = PKCS1_v1_5.new(key) # Noncompliant {{(PublicKeyEncryption) RSA}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/RC2TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/RC2TestFile.py new file mode 100644 index 000000000..9ba68e2c4 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/RC2TestFile.py @@ -0,0 +1,4 @@ +from Crypto.Cipher import ARC2 + +key_arc2 = b"0123456789abcdef" +cipher_arc2 = ARC2.new(key_arc2, ARC2.MODE_CBC) # Noncompliant {{(BlockCipher) RC2-CBC}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/RC4TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/RC4TestFile.py new file mode 100644 index 000000000..c1d08fd5f --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/RC4TestFile.py @@ -0,0 +1,4 @@ +from Crypto.Cipher import ARC4 + +key_arc4 = b"0123456789abcdef" +cipher_arc4 = ARC4.new(key_arc4) # Noncompliant {{(StreamCipher) RC4}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/Salsa20TestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/Salsa20TestFile.py new file mode 100644 index 000000000..13d498ade --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/Salsa20TestFile.py @@ -0,0 +1,5 @@ +from Crypto.Cipher import Salsa20 + +key_salsa20 = b"0123456789abcdef0123456789abcdef" +nonce_salsa20 = b"01234567" +cipher_salsa20 = Salsa20.new(key=key_salsa20, nonce=nonce_salsa20) # Noncompliant {{(StreamCipher) Salsa20}} diff --git a/python/src/test/files/rules/detection/pycrypto/cipher/TripleDESTestFile.py b/python/src/test/files/rules/detection/pycrypto/cipher/TripleDESTestFile.py new file mode 100644 index 000000000..45ac67190 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/cipher/TripleDESTestFile.py @@ -0,0 +1,4 @@ +from Crypto.Cipher import DES3 + +key_3des = b"0123456789abcdef01234567" +cipher_3des = DES3.new(key_3des, DES3.MODE_CBC) # Noncompliant {{(BlockCipher) 3DES-CBC}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/BLAKE2bTestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/BLAKE2bTestFile.py new file mode 100644 index 000000000..190bcdf7f --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/BLAKE2bTestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import BLAKE2b + +hash_blake2b = BLAKE2b.new() # Noncompliant {{(MessageDigest) BLAKE2b}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/BLAKE2sTestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/BLAKE2sTestFile.py new file mode 100644 index 000000000..5b5928fea --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/BLAKE2sTestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import BLAKE2s + +hash_blake2s = BLAKE2s.new() # Noncompliant {{(MessageDigest) BLAKE2s}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/MD2TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/MD2TestFile.py new file mode 100644 index 000000000..8c1894f04 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/MD2TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import MD2 + +hash_md2 = MD2.new() # Noncompliant {{(MessageDigest) MD2}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/MD5TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/MD5TestFile.py new file mode 100644 index 000000000..4d830526b --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/MD5TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import MD5 + +hash_md5 = MD5.new() # Noncompliant {{(MessageDigest) MD5}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/RIPEMD160TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/RIPEMD160TestFile.py new file mode 100644 index 000000000..8c5d05634 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/RIPEMD160TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import RIPEMD160 + +hash_ripemd160 = RIPEMD160.new() # Noncompliant {{(MessageDigest) RIPEMD-160}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA1TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA1TestFile.py new file mode 100644 index 000000000..49966efe3 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA1TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA1 + +hash_sha1 = SHA1.new() # Noncompliant {{(MessageDigest) SHA-1}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA224TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA224TestFile.py new file mode 100644 index 000000000..b6cdab156 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA224TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA224 + +hash_sha224 = SHA224.new() # Noncompliant {{(MessageDigest) SHA-224}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA256TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA256TestFile.py new file mode 100644 index 000000000..46a45ccd2 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA256TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA256 + +hash_sha256 = SHA256.new() # Noncompliant {{(MessageDigest) SHA-256}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA384TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA384TestFile.py new file mode 100644 index 000000000..69c9d06d9 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA384TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA384 + +hash_sha384 = SHA384.new() # Noncompliant {{(MessageDigest) SHA-384}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA3_224TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_224TestFile.py new file mode 100644 index 000000000..c539ca916 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_224TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA3_224 + +hash_sha3_224 = SHA3_224.new() # Noncompliant {{(MessageDigest) SHA3-224}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA3_256TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_256TestFile.py new file mode 100644 index 000000000..c6975d0a1 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_256TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA3_256 + +hash_sha3_256 = SHA3_256.new() # Noncompliant {{(MessageDigest) SHA3-256}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA3_384TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_384TestFile.py new file mode 100644 index 000000000..03b29d382 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_384TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA3_384 + +hash_sha3_384 = SHA3_384.new() # Noncompliant {{(MessageDigest) SHA3-384}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA3_512TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_512TestFile.py new file mode 100644 index 000000000..338c5b305 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA3_512TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA3_512 + +hash_sha3_512 = SHA3_512.new() # Noncompliant {{(MessageDigest) SHA3-512}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA512TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA512TestFile.py new file mode 100644 index 000000000..d49f1fe8a --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA512TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA512 + +hash_sha512 = SHA512.new() # Noncompliant {{(MessageDigest) SHA-512}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py new file mode 100644 index 000000000..3fbf6f77f --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA512 + +hash_sha512 = SHA512.new(truncate=224) # Noncompliant {{(MessageDigest) SHA-512/224}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py new file mode 100644 index 000000000..05534dc69 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import SHA512 + +hash_sha512 = SHA512.new(truncate=256) # Noncompliant {{(MessageDigest) SHA-512/256}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py new file mode 100644 index 000000000..cfdb54d28 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import TupleHash128 + +hash_t128 = TupleHash128.new() # Noncompliant {{(ExtendableOutputFunction) TupleHash}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/cSHAKE256TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/cSHAKE256TestFile.py new file mode 100644 index 000000000..dc8db14df --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/cSHAKE256TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import cSHAKE256 + +hash_t128 = cSHAKE256.new() # Noncompliant {{(ExtendableOutputFunction) cSHAKE256}} diff --git a/python/src/test/files/rules/detection/pycrypto/kdf/HKDFTestFile.py b/python/src/test/files/rules/detection/pycrypto/kdf/HKDFTestFile.py new file mode 100644 index 000000000..1ce3a8994 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/kdf/HKDFTestFile.py @@ -0,0 +1,7 @@ +from Crypto.Protocol.KDF import HKDF +from Crypto.Hash import SHA512 + +def test_hkdf(): + secret = b"secret" + salt = b"salt1234567890ab" + key = HKDF(master=secret, key_len=32, salt=salt, hashmod=SHA512) # Noncompliant {{(KeyDerivationFunction) HKDF-SHA-512}} diff --git a/python/src/test/files/rules/detection/pycrypto/kdf/PBKDF1TestFile.py b/python/src/test/files/rules/detection/pycrypto/kdf/PBKDF1TestFile.py new file mode 100644 index 000000000..be240b09f --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/kdf/PBKDF1TestFile.py @@ -0,0 +1,7 @@ +from Crypto.Protocol.KDF import PBKDF1 +from Crypto.Hash import SHA256 + +def test_pbkdf1(): + password = b"password" + salt = b"salt1234" + key = PBKDF1(password=password, salt=salt, dkLen=16, hashAlgo=SHA256) # Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF1-SHA-256}} diff --git a/python/src/test/files/rules/detection/pycrypto/kdf/PBKDF2TestFile.py b/python/src/test/files/rules/detection/pycrypto/kdf/PBKDF2TestFile.py new file mode 100644 index 000000000..4387b46f4 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/kdf/PBKDF2TestFile.py @@ -0,0 +1,7 @@ +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Hash import SHA512 + +def test_pbkdf2(): + password = b"password" + salt = b"salt1234" + key = PBKDF2(password=password, salt=salt, dkLen=64, count=1000, hmac_hash_module=SHA512) # Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-512}} diff --git a/python/src/test/files/rules/detection/pycrypto/kdf/SP800108CounterTestFile.py b/python/src/test/files/rules/detection/pycrypto/kdf/SP800108CounterTestFile.py new file mode 100644 index 000000000..ec649c9b9 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/kdf/SP800108CounterTestFile.py @@ -0,0 +1,9 @@ +from Crypto.Protocol.KDF import SP800_108_Counter +from Crypto.Hash import HMAC, SHA256 + +def prf(s, x): + return HMAC.new(s, x, SHA256).digest() # NonCompliant {{(Mac) HMAC-SHA-256}} + +def test_sp800_108_counter(): + secret = b"secret" + key = SP800_108_Counter(master=secret, key_len=16, prf=prf) # Noncompliant {{(KeyDerivationFunction) SP800_108_CounterKDF}} diff --git a/python/src/test/files/rules/detection/pycrypto/kdf/ScryptTestFile.py b/python/src/test/files/rules/detection/pycrypto/kdf/ScryptTestFile.py new file mode 100644 index 000000000..3dacc3111 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/kdf/ScryptTestFile.py @@ -0,0 +1,6 @@ +from Crypto.Protocol.KDF import scrypt + +def test_scrypt(): + password = b"password" + salt = b"salt1234" + key = scrypt(password, salt, key_len=32, N=2**14, r=8, p=1, num_keys=1) # Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} diff --git a/python/src/test/files/rules/detection/pycrypto/keyagreement/ECDHTestFile.py b/python/src/test/files/rules/detection/pycrypto/keyagreement/ECDHTestFile.py new file mode 100644 index 000000000..4ba7beb6b --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/keyagreement/ECDHTestFile.py @@ -0,0 +1,14 @@ +from Crypto.Hash import SHAKE128 +from Crypto.PublicKey import ECC +from Crypto.Protocol.DH import key_agreement + +def kdf(x): + return SHAKE128.new(x).read(32) # Noncompliant {{(ExtendableOutputFunction) SHAKE128}} + +priv_key = ECC.generate(curve='p256') +pub_key = priv_key.public_key() + +session_key = key_agreement( # Noncompliant {{(KeyAgreement) ECDH}} + kdf=kdf, + static_priv=priv_key, + static_pub=pub_key) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py b/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py new file mode 100644 index 000000000..e99f2d72e --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py @@ -0,0 +1,14 @@ +from Crypto.Hash import SHAKE128 +from Crypto.Protocol.DH import key_agreement, import_x25519_public_key, import_x25519_private_key + +def kdf(x): + return SHAKE128.new(x).read(32) # Noncompliant {{(ExtendableOutputFunction) SHAKE128}} + +pub_key = import_x25519_public_key(b'some 32 bytes public key') +priv_key = import_x25519_private_key(b'some 32 bytes private key') + +session_key = key_agreement( # Noncompliant {{(KeyAgreement) x25519}} + kdf=kdf, + static_priv=priv_key, + static_pub=pub_key) + diff --git a/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py b/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py new file mode 100644 index 000000000..bef21c389 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py @@ -0,0 +1,13 @@ +from Crypto.Hash import SHAKE128 +from Crypto.Protocol.DH import key_agreement, import_x448_public_key, import_x448_private_key + +def kdf(x): + return SHAKE128.new(x).read(32) # Noncompliant {{(ExtendableOutputFunction) SHAKE128}} + +pub_key = import_x448_public_key(b'some 32 bytes public key') +priv_key = import_x448_private_key(b'some 32 bytes private key') + +session_key = key_agreement( # Noncompliant {{(KeyAgreement) x448}} + kdf=kdf, + static_priv=priv_key, + static_pub=pub_key) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/mac/CMACTestFile.py b/python/src/test/files/rules/detection/pycrypto/mac/CMACTestFile.py new file mode 100644 index 000000000..3d51283ff --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/mac/CMACTestFile.py @@ -0,0 +1,5 @@ +from Crypto.Hash import CMAC +from Crypto.Cipher import AES + +key = b'some key' +cmac = CMAC.new(key, ciphermod=AES) # Noncompliant {{(Mac) CMAC-AES}} diff --git a/python/src/test/files/rules/detection/pycrypto/mac/HMACTestFile.py b/python/src/test/files/rules/detection/pycrypto/mac/HMACTestFile.py new file mode 100644 index 000000000..bc776cb2e --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/mac/HMACTestFile.py @@ -0,0 +1,4 @@ +from Crypto.Hash import HMAC, SHA256 + +secret = b'some secret' +hmac = HMAC.new(secret, digestmod=SHA256) # Noncompliant {{(Mac) HMAC-SHA-256}} diff --git a/python/src/test/files/rules/detection/pycrypto/mac/KMAC128TestFile.py b/python/src/test/files/rules/detection/pycrypto/mac/KMAC128TestFile.py new file mode 100644 index 000000000..fa40ab43a --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/mac/KMAC128TestFile.py @@ -0,0 +1,4 @@ +from Crypto.Hash import KMAC128 + +key = b'Sixteen byte key' +mac = KMAC128.new(key=key) # Noncompliant {{(Mac) KMAC128}} diff --git a/python/src/test/files/rules/detection/pycrypto/mac/KMAC256TestFile.py b/python/src/test/files/rules/detection/pycrypto/mac/KMAC256TestFile.py new file mode 100644 index 000000000..592c3d70d --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/mac/KMAC256TestFile.py @@ -0,0 +1,4 @@ +from Crypto.Hash import KMAC256 + +key = b'Sixteen byte key' +mac = KMAC256.new(key=key) # Noncompliant {{(Mac) KMAC256}} diff --git a/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py b/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py new file mode 100644 index 000000000..3c27d3cfb --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py @@ -0,0 +1,4 @@ +from Crypto.Hash import Poly1305 + +key = b'Sixteen byte key' +mac = Poly1305.new(key) # Noncompliant {{(Mac) Poly1305}} diff --git a/python/src/test/files/rules/detection/pycrypto/publickey/DSATestFile.py b/python/src/test/files/rules/detection/pycrypto/publickey/DSATestFile.py new file mode 100644 index 000000000..1d78c629c --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/publickey/DSATestFile.py @@ -0,0 +1,6 @@ +from Crypto.PublicKey import DSA + +def test_dsa(): + key1 = DSA.generate(bits=2048) # Noncompliant {{(PrivateKey) DSA}} + key2 = DSA.construct((2,3), True) # Noncompliant {{(Key) DSA}} + key3 = DSA.import_key("abcdef") # Noncompliant {{(Key) DSA}} diff --git a/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py b/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py new file mode 100644 index 000000000..c8b3d83cf --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py @@ -0,0 +1,6 @@ +from Crypto.PublicKey import ECC + +def test_ecc(): + key1 = ECC.generate(curve="Ed25519") # Noncompliant {{(PrivateKey) EC-Edwards25519}} + key2 = ECC.construct(curve="Curve448", seed=b"A" * 56) # Noncompliant {{(Key) EC-Curve448}} + key3 = ECC.import_key("abcdef") # Noncompliant {{(Key) EC}} diff --git a/python/src/test/files/rules/detection/pycrypto/publickey/ElGamalTestFile.py b/python/src/test/files/rules/detection/pycrypto/publickey/ElGamalTestFile.py new file mode 100644 index 000000000..af0359230 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/publickey/ElGamalTestFile.py @@ -0,0 +1,5 @@ +from Crypto.PublicKey import ElGamal + +def test_elgamal(): + key1 = ElGamal.generate(bits=2048, randfunc=None) # Noncompliant {{(PrivateKey) ElGamal}} + key2 = ElGamal.construct((2,3,4)) # Noncompliant {{(Key) ElGamal}} diff --git a/python/src/test/files/rules/detection/pycrypto/publickey/RSATestFile.py b/python/src/test/files/rules/detection/pycrypto/publickey/RSATestFile.py new file mode 100644 index 000000000..9699b98ad --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/publickey/RSATestFile.py @@ -0,0 +1,6 @@ +from Crypto.PublicKey import RSA + +def test_rsa(): + key1 = RSA.generate(bits=2048) # Noncompliant {{(PrivateKey) RSA}} + key2 = RSA.construct((2,3), True) # Noncompliant {{(Key) RSA}} + key3 = RSA.import_key("abcdef") # Noncompliant {{(Key) RSA}} diff --git a/python/src/test/files/rules/detection/pycrypto/signature/DSSSignTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/DSSSignTestFile.py new file mode 100644 index 000000000..c49b2b9bd --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/DSSSignTestFile.py @@ -0,0 +1,9 @@ +from Crypto.Hash import SHA256 +from Crypto.PublicKey import DSA +from Crypto.Signature import DSS + +message = b'some message' +key = DSA.import_key(open('privkey.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +signer = DSS.new(key=key, mode='fips-186-3') # Noncompliant {{(Signature) DSA-SHA-256}} +signature = signer.sign(h) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/DSSVerifyTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/DSSVerifyTestFile.py new file mode 100644 index 000000000..a904abae0 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/DSSVerifyTestFile.py @@ -0,0 +1,15 @@ +from Crypto.Hash import SHA256 +from Crypto.PublicKey import DSA +from Crypto.Signature import DSS + +message = b'some message' +key = DSA.import_key(open('pubkey.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +verifier = DSS.new(key=key, mode='fips-186-3') # Noncompliant {{(Signature) DSA-SHA-256}} +signature = b'some signature' + +try: + verifier.verify(h, signature) + print("The message is authentic.") +except ValueError: + print("The message is not authentic.") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/ECDSASignTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/ECDSASignTestFile.py new file mode 100644 index 000000000..35c1c6daf --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/ECDSASignTestFile.py @@ -0,0 +1,9 @@ +from Crypto.Hash import SHA256 +from Crypto.PublicKey import ECC +from Crypto.Signature import DSS + +message = b'some message' +key = ECC.import_key(open('privkey.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +signer = DSS.new(key=key, mode='fips-186-3') # Noncompliant {{(Signature) ECDSA-SHA-256}} +signature = signer.sign(h) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/ECDSAVerifyTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/ECDSAVerifyTestFile.py new file mode 100644 index 000000000..e53953f81 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/ECDSAVerifyTestFile.py @@ -0,0 +1,15 @@ +from Crypto.Hash import SHA256 +from Crypto.PublicKey import ECC +from Crypto.Signature import DSS + +message = b'some message' +key = ECC.import_key(open('pubkey.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +verifier = DSS.new(key=key, mode='fips-186-3') # Noncompliant {{(Signature) ECDSA-SHA-256}} +signature = b'some signature' + +try: + verifier.verify(h, signature) + print("The message is authentic.") +except ValueError: + print("The message is not authentic.") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/EdDSASignTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/EdDSASignTestFile.py new file mode 100644 index 000000000..3eb5f573c --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/EdDSASignTestFile.py @@ -0,0 +1,9 @@ +from Crypto.PublicKey import ECC +from Crypto.Signature import eddsa +from Crypto.Hash import SHA512 + +message = b'some message' +prehashed_message = SHA512.new(message) # Noncompliant {{(MessageDigest) SHA-512}} +key = ECC.import_key(open("private_ed25519.pem").read()) +signer = eddsa.new(key=key, mode='rfc8032') # Noncompliant {{(Signature) EdDSA}} +signature = signer.sign(prehashed_message) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/EdDSAVerifyTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/EdDSAVerifyTestFile.py new file mode 100644 index 000000000..41bcd2734 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/EdDSAVerifyTestFile.py @@ -0,0 +1,14 @@ +from Crypto.PublicKey import ECC +from Crypto.Signature import eddsa +from Crypto.Hash import SHA512 + +signature = b'some signature' +raw_public_bytes = b"\x01" * 32 +public_key = eddsa.import_public_key(raw_public_bytes) +verifier = eddsa.new(key=public_key, mode='rfc8032') # Noncompliant {{(Signature) EdDSA}} + +try: + verifier.verify(h, signature) + print("The message is authentic") +except ValueError: + print("The message is not authentic") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15SignTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15SignTestFile.py new file mode 100644 index 000000000..a70c95c08 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15SignTestFile.py @@ -0,0 +1,9 @@ +from Crypto.Signature import pkcs1_15 +from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA + +message = b'To be signed' +key = RSA.import_key(open('private_key.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +scheme = pkcs1_15.new(rsa_key=key) # Noncompliant {{(Signature) RSA-PKCS1-1.5-SHA-256}} +signature = scheme.sign(h) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15VerifyTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15VerifyTestFile.py new file mode 100644 index 000000000..bd01ce006 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/PKCS1v15VerifyTestFile.py @@ -0,0 +1,15 @@ +from Crypto.Signature import pkcs1_15 +from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA + +message = b'To be signed' +key = RSA.import_key(open('public_key.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +scheme = pkcs1_15.new(rsa_key=key) # Noncompliant {{(Signature) RSA-PKCS1-1.5-SHA-256}} +signature = b'some sginature' + +try: + scheme.verify(h, signature) + print("The signature is valid.") +except (ValueError, TypeError): + print("The signature is not valid.") \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/PSSSignTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/PSSSignTestFile.py new file mode 100644 index 000000000..36b5e24c6 --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/PSSSignTestFile.py @@ -0,0 +1,9 @@ +from Crypto.Signature import pss +from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA + +message = b'To be signed' +key = RSA.import_key(open('private_key.der').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +scheme = pss.new(rsa_key=key) # Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} +signature = scheme.sign(h) \ No newline at end of file diff --git a/python/src/test/files/rules/detection/pycrypto/signature/PSSVerifyTestFile.py b/python/src/test/files/rules/detection/pycrypto/signature/PSSVerifyTestFile.py new file mode 100644 index 000000000..dda6f698e --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/signature/PSSVerifyTestFile.py @@ -0,0 +1,16 @@ +from Crypto.Signature import pss +from Crypto.Signature.pss import PSS_SigScheme +from Crypto.Hash import SHA256 +from Crypto.PublicKey import RSA + +message = b'To be signed' +key = RSA.import_key(open('pubkey.der', 'rb').read()) +h = SHA256.new(message) # Noncompliant {{(MessageDigest) SHA-256}} +verifier = pss.new(rsa_key=key) # Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} +signature = b'some sginature' + +try: + verifier.verify(h, signature) + print("The signature is authentic.") +except (ValueError): + print("The signature is not authentic.") \ No newline at end of file diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/AESTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/AESTest.java new file mode 100644 index 000000000..3f2add569 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/AESTest.java @@ -0,0 +1,106 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class AESTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/AESTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("AES"); + + DetectionStore modeStore = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues().get(0).asString()).isEqualTo("MODE_CBC"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(BlockCipher.class); + assertThat(cipher.getChildren()).hasSize(4); + assertThat(cipher.asString()).isEqualTo("AES-CBC"); + + INode mode = cipher.getChildren().get(Mode.class); + assertThat(mode).isNotNull(); + assertThat(mode.getChildren()).isEmpty(); + assertThat(mode.asString()).isEqualTo("CBC"); + + INode oid = cipher.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + + INode blockSize = cipher.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("128"); + + INode encrypt = cipher.getChildren().get(Encrypt.class); + assertThat(encrypt).isNotNull(); + assertThat(encrypt.getChildren()).isEmpty(); + assertThat(encrypt.asString()).isEqualTo("ENCRYPT"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/BlowfishTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/BlowfishTest.java new file mode 100644 index 000000000..22cbae93c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/BlowfishTest.java @@ -0,0 +1,88 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class BlowfishTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/BlowfishTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("Blowfish"); + + DetectionStore modeStore = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues().get(0).asString()).isEqualTo("MODE_CBC"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(BlockCipher.class); + assertThat(cipher.getChildren()).hasSize(1); + assertThat(cipher.asString()).isEqualTo("Blowfish-CBC"); + + INode mode = cipher.getChildren().get(Mode.class); + assertThat(mode).isNotNull(); + assertThat(mode.getChildren()).isEmpty(); + assertThat(mode.asString()).isEqualTo("CBC"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/CAST5Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/CAST5Test.java new file mode 100644 index 000000000..5c174d392 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/CAST5Test.java @@ -0,0 +1,94 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class CAST5Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/CAST5TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("CAST5"); + + DetectionStore modeStore = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues().get(0).asString()).isEqualTo("MODE_CBC"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(BlockCipher.class); + assertThat(cipher.getChildren()).hasSize(2); + assertThat(cipher.asString()).isEqualTo("CAST5-CBC"); + + INode mode = cipher.getChildren().get(Mode.class); + assertThat(mode).isNotNull(); + assertThat(mode.getChildren()).isEmpty(); + assertThat(mode.asString()).isEqualTo("CBC"); + + INode blockSize = cipher.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("64"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Poly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Poly1305Test.java new file mode 100644 index 000000000..2e82ca849 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Poly1305Test.java @@ -0,0 +1,79 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ChaCha20Poly1305Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/ChaCha20Poly1305TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("ChaCha20Poly1305"); + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(AuthenticatedEncryption.class); + assertThat(cipher.getChildren()).hasSize(1); + assertThat(cipher.asString()).isEqualTo("ChaCha20-Poly1305"); + + INode messageDigest = cipher.getChildren().get(MessageDigest.class); + assertThat(messageDigest).isNotNull(); + assertThat(messageDigest.getChildren()).hasSize(1); + assertThat(messageDigest.asString()).isEqualTo("Poly1305"); + + INode digest = messageDigest.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Test.java new file mode 100644 index 000000000..0ce2ec9cd --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/ChaCha20Test.java @@ -0,0 +1,67 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.StreamCipher; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ChaCha20Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/ChaCha20TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("ChaCha20"); + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(StreamCipher.class); + assertThat(cipher.getChildren()).isEmpty(); + assertThat(cipher.asString()).isEqualTo("ChaCha20"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/DESTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/DESTest.java new file mode 100644 index 000000000..cee58d89c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/DESTest.java @@ -0,0 +1,100 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class DESTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/DESTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("DES"); + + DetectionStore modeStore = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues().get(0).asString()).isEqualTo("MODE_ECB"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(BlockCipher.class); + assertThat(cipher.getChildren()).hasSize(4); + assertThat(cipher.asString()).isEqualTo("DES-56-ECB"); + + INode mode = cipher.getChildren().get(Mode.class); + assertThat(mode).isNotNull(); + assertThat(mode.getChildren()).isEmpty(); + assertThat(mode.asString()).isEqualTo("ECB"); + + INode keyLength = cipher.getChildren().get(KeyLength.class); + assertThat(keyLength).isNotNull(); + assertThat(keyLength.getChildren()).isEmpty(); + assertThat(keyLength.asString()).isEqualTo("56"); + + INode blockSize = cipher.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("64"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1OAEPTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1OAEPTest.java new file mode 100644 index 000000000..64e5360eb --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1OAEPTest.java @@ -0,0 +1,117 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PKCS1OAEPTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/PKCS1OAEPTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + // cipher = PKCS1_OAEP.new(key, SHA256) + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("PKCS1_OAEP"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + DetectionStore keyStore = + getStoreOfValueType(Algorithm.class, detectionStore.getChildren()); + if (keyStore != null) { + assertThat(keyStore.getDetectionValues()).hasSize(1); + assertThat(keyStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(keyStore.getDetectionValues().get(0)).isInstanceOf(Algorithm.class); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(PublicKeyEncryption.class); + assertThat(cipher.getChildren()).hasSize(3); + assertThat(cipher.asString()).isEqualTo("RSA-OAEP"); + + INode oid = cipher.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.113549.1.1.7"); + + INode padding = cipher.getChildren().get(Padding.class); + assertThat(padding).isNotNull(); + assertThat(padding.getChildren()).isEmpty(); + assertThat(padding.asString()).isEqualTo("OAEP"); + + INode key = cipher.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("RSA"); + + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.asString()).isEqualTo("RSA"); + + INode pkeOID = pke.getChildren().get(Oid.class); + assertThat(pkeOID).isNotNull(); + assertThat(pkeOID.asString()).isEqualTo("1.2.840.113549.1.1.1"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1v15Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1v15Test.java new file mode 100644 index 000000000..ae488c6ff --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PKCS1v15Test.java @@ -0,0 +1,89 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PKCS1v15Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/PKCS1v15TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("PKCS1_v1_5"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(PublicKeyEncryption.class); + assertThat(cipher.getChildren()).hasSize(2); + assertThat(cipher.asString()).isEqualTo("RSA"); + + INode oid = cipher.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + INode padding = cipher.getChildren().get(Padding.class); + assertThat(padding).isNotNull(); + assertThat(padding.getChildren()).isEmpty(); + assertThat(padding.asString()).isEqualTo("PKCS1"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC2Test.java new file mode 100644 index 000000000..bd456aa8b --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC2Test.java @@ -0,0 +1,88 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class RC2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/RC2TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RC2"); + + DetectionStore modeStore = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues().get(0).asString()).isEqualTo("MODE_CBC"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(BlockCipher.class); + assertThat(cipher.getChildren()).hasSize(1); + assertThat(cipher.asString()).isEqualTo("RC2-CBC"); + + INode mode = cipher.getChildren().get(Mode.class); + assertThat(mode).isNotNull(); + assertThat(mode.getChildren()).isEmpty(); + assertThat(mode.asString()).isEqualTo("CBC"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC4Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC4Test.java new file mode 100644 index 000000000..c5e0b7336 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/RC4Test.java @@ -0,0 +1,77 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.StreamCipher; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class RC4Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/RC4TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RC4"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(StreamCipher.class); + assertThat(cipher.getChildren()).isEmpty(); + assertThat(cipher.asString()).isEqualTo("RC4"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/Salsa20Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/Salsa20Test.java new file mode 100644 index 000000000..5805dcca0 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/Salsa20Test.java @@ -0,0 +1,83 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.StreamCipher; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class Salsa20Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/Salsa20TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("Salsa20"); + + DetectionStore actionStore = + getStoreOfValueType(CipherAction.class, detectionStore.getChildren()); + if (actionStore != null) { + assertThat(actionStore.getDetectionValues().get(0).asString()) + .satisfiesAnyOf( + s -> assertThat(s).isEqualTo("ENCRYPT"), + s -> assertThat(s).isEqualTo("DECRYPT")); + } + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(StreamCipher.class); + assertThat(cipher.getChildren()).hasSize(1); + assertThat(cipher.asString()).isEqualTo("Salsa20"); + + INode keyLength = cipher.getChildren().get(KeyLength.class); + assertThat(keyLength).isNotNull(); + assertThat(keyLength.getChildren()).isEmpty(); + assertThat(keyLength.asString()).isEqualTo("128"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/TripleDESTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/TripleDESTest.java new file mode 100644 index 000000000..2437eb099 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/cipher/TripleDESTest.java @@ -0,0 +1,84 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.cipher; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class TripleDESTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/cipher/TripleDESTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("3DES"); + + DetectionStore modeStore = + getStoreOfValueType(com.ibm.engine.model.Mode.class, detectionStore.getChildren()); + assertThat(modeStore).isNotNull(); + assertThat(modeStore.getDetectionValues().get(0).asString()).isEqualTo("MODE_CBC"); + + assertThat(nodes).hasSize(1); + INode cipher = nodes.get(0); + assertThat(cipher.getKind()).isEqualTo(BlockCipher.class); + assertThat(cipher.getChildren()).hasSize(2); + assertThat(cipher.asString()).isEqualTo("3DES-CBC"); + + INode mode = cipher.getChildren().get(Mode.class); + assertThat(mode).isNotNull(); + assertThat(mode.getChildren()).isEmpty(); + assertThat(mode.asString()).isEqualTo("CBC"); + + INode blockSize = cipher.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("64"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2bTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2bTest.java new file mode 100644 index 000000000..f1c54fe17 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2bTest.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class BLAKE2bTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/BLAKE2bTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("BLAKE2b"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(2); + assertThat(md.asString()).isEqualTo("BLAKE2b"); + + INode saltLength = md.getChildren().get(SaltLength.class); + assertThat(saltLength).isNotNull(); + assertThat(saltLength.getChildren()).isEmpty(); + assertThat(saltLength.asString()).isEqualTo("128"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2sTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2sTest.java new file mode 100644 index 000000000..53a984914 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/BLAKE2sTest.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class BLAKE2sTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/BLAKE2sTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("BLAKE2s"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(2); + assertThat(md.asString()).isEqualTo("BLAKE2s"); + + INode saltLength = md.getChildren().get(SaltLength.class); + assertThat(saltLength).isNotNull(); + assertThat(saltLength.getChildren()).isEmpty(); + assertThat(saltLength.asString()).isEqualTo("64"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD2Test.java new file mode 100644 index 000000000..9603d7ff5 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD2Test.java @@ -0,0 +1,86 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class MD2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/MD2TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("MD2"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(3); + assertThat(md.asString()).isEqualTo("MD2"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("128"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("128"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD5Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD5Test.java new file mode 100644 index 000000000..4841ea4b2 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/MD5Test.java @@ -0,0 +1,86 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class MD5Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/MD5TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("MD5"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(3); + assertThat(md.asString()).isEqualTo("MD5"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("128"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/RIPEMD160Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/RIPEMD160Test.java new file mode 100644 index 000000000..9ae8ab849 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/RIPEMD160Test.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class RIPEMD160Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/RIPEMD160TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RIPEMD160"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(2); + assertThat(md.asString()).isEqualTo("RIPEMD-160"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("160"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA1Test.java new file mode 100644 index 000000000..43f1f8a88 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA1Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA1Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA1TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA1"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-1"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.3.14.3.2.26"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("160"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA224Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA224Test.java new file mode 100644 index 000000000..55dc5bb4c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA224Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA224Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA224TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA224"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-224"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.4"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("224"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA256Test.java new file mode 100644 index 000000000..7984cf644 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA256Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA256Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA256TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA256"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-256"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA384Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA384Test.java new file mode 100644 index 000000000..8a4698440 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA384Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA384Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA384TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA384"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-384"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1024"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.2"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("384"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_224Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_224Test.java new file mode 100644 index 000000000..028159f49 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_224Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA3_224Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA3_224TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA3_224"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA3-224"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1152"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.7"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("224"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_256Test.java new file mode 100644 index 000000000..59872895c --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_256Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA3_256Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA3_256TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA3_256"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA3-256"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1088"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.8"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_384Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_384Test.java new file mode 100644 index 000000000..15e7518cb --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_384Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA3_384Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA3_384TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA3_384"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA3-384"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("832"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.9"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("384"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_512Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_512Test.java new file mode 100644 index 000000000..1b757a736 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA3_512Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA3_512Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA3_512TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA3_512"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA3-512"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("576"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.10"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("512"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512Test.java new file mode 100644 index 000000000..9d0b7d92d --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512Test.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA512Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA512TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA512"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-512"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1024"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("512"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_224Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_224Test.java new file mode 100644 index 000000000..24be5c49d --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_224Test.java @@ -0,0 +1,95 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA512_224Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA512"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.asString()).isEqualTo("SHA-512/224"); + assertThat(md.getChildren()).hasSize(5); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("224"); + + INode preHash = md.getChildren().get(MessageDigest.class); + assertThat(preHash).isNotNull(); + assertThat(preHash.asString()).isEqualTo("SHA-512"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1024"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.5"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_256Test.java new file mode 100644 index 000000000..3a83b19bc --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/SHA512_256Test.java @@ -0,0 +1,95 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SHA512_256Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SHA512"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.asString()).isEqualTo("SHA-512/256"); + assertThat(md.getChildren()).hasSize(5); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + + INode preHash = md.getChildren().get(MessageDigest.class); + assertThat(preHash).isNotNull(); + assertThat(preHash.asString()).isEqualTo("SHA-512"); + + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1024"); + + INode oid = md.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.2.6"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java new file mode 100644 index 000000000..4ba2c2ad0 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class TupleHash128Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("TupleHash128"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(2); + assertThat(md.asString()).isEqualTo("TupleHash"); + + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("128"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/cSHAKE256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/cSHAKE256Test.java new file mode 100644 index 000000000..82a4b7db7 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/cSHAKE256Test.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ParameterSetIdentifier; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class cSHAKE256Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/cSHAKE256TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("cSHAKE256"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(2); + assertThat(md.asString()).isEqualTo("cSHAKE256"); + + INode parameterSetIdentifier = md.getChildren().get(ParameterSetIdentifier.class); + assertThat(parameterSetIdentifier).isNotNull(); + assertThat(parameterSetIdentifier.getChildren()).isEmpty(); + assertThat(parameterSetIdentifier.asString()).isEqualTo("256"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/HKDFTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/HKDFTest.java new file mode 100644 index 000000000..d58e52ca7 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/HKDFTest.java @@ -0,0 +1,102 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.algorithms.HKDF; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class HKDFTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/kdf/HKDFTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("HKDF"); + + DetectionStore keySizeStore = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(keySizeStore).isNotNull(); + assertThat(keySizeStore.getDetectionValues().get(0).asString()).isEqualTo("256"); + + DetectionStore algorithmStore = + getStoreOfValueType(Algorithm.class, detectionStore.getChildren()); + assertThat(algorithmStore).isNotNull(); + assertThat(algorithmStore.getDetectionValues().get(0).asString()).isEqualTo("SHA512"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(root).isInstanceOf(HKDF.class); + assertThat(root.getChildren()).hasSize(3); + assertThat(root.asString()).isEqualTo("HKDF-SHA-512"); + + INode md = root.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-512"); + assertThat(md.getChildren().get(DigestSize.class).asString()).isEqualTo("512"); + assertThat(md.getChildren().get(Oid.class).asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); + assertThat(md.getChildren().get(Digest.class).asString()).isEqualTo("DIGEST"); + assertThat(md.getChildren().get(BlockSize.class).asString()).isEqualTo("1024"); + + assertThat(root.getChildren().get(KeyLength.class).asString()).isEqualTo("256"); + assertThat(root.getChildren().get(KeyDerivation.class).asString()) + .isEqualTo("KEYDERIVATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF1Test.java new file mode 100644 index 000000000..50318602e --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF1Test.java @@ -0,0 +1,117 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.algorithms.PBKDF1; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PBKDF1Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/kdf/PBKDF1TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("PBKDF1"); + + DetectionStore keySizeStore = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(keySizeStore).isNotNull(); + assertThat(keySizeStore.getDetectionValues()).hasSize(1); + assertThat(keySizeStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + assertThat(keySizeStore.getDetectionValues().get(0)).isInstanceOf(KeySize.class); + assertThat(keySizeStore.getDetectionValues().get(0).asString()).isEqualTo("128"); + + DetectionStore algorithmStore = + getStoreOfValueType(Algorithm.class, detectionStore.getChildren()); + assertThat(algorithmStore).isNotNull(); + assertThat(algorithmStore.getDetectionValues()).hasSize(1); + assertThat(algorithmStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + assertThat(algorithmStore.getDetectionValues().get(0)).isInstanceOf(Algorithm.class); + assertThat(algorithmStore.getDetectionValues().get(0).asString()).isEqualTo("SHA256"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PasswordBasedKeyDerivationFunction.class); + assertThat(root).isInstanceOf(PBKDF1.class); + assertThat(root.getChildren()).hasSize(3); + assertThat(root.asString()).isEqualTo("PBKDF1-SHA-256"); + + INode md = root.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-256"); + + assertThat(md.getChildren().get(DigestSize.class)).isNotNull(); + assertThat(md.getChildren().get(DigestSize.class).asString()).isEqualTo("256"); + assertThat(md.getChildren().get(Oid.class)).isNotNull(); + assertThat(md.getChildren().get(Oid.class).asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + assertThat(md.getChildren().get(Digest.class)).isNotNull(); + assertThat(md.getChildren().get(Digest.class).asString()).isEqualTo("DIGEST"); + assertThat(md.getChildren().get(BlockSize.class)).isNotNull(); + assertThat(md.getChildren().get(BlockSize.class).asString()).isEqualTo("512"); + + assertThat(root.getChildren().get(KeyLength.class)).isNotNull(); + assertThat(root.getChildren().get(KeyLength.class).asString()).isEqualTo("128"); + assertThat(root.getChildren().get(KeyDerivation.class)).isNotNull(); + assertThat(root.getChildren().get(KeyDerivation.class).asString()) + .isEqualTo("KEYDERIVATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF2Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF2Test.java new file mode 100644 index 000000000..429a251f7 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PBKDF2Test.java @@ -0,0 +1,111 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.IterationCount; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.NumberOfIterations; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.algorithms.PBKDF2; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PBKDF2Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/kdf/PBKDF2TestFile.py", this); + } + + @Override + @SuppressWarnings("unchecked") + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("PBKDF2"); + + DetectionStore iterStore = + getStoreOfValueType(IterationCount.class, detectionStore.getChildren()); + assertThat(iterStore).isNotNull(); + assertThat(iterStore.getDetectionValues().get(0).asString()).isEqualTo("1000"); + + DetectionStore keySizeStore = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(keySizeStore).isNotNull(); + assertThat(keySizeStore.getDetectionValues().get(0).asString()).isEqualTo("512"); + + DetectionStore algorithmStore = + getStoreOfValueType(Algorithm.class, detectionStore.getChildren()); + assertThat(algorithmStore).isNotNull(); + assertThat(algorithmStore.getDetectionValues().get(0).asString()).isEqualTo("SHA512"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PasswordBasedKeyDerivationFunction.class); + assertThat(root).isInstanceOf(PBKDF2.class); + assertThat(root.getChildren()).hasSize(4); + assertThat(root.asString()).isEqualTo("PBKDF2-SHA-512"); + + INode md = root.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.getChildren()).hasSize(4); + assertThat(md.asString()).isEqualTo("SHA-512"); + assertThat(md.getChildren().get(DigestSize.class).asString()).isEqualTo("512"); + assertThat(md.getChildren().get(Oid.class).asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); + assertThat(md.getChildren().get(Digest.class).asString()).isEqualTo("DIGEST"); + assertThat(md.getChildren().get(BlockSize.class).asString()).isEqualTo("1024"); + + assertThat(root.getChildren().get(KeyLength.class).asString()).isEqualTo("512"); + assertThat(root.getChildren().get(KeyDerivation.class).asString()) + .isEqualTo("KEYDERIVATION"); + assertThat(root.getChildren().get(NumberOfIterations.class).asString()).isEqualTo("1000"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/SP800108CounterTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/SP800108CounterTest.java new file mode 100644 index 000000000..f30fec7d2 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/SP800108CounterTest.java @@ -0,0 +1,82 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.algorithms.KDFCounter; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class SP800108CounterTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/kdf/SP800108CounterTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 1) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("SP800_108_Counter"); + + DetectionStore keySizeStore = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(keySizeStore).isNotNull(); + assertThat(keySizeStore.getDetectionValues().get(0).asString()).isEqualTo("128"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(KeyDerivationFunction.class); + assertThat(root).isInstanceOf(KDFCounter.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("SP800_108_CounterKDF"); + + assertThat(root.getChildren().get(KeyLength.class).asString()).isEqualTo("128"); + assertThat(root.getChildren().get(KeyDerivation.class).asString()) + .isEqualTo("KEYDERIVATION"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/ScryptTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/ScryptTest.java new file mode 100644 index 000000000..9aaac66ae --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/kdf/ScryptTest.java @@ -0,0 +1,82 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.kdf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.algorithms.Scrypt; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ScryptTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/kdf/ScryptTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyDerivationFunctionContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("scrypt"); + + DetectionStore keySizeStore = + getStoreOfValueType(KeySize.class, detectionStore.getChildren()); + assertThat(keySizeStore).isNotNull(); + assertThat(keySizeStore.getDetectionValues().get(0).asString()).isEqualTo("256"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PasswordBasedKeyDerivationFunction.class); + assertThat(root).isInstanceOf(Scrypt.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("scrypt"); + + assertThat(root.getChildren().get(KeyLength.class).asString()).isEqualTo("256"); + assertThat(root.getChildren().get(KeyDerivation.class).asString()) + .isEqualTo("KEYDERIVATION"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/ECDHTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/ECDHTest.java new file mode 100644 index 000000000..98ca80bc8 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/ECDHTest.java @@ -0,0 +1,82 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.keyagreement; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ECDHTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/keyagreement/ECDHTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 1) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyAgreementContext.class); + assertThat(detectionStore.getDetectionValues().get(0)).isInstanceOf(ValueAction.class); + assertThat(detectionStore.getDetectionValues().get(0).asString()).isEqualTo("ECDH"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(KeyAgreement.class); + assertThat(root).isInstanceOf(ECDH.class); + assertThat(root.asString()).isEqualTo("ECDH"); + + assertThat(root.getChildren().get(Oid.class)).isNotNull(); + assertThat(root.getChildren().get(Oid.class).asString()).isEqualTo("1.3.132.1.12"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat(root.getChildren().get(KeyGeneration.class).asString()) + .isEqualTo("KEYGENERATION"); + assertThat(root.getChildren().get(PublicKey.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKey.class).asString()).isEqualTo("EC"); + assertThat(root.getChildren().get(PrivateKey.class)).isNotNull(); + assertThat(root.getChildren().get(PrivateKey.class).asString()) + .isEqualTo("EC-secp256r1"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X25519Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X25519Test.java new file mode 100644 index 000000000..0417b028f --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X25519Test.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.keyagreement; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class X25519Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 1) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyAgreementContext.class); + assertThat(detectionStore.getDetectionValues().get(0)).isInstanceOf(ValueAction.class); + assertThat(detectionStore.getDetectionValues().get(0).asString()).isEqualTo("ECDH"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(KeyAgreement.class); + assertThat(root).isInstanceOf(X25519.class); + assertThat(root.asString()).isEqualTo("x25519"); + + assertThat(root.getChildren().get(Oid.class)).isNotNull(); + assertThat(root.getChildren().get(Oid.class).asString()).isEqualTo("1.3.101.110"); + assertThat(root.getChildren().get(EllipticCurve.class)).isNotNull(); + assertThat(root.getChildren().get(EllipticCurve.class).asString()) + .isEqualTo("Curve25519"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat(root.getChildren().get(KeyGeneration.class).asString()) + .isEqualTo("KEYGENERATION"); + assertThat(root.getChildren().get(PublicKey.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKey.class).asString()) + .isEqualTo("EC-Curve25519"); + assertThat(root.getChildren().get(PrivateKey.class)).isNotNull(); + assertThat(root.getChildren().get(PrivateKey.class).asString()) + .isEqualTo("EC-Curve25519"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X448Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X448Test.java new file mode 100644 index 000000000..ba0be8725 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/X448Test.java @@ -0,0 +1,86 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.keyagreement; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class X448Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 1) { + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyAgreementContext.class); + assertThat(detectionStore.getDetectionValues().get(0)).isInstanceOf(ValueAction.class); + assertThat(detectionStore.getDetectionValues().get(0).asString()).isEqualTo("ECDH"); + + assertThat(nodes).hasSize(1); + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(KeyAgreement.class); + assertThat(root).isInstanceOf(X448.class); + assertThat(root.asString()).isEqualTo("x448"); + + assertThat(root.getChildren().get(Oid.class)).isNotNull(); + assertThat(root.getChildren().get(Oid.class).asString()).isEqualTo("1.3.101.111"); + assertThat(root.getChildren().get(EllipticCurve.class)).isNotNull(); + assertThat(root.getChildren().get(EllipticCurve.class).asString()) + .isEqualTo("Curve448"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat(root.getChildren().get(KeyGeneration.class).asString()) + .isEqualTo("KEYGENERATION"); + assertThat(root.getChildren().get(PublicKey.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKey.class).asString()).isEqualTo("EC-Curve448"); + assertThat(root.getChildren().get(PrivateKey.class)).isNotNull(); + assertThat(root.getChildren().get(PrivateKey.class).asString()) + .isEqualTo("EC-Curve448"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/CMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/CMACTest.java new file mode 100644 index 000000000..5daf5dbff --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/CMACTest.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.Oid; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class CMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/mac/CMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(Algorithm.class); + assertThat(value.asString()).isEqualTo("AES"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + INode mac = nodes.get(0); + assertThat(mac).isInstanceOf(Mac.class); + assertThat(mac.asString()).isEqualTo("CMAC-AES"); + assertThat(mac.getChildren()).hasSize(2); + + INode cipher = mac.getChildren().get(BlockCipher.class); + assertThat(cipher).isNotNull(); + assertThat(cipher.asString()).isEqualTo("AES"); + + INode blockSize = cipher.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("128"); + + INode oid = cipher.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/HMACTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/HMACTest.java new file mode 100644 index 000000000..b62ec4557 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/HMACTest.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class HMACTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/mac/HMACTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(Algorithm.class); + assertThat(value.asString()).isEqualTo("SHA256"); + + assertThat(nodes).hasSize(1); + INode mac = nodes.get(0); + assertThat(mac).isInstanceOf(Mac.class); + assertThat(mac.asString()).isEqualTo("HMAC-SHA-256"); + assertThat(mac.getChildren()).hasSize(3); + + INode digest = mac.getChildren().get(MessageDigest.class); + assertThat(digest).isNotNull(); + assertThat(digest.asString()).isEqualTo("SHA-256"); + assertThat(digest.getChildren()).hasSize(4); + + assertThat(digest.getChildren().get(Digest.class)).isNotNull(); + assertThat(digest.getChildren().get(Digest.class).asString()).isEqualTo("DIGEST"); + assertThat(digest.getChildren().get(BlockSize.class)).isNotNull(); + assertThat(digest.getChildren().get(BlockSize.class).asString()).isEqualTo("512"); + assertThat(digest.getChildren().get(Oid.class)).isNotNull(); + assertThat(digest.getChildren().get(Oid.class).asString()) + .isEqualTo("2.16.840.1.101.3.4.2.1"); + assertThat(digest.getChildren().get(DigestSize.class)).isNotNull(); + assertThat(digest.getChildren().get(DigestSize.class).asString()).isEqualTo("256"); + + assertThat(mac.getChildren().get(Oid.class)).isNotNull(); + assertThat(mac.getChildren().get(Oid.class).asString()).isEqualTo("1.2.840.113549.2.9"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java new file mode 100644 index 000000000..0e471fd74 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java @@ -0,0 +1,99 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.ParameterSetIdentifier; +import com.ibm.mapper.model.algorithms.KMAC; +import com.ibm.mapper.model.algorithms.shake.CSHAKE; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class KMAC128Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/mac/KMAC128TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("KMAC128"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + INode mac = nodes.get(0); + assertThat(mac).isInstanceOf(KMAC.class); + assertThat(mac.getKind()).isEqualTo(Mac.class); + assertThat(mac.asString()).isEqualTo("KMAC128"); + assertThat(mac.getChildren()).hasSize(4); + + INode parameterSetIdentifier = mac.getChildren().get(ParameterSetIdentifier.class); + assertThat(parameterSetIdentifier).isNotNull(); + assertThat(parameterSetIdentifier.asString()).isEqualTo("128"); + + INode digestSize = mac.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.asString()).isEqualTo("256"); + + INode cshake = mac.getChildren().get(ExtendableOutputFunction.class); + assertThat(cshake).isNotNull(); + assertThat(cshake).isInstanceOf(CSHAKE.class); + assertThat(cshake.asString()).isEqualTo("cSHAKE128"); + + INode tag = mac.getChildren().get(Tag.class); + assertThat(tag).isNotNull(); + assertThat(tag.asString()).isEqualTo("TAG"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java new file mode 100644 index 000000000..62a6987ee --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java @@ -0,0 +1,99 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.ParameterSetIdentifier; +import com.ibm.mapper.model.algorithms.KMAC; +import com.ibm.mapper.model.algorithms.shake.CSHAKE; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class KMAC256Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/mac/KMAC256TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("KMAC256"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + INode mac = nodes.get(0); + assertThat(mac).isInstanceOf(KMAC.class); + assertThat(mac.getKind()).isEqualTo(Mac.class); + assertThat(mac.asString()).isEqualTo("KMAC256"); + assertThat(mac.getChildren()).hasSize(4); + + INode parameterSetIdentifier = mac.getChildren().get(ParameterSetIdentifier.class); + assertThat(parameterSetIdentifier).isNotNull(); + assertThat(parameterSetIdentifier.asString()).isEqualTo("256"); + + INode digestSize = mac.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.asString()).isEqualTo("512"); + + INode cshake = mac.getChildren().get(ExtendableOutputFunction.class); + assertThat(cshake).isNotNull(); + assertThat(cshake).isInstanceOf(CSHAKE.class); + assertThat(cshake.asString()).isEqualTo("cSHAKE256"); + + INode tag = mac.getChildren().get(Tag.class); + assertThat(tag).isNotNull(); + assertThat(tag.asString()).isEqualTo("TAG"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java new file mode 100644 index 000000000..556e26eca --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.mac; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class Poly1305Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + /* + * Detection Store + */ + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("Poly1305"); + + /* + * Translation + */ + assertThat(nodes).hasSize(1); + + INode mac = nodes.get(0); + assertThat(mac.getKind()).isEqualTo(Mac.class); + assertThat(mac.asString()).isEqualTo("Poly1305"); + assertThat(mac.getChildren()).hasSize(1); + + INode tag = mac.getChildren().get(Tag.class); + assertThat(tag).isNotNull(); + assertThat(tag.asString()).isEqualTo("TAG"); + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/DSATest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/DSATest.java new file mode 100644 index 000000000..c247d110a --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/DSATest.java @@ -0,0 +1,144 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class DSATest extends TestBase { + + public DSATest() { + super(RuleSets.rulesOf(PythonCryptoDSA.class)); + } + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/publickey/DSATestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + switch (findingId) { + case 0 -> { + // DSA.generate(bits=2048) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue kSz = detectionStore.getDetectionValues().get(0); + assertThat(kSz).isInstanceOf(KeySize.class); + assertThat(((KeySize) kSz).asString()).isEqualTo("2048"); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PrivateKey.class); + assertThat(root.getChildren()).hasSize(3); + assertThat(root.asString()).isEqualTo("DSA"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + + INode sig = root.getChildren().get(Signature.class); + assertThat(sig).isNotNull(); + assertThat(sig.getChildren().get(Oid.class).asString()) + .isEqualTo("1.2.840.10040.4.1"); + + INode kgen = root.getChildren().get(KeyGeneration.class); + assertThat(kgen).isNotNull(); + + INode klen = root.getChildren().get(KeyLength.class); + assertThat(klen).isNotNull(); + assertThat(klen.asString()).isEqualTo("2048"); + } + case 1 -> { + // DSA.construct(...) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + assertThat( + ((KeyAction) detectionStore.getDetectionValues().get(0)) + .getAction()) + .isEqualTo(KeyAction.Action.GENERATION); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("DSA"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat( + root.getChildren() + .get(Signature.class) + .getChildren() + .get(Oid.class) + .asString()) + .isEqualTo("1.2.840.10040.4.1"); + } + case 2 -> { + // DSA.import_key(...) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + assertThat( + ((KeyAction) detectionStore.getDetectionValues().get(0)) + .getAction()) + .isEqualTo(KeyAction.Action.GENERATION); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("DSA"); + assertThat( + root.getChildren() + .get(Signature.class) + .getChildren() + .get(Oid.class) + .asString()) + .isEqualTo("1.2.840.10040.4.1"); + } + default -> throw new AssertionError("Unexpected findingId: " + findingId); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java new file mode 100644 index 000000000..6e3af6ba3 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java @@ -0,0 +1,143 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ECCTest extends TestBase { + + public ECCTest() { + super(RuleSets.rulesOf(PythonCryptoECC.class)); + } + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + switch (findingId) { + case 0 -> { + // ECC.generate(curve="Ed25519") + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue v = detectionStore.getDetectionValues().get(0); + assertThat(v).isInstanceOf(Curve.class); + assertThat(v.asString()).isEqualTo("Ed25519"); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PrivateKey.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("EC-Edwards25519"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + + INode pke = root.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren()).hasSize(2); + assertThat(pke.asString()).isEqualTo("EC-Edwards25519"); + assertThat(pke.getChildren().get(EllipticCurve.class).asString()) + .isEqualTo("Edwards25519"); + assertThat(pke.getChildren().get(Oid.class).asString()) + .isEqualTo("1.2.840.10045.2.1"); + } + case 1 -> { + // ECC.construct(curve="Curve448", seed=b"A" * 56) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + IValue v = detectionStore.getDetectionValues().get(0); + assertThat(v).isInstanceOf(Curve.class); + assertThat(v.asString()).isEqualTo("Curve448"); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("EC-Curve448"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + + INode pke = root.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren()).hasSize(2); + assertThat(pke.asString()).isEqualTo("EC-Curve448"); + assertThat(pke.getChildren().get(EllipticCurve.class).asString()) + .isEqualTo("Curve448"); + assertThat(pke.getChildren().get(Oid.class).asString()) + .isEqualTo("1.2.840.10045.2.1"); + } + case 2 -> { + // ECC.import_key(...) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + assertThat( + ((KeyAction) detectionStore.getDetectionValues().get(0)) + .getAction()) + .isEqualTo(KeyAction.Action.GENERATION); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("EC"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat( + root.getChildren() + .get(PublicKeyEncryption.class) + .getChildren() + .get(Oid.class) + .asString()) + .isEqualTo("1.2.840.10045.2.1"); + } + default -> throw new AssertionError("Unexpected findingId: " + findingId); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ElGamalTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ElGamalTest.java new file mode 100644 index 000000000..050dd9292 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ElGamalTest.java @@ -0,0 +1,100 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ElGamalTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/publickey/ElGamalTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + switch (findingId) { + case 0 -> { + // ElGamal.generate(2048, None) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue v = detectionStore.getDetectionValues().get(0); + assertThat(v).isInstanceOf(KeySize.class); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PrivateKey.class); + assertThat(root.getChildren()).hasSize(3); + assertThat(root.asString()).isEqualTo("ElGamal"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKeyEncryption.class)).isNotNull(); + assertThat(root.getChildren().get(KeyLength.class)).isNotNull(); + } + case 1 -> { + // ElGamal.construct((2,3,4)) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + assertThat( + ((KeyAction) detectionStore.getDetectionValues().get(0)) + .getAction()) + .isEqualTo(KeyAction.Action.GENERATION); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("ElGamal"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKeyEncryption.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKeyEncryption.class).getChildren()) + .isEmpty(); + } + default -> throw new AssertionError("Unexpected findingId: " + findingId); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/RSATest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/RSATest.java new file mode 100644 index 000000000..27e1cfdcd --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/RSATest.java @@ -0,0 +1,146 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.publickey; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class RSATest extends TestBase { + + public RSATest() { + super(RuleSets.rulesOf(PythonCryptoRSA.class)); + } + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/publickey/RSATestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + switch (findingId) { + case 0 -> { + // RSA.generate(bits=2048) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(PrivateKeyContext.class); + IValue kSz = detectionStore.getDetectionValues().get(0); + assertThat(kSz).isInstanceOf(KeySize.class); + assertThat(((KeySize) kSz).asString()).isEqualTo("2048"); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(PrivateKey.class); + assertThat(root.getChildren()).hasSize(3); + assertThat(root.asString()).isEqualTo("RSA"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + + INode pke = root.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren().get(Oid.class).asString()) + .isEqualTo("1.2.840.113549.1.1.1"); + + INode kgen = root.getChildren().get(KeyGeneration.class); + assertThat(kgen).isNotNull(); + + INode klen = root.getChildren().get(KeyLength.class); + assertThat(klen).isNotNull(); + assertThat(klen.asString()).isEqualTo("2048"); + } + case 1 -> { + // RSA.construct(...) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + IValue kAction = detectionStore.getDetectionValues().get(0); + assertThat(kAction).isInstanceOf(KeyAction.class); + assertThat(((KeyAction) kAction).getAction()) + .isEqualTo(KeyAction.Action.GENERATION); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("RSA"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat(root.getChildren().get(PublicKeyEncryption.class)).isNotNull(); + assertThat( + root.getChildren() + .get(PublicKeyEncryption.class) + .getChildren() + .get(Oid.class) + .asString()) + .isEqualTo("1.2.840.113549.1.1.1"); + } + case 2 -> { + // RSA.import_key(...) + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(KeyContext.class); + assertThat( + ((KeyAction) detectionStore.getDetectionValues().get(0)) + .getAction()) + .isEqualTo(KeyAction.Action.GENERATION); + assertThat(detectionStore.getChildren()).isEmpty(); + + INode root = nodes.get(0); + assertThat(root.getKind()).isEqualTo(Key.class); + assertThat(root.getChildren()).hasSize(2); + assertThat(root.asString()).isEqualTo("RSA"); + assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); + assertThat( + root.getChildren() + .get(PublicKeyEncryption.class) + .getChildren() + .get(Oid.class) + .asString()) + .isEqualTo("1.2.840.113549.1.1.1"); + } + default -> throw new AssertionError("Unexpected findingId: " + findingId); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSSignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSSignTest.java new file mode 100644 index 000000000..1f63da7a4 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSSignTest.java @@ -0,0 +1,128 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class DSSSignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/DSSSignTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("DSS"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("SIGN"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(4); + assertThat(sig.asString()).isEqualTo("DSA-SHA-256"); + + INode oid = sig.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.3.2"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("DSA"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode sign = sig.getChildren().get(Sign.class); + assertThat(sign).isNotNull(); + assertThat(sign.getChildren()).isEmpty(); + assertThat(sign.asString()).isEqualTo("SIGN"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSVerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSVerifyTest.java new file mode 100644 index 000000000..f953bb88d --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/DSSVerifyTest.java @@ -0,0 +1,128 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class DSSVerifyTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/DSSVerifyTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("DSS"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("VERIFY"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(4); + assertThat(sig.asString()).isEqualTo("DSA-SHA-256"); + + INode oid = sig.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("2.16.840.1.101.3.4.3.2"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("DSA"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode verify = sig.getChildren().get(Verify.class); + assertThat(verify).isNotNull(); + assertThat(verify.getChildren()).isEmpty(); + assertThat(verify.asString()).isEqualTo("VERIFY"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSASignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSASignTest.java new file mode 100644 index 000000000..44c82d828 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSASignTest.java @@ -0,0 +1,132 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ECDSASignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/ECDSASignTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("ECDSA"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("SIGN"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(4); + assertThat(sig.asString()).isEqualTo("ECDSA-SHA-256"); + + INode oid = sig.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.10045.4.3.2"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("EC"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren().get(Oid.class).asString()).isEqualTo("1.2.840.10045.2.1"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode sign = sig.getChildren().get(Sign.class); + assertThat(sign).isNotNull(); + assertThat(sign.getChildren()).isEmpty(); + assertThat(sign.asString()).isEqualTo("SIGN"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSAVerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSAVerifyTest.java new file mode 100644 index 000000000..e34b57645 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/ECDSAVerifyTest.java @@ -0,0 +1,132 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class ECDSAVerifyTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/ECDSAVerifyTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("ECDSA"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("VERIFY"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(4); + assertThat(sig.asString()).isEqualTo("ECDSA-SHA-256"); + + INode oid = sig.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.10045.4.3.2"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("EC"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren().get(Oid.class).asString()).isEqualTo("1.2.840.10045.2.1"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode verify = sig.getChildren().get(Verify.class); + assertThat(verify).isNotNull(); + assertThat(verify.getChildren()).isEmpty(); + assertThat(verify.asString()).isEqualTo("VERIFY"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSASignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSASignTest.java new file mode 100644 index 000000000..8f4f6d267 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSASignTest.java @@ -0,0 +1,127 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class EdDSASignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/EdDSASignTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("EDDSA"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("SIGN"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(3); + assertThat(sig.asString()).isEqualTo("EdDSA"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("EC"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren().get(Oid.class).asString()).isEqualTo("1.2.840.10045.2.1"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-512"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("512"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1024"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode sign = sig.getChildren().get(Sign.class); + assertThat(sign).isNotNull(); + assertThat(sign.getChildren()).isEmpty(); + assertThat(sign.asString()).isEqualTo("SIGN"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSAVerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSAVerifyTest.java new file mode 100644 index 000000000..12582c85d --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/EdDSAVerifyTest.java @@ -0,0 +1,127 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class EdDSAVerifyTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/EdDSAVerifyTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("EDDSA"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("VERIFY"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(3); + assertThat(sig.asString()).isEqualTo("EdDSA"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("EC"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + assertThat(pke.getChildren().get(Oid.class).asString()).isEqualTo("1.2.840.10045.2.1"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-512"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.3"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("512"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("1024"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode verify = sig.getChildren().get(Verify.class); + assertThat(verify).isNotNull(); + assertThat(verify.getChildren()).isEmpty(); + assertThat(verify.asString()).isEqualTo("VERIFY"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15SignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15SignTest.java new file mode 100644 index 000000000..4fd919bd5 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15SignTest.java @@ -0,0 +1,141 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PKCS1v15SignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/PKCS1v15SignTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RSA"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("SIGN"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(4); + assertThat(sig.asString()).isEqualTo("RSA-PKCS1-1.5-SHA-256"); + + INode oid = sig.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.113549.1.1.11"); + + INode padding = sig.getChildren().get(Padding.class); + assertThat(padding).isNotNull(); + assertThat(padding.getChildren()).isEmpty(); + assertThat(padding.asString()).isEqualTo("PKCS1"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("RSA"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + INode pkeOid = pke.getChildren().get(Oid.class); + assertThat(pkeOid).isNotNull(); + assertThat(pkeOid.getChildren()).isEmpty(); + assertThat(pkeOid.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode sign = sig.getChildren().get(Sign.class); + assertThat(sign).isNotNull(); + assertThat(sign.getChildren()).isEmpty(); + assertThat(sign.asString()).isEqualTo("SIGN"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15VerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15VerifyTest.java new file mode 100644 index 000000000..d5486d323 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PKCS1v15VerifyTest.java @@ -0,0 +1,142 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PKCS1v15VerifyTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/PKCS1v15VerifyTestFile.py", + this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RSA"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("VERIFY"); + + // translation + assertThat(nodes).hasSize(1); + INode sig = nodes.get(0); + assertThat(sig.getKind()).isEqualTo(Signature.class); + assertThat(sig.getChildren()).hasSize(4); + assertThat(sig.asString()).isEqualTo("RSA-PKCS1-1.5-SHA-256"); + + INode oid = sig.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.113549.1.1.11"); + + INode padding = sig.getChildren().get(Padding.class); + assertThat(padding).isNotNull(); + assertThat(padding.getChildren()).isEmpty(); + assertThat(padding.asString()).isEqualTo("PKCS1"); + + INode key = sig.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("RSA"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + INode pkeOid = pke.getChildren().get(Oid.class); + assertThat(pkeOid).isNotNull(); + assertThat(pkeOid.getChildren()).isEmpty(); + assertThat(pkeOid.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + INode md = sig.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode verify = sig.getChildren().get(Verify.class); + assertThat(verify).isNotNull(); + assertThat(verify.getChildren()).isEmpty(); + assertThat(verify.asString()).isEqualTo("VERIFY"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSSignTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSSignTest.java new file mode 100644 index 000000000..f87f57004 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSSignTest.java @@ -0,0 +1,135 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PSSSignTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/PSSSignTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RSA-PSS"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("SIGN"); + + // translation + assertThat(nodes).hasSize(1); + INode pss = nodes.get(0); + assertThat(pss.getKind()).isEqualTo(ProbabilisticSignatureScheme.class); + assertThat(pss.getChildren()).hasSize(4); + assertThat(pss.asString()).isEqualTo("RSA-PSS"); + + INode oid = pss.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.113549.1.1.10"); + + INode key = pss.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("RSA"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + INode pkeOid = pke.getChildren().get(Oid.class); + assertThat(pkeOid).isNotNull(); + assertThat(pkeOid.getChildren()).isEmpty(); + assertThat(pkeOid.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + INode md = pss.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode sign = pss.getChildren().get(Sign.class); + assertThat(sign).isNotNull(); + assertThat(sign.getChildren()).isEmpty(); + assertThat(sign.asString()).isEqualTo("SIGN"); + } + } +} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSVerifyTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSVerifyTest.java new file mode 100644 index 000000000..6ada8cfb9 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/signature/PSSVerifyTest.java @@ -0,0 +1,135 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class PSSVerifyTest extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/signature/PSSVerifyTestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (findingId == 2) { + // detection store + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()) + .isInstanceOf(SignatureContext.class); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("RSA-PSS"); + + DetectionStore sigStore = + getStoreOfValueType(SignatureAction.class, detectionStore.getChildren()); + assertThat(sigStore).isNotNull(); + assertThat(sigStore.getDetectionValues()).hasSize(1); + assertThat(sigStore.getDetectionValueContext()).isInstanceOf(SignatureContext.class); + IValue sigValue = sigStore.getDetectionValues().get(0); + assertThat(sigValue).isInstanceOf(SignatureAction.class); + assertThat(sigValue.asString()).isEqualTo("VERIFY"); + + // translation + assertThat(nodes).hasSize(1); + INode pss = nodes.get(0); + assertThat(pss.getKind()).isEqualTo(ProbabilisticSignatureScheme.class); + assertThat(pss.getChildren()).hasSize(4); + assertThat(pss.asString()).isEqualTo("RSA-PSS"); + + INode oid = pss.getChildren().get(Oid.class); + assertThat(oid).isNotNull(); + assertThat(oid.getChildren()).isEmpty(); + assertThat(oid.asString()).isEqualTo("1.2.840.113549.1.1.10"); + + INode key = pss.getChildren().get(Key.class); + assertThat(key).isNotNull(); + assertThat(key.asString()).isEqualTo("RSA"); + assertThat(key.getChildren()).hasSize(2); + INode keyGen = key.getChildren().get(KeyGeneration.class); + assertThat(keyGen).isNotNull(); + assertThat(keyGen.getChildren()).isEmpty(); + assertThat(keyGen.asString()).isEqualTo("KEYGENERATION"); + INode pke = key.getChildren().get(PublicKeyEncryption.class); + assertThat(pke).isNotNull(); + INode pkeOid = pke.getChildren().get(Oid.class); + assertThat(pkeOid).isNotNull(); + assertThat(pkeOid.getChildren()).isEmpty(); + assertThat(pkeOid.asString()).isEqualTo("1.2.840.113549.1.1.1"); + + INode md = pss.getChildren().get(MessageDigest.class); + assertThat(md).isNotNull(); + assertThat(md.asString()).isEqualTo("SHA-256"); + assertThat(md.getChildren()).hasSize(4); + INode mdOid = md.getChildren().get(Oid.class); + assertThat(mdOid).isNotNull(); + assertThat(mdOid.getChildren()).isEmpty(); + assertThat(mdOid.asString()).isEqualTo("2.16.840.1.101.3.4.2.1"); + INode digestSize = md.getChildren().get(DigestSize.class); + assertThat(digestSize).isNotNull(); + assertThat(digestSize.getChildren()).isEmpty(); + assertThat(digestSize.asString()).isEqualTo("256"); + INode blockSize = md.getChildren().get(BlockSize.class); + assertThat(blockSize).isNotNull(); + assertThat(blockSize.getChildren()).isEmpty(); + assertThat(blockSize.asString()).isEqualTo("512"); + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + + INode verify = pss.getChildren().get(Verify.class); + assertThat(verify).isNotNull(); + assertThat(verify.getChildren()).isEmpty(); + assertThat(verify.asString()).isEqualTo("VERIFY"); + } + } +} From 83c768105090e58a580f66711165f9771ae7bffe Mon Sep 17 00:00:00 2001 From: san-zrl Date: Mon, 5 Oct 2026 11:58:02 +0200 Subject: [PATCH 10/24] fix/DH.key_agreement detection rule Signed-off-by: san-zrl --- .../PythonCryptoKeyAgreement.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java index 7f35538c2..637c0e583 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.java @@ -101,8 +101,9 @@ private PythonCryptoKeyAgreement() { .forObjectTypes("Crypto.Protocol.DH", "Cryptodome.Protocol.DH") .forMethods("key_agreement") .shouldBeDetectedAs(new ValueActionFactory<>("ECDH")) - .withMethodParameter(ANY) // kdf - .withMethodParameter( + .withNamedMethodParameter("kdf", ANY) // kdf + .withOptionalNamedMethodParameter( + "static_priv", ANY) // Crypto.PublicKey.ECC.ECCKey or Cryptodome.PublicKey.ECC.ECCKey .shouldBeDetectedAs(new AlgorithmFactory<>()) .asChildOfParameterWithId(-1) @@ -113,7 +114,32 @@ private PythonCryptoKeyAgreement() { IMPORT_X25519_PRIVATE_KEY, IMPORT_X448_PRIVATE_KEY)) .toList()) - .withMethodParameter( + .withOptionalNamedMethodParameter( + "static_pub", + ANY) // Crypto.PublicKey.ECC.ECCKey or Cryptodome.PublicKey.ECC.ECCKey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules( + Stream.concat( + RuleSets.rulesOf(PythonCryptoECC.class).stream(), + Stream.of( + IMPORT_X25519_PUBLIC_KEY, + IMPORT_X448_PUBLIC_KEY)) + .toList()) + .withOptionalNamedMethodParameter( + "eph_priv", + ANY) // Crypto.PublicKey.ECC.ECCKey or Cryptodome.PublicKey.ECC.ECCKey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules( + Stream.concat( + RuleSets.rulesOf(PythonCryptoECC.class).stream(), + Stream.of( + IMPORT_X25519_PRIVATE_KEY, + IMPORT_X448_PRIVATE_KEY)) + .toList()) + .withOptionalNamedMethodParameter( + "eph_pub", ANY) // Crypto.PublicKey.ECC.ECCKey or Cryptodome.PublicKey.ECC.ECCKey .shouldBeDetectedAs(new AlgorithmFactory<>()) .asChildOfParameterWithId(-1) From 343be2069eb65657dd7641106d51102a17fb2864 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Mon, 5 Oct 2026 14:44:32 +0200 Subject: [PATCH 11/24] fix/MGF1 parameter name is mgfSeed Signed-off-by: san-zrl --- .../detection/pycrypto/signature/PythonCryptoSignature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java index 090769d0e..b2942ef4d 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java @@ -127,7 +127,7 @@ private PythonCryptoSignature() { .forObjectTypes("Crypto.Signature.pss", "Cryptodome.Signature.pss") .forMethods("MGF1") .shouldBeDetectedAs(new ValueActionFactory<>("MGF1")) - .withNamedMethodParameter("msgSeed", ANY) + .withNamedMethodParameter("mgfSeed", ANY) .withNamedMethodParameter("maskLen", "int") .withNamedMethodParameter("hash_gen", ANY) // Crypto.Hash.* or Cryptodome.Hash.* .shouldBeDetectedAs(new AlgorithmFactory<>()) From 40d437488c93679dcb47d481c448b18c960a07d2 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Mon, 5 Oct 2026 14:59:37 +0200 Subject: [PATCH 12/24] fix/place holder size in X25519TestFile.py and X448TestFile.py Signed-off-by: san-zrl --- .../rules/detection/pycrypto/keyagreement/X25519TestFile.py | 4 ++-- .../rules/detection/pycrypto/keyagreement/X448TestFile.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py b/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py index e99f2d72e..04fbad759 100644 --- a/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/keyagreement/X25519TestFile.py @@ -4,8 +4,8 @@ def kdf(x): return SHAKE128.new(x).read(32) # Noncompliant {{(ExtendableOutputFunction) SHAKE128}} -pub_key = import_x25519_public_key(b'some 32 bytes public key') -priv_key = import_x25519_private_key(b'some 32 bytes private key') +pub_key = import_x25519_public_key(b'\xab' * 32) +priv_key = import_x25519_private_key(b'\xcd' * 32) session_key = key_agreement( # Noncompliant {{(KeyAgreement) x25519}} kdf=kdf, diff --git a/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py b/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py index bef21c389..024eeb19d 100644 --- a/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/keyagreement/X448TestFile.py @@ -4,8 +4,8 @@ def kdf(x): return SHAKE128.new(x).read(32) # Noncompliant {{(ExtendableOutputFunction) SHAKE128}} -pub_key = import_x448_public_key(b'some 32 bytes public key') -priv_key = import_x448_private_key(b'some 32 bytes private key') +pub_key = import_x448_public_key(b'\xab' * 56) +priv_key = import_x448_private_key(b'\xcd' * 56) session_key = key_agreement( # Noncompliant {{(KeyAgreement) x448}} kdf=kdf, From 612572f169b6b1b6a8686d93d87c742a01a780ee Mon Sep 17 00:00:00 2001 From: san-zrl Date: Mon, 5 Oct 2026 15:51:07 +0200 Subject: [PATCH 13/24] fix/PSS option is salt_bytes Signed-off-by: san-zrl --- .../detection/pycrypto/signature/PythonCryptoSignature.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java index b2942ef4d..6c5f603e8 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java @@ -112,7 +112,7 @@ private PythonCryptoSignature() { .asChildOfParameterWithId(-1) .addDependingDetectionRules(RuleSets.rulesOf(PythonCryptoRSA.class)) .withOptionalNamedMethodParameter("mask_func", ANY) - .withOptionalNamedMethodParameter("salt", "int") + .withOptionalNamedMethodParameter("salt_bytes", "int") .shouldBeDetectedAs(new SaltSizeFactory<>(UnitType.BYTE)) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("rand_func", ANY) From 8710625c5686673cbf558cc36b035671e3c6407f Mon Sep 17 00:00:00 2001 From: san-zrl Date: Mon, 5 Oct 2026 16:05:48 +0200 Subject: [PATCH 14/24] fix/PCD SHA512 requires truncate as str parameter Signed-off-by: san-zrl --- engine/src/main/java/com/ibm/engine/utils/Utils.java | 6 +++++- .../rules/detection/pycrypto/hash/PythonCryptoHash.java | 2 +- .../rules/detection/pycrypto/hash/SHA512_224TestFile.py | 2 +- .../rules/detection/pycrypto/hash/SHA512_256TestFile.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/engine/src/main/java/com/ibm/engine/utils/Utils.java b/engine/src/main/java/com/ibm/engine/utils/Utils.java index e3ac33f17..50af9e565 100644 --- a/engine/src/main/java/com/ibm/engine/utils/Utils.java +++ b/engine/src/main/java/com/ibm/engine/utils/Utils.java @@ -118,7 +118,11 @@ public static Optional bitSizeValueToBitSizeInteger( if (resolvedValue.value() instanceof Integer i) { return Optional.of(i); } else if (resolvedValue.value() instanceof String str) { - return Optional.of(Utils.byteSizeToBitSize(str.getBytes().length)); + try { + return Optional.of(Integer.parseInt(str)); + } catch (NumberFormatException e) { + return Optional.of(Utils.byteSizeToBitSize(str.getBytes().length)); + } } else { return Optional.empty(); } diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java index b51be775c..8f67f0d65 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java @@ -75,7 +75,7 @@ private PythonCryptoHash() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("SHA512")) .withOptionalNamedMethodParameter("data", ANY) - .withOptionalNamedMethodParameter("truncate", "int") + .withOptionalNamedMethodParameter("truncate", "str") .shouldBeDetectedAs(new DigestSizeFactory<>(UnitType.BIT)) .asChildOfParameterWithId(-1) .buildForContext(new DigestContext(Map.of("preHash", "SHA512"))) diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py index 3fbf6f77f..a6a808325 100644 --- a/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_224TestFile.py @@ -1,3 +1,3 @@ from Crypto.Hash import SHA512 -hash_sha512 = SHA512.new(truncate=224) # Noncompliant {{(MessageDigest) SHA-512/224}} +hash_sha512 = SHA512.new(truncate="224") # Noncompliant {{(MessageDigest) SHA-512/224}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py index 05534dc69..d41a8794e 100644 --- a/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/hash/SHA512_256TestFile.py @@ -1,3 +1,3 @@ from Crypto.Hash import SHA512 -hash_sha512 = SHA512.new(truncate=256) # Noncompliant {{(MessageDigest) SHA-512/256}} +hash_sha512 = SHA512.new(truncate="256") # Noncompliant {{(MessageDigest) SHA-512/256}} From 42957e7236d7151ef3729eddddae1f7570c5ccca Mon Sep 17 00:00:00 2001 From: san-zrl Date: Mon, 5 Oct 2026 17:00:23 +0200 Subject: [PATCH 15/24] fix/PKCS1_OAEP option is spelled mgfunc Signed-off-by: san-zrl --- .../rules/detection/pycrypto/cipher/PythonCryptoCipher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java index a2b87baa8..bdbbadd91 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java @@ -246,7 +246,7 @@ private PythonCryptoCipher() { .withOptionalNamedMethodParameter("hashAlgo", ANY) .shouldBeDetectedAs(new AlgorithmFactory<>()) .asChildOfParameterWithId(-1) - .withOptionalNamedMethodParameter("msgfunc", ANY) + .withOptionalNamedMethodParameter("mgfunc", ANY) .withOptionalNamedMethodParameter("label", ANY) .withOptionalNamedMethodParameter("randfunc", ANY) .buildForContext(new CipherContext()) From dab8ca3256409108a938dee678397e9a175d41fb Mon Sep 17 00:00:00 2001 From: san-zrl Date: Tue, 6 Oct 2026 10:20:45 +0200 Subject: [PATCH 16/24] fix/allow named parameters for key and mode in AES, DES, 3DES, Blowfish, Cast and ARC2 ciphers Signed-off-by: san-zrl --- .../pycrypto/cipher/PythonCryptoCipher.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java index bdbbadd91..5648a43d4 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java @@ -72,8 +72,8 @@ private PythonCryptoCipher() { .forObjectTypes("Crypto.Cipher.AES", "Cryptodome.Cipher.AES") .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("AES")) - .withMethodParameter(ANY) - .withMethodParameter(ANY) // Crypto.Cipher.AES.* or Cryptodome.Cipher.AES.* + .withNamedMethodParameter("key", ANY) + .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.AES.* or Cryptodome.Cipher.AES.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -95,8 +95,8 @@ private PythonCryptoCipher() { .forObjectTypes("Crypto.Cipher.DES", "Cryptodome.Cipher.DES") .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("DES")) - .withMethodParameter(ANY) - .withMethodParameter(ANY) // Crypto.Cipher.DES.* or Cryptodome.Cipher.DES.* + .withNamedMethodParameter("key", ANY) + .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.DES.* or Cryptodome.Cipher.DES.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -114,8 +114,8 @@ private PythonCryptoCipher() { .forObjectTypes("Crypto.Cipher.DES3", "Cryptodome.Cipher.DES3") .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("3DES")) - .withMethodParameter(ANY) - .withMethodParameter(ANY) // Crypto.Cipher.DES3.* or Cryptodome.Cipher.DES3.* + .withNamedMethodParameter("key", ANY) + .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.DES3.* or Cryptodome.Cipher.DES3.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -133,9 +133,8 @@ private PythonCryptoCipher() { .forObjectTypes("Crypto.Cipher.Blowfish", "Cryptodome.Cipher.Blowfish") .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("Blowfish")) - .withMethodParameter(ANY) - .withMethodParameter( - ANY) // Crypto.Cipher.Blowfish.* or Cryptodome.Cipher.Blowfish.* + .withNamedMethodParameter("key", ANY) + .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.Blowfish.* or Cryptodome.Cipher.Blowfish.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -153,8 +152,8 @@ private PythonCryptoCipher() { .forObjectTypes("Crypto.Cipher.CAST", "Cryptodome.Cipher.CAST") .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("CAST5")) - .withMethodParameter(ANY) - .withMethodParameter(ANY) // Crypto.Cipher.CAST.* or Cryptodome.Cipher.CAST.* + .withNamedMethodParameter("key", ANY) + .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.CAST.* or Cryptodome.Cipher.CAST.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -172,8 +171,8 @@ private PythonCryptoCipher() { .forObjectTypes("Crypto.Cipher.ARC2", "Cryptodome.Cipher.ARC2") .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("RC2")) - .withMethodParameter(ANY) - .withMethodParameter(ANY) // Crypto.Cipher.ARC2.* or Cryptodome.Cipher.ARC2.* + .withNamedMethodParameter("key", ANY) + .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.ARC2.* or Cryptodome.Cipher.ARC2.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) From 64d51b6eb02df1ccc577e59ccc762fe353490cc9 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Tue, 6 Oct 2026 10:38:53 +0200 Subject: [PATCH 17/24] style: apply spotless formatting Signed-off-by: san-zrl --- .../pycrypto/cipher/PythonCryptoCipher.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java index 5648a43d4..04f3ee33c 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java @@ -73,7 +73,8 @@ private PythonCryptoCipher() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("AES")) .withNamedMethodParameter("key", ANY) - .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.AES.* or Cryptodome.Cipher.AES.* + .withNamedMethodParameter( + "mode", ANY) // Crypto.Cipher.AES.* or Cryptodome.Cipher.AES.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -96,7 +97,8 @@ private PythonCryptoCipher() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("DES")) .withNamedMethodParameter("key", ANY) - .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.DES.* or Cryptodome.Cipher.DES.* + .withNamedMethodParameter( + "mode", ANY) // Crypto.Cipher.DES.* or Cryptodome.Cipher.DES.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -115,7 +117,8 @@ private PythonCryptoCipher() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("3DES")) .withNamedMethodParameter("key", ANY) - .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.DES3.* or Cryptodome.Cipher.DES3.* + .withNamedMethodParameter( + "mode", ANY) // Crypto.Cipher.DES3.* or Cryptodome.Cipher.DES3.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -134,7 +137,8 @@ private PythonCryptoCipher() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("Blowfish")) .withNamedMethodParameter("key", ANY) - .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.Blowfish.* or Cryptodome.Cipher.Blowfish.* + .withNamedMethodParameter( + "mode", ANY) // Crypto.Cipher.Blowfish.* or Cryptodome.Cipher.Blowfish.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -153,7 +157,8 @@ private PythonCryptoCipher() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("CAST5")) .withNamedMethodParameter("key", ANY) - .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.CAST.* or Cryptodome.Cipher.CAST.* + .withNamedMethodParameter( + "mode", ANY) // Crypto.Cipher.CAST.* or Cryptodome.Cipher.CAST.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) @@ -172,7 +177,8 @@ private PythonCryptoCipher() { .forMethods("new") .shouldBeDetectedAs(new ValueActionFactory<>("RC2")) .withNamedMethodParameter("key", ANY) - .withNamedMethodParameter("mode", ANY) // Crypto.Cipher.ARC2.* or Cryptodome.Cipher.ARC2.* + .withNamedMethodParameter( + "mode", ANY) // Crypto.Cipher.ARC2.* or Cryptodome.Cipher.ARC2.* .shouldBeDetectedAs(new ModeFactory<>()) .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("iv", ANY) From 98586ad89a1fa32eab0e21141e6ee7a33a05d581 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Tue, 6 Oct 2026 11:44:12 +0200 Subject: [PATCH 18/24] fix/simplify enrcypt and decrpyt rules Signed-off-by: san-zrl --- .../rules/detection/pycrypto/cipher/PythonCryptoCipher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java index 04f3ee33c..72dad7457 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java @@ -50,7 +50,7 @@ private PythonCryptoCipher() { .forObjectTypes(ANY) .forMethods("encrypt", "encrypt_and_digest") .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) - .withMethodParameter(ANY) + .withAnyParameters() .buildForContext(new CipherContext()) .inBundle(() -> "PyCrypto") .withoutDependingDetectionRules(); @@ -61,7 +61,7 @@ private PythonCryptoCipher() { .forObjectTypes(ANY) .forMethods("decrypt", "decrypt_and_verify") .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) - .withMethodParameter(ANY) + .withAnyParameters() .buildForContext(new CipherContext()) .inBundle(() -> "PyCrypto") .withoutDependingDetectionRules(); From fd6121073112af485d393796086be6c4938aefb7 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Tue, 6 Oct 2026 12:49:31 +0200 Subject: [PATCH 19/24] fix/add EdDSA-specific sign/verify rules without requiring a hash child Signed-off-by: san-zrl --- .../signature/PythonCryptoSignature.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java index 6c5f603e8..5d3b47435 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java @@ -134,7 +134,7 @@ private PythonCryptoSignature() { .asChildOfParameterWithId(-1) .buildForContext(new SignatureContext()) .inBundle(() -> "PyCrypto") - .withDependingDetectionRules(List.of(SIGN, VERIFY)); + .withoutDependingDetectionRules(); // DSS signature scheme - sign and verify methods (called on the result of .new()) private static final IDetectionRule DSS = @@ -208,6 +208,29 @@ private PythonCryptoSignature() { .inBundle(() -> "PyCrypto") .withDependingDetectionRules(List.of(SIGN, VERIFY)); + private static final IDetectionRule EDDSA_SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withNamedMethodParameter("msg_or_hash", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule EDDSA_VERIFY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("verify") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.VERIFY)) + .withNamedMethodParameter("msg_or_hash", ANY) + .withNamedMethodParameter("signature", ANY) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + // EdDSA import private key private static final IDetectionRule EDDSA_IMPORT_PRIVATE_KEY = new DetectionRuleBuilder() @@ -256,7 +279,7 @@ private PythonCryptoSignature() { .withOptionalNamedMethodParameter("context", ANY) .buildForContext(new SignatureContext()) .inBundle(() -> "PyCrypto") - .withDependingDetectionRules(List.of(SIGN, VERIFY)); + .withDependingDetectionRules(List.of(EDDSA_SIGN, EDDSA_VERIFY)); @Nonnull @Override From a84fa00f1f175979db60e7f4407ba06f3d98dbdb Mon Sep 17 00:00:00 2001 From: san-zrl Date: Tue, 6 Oct 2026 13:10:13 +0200 Subject: [PATCH 20/24] fix/use RFC 8410 OIDs for Ed25519, Ed448, X25519, X448 in PycaCurveMapper Signed-off-by: san-zrl --- .../mapper/mapper/pyca/PycaCurveMapper.java | 26 +++++++---------- .../java/com/ibm/mapper/model/PrivateKey.java | 4 +++ .../java/com/ibm/mapper/model/PublicKey.java | 4 +++ .../PycaPrivateKeyContextTranslator.java | 15 +++++++--- .../PycaPublicKeyContextTranslator.java | 15 +++++++--- .../pycrypto/publickey/ECCTestFile.py | 4 +-- .../detection/pycrypto/publickey/ECCTest.java | 29 +++++++------------ 7 files changed, 53 insertions(+), 44 deletions(-) diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java index b4cee3881..26b1229ff 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java @@ -20,14 +20,15 @@ package com.ibm.mapper.mapper.pyca; import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.algorithms.Ed25519; +import com.ibm.mapper.model.algorithms.Ed448; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X448; import com.ibm.mapper.model.curves.Brainpoolp256r1; import com.ibm.mapper.model.curves.Brainpoolp384r1; import com.ibm.mapper.model.curves.Brainpoolp512r1; -import com.ibm.mapper.model.curves.Curve25519; -import com.ibm.mapper.model.curves.Curve448; -import com.ibm.mapper.model.curves.Edwards25519; -import com.ibm.mapper.model.curves.Edwards448; import com.ibm.mapper.model.curves.Secp192r1; import com.ibm.mapper.model.curves.Secp224r1; import com.ibm.mapper.model.curves.Secp256k1; @@ -53,14 +54,13 @@ public final class PycaCurveMapper implements IMapper { @Nonnull @Override - public Optional parse( + public Optional parse( @Nullable String str, @Nonnull DetectionLocation detectionLocation) { if (str == null) { return Optional.empty(); } - @Nonnull String curve = str; - return switch (curve.toUpperCase().trim()) { + return switch (str.toUpperCase().trim()) { case "SECP192R1", "PRIME192V1", "P-192", "P192", "NIST P-192" -> Optional.of(new EllipticCurveAlgorithm(new Secp192r1(detectionLocation))); case "SECP224R1", "PRIME224V1", "P-224", "P224", "NIST P-224" -> @@ -73,14 +73,10 @@ public Optional parse( Optional.of(new EllipticCurveAlgorithm(new Secp521r1(detectionLocation))); case "SECP256K1" -> Optional.of(new EllipticCurveAlgorithm(new Secp256k1(detectionLocation))); - case "CURVE25519" -> - Optional.of(new EllipticCurveAlgorithm(new Curve25519(detectionLocation))); - case "ED25519" -> - Optional.of(new EllipticCurveAlgorithm(new Edwards25519(detectionLocation))); - case "CURVE448" -> - Optional.of(new EllipticCurveAlgorithm(new Curve448(detectionLocation))); - case "ED448" -> - Optional.of(new EllipticCurveAlgorithm(new Edwards448(detectionLocation))); + case "CURVE25519" -> Optional.of(new X25519(detectionLocation)); + case "ED25519" -> Optional.of(new Ed25519(detectionLocation)); + case "CURVE448" -> Optional.of(new X448(detectionLocation)); + case "ED448" -> Optional.of(new Ed448(detectionLocation)); case "BRAINPOOLP256R1" -> Optional.of(new EllipticCurveAlgorithm(new Brainpoolp256r1(detectionLocation))); case "BRAINPOOLP384R1" -> diff --git a/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java b/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java index 3fb96f585..97f12f3d1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java +++ b/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java @@ -30,6 +30,10 @@ public PrivateKey(@Nonnull Signature signature) { super(signature, PrivateKey.class); } + public PrivateKey(@Nonnull KeyAgreement keyAgreement) { + super(keyAgreement, PrivateKey.class); + } + public PrivateKey(@Nonnull Key key) { super(key, key.detectionLocation, PrivateKey.class); } diff --git a/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java b/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java index d080d7fee..6919e8db7 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java +++ b/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java @@ -30,6 +30,10 @@ public PublicKey(@Nonnull Signature signature) { super(signature, PublicKey.class); } + public PublicKey(@Nonnull KeyAgreement keyAgreement) { + super(keyAgreement, PublicKey.class); + } + public PublicKey(@Nonnull Key key) { super(key, key.detectionLocation, PublicKey.class); } diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java index dc1e4a91b..163134775 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPrivateKeyContextTranslator.java @@ -30,9 +30,11 @@ import com.ibm.mapper.mapper.pyca.PycaKeyBasedAlgorithmMapper; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.PrivateKey; import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; import com.ibm.mapper.model.functionality.KeyGeneration; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -65,11 +67,16 @@ public final class PycaPrivateKeyContextTranslator implements IContextTranslatio return mapper.parse(curve.asString(), detectionLocation) .map( ec -> { - PrivateKey privateKey = new PrivateKey((PublicKeyEncryption) ec); + // currently only GENERATE is used as key action in this context + PrivateKey privateKey; + if (ec instanceof Signature sig) { + privateKey = new PrivateKey(sig); + } else if (ec instanceof KeyAgreement ka) { + privateKey = new PrivateKey(ka); + } else { + privateKey = new PrivateKey((PublicKeyEncryption) ec); + } privateKey.put(new KeyGeneration(detectionLocation)); - // currently only GENERATE is - // used as key action is this - // context return privateKey; }); } diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java index f747fd4f0..63ed4b6d1 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaPublicKeyContextTranslator.java @@ -29,8 +29,10 @@ import com.ibm.mapper.mapper.pyca.PycaKeyBasedAlgorithmMapper; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.model.PublicKey; import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; import com.ibm.mapper.model.functionality.KeyGeneration; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -62,10 +64,15 @@ public final class PycaPublicKeyContextTranslator implements IContextTranslation return mapper.parse(value.asString(), detectionLocation) .map( algo -> { - PublicKey publicKey = new PublicKey((PublicKeyEncryption) algo); - // currently only GENERATE is - // used as key action is this - // context + // currently only GENERATE is used as key action in this context + PublicKey publicKey; + if (algo instanceof Signature sig) { + publicKey = new PublicKey(sig); + } else if (algo instanceof KeyAgreement ka) { + publicKey = new PublicKey(ka); + } else { + publicKey = new PublicKey((PublicKeyEncryption) algo); + } publicKey.put(new KeyGeneration(detectionLocation)); return publicKey; }); diff --git a/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py b/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py index c8b3d83cf..bdffd456f 100644 --- a/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/publickey/ECCTestFile.py @@ -1,6 +1,6 @@ from Crypto.PublicKey import ECC def test_ecc(): - key1 = ECC.generate(curve="Ed25519") # Noncompliant {{(PrivateKey) EC-Edwards25519}} - key2 = ECC.construct(curve="Curve448", seed=b"A" * 56) # Noncompliant {{(Key) EC-Curve448}} + key1 = ECC.generate(curve="Ed25519") # Noncompliant {{(PrivateKey) Ed25519}} + key2 = ECC.construct(curve="Curve448", seed=b"A" * 56) # Noncompliant {{(Key) x448}} key3 = ECC.import_key("abcdef") # Noncompliant {{(Key) EC}} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java index 6e3af6ba3..18c970a5e 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.java @@ -28,12 +28,13 @@ import com.ibm.engine.model.context.KeyContext; import com.ibm.engine.model.context.PrivateKeyContext; import com.ibm.engine.rule.RuleSets; -import com.ibm.mapper.model.EllipticCurve; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.PrivateKey; import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; import com.ibm.mapper.model.functionality.KeyGeneration; import com.ibm.plugin.TestBase; import java.util.List; @@ -76,17 +77,12 @@ public void asserts( INode root = nodes.get(0); assertThat(root.getKind()).isEqualTo(PrivateKey.class); assertThat(root.getChildren()).hasSize(2); - assertThat(root.asString()).isEqualTo("EC-Edwards25519"); + assertThat(root.asString()).isEqualTo("Ed25519"); assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); - INode pke = root.getChildren().get(PublicKeyEncryption.class); - assertThat(pke).isNotNull(); - assertThat(pke.getChildren()).hasSize(2); - assertThat(pke.asString()).isEqualTo("EC-Edwards25519"); - assertThat(pke.getChildren().get(EllipticCurve.class).asString()) - .isEqualTo("Edwards25519"); - assertThat(pke.getChildren().get(Oid.class).asString()) - .isEqualTo("1.2.840.10045.2.1"); + INode sig = root.getChildren().get(Signature.class); + assertThat(sig).isNotNull(); + assertThat(sig.getChildren().get(Oid.class).asString()).isEqualTo("1.3.101.112"); } case 1 -> { // ECC.construct(curve="Curve448", seed=b"A" * 56) @@ -101,17 +97,12 @@ public void asserts( INode root = nodes.get(0); assertThat(root.getKind()).isEqualTo(Key.class); assertThat(root.getChildren()).hasSize(2); - assertThat(root.asString()).isEqualTo("EC-Curve448"); + assertThat(root.asString()).isEqualTo("x448"); assertThat(root.getChildren().get(KeyGeneration.class)).isNotNull(); - INode pke = root.getChildren().get(PublicKeyEncryption.class); - assertThat(pke).isNotNull(); - assertThat(pke.getChildren()).hasSize(2); - assertThat(pke.asString()).isEqualTo("EC-Curve448"); - assertThat(pke.getChildren().get(EllipticCurve.class).asString()) - .isEqualTo("Curve448"); - assertThat(pke.getChildren().get(Oid.class).asString()) - .isEqualTo("1.2.840.10045.2.1"); + INode ka = root.getChildren().get(KeyAgreement.class); + assertThat(ka).isNotNull(); + assertThat(ka.getChildren().get(Oid.class).asString()).isEqualTo("1.3.101.111"); } case 2 -> { // ECC.import_key(...) From b7f0849e375776ef33c7cc7f9d1b398751517326 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Fri, 16 Oct 2026 14:54:31 +0200 Subject: [PATCH 21/24] fix: stop exporting KMAC security strength as a digest size KMAC(int, ...) added a DigestSize of 2 * parameterSetIdentifier, so KMAC128 carried 256 and KMAC256 carried 512. The CBOM output picks DigestSize before ParameterSetIdentifier, so those values were exported as the parameter set. The 128/256 is the security strength of the underlying cSHAKE, not the tag length, and the default tag length differs between libraries. Drop the DigestSize so KMAC128 and KMAC256 export 128 and 256. Output length is not captured, since CycloneDX has no field for it. Signed-off-by: san-zrl --- .../main/java/com/ibm/mapper/model/algorithms/KMAC.java | 6 ++++-- .../plugin/rules/detection/pycrypto/mac/KMAC128Test.java | 7 +++---- .../plugin/rules/detection/pycrypto/mac/KMAC256Test.java | 7 +++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java index 373d538e3..a6b68583f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java @@ -20,7 +20,6 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; -import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; @@ -71,6 +70,10 @@ public KMAC(@Nonnull DetectionLocation detectionLocation) { this.put(new CSHAKE(detectionLocation)); } + /** + * The parameter set identifier (128 or 256) denotes the security strength only. The output + * length is independent of it and its default differs between libraries, so it is not modelled. + */ public KMAC(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLocation) { /* KMAC is a hash function mostly used as a MAC */ super(NAME, Mac.class, detectionLocation); @@ -78,7 +81,6 @@ public KMAC(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLoca new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), detectionLocation)); this.put(new CSHAKE(parameterSetIdentifier, detectionLocation)); - this.put(new DigestSize(2 * parameterSetIdentifier, detectionLocation)); } public KMAC(@Nonnull final Class asKind, @Nonnull KMAC kmac) { diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java index 0e471fd74..fd08672ab 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC128Test.java @@ -77,15 +77,14 @@ public void asserts( assertThat(mac).isInstanceOf(KMAC.class); assertThat(mac.getKind()).isEqualTo(Mac.class); assertThat(mac.asString()).isEqualTo("KMAC128"); - assertThat(mac.getChildren()).hasSize(4); + assertThat(mac.getChildren()).hasSize(3); INode parameterSetIdentifier = mac.getChildren().get(ParameterSetIdentifier.class); assertThat(parameterSetIdentifier).isNotNull(); assertThat(parameterSetIdentifier.asString()).isEqualTo("128"); - INode digestSize = mac.getChildren().get(DigestSize.class); - assertThat(digestSize).isNotNull(); - assertThat(digestSize.asString()).isEqualTo("256"); + // the parameter set identifier is the security strength, not an output length + assertThat(mac.getChildren().get(DigestSize.class)).isNull(); INode cshake = mac.getChildren().get(ExtendableOutputFunction.class); assertThat(cshake).isNotNull(); diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java index 62a6987ee..6d66676f6 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/KMAC256Test.java @@ -77,15 +77,14 @@ public void asserts( assertThat(mac).isInstanceOf(KMAC.class); assertThat(mac.getKind()).isEqualTo(Mac.class); assertThat(mac.asString()).isEqualTo("KMAC256"); - assertThat(mac.getChildren()).hasSize(4); + assertThat(mac.getChildren()).hasSize(3); INode parameterSetIdentifier = mac.getChildren().get(ParameterSetIdentifier.class); assertThat(parameterSetIdentifier).isNotNull(); assertThat(parameterSetIdentifier.asString()).isEqualTo("256"); - INode digestSize = mac.getChildren().get(DigestSize.class); - assertThat(digestSize).isNotNull(); - assertThat(digestSize.asString()).isEqualTo("512"); + // the parameter set identifier is the security strength, not an output length + assertThat(mac.getChildren().get(DigestSize.class)).isNull(); INode cshake = mac.getChildren().get(ExtendableOutputFunction.class); assertThat(cshake).isNotNull(); From d05bd7a6d12640e32ce3f3bf2083cf1580f27e94 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Fri, 16 Oct 2026 14:54:44 +0200 Subject: [PATCH 22/24] fix: model TupleHash128/256 as a parameter set, not a digest size TupleHash(int, ...) stored the 128/256 as a DigestSize, but it is the variant's security strength; the default digest is 512 bits. The class also had no asString(), so both variants were named TupleHash. Store the value as a ParameterSetIdentifier and append it to the name, as KMAC and cSHAKE do, giving TupleHash128 and TupleHash256. The Bouncy Castle mapping uses the no-argument constructor and still reports plain TupleHash. Signed-off-by: san-zrl --- .../mapper/model/algorithms/TupleHash.java | 25 +++++- .../pycrypto/hash/TupleHash128TestFile.py | 2 +- .../pycrypto/hash/TupleHash256TestFile.py | 3 + .../pycrypto/hash/TupleHash128Test.java | 13 +-- .../pycrypto/hash/TupleHash256Test.java | 83 +++++++++++++++++++ 5 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 python/src/test/files/rules/detection/pycrypto/hash/TupleHash256TestFile.py create mode 100644 python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash256Test.java diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java index 9a765a455..f48673486 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java @@ -20,9 +20,11 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; -import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; import javax.annotation.Nonnull; /** @@ -51,8 +53,25 @@ public TupleHash(@Nonnull DetectionLocation detectionLocation) { super(NAME, ExtendableOutputFunction.class, detectionLocation); } - public TupleHash(int digestSize, @Nonnull DetectionLocation detectionLocation) { + /** Returns a name of the form "TupleHashXXX" where XXX is the parameter set identifier */ + @Override + @Nonnull + public String asString() { + final StringBuilder builtName = new StringBuilder(this.name); + final Optional parameterSetIdentifier = + this.hasChildOfType(ParameterSetIdentifier.class); + parameterSetIdentifier.ifPresent(node -> builtName.append(node.asString())); + return builtName.toString(); + } + + /** + * The parameter set identifier (128 or 256) denotes the security strength of the variant, not + * the digest length. + */ + public TupleHash(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLocation) { this(detectionLocation); - this.put(new DigestSize(digestSize, detectionLocation)); + this.put( + new ParameterSetIdentifier( + String.valueOf(parameterSetIdentifier), detectionLocation)); } } diff --git a/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py index cfdb54d28..6c9c11419 100644 --- a/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/hash/TupleHash128TestFile.py @@ -1,3 +1,3 @@ from Crypto.Hash import TupleHash128 -hash_t128 = TupleHash128.new() # Noncompliant {{(ExtendableOutputFunction) TupleHash}} +hash_t128 = TupleHash128.new() # Noncompliant {{(ExtendableOutputFunction) TupleHash128}} diff --git a/python/src/test/files/rules/detection/pycrypto/hash/TupleHash256TestFile.py b/python/src/test/files/rules/detection/pycrypto/hash/TupleHash256TestFile.py new file mode 100644 index 000000000..9613b062c --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/hash/TupleHash256TestFile.py @@ -0,0 +1,3 @@ +from Crypto.Hash import TupleHash256 + +hash_t256 = TupleHash256.new() # Noncompliant {{(ExtendableOutputFunction) TupleHash256}} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java index 4ba2c2ad0..9a2b50bcf 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash128Test.java @@ -28,6 +28,7 @@ import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.functionality.Digest; import com.ibm.plugin.TestBase; import java.util.List; @@ -65,12 +66,14 @@ public void asserts( INode md = nodes.get(0); assertThat(md).isInstanceOf(MessageDigest.class); assertThat(md.getChildren()).hasSize(2); - assertThat(md.asString()).isEqualTo("TupleHash"); + assertThat(md.asString()).isEqualTo("TupleHash128"); - INode digestSize = md.getChildren().get(DigestSize.class); - assertThat(digestSize).isNotNull(); - assertThat(digestSize.getChildren()).isEmpty(); - assertThat(digestSize.asString()).isEqualTo("128"); + // the parameter set identifier is the security strength, not a digest length + assertThat(md.getChildren().get(DigestSize.class)).isNull(); + INode parameterSetIdentifier = md.getChildren().get(ParameterSetIdentifier.class); + assertThat(parameterSetIdentifier).isNotNull(); + assertThat(parameterSetIdentifier.getChildren()).isEmpty(); + assertThat(parameterSetIdentifier.asString()).isEqualTo("128"); INode digest = md.getChildren().get(Digest.class); assertThat(digest).isNotNull(); diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash256Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash256Test.java new file mode 100644 index 000000000..4d1e0754f --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/hash/TupleHash256Test.java @@ -0,0 +1,83 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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 + * + * http://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. + */ +package com.ibm.plugin.rules.detection.pycrypto.hash; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ParameterSetIdentifier; +import com.ibm.mapper.model.functionality.Digest; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.plugins.python.api.PythonCheck; +import org.sonar.plugins.python.api.PythonVisitorContext; +import org.sonar.plugins.python.api.symbols.Symbol; +import org.sonar.plugins.python.api.tree.Tree; +import org.sonar.python.checks.utils.PythonCheckVerifier; + +public class TupleHash256Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pycrypto/hash/TupleHash256TestFile.py", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + assertThat(findingId).isZero(); + + assertThat(detectionStore.getDetectionValues()).hasSize(1); + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(DigestContext.class); + assertThat(detectionStore.getChildren()).isEmpty(); + IValue value = detectionStore.getDetectionValues().get(0); + assertThat(value).isInstanceOf(ValueAction.class); + assertThat(value.asString()).isEqualTo("TupleHash256"); + + assertThat(nodes).hasSize(1); + INode md = nodes.get(0); + assertThat(md).isInstanceOf(MessageDigest.class); + assertThat(md.getChildren()).hasSize(2); + assertThat(md.asString()).isEqualTo("TupleHash256"); + + // the parameter set identifier is the security strength, not a digest length + assertThat(md.getChildren().get(DigestSize.class)).isNull(); + INode parameterSetIdentifier = md.getChildren().get(ParameterSetIdentifier.class); + assertThat(parameterSetIdentifier).isNotNull(); + assertThat(parameterSetIdentifier.getChildren()).isEmpty(); + assertThat(parameterSetIdentifier.asString()).isEqualTo("256"); + + INode digest = md.getChildren().get(Digest.class); + assertThat(digest).isNotNull(); + assertThat(digest.getChildren()).isEmpty(); + assertThat(digest.asString()).isEqualTo("DIGEST"); + } +} From fc0ace24668bfbe791dabe17995af0de66c4cb04 Mon Sep 17 00:00:00 2001 From: san-zrl Date: Fri, 16 Oct 2026 14:55:45 +0200 Subject: [PATCH 23/24] test: check KMAC and TupleHash parameter set identifiers in the CBOM Assert that KMAC128/256 and TupleHash128/256 are exported under their own names with parameterSetIdentifier 128 or 256, so the security strength can no longer be replaced by an output length. Signed-off-by: san-zrl --- .../ibm/output/cyclonedx/AlgorithmTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java b/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java index d7a6f60eb..f27f4923f 100644 --- a/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java +++ b/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java @@ -31,12 +31,14 @@ import com.ibm.mapper.model.SaltLength; import com.ibm.mapper.model.algorithms.DSA; import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.KMAC; import com.ibm.mapper.model.algorithms.MGF1; import com.ibm.mapper.model.algorithms.PBKDF2; import com.ibm.mapper.model.algorithms.RSA; import com.ibm.mapper.model.algorithms.RSAssaPSS; import com.ibm.mapper.model.algorithms.SHA; import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.TupleHash; import com.ibm.mapper.model.curves.Secp256r1; import com.ibm.mapper.model.curves.Secp384r1; import com.ibm.mapper.model.functionality.Decrypt; @@ -410,4 +412,41 @@ void eccKeyAgree() { assertThat(cryptoProperties.getOid()).isEqualTo("1.3.132.1.12"); }); } + + @Test + void kmacParameterSetIdentifierIsSecurityStrength() { + for (final int strength : new int[] {128, 256}) { + this.assertsNode( + () -> new KMAC(strength, detectionLocation), + bom -> { + final Component component = componentByName(bom, "KMAC" + strength); + final AlgorithmProperties algorithmProperties = + component.getCryptoProperties().getAlgorithmProperties(); + assertThat(algorithmProperties.getParameterSetIdentifier()) + .isEqualTo(String.valueOf(strength)); + }); + } + } + + @Test + void tupleHashParameterSetIdentifierIsSecurityStrength() { + for (final int strength : new int[] {128, 256}) { + this.assertsNode( + () -> new TupleHash(strength, detectionLocation), + bom -> { + final Component component = componentByName(bom, "TupleHash" + strength); + final AlgorithmProperties algorithmProperties = + component.getCryptoProperties().getAlgorithmProperties(); + assertThat(algorithmProperties.getParameterSetIdentifier()) + .isEqualTo(String.valueOf(strength)); + }); + } + } + + private static Component componentByName(final org.cyclonedx.model.Bom bom, final String name) { + return bom.getComponents().stream() + .filter(c -> name.equals(c.getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("component not found: " + name)); + } } From ceca12dd4c3e2d53a113e14589bf2329aaa3cb2d Mon Sep 17 00:00:00 2001 From: san-zrl Date: Fri, 16 Oct 2026 14:57:33 +0200 Subject: [PATCH 24/24] feat: use named parameters for PyCryptodome MAC rules Replace withAnyParameters() on KMAC128, KMAC256 and Poly1305 with the parameters PyCryptodome documents (KMAC: key, data, mac_len, custom; Poly1305: key, cipher, nonce, data), each in its own private static final field. Capture the Poly1305 cipher and attach it under the MAC, and attach the CMAC ciphermod the same way HMAC does with digestmod. A Poly1305 with cipher=AES now has an AES child. Stop binding the CMAC mac_len to a digest size: it is the tag length, which the CBOM cannot express. The parameter is still accepted. Signed-off-by: san-zrl --- .../pycrypto/mac/PythonCryptoMac.java | 65 ++++++++++++------- .../contexts/PycaMacContextTranslator.java | 8 +++ .../pycrypto/mac/Poly1305TestFile.py | 5 +- .../detection/pycrypto/mac/Poly1305Test.java | 18 ++++- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java index 33bfa7b4f..7d9c8dd76 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java @@ -21,15 +21,12 @@ import static com.ibm.engine.detection.MethodMatcher.ANY; -import com.ibm.engine.model.Size.UnitType; import com.ibm.engine.model.context.MacContext; import com.ibm.engine.model.factory.AlgorithmFactory; -import com.ibm.engine.model.factory.DigestSizeFactory; import com.ibm.engine.model.factory.ValueActionFactory; import com.ibm.engine.rule.DetectionRuleSet; import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.builder.DetectionRuleBuilder; -import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; @@ -50,12 +47,11 @@ private PythonCryptoMac() { .withNamedMethodParameter("key", ANY) .withOptionalNamedMethodParameter("msg", ANY) .withOptionalNamedMethodParameter( - "ciphermod", ANY) // Crypto.Hash.* or Cryptodome.Hash.* + "ciphermod", ANY) // Crypto.Cipher.* or Cryptodome.Cipher.* .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("cipher_params", ANY) .withOptionalNamedMethodParameter("mac_len", "int") - .shouldBeDetectedAs(new DigestSizeFactory<>(UnitType.BYTE)) - .asChildOfParameterWithId(-1) .withOptionalNamedMethodParameter("update_after_digest", ANY) .buildForContext(new MacContext(Map.of("kind", "cmac"))) .inBundle(() -> "PyCrypto") @@ -76,26 +72,47 @@ private PythonCryptoMac() { .inBundle(() -> "PyCrypto") .withoutDependingDetectionRules(); + // The output length (mac_len) is not captured, as it cannot be exported to the CBOM. + private static final IDetectionRule KMAC128 = kmac("KMAC128"); + + private static final IDetectionRule KMAC256 = kmac("KMAC256"); + + private static final IDetectionRule POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.Poly1305", "Cryptodome.Hash.Poly1305") + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>("Poly1305")) + .withNamedMethodParameter("key", ANY) + .withOptionalNamedMethodParameter( + "cipher", ANY) // Crypto.Cipher.AES or Crypto.Cipher.ChaCha20 + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .withOptionalNamedMethodParameter("nonce", ANY) + .withOptionalNamedMethodParameter("data", ANY) + .buildForContext(new MacContext(Map.of("kind", "poly1305"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + private static IDetectionRule kmac(@Nonnull String name) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash." + name, "Cryptodome.Hash." + name) + .forMethods("new") + .shouldBeDetectedAs(new ValueActionFactory<>(name)) + .withNamedMethodParameter("key", ANY) + .withOptionalNamedMethodParameter("data", ANY) + .withOptionalNamedMethodParameter("mac_len", "int") + .withOptionalNamedMethodParameter("custom", ANY) + .buildForContext(new MacContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + } + @Nonnull @Override protected List> buildRules() { - List> rules = new ArrayList<>(); - // add CMAC + HMAC - rules.addAll(List.of(CMAC, HMAC)); - - // add "simple" MACs - for (final String mac : List.of("KMAC128", "KMAC256", "Poly1305")) { - rules.add( - new DetectionRuleBuilder() - .createDetectionRule() - .forObjectTypes("Crypto.Hash." + mac, "Cryptodome.Hash." + mac) - .forMethods("new") - .shouldBeDetectedAs(new ValueActionFactory<>(mac)) - .withAnyParameters() - .buildForContext(new MacContext()) - .inBundle(() -> "PyCrypto") - .withoutDependingDetectionRules()); - } - return rules; + return List.of(CMAC, HMAC, KMAC128, KMAC256, POLY1305); } } diff --git a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java index d06066ce0..b3e92618e 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaMacContextTranslator.java @@ -71,6 +71,14 @@ public final class PycaMacContextTranslator implements IContextTranslation .parse(algorithm.asString(), detectionLocation) .map(HMAC::new); } + // the cipher used to derive the Poly1305 key pair; it is attached as a child + // of the Poly1305 node + case "poly1305" -> { + final PycaCipherMapper cipherMapper = new PycaCipherMapper(); + yield cipherMapper + .parse(algorithm.asString(), detectionLocation) + .map(c -> c); + } default -> Optional.empty(); }; } diff --git a/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py b/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py index 3c27d3cfb..5c610e09c 100644 --- a/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py +++ b/python/src/test/files/rules/detection/pycrypto/mac/Poly1305TestFile.py @@ -1,4 +1,5 @@ from Crypto.Hash import Poly1305 +from Crypto.Cipher import AES -key = b'Sixteen byte key' -mac = Poly1305.new(key) # Noncompliant {{(Mac) Poly1305}} +key = b'0123456789abcdef0123456789abcdef' +mac = Poly1305.new(key=key, cipher=AES) # Noncompliant {{(Mac) Poly1305}} diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java index 556e26eca..5b5af659f 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/mac/Poly1305Test.java @@ -22,9 +22,11 @@ import static org.assertj.core.api.Assertions.assertThat; import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.Algorithm; import com.ibm.engine.model.IValue; import com.ibm.engine.model.ValueAction; import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.functionality.Tag; @@ -58,11 +60,19 @@ public void asserts( */ assertThat(detectionStore.getDetectionValues()).hasSize(1); assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(MacContext.class); - assertThat(detectionStore.getChildren()).isEmpty(); IValue value = detectionStore.getDetectionValues().get(0); assertThat(value).isInstanceOf(ValueAction.class); assertThat(value.asString()).isEqualTo("Poly1305"); + // the cipher is detected as a child of the Poly1305 detection + assertThat(detectionStore.getChildren()).hasSize(1); + DetectionStore cipherStore = + detectionStore.getChildren().get(0); + assertThat(cipherStore.getDetectionValues()).hasSize(1); + IValue cipherValue = cipherStore.getDetectionValues().get(0); + assertThat(cipherValue).isInstanceOf(Algorithm.class); + assertThat(cipherValue.asString()).isEqualTo("AES"); + /* * Translation */ @@ -71,10 +81,14 @@ public void asserts( INode mac = nodes.get(0); assertThat(mac.getKind()).isEqualTo(Mac.class); assertThat(mac.asString()).isEqualTo("Poly1305"); - assertThat(mac.getChildren()).hasSize(1); + assertThat(mac.getChildren()).hasSize(2); INode tag = mac.getChildren().get(Tag.class); assertThat(tag).isNotNull(); assertThat(tag.asString()).isEqualTo("TAG"); + + INode cipher = mac.getChildren().get(BlockCipher.class); + assertThat(cipher).isNotNull(); + assertThat(cipher.asString()).isEqualTo("AES"); } }