diff --git a/README.md b/README.md index 4055c9a1b..73ff4f2fa 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ It is part of **the [CBOMKit](https://github.com/cbomkit) toolset**. | Java | [JCA](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html) | 100% | | | [BouncyCastle](https://github.com/bcgit/bc-java) (*light-weight API*) | 100%[^1] | | Python | [pyca/cryptography](https://cryptography.io/en/latest/) | 100% | +| | [PyCryptodome(x)](https://www.pycryptodome.org/) | 100%[^4] | | Go | [crypto](https://pkg.go.dev/crypto) (*standard library*) | 100%[^2] | | | [golang.org/x/crypto](https://pkg.go.dev/golang.org/x/crypto) | Partial[^3] | @@ -43,6 +44,7 @@ It is part of **the [CBOMKit](https://github.com/cbomkit) toolset**. [^1]: We only cover the BouncyCastle *light-weight API* according to [this specification](https://javadoc.io/static/org.bouncycastle/bctls-jdk14/1.80/specifications.html) [^2]: All packages under [`crypto`](https://pkg.go.dev/crypto@go1.25.6#section-directories) are covered except `crypto/x509` [^3]: Covers `golang.org/x/crypto/hkdf`, `golang.org/x/crypto/pbkdf2`, and `golang.org/x/crypto/sha3` +[^4]: Also covers the legacy PyCrypto library. > [!NOTE] > The plugin is designed in a modular way so that it can be extended to support additional languages and recognition rules to support more libraries. diff --git a/docs/DETECTION_RULE_STRUCTURE.md b/docs/DETECTION_RULE_STRUCTURE.md index 924c61e81..2c65b4eb4 100644 --- a/docs/DETECTION_RULE_STRUCTURE.md +++ b/docs/DETECTION_RULE_STRUCTURE.md @@ -40,6 +40,9 @@ new DetectionRuleBuilder() ]? [.addDependingDetectionRules(detectionRules)]? ]+ + [ + .withOtherParameters() + ] .buildForContext(detectionValueContext) .inBundle(bundle) .withDependingDetectionRules(detectionRules) | .withoutDependingDetectionRules() @@ -94,6 +97,8 @@ In the tree of detected values, the values detected by these dependent detection At this point, you should have repeated all the steps starting from the `withMethodParameter` to here as many times as there are parameters in the function that you want to capture. +The `withMethodParameter` section may be followed by one `withOtherParameters` indicating that the rule still matches even if an arbirary number of additional parameters follows. This feature should be used with care since it may lead to overlapping rules that cause duplicate detections. In languges like Python it allows the matching of combinations of optional parameters without analysing them in detail. + Then, `buildForContext(IDetectionContext detectionValueContext)` defines the detection context ([`IDetectionContext`](../engine/src/main/java/com/ibm/engine/model/context/IDetectionContext.java)) for all the detected values of your rule (but detections from dependent rules have their own context). A detection context is therefore linked to each detected value, and is designed to categorize your findings and to help you carry additional information that is not present in the detected value. For example, suppose you have two function calls `Cipher.getInstance("AES")` and `SecretKeyFactory.getInstance("AES")`. When writing detection rules to capture their cryptography information, you will in both cases capture the algorithm value "AES". diff --git a/engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java b/engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java index dc59a5884..f4f2a21e3 100644 --- a/engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java +++ b/engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java @@ -43,6 +43,7 @@ public final class MethodMatcher { @Nonnull private final List invokedObjectTypeStringsSerializable; @Nonnull private final List methodNamesSerializable; @Nonnull private final List parameterTypesSerializable; + private final boolean prefixMatch; public MethodMatcher( @Nonnull String invokedObjectTypeString, @@ -52,6 +53,7 @@ public MethodMatcher( this.invokedObjectTypeStringsSerializable = List.of(invokedObjectTypeString); this.methodNamesSerializable = List.of(methodName); this.parameterTypesSerializable = parameterTypes; + this.prefixMatch = false; this.invokedObjectTypeString = createPredicate(invokedObjectTypeString, (type1 -> (iType -> iType.is(type1)))); @@ -73,10 +75,19 @@ public MethodMatcher( @Nonnull String[] invokedObjectTypeStrings, @Nonnull String[] methodNames, @Nonnull List parameterTypes) { + this(invokedObjectTypeStrings, methodNames, parameterTypes, false); + } + + public MethodMatcher( + @Nonnull String[] invokedObjectTypeStrings, + @Nonnull String[] methodNames, + @Nonnull List parameterTypes, + boolean prefixMatch) { this.invokedObjectTypeStringsSerializable = Arrays.asList(invokedObjectTypeStrings); this.methodNamesSerializable = Arrays.asList(methodNames); this.parameterTypesSerializable = parameterTypes; + this.prefixMatch = prefixMatch; this.invokedObjectTypeString = createPredicate( @@ -95,7 +106,9 @@ public MethodMatcher( type -> type.is(parameterType), parameterType)) .toList(); this.parameterTypes = - (List actualTypes) -> exactMatchesParameters(types, actualTypes); + prefixMatch + ? (List actualTypes) -> prefixMatchesParameters(types, actualTypes) + : (List actualTypes) -> exactMatchesParameters(types, actualTypes); } public MethodMatcher( @@ -104,6 +117,7 @@ public MethodMatcher( this.invokedObjectTypeStringsSerializable = Arrays.asList(invokedObjectTypeStrings); this.methodNamesSerializable = Arrays.asList(methodNames); this.parameterTypesSerializable = List.of(); + this.prefixMatch = false; this.invokedObjectTypeString = createPredicate( @@ -146,6 +160,13 @@ private boolean exactMatchesParameters( && matchesParameters(expectedTypes, actualTypes); } + private boolean prefixMatchesParameters( + @Nonnull List> expectedTypes, @Nonnull List actualTypes) { + return !expectedTypes.isEmpty() + && actualTypes.size() >= expectedTypes.size() + && matchesParameters(expectedTypes, actualTypes); + } + private boolean matchesParameters( @Nonnull List> expectedTypes, @Nonnull List actualTypes) { for (int i = 0; i < expectedTypes.size(); i++) { @@ -244,4 +265,8 @@ public List getMethodNamesSerializable() { public List getParameterTypesSerializable() { return this.parameterTypesSerializable; } + + public boolean isPrefixMatch() { + return this.prefixMatch; + } } diff --git a/engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java b/engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java index 17c4d7662..bbc9330e1 100644 --- a/engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java +++ b/engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java @@ -102,6 +102,9 @@ interface ParametersFactoryBuilder { ParametersFinalDetectionRuleBuilder addDependingDetectionRules( @Nonnull List> detectionRules); + @Nonnull + FinalDetectionRuleBuilder withOtherParameters(); + @Nonnull AddBundleDetectionRuleBuilder buildForContext( @Nonnull IDetectionContext detectionValueContext); @@ -121,6 +124,9 @@ interface PositionBuilder { ParametersFinalDetectionRuleBuilder addDependingDetectionRules( @Nonnull List> detectionRules); + @Nonnull + FinalDetectionRuleBuilder withOtherParameters(); + @Nonnull AddBundleDetectionRuleBuilder buildForContext( @Nonnull IDetectionContext detectionValueContext); @@ -137,6 +143,9 @@ interface ParametersDependingRulesBuilder { ParametersFinalDetectionRuleBuilder addDependingDetectionRules( @Nonnull List> detectionRules); + @Nonnull + FinalDetectionRuleBuilder withOtherParameters(); + @Nonnull AddBundleDetectionRuleBuilder buildForContext( @Nonnull IDetectionContext detectionValueContext); @@ -149,6 +158,9 @@ interface ParametersFinalDetectionRuleBuilder { @Nonnull ParametersFactoryBuilder withMethodParameterMatchExactType(@Nonnull String type); + @Nonnull + FinalDetectionRuleBuilder withOtherParameters(); + @Nonnull AddBundleDetectionRuleBuilder buildForContext( @Nonnull IDetectionContext detectionValueContext); diff --git a/engine/src/main/java/com/ibm/engine/rule/builder/DetectionRuleBuilderImpl.java b/engine/src/main/java/com/ibm/engine/rule/builder/DetectionRuleBuilderImpl.java index 75f6b47f0..e1ab7be5b 100644 --- a/engine/src/main/java/com/ibm/engine/rule/builder/DetectionRuleBuilderImpl.java +++ b/engine/src/main/java/com/ibm/engine/rule/builder/DetectionRuleBuilderImpl.java @@ -314,6 +314,29 @@ public IDetectionRule.FinalDetectionRuleBuilder withAnyParameters() { bundle); } + @Nonnull + @Override + public IDetectionRule.FinalDetectionRuleBuilder withOtherParameters() { + checkDetectionParameterState(); + this.capturedParameterScope = CapturedParameterScope.SOME_WITH_REMAINDER; + return new DetectionRuleBuilderImpl<>( + objectTypes, + methodNames, + parameters, + capturedParameterScope, + detectionValueContext, + shouldMatchExactTypes, + invokedObjectDependingDetectionRules, + parameterType, + iValueFactory, + iActionFactory, + detectionRules, + positionMove, + parameterShouldMatchExactTypes, + buildingNewDetectionParameter, + bundle); + } + @Nonnull @Override public IDetectionRule.ParametersDependingRulesBuilder asChildOfParameterWithId(int id) { @@ -489,7 +512,8 @@ private IDetectionRule build() { new MethodMatcher<>( this.objectTypes, this.methodNames, - this.parameters.stream().map(Parameter::getParameterType).toList()); + this.parameters.stream().map(Parameter::getParameterType).toList(), + capturedParameterScope == CapturedParameterScope.SOME_WITH_REMAINDER); return new DetectionRule<>( methodMatcher, @@ -543,6 +567,7 @@ private void checkDetectionParameterState() { enum CapturedParameterScope { SOME, + SOME_WITH_REMAINDER, ANY, NONE } diff --git a/engine/src/main/java/com/ibm/engine/serializer/DetectionRuleStore.java b/engine/src/main/java/com/ibm/engine/serializer/DetectionRuleStore.java index 0dd10d23d..e509a7576 100644 --- a/engine/src/main/java/com/ibm/engine/serializer/DetectionRuleStore.java +++ b/engine/src/main/java/com/ibm/engine/serializer/DetectionRuleStore.java @@ -72,6 +72,7 @@ public static String getMatcherID(MethodMatcher methodMatcher) { for (String parameterType : methodMatcher.getParameterTypesSerializable()) { stringID += parameterType + " "; } + stringID += "| " + methodMatcher.isPrefixMatch(); return stringID; } diff --git a/engine/src/main/java/com/ibm/engine/serializer/MethodMatcherSerializer.java b/engine/src/main/java/com/ibm/engine/serializer/MethodMatcherSerializer.java index 1261c7a2f..df5a5988f 100644 --- a/engine/src/main/java/com/ibm/engine/serializer/MethodMatcherSerializer.java +++ b/engine/src/main/java/com/ibm/engine/serializer/MethodMatcherSerializer.java @@ -63,6 +63,8 @@ public void serialize(MethodMatcher matcher, JsonGenerator jgen, SerializerProvi } jgen.writeEndArray(); + jgen.writeBooleanField("prefixMatch", matcher.isPrefixMatch()); + jgen.writeEndObject(); } } 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/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/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/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(); + }; + } +} 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/Algorithm.java b/mapper/src/main/java/com/ibm/mapper/model/Algorithm.java index 95c7308c7..575cd8608 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Algorithm.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Algorithm.java @@ -36,7 +36,7 @@ public class Algorithm implements IAlgorithm { public Algorithm( @Nonnull IAlgorithm algorithm, @Nonnull final Class asKind) { this.name = algorithm.getName(); - this.children = algorithm.getChildren(); + this.children = new HashMap<>(algorithm.getChildren()); this.detectionLocation = algorithm.getDetectionContext(); this.kind = asKind; this.origin = algorithm.getOrigin(); diff --git a/mapper/src/main/java/com/ibm/mapper/model/Key.java b/mapper/src/main/java/com/ibm/mapper/model/Key.java index cf0d98705..ca82e11c9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Key.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Key.java @@ -45,7 +45,7 @@ protected Key( @Nonnull DetectionLocation detectionLocation, @Nonnull final Class asKind) { this.name = key.name; - this.children = key.getChildren(); + this.children = new HashMap<>(key.getChildren()); this.detectionLocation = detectionLocation; this.kind = asKind; } 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/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 b6453be38..eeb3c16ce 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 @@ -20,20 +20,28 @@ package com.ibm.plugin.rules.detection; import com.ibm.engine.rule.IDetectionRule; -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.PycaHash; -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.PycaHash; +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.PythonCryptoPublicKey; +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.function.Supplier; import java.util.stream.Stream; @@ -70,7 +78,15 @@ private static List> buildRules() { PycaMAC.rules().stream(), PycaWrapping.rules().stream(), PycaKDF.rules().stream(), - PycaFernet.rules().stream()) + PycaFernet.rules().stream(), + PythonCryptoHash.rules().stream(), + PythonCryptoMac.rules().stream(), + PythonCryptoRandom.rules().stream(), + PythonCryptoCipher.rules().stream(), + PythonCryptoPublicKey.rules().stream(), + PythonCryptoSignature.rules().stream(), + PythonCryptoKDF.rules().stream(), + PythonCryptoKeyAgreement.rules().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/pyca/aead/PycaAEAD.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAEAD.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAEAD.java index 1b1a9812a..5c15bc309 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAEAD.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAEAD.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.aead; +package com.ibm.plugin.rules.detection.pyca.aead; import com.ibm.engine.model.CipherAction; import com.ibm.engine.model.KeyAction; 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/pyca/aead/PycaAES.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAES.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAES.java index 93d7dc0c5..85e16955f 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/aead/PycaAES.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAES.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.aead; +package com.ibm.plugin.rules.detection.pyca.aead; import com.ibm.engine.model.CipherAction; import com.ibm.engine.model.Size; 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/pyca/asymmetric/PycaDSA.java similarity index 97% rename from python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDSA.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDSA.java index 51211987f..e53f390df 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDSA.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDSA.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric; +package com.ibm.plugin.rules.detection.pyca.asymmetric; import static com.ibm.engine.detection.MethodMatcher.ANY; @@ -33,7 +33,7 @@ import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.builder.DetectionRuleBuilder; import com.ibm.plugin.rules.detection.Memoize; -import com.ibm.plugin.rules.detection.hash.PycaHash; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHash; import java.util.List; import java.util.Map; import java.util.function.Supplier; 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/pyca/asymmetric/PycaDiffieHellman.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDiffieHellman.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDiffieHellman.java index ae1fef88d..cabfe6b03 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaDiffieHellman.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaDiffieHellman.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric; +package com.ibm.plugin.rules.detection.pyca.asymmetric; import static com.ibm.engine.detection.MethodMatcher.ANY; 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/pyca/asymmetric/PycaEllipticCurve.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaEllipticCurve.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaEllipticCurve.java index 195cc53b5..3e1842f6f 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaEllipticCurve.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaEllipticCurve.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric; +package com.ibm.plugin.rules.detection.pyca.asymmetric; import static com.ibm.engine.detection.MethodMatcher.ANY; @@ -34,7 +34,7 @@ import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.builder.DetectionRuleBuilder; import com.ibm.plugin.rules.detection.Memoize; -import com.ibm.plugin.rules.detection.hash.PycaHash; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHash; import java.util.List; import java.util.Map; import java.util.function.Supplier; 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/pyca/asymmetric/PycaRSA.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaRSA.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaRSA.java index 8eeaf5766..4d3364e26 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaRSA.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaRSA.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric; +package com.ibm.plugin.rules.detection.pyca.asymmetric; import static com.ibm.engine.detection.MethodMatcher.ANY; @@ -37,7 +37,7 @@ import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.builder.DetectionRuleBuilder; import com.ibm.plugin.rules.detection.Memoize; -import com.ibm.plugin.rules.detection.hash.PycaHash; +import com.ibm.plugin.rules.detection.pyca.hash.PycaHash; import java.util.List; import java.util.Map; import java.util.function.Supplier; 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/pyca/asymmetric/PycaSign.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaSign.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaSign.java index 6b50bf24a..7e910e632 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/asymmetric/PycaSign.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/asymmetric/PycaSign.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric; +package com.ibm.plugin.rules.detection.pyca.asymmetric; import com.ibm.engine.model.KeyAction; import com.ibm.engine.model.context.PrivateKeyContext; 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/pyca/fernet/PycaFernet.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/fernet/PycaFernet.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernet.java index a1eb299f9..4a4fcf607 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/fernet/PycaFernet.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernet.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.fernet; +package com.ibm.plugin.rules.detection.pyca.fernet; import com.ibm.engine.model.CipherAction; import com.ibm.engine.model.KeyAction; 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/pyca/hash/PycaHash.java similarity index 93% rename from python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHash.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHash.java index 27aead4c7..f3da3342f 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/hash/PycaHash.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHash.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.hash; +package com.ibm.plugin.rules.detection.pyca.hash; import com.ibm.engine.model.context.DigestContext; import com.ibm.engine.model.factory.AlgorithmFactory; @@ -116,7 +116,16 @@ private static List> buildRules() { } @Nonnull - public static List> wrapperRules() { + private static final Supplier>> WRAPPER_RULES = + Memoize.of(PycaHash::wrapperRule); + + @Nonnull + public static List> wrapperRule() { return List.of(HASH_WRAPPER); } + + @Nonnull + public static List> wrapperRules() { + return WRAPPER_RULES.get(); + } } 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/pyca/kdf/PycaKDF.java similarity index 99% rename from python/src/main/java/com/ibm/plugin/rules/detection/kdf/PycaKDF.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKDF.java index 2c2dc8a3c..ae099981a 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/kdf/PycaKDF.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKDF.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static com.ibm.engine.detection.MethodMatcher.ANY; 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/pyca/keyagreement/PycaKeyAgreement.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreement.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreement.java index 127c0bca2..13d48c151 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreement.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreement.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.keyagreement; +package com.ibm.plugin.rules.detection.pyca.keyagreement; import com.ibm.engine.model.KeyAction; import com.ibm.engine.model.context.KeyAgreementContext; 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/pyca/mac/PycaMAC.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/mac/PycaMAC.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMAC.java index 4cbedf859..20b477869 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/mac/PycaMAC.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMAC.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.mac; +package com.ibm.plugin.rules.detection.pyca.mac; import static com.ibm.engine.detection.MethodMatcher.ANY; 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/pyca/padding/PycaPadding.java similarity index 97% rename from python/src/main/java/com/ibm/plugin/rules/detection/padding/PycaPadding.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPadding.java index 404367ff3..314b90058 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/padding/PycaPadding.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPadding.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.padding; +package com.ibm.plugin.rules.detection.pyca.padding; import com.ibm.engine.model.Size; import com.ibm.engine.model.context.CipherContext; @@ -26,7 +26,7 @@ import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.builder.DetectionRuleBuilder; import com.ibm.plugin.rules.detection.Memoize; -import com.ibm.plugin.rules.detection.symmetric.PycaCipher; +import com.ibm.plugin.rules.detection.pyca.symmetric.PycaCipher; import java.util.Arrays; import java.util.LinkedList; import java.util.List; 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/pyca/symmetric/PycaCipher.java similarity index 97% rename from python/src/main/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher.java index 2438d9eaf..06b8d0b0e 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.symmetric; +package com.ibm.plugin.rules.detection.pyca.symmetric; import com.ibm.engine.model.CipherAction; import com.ibm.engine.model.context.CipherContext; @@ -27,7 +27,7 @@ import com.ibm.engine.rule.IDetectionRule; import com.ibm.engine.rule.builder.DetectionRuleBuilder; import com.ibm.plugin.rules.detection.Memoize; -import com.ibm.plugin.rules.detection.padding.PycaPadding; +import com.ibm.plugin.rules.detection.pyca.padding.PycaPadding; import java.util.Arrays; import java.util.LinkedList; import java.util.List; 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/pyca/wrapping/PycaWrapping.java similarity index 98% rename from python/src/main/java/com/ibm/plugin/rules/detection/wrapping/PycaWrapping.java rename to python/src/main/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrapping.java index 7ffd52972..18f782847 100644 --- a/python/src/main/java/com/ibm/plugin/rules/detection/wrapping/PycaWrapping.java +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrapping.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.wrapping; +package com.ibm.plugin.rules.detection.pyca.wrapping; import com.ibm.engine.model.CipherAction; import com.ibm.engine.model.context.CipherContext; 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..4efb6bd40 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/cipher/PythonCryptoCipher.java @@ -0,0 +1,258 @@ +/* + * 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.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoPublicKey; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoCipher { + + 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) + .withOtherParameters() + .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) + .withOtherParameters() + .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) + .withOtherParameters() + .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) + .withOtherParameters() + .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) + .withOtherParameters() + .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) + .withOtherParameters() + .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(PythonCryptoPublicKey.RSARules()) + .withOtherParameters() + .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")) + .withAnyParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(ENCRYPT, DECRYPT)); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoCipher::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static 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..a1dd7ee58 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/hash/PythonCryptoHash.java @@ -0,0 +1,93 @@ +/* + * 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 com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoHash { + + 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"); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoHash::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static List> buildRules() { + LinkedList> rules = new LinkedList<>(); + 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..86b9328b5 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/kdf/PythonCryptoKDF.java @@ -0,0 +1,326 @@ +/* + * 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.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoKDF { + + 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")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf1"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF1_WITH_COUNT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF1") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF1")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // count + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf1"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF1_WITH_HASH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF1") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF1")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hashAlgo) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf1"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF1_WITH_COUNT_AND_HASH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF1") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF1")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // count + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hashAlgo) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf1"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF1_WITH_HASH_AND_COUNT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF1") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF1")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hashAlgo) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // count + .shouldBeDetectedAs(new IterationCountFactory<>()) + .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")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf2"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF2_WITH_COUNT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF2") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // count + .shouldBeDetectedAs(new IterationCountFactory<>()) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf2"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF2_WITH_HASH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF2") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hashAlgo) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf2"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF2_WITH_HASH_AND_COUNT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF2") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hashAlgo) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // count + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .buildForContext( + new KeyDerivationFunctionContext(Map.of("kind", "pycrypto-pbkdf2"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule PBKDF2_WITH_COUNT_AND_HASH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Protocol.KDF", "Cryptodome.Protocol.KDF") + .forMethods("PBKDF2") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // dkLen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // count + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hashAlgo) + .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")) + .withMethodParameter(ANY) // password + .withMethodParameter(ANY) // salt + .withMethodParameter("int") // key_len + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter("int") // N + .withMethodParameter("int") // r + .withMethodParameter("int") // p + .withOtherParameters() // num_keys + .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")) + .withMethodParameter(ANY) // master + .withMethodParameter("int") // keylen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // salt + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* (hash_mod) + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .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")) + .withMethodParameter(ANY) // master + .withMethodParameter("int") // key_len + .shouldBeDetectedAs(new KeySizeFactory<>(UnitType.BYTE)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // prf + .withOtherParameters() // num_keys, label + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoKDF::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static List> buildRules() { + return List.of( + PBKDF1, + PBKDF1_WITH_HASH, + PBKDF1_WITH_COUNT, + PBKDF1_WITH_HASH_AND_COUNT, + PBKDF1_WITH_COUNT_AND_HASH, + PBKDF2, + PBKDF2_WITH_HASH, + PBKDF2_WITH_COUNT, + PBKDF2_WITH_HASH_AND_COUNT, + PBKDF2_WITH_COUNT_AND_HASH, + 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..a615a09ad --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/keyagreement/PythonCryptoKeyAgreement.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.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.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoPublicKey; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoKeyAgreement { + + 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( + PythonCryptoPublicKey.ECCRules().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( + PythonCryptoPublicKey.ECCRules().stream(), + Stream.of( + IMPORT_X25519_PUBLIC_KEY, + IMPORT_X448_PUBLIC_KEY)) + .toList()) + .buildForContext(new KeyAgreementContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoKeyAgreement::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static 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..6189c739a --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/mac/PythonCryptoMac.java @@ -0,0 +1,127 @@ +/* + * 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.context.MacContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoMac { + + private PythonCryptoMac() { + // private + } + + private static final IDetectionRule CMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.CMAC", "Cryptodome.Hash.CMAC") + .forMethods("new") + .withMethodParameter(ANY) // key + .withMethodParameter(ANY) // Crypto.Cipher.* or Cryptodome.Cipher.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new MacContext(Map.of("kind", "cmac"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMAC_MSG = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.CMAC", "Cryptodome.Hash.CMAC") + .forMethods("new") + .withMethodParameter(ANY) // key + .withMethodParameter(ANY) // msg + .withMethodParameter(ANY) // Crypto.Cipher.* or Cryptodome.Cipher.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withOtherParameters() + .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") + .withMethodParameter(ANY) // secret + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new MacContext(Map.of("kind", "hmac"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule HMAC_MSG = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("Crypto.Hash.HMAC", "Cryptodome.Hash.HMAC") + .forMethods("new") + .withMethodParameter(ANY) // secret + .withMethodParameter(ANY) // message + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new MacContext(Map.of("kind", "hmac"))) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + public static final List simpleMACs = List.of("KMAC128", "KMAC256", "Poly1305"); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoMac::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static List> buildRules() { + List> rules = new ArrayList<>(); + // add CMAC + HMAC + rules.addAll(List.of(CMAC, CMAC_MSG, HMAC, HMAC_MSG)); + + // add "simple" MACs + for (final String mac : PythonCryptoMac.simpleMACs) { + 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/PythonCryptoPublicKey.java b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoPublicKey.java new file mode 100644 index 000000000..9a4691b55 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/publickey/PythonCryptoPublicKey.java @@ -0,0 +1,281 @@ +/* + * 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.CurveFactory; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +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. +// +// 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 PythonCryptoPublicKey { + + private PythonCryptoPublicKey() { + // 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)) + .withMethodParameter("int") // keylen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withOtherParameters() // randfunc, e + .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(); + + // 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)) + .withMethodParameter("int") // keylen + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withOtherParameters() // randfunc, domain + .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(); + + // 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)) + // signature is **kwargs! + .withMethodParameter("str") // assuming curve as 1st parameter + .shouldBeDetectedAs(new CurveFactory<>()) + .asChildOfParameterWithId(-1) + .withOtherParameters() // randfunc + .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)) + .withMethodParameter("str") // curve + .shouldBeDetectedAs(new CurveFactory<>()) + .asChildOfParameterWithId(-1) + .withOtherParameters() // d, seed, point_x, point_y + .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(); + + // 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)) + .withMethodParameter("int") // bits + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withMethodParameter(ANY) // randfunc + .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 + private static final Supplier>> RSA_RULES = + Memoize.of(PythonCryptoPublicKey::buildRSARules); + + @Nonnull + public static List> RSARules() { + return RSA_RULES.get(); + } + + @Nonnull + private static List> buildRSARules() { + return List.of(RSA_CONSTRUCT, RSA_GENERATE, RSA_PUBLIC_KEY); + } + + @Nonnull + private static final Supplier>> DSA_RULES = + Memoize.of(PythonCryptoPublicKey::buildDSARules); + + @Nonnull + public static List> DSARules() { + return DSA_RULES.get(); + } + + @Nonnull + private static List> buildDSARules() { + return List.of(DSA_CONSTRUCT, DSA_GENERATE, DSA_PUBLIC_KEY); + } + + @Nonnull + private static final Supplier>> ECC_RULES = + Memoize.of(PythonCryptoPublicKey::buildECCRules); + + @Nonnull + public static List> ECCRules() { + return ECC_RULES.get(); + } + + @Nonnull + private static List> buildECCRules() { + return List.of(ECC_CONSTRUCT, ECC_GENERATE, ECC_IMPORT, ECC_PUBLIC_KEY); + } + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoPublicKey::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static List> buildRules() { + return List.of(ELGAMAL_CONSTRUCT, ELGAMAL_GENERATE); + } +} 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..ef7de8d87 --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/random/PythonCryptoRandom.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.random; + +import static com.ibm.engine.detection.MethodMatcher.ANY; + +import com.ibm.engine.model.context.PRNGContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import java.util.List; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoRandom { + + 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")) + .withMethodParameter(ANY) + .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")) + .withMethodParameter(ANY) + .buildForContext(new PRNGContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoRandom::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static 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..9a426de7f --- /dev/null +++ b/python/src/main/java/com/ibm/plugin/rules/detection/pycrypto/signature/PythonCryptoSignature.java @@ -0,0 +1,256 @@ +/* + * 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.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.SignatureActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.Memoize; +import com.ibm.plugin.rules.detection.pycrypto.hash.PythonCryptoHash; +import com.ibm.plugin.rules.detection.pycrypto.publickey.PythonCryptoPublicKey; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.sonar.plugins.python.api.tree.Tree; + +@SuppressWarnings("java:S1192") +public final class PythonCryptoSignature { + + private PythonCryptoSignature() { + // private + } + + private static final IDetectionRule SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoHash.rules()) + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withoutDependingDetectionRules(); + + private static final IDetectionRule VERIFY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(ANY) + .forMethods("verify") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.VERIFY)) + .withMethodParameter(ANY) // Crypto.Hash.* or Cryptodome.Hash.* + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoHash.rules()) + .withMethodParameter(ANY) // the signature to be verified + .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")) + .withMethodParameter(ANY) // Crypto.PublicKey.RSA or Cryptodome.PublicKey.RSA + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoPublicKey.RSARules()) + .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")) + .withMethodParameter( + ANY) // Crypto.PublicKey.RSAkey or Cryptodome.PublicKey.RSAkey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoPublicKey.RSARules()) + .withOtherParameters() + .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")) + .withMethodParameter(ANY) + .withMethodParameter(ANY) + .withMethodParameter(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")) + .withMethodParameter("Crypto.PublicKey.DSA") // key + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoPublicKey.DSARules()) + .withOtherParameters() // mode, encoding, randfunc + .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")) + .withMethodParameter("Cryptodome.PublicKey.DSA") // key + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoPublicKey.DSARules()) + .withOtherParameters() // mode, encoding, randfunc + .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")) + .withMethodParameter("Crypto.PublicKey.ECC") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoPublicKey.ECCRules()) + .withOtherParameters() // mode, encoding, rand_func + .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")) + .withMethodParameter("Cryptodome.PublicKey.ECC") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules(PythonCryptoPublicKey.ECCRules()) + .withOtherParameters() // mode, encoding, rand_func + .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")) + .withMethodParameter( + ANY) // Crypto.PublicKey.ECCkey or Cryptodome.PublicKey.ECCkey + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .asChildOfParameterWithId(-1) + .addDependingDetectionRules( + Stream.concat( + PythonCryptoPublicKey.ECCRules().stream(), + Stream.of( + EDDSA_IMPORT_PRIVATE_KEY, + EDDSA_IMPORT_PUBLIC_KEY)) + .toList()) + .withOtherParameters() // mode, context + .buildForContext(new SignatureContext()) + .inBundle(() -> "PyCrypto") + .withDependingDetectionRules(List.of(SIGN, VERIFY)); + + @Nonnull + private static final Supplier>> RULES = + Memoize.of(PythonCryptoSignature::buildRules); + + @Nonnull + public static List> rules() { + return RULES.get(); + } + + @Nonnull + private static List> buildRules() { + return List.of( + PKCS1V15, PSS, PSS_MGF1, DSS, DSS_CRYPTODOME, ECDSA, ECDSA_CRYPTODOME, EDDSA); + } +} 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..af75bcbf0 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,9 +21,11 @@ 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; @@ -42,6 +44,14 @@ private PythonReorganizerRules() { @Nonnull public static List rules() { return Stream.of( + 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 +61,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 4a22bb4a2..a349601c3 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 eec44d2aa..226148286 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; @@ -30,10 +31,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; @@ -41,15 +45,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; @@ -66,7 +74,7 @@ public final class PycaCipherContextTranslator implements IContextTranslation + if (value instanceof Algorithm && detectionContext instanceof DetectionContext context) { if (context.get("kind").map(k -> k.equals("AEAD")).orElse(false)) { return switch (value.asString().toUpperCase().trim()) { @@ -85,25 +93,49 @@ public final class PycaCipherContextTranslator implements IContextTranslation i); } else if (value instanceof ValueAction - && detectionContext instanceof DetectionContext context - && context.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(); + && detectionContext instanceof DetectionContext context) { + if (context.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 face8bc21..6333c5cc8 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,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.IDetectionContext; @@ -41,7 +42,7 @@ public final class PycaDigestContextTranslator implements IContextTranslation value, @Nonnull IDetectionContext detectionContext, @Nonnull DetectionLocation detectionLocation) { - if (value instanceof ValueAction || value instanceof com.ibm.engine.model.Algorithm) { + 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 89b68213b..0407e5879 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.model.context.IDetectionContext; import com.ibm.engine.rule.IBundle; @@ -46,17 +47,19 @@ public class PycaKeyAgreementContextTranslator implements IContextTranslation value, @Nonnull IDetectionContext 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/PycaSecretContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java similarity index 63% rename from python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSecretContextTranslator.java rename to python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java index f85a24d14..396f8efd1 100644 --- a/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaSecretContextTranslator.java +++ b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaKeyContextTranslator.java @@ -19,22 +19,24 @@ */ 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.model.context.IDetectionContext; 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.SecretKey; -import com.ibm.mapper.model.algorithms.Fernet; +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 PycaSecretContextTranslator implements IContextTranslation { +public final class PycaKeyContextTranslator implements IContextTranslation { @Override public @Nonnull Optional translate( @Nonnull IBundle bundleIdentifier, @@ -44,17 +46,27 @@ public final class PycaSecretContextTranslator implements IContextTranslation && detectionContext instanceof DetectionContext context) { // action is always "generate" + final PycaKeyBasedAlgorithmMapper mapper = new PycaKeyBasedAlgorithmMapper(); return context.get("algorithm") - .map( - str -> - switch (str.toUpperCase().trim()) { - case "FERNET" -> new Fernet(detectionLocation); - default -> null; - }) + .flatMap(str -> mapper.parse(str, detectionLocation)) .map( algo -> { - final SecretKey key = new SecretKey(algo); + final Key key = new Key(algo); + key.put(new KeyGeneration(detectionLocation)); + return key; + }); + } else if (value instanceof Curve curve + && detectionContext instanceof DetectionContext context + && context.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; }); } 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 72ed5115d..951c8847a 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.model.context.IDetectionContext; @@ -33,11 +35,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; @@ -101,6 +107,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 @@ -112,6 +129,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 @@ -146,12 +171,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 8f9e14618..8ad9f8ec0 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; @@ -27,11 +28,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; @@ -47,7 +48,7 @@ public final class PycaMacContextTranslator implements IContextTranslation @Nonnull IDetectionContext detectionContext, @Nonnull DetectionLocation detectionLocation) { - if (value instanceof com.ibm.engine.model.Algorithm algorithm + if (value instanceof Algorithm algorithm && detectionContext instanceof DetectionContext context) { // hash algorithm Optional possibleKind = context.get("kind"); @@ -76,9 +77,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 b82629bd7..1b5c084f8 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 @@ -27,35 +27,13 @@ import com.ibm.engine.model.context.IDetectionContext; 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; @@ -84,41 +62,13 @@ public final class PycaPrivateKeyContextTranslator implements IContextTranslatio } else if (value instanceof Curve curve && detectionContext instanceof DetectionContext context && context.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; @@ -131,26 +81,13 @@ public final class PycaPrivateKeyContextTranslator implements IContextTranslatio @Nonnull DetectionContext context, @Nullable Integer keySize, @Nonnull DetectionLocation detectionLocation) { + final PycaKeyBasedAlgorithmMapper mapper = new PycaKeyBasedAlgorithmMapper(); return context.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 b0f14a52e..95245c481 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,17 +19,19 @@ */ 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.model.context.IDetectionContext; 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; @@ -47,23 +49,25 @@ public final class PycaPublicKeyContextTranslator implements IContextTranslation @Nonnull DetectionLocation detectionLocation) { if (value instanceof KeyAction && detectionContext instanceof DetectionContext context) { + final PycaKeyBasedAlgorithmMapper mapper = new PycaKeyBasedAlgorithmMapper(); return context.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/PycaRandomContextTranslator.java b/python/src/main/java/com/ibm/plugin/translation/translator/contexts/PycaRandomContextTranslator.java new file mode 100644 index 000000000..4cd66fb3d --- /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.IDetectionContext; +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 IDetectionContext 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(); + } +} 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 363c68378..3ac9d725b 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 @@ -30,12 +30,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/pyca/aead/PycaAESGCMTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/aead/PycaAESGCMTestFile.py rename to python/src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py diff --git a/python/src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py b/python/src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py rename to python/src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py diff --git a/python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py b/python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py rename to python/src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py diff --git a/python/src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py b/python/src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py rename to python/src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py diff --git a/python/src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py b/python/src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py rename to python/src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py diff --git a/python/src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py b/python/src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py rename to python/src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py diff --git a/python/src/test/files/rules/detection/hash/PycaHashDirectTest.py b/python/src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py similarity index 100% rename from python/src/test/files/rules/detection/hash/PycaHashDirectTest.py rename to python/src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py diff --git a/python/src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaHKDFTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaHKDFTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaScryptTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaScryptTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py diff --git a/python/src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py b/python/src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py rename to python/src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py diff --git a/python/src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py b/python/src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py rename to python/src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py diff --git a/python/src/test/files/rules/detection/mac/PycaCMACTestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/mac/PycaCMACTestFile.py rename to python/src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py diff --git a/python/src/test/files/rules/detection/mac/PycaHMACTestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/mac/PycaHMACTestFile.py rename to python/src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py diff --git a/python/src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py rename to python/src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py diff --git a/python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py b/python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py similarity index 89% rename from python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py rename to python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py index b51c92c91..2fbaec802 100644 --- a/python/src/test/files/rules/detection/mac/PycaPoly1305TestFile.py +++ b/python/src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py @@ -2,7 +2,7 @@ def generate_poly1305(key, data): # Create a Poly1305 context with the given key - poly1305_ctx = Poly1305(key) # Noncompliant {{(Mac) HMAC-Poly1305}} + poly1305_ctx = Poly1305(key) # Noncompliant {{(Mac) Poly1305}} # Update the context with the data poly1305_ctx.update(data) diff --git a/python/src/test/files/rules/detection/padding/PycaPaddingTestFile.py b/python/src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/padding/PycaPaddingTestFile.py rename to python/src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py diff --git a/python/src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py rename to python/src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py diff --git a/python/src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py rename to python/src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py diff --git a/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher3TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher3TestFile.py new file mode 100644 index 000000000..45ca10c8c --- /dev/null +++ b/python/src/test/files/rules/detection/pyca/symmetric/PycaCipher3TestFile.py @@ -0,0 +1,36 @@ + +import os +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +key64 = b"01234567" +key128 = b"0123456789abcdef" +key192 = b"0123456789abcdef01234567" +key256 = b"0123456789abcdef0123456789abcdef" + +iv = b"1234567890abcdef" +nonce = b"123456789012" +data = b"hello world!!!!!" + +# AES-CBC (invalid 64-bit key — should fail at runtime, but useful for static detection testing) +algo_small = algorithms.AES(key64) +c_small = Cipher(algo_small, modes.CBC(iv)) # Noncompliant {{(BlockCipher) AES-CBC}} +encryptor_small = c_small.encryptor() +ct_small = encryptor_small.update(data) + encryptor_small.finalize() + +# AES-CBC (128-bit) +algo_128 = algorithms.AES(key128) +c_128 = Cipher(algo_128, modes.CBC(iv)) # Noncompliant {{(BlockCipher) AES-CBC}} +encryptor_128 = c_128.encryptor() +ct_128 = encryptor_128.update(data) + encryptor_128.finalize() + +# AES-CBC (192-bit) +algo_192 = algorithms.AES(key192) +c_192 = Cipher(algo_192, modes.CBC(iv)) # Noncompliant {{(BlockCipher) AES-CBC}} +encryptor_192 = c_192.encryptor() +ct_192 = encryptor_192.update(data) + encryptor_192.finalize() + +# AES-CBC (256-bit) +algo_256 = algorithms.AES(key256) +c_256 = Cipher(algo_256, modes.CBC(iv)) # Noncompliant {{(BlockCipher) AES-CBC}} +encryptor_256 = c_256.encryptor() +ct_256 = encryptor_256.update(data) + encryptor_256.finalize() \ No newline at end of file diff --git a/python/src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py b/python/src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py similarity index 100% rename from python/src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py rename to python/src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py diff --git a/python/src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py b/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py rename to python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py diff --git a/python/src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py b/python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py similarity index 100% rename from python/src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py rename to python/src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py 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..b24c6c530 --- /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, 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..c937ccb62 --- /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, 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/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/BcryptTestFile.py b/python/src/test/files/rules/detection/pycrypto/kdf/BcryptTestFile.py new file mode 100644 index 000000000..f99740baf --- /dev/null +++ b/python/src/test/files/rules/detection/pycrypto/kdf/BcryptTestFile.py @@ -0,0 +1,6 @@ +from Crypto.Protocol.KDF import bcrypt + +def test_bcrypt(): + password = b"password" + salt = b"salt1234567890ab" + key = bcrypt(password, cost=10, salt=salt) # detected but not mapped 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..9641cf2aa --- /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(secret, 32, salt, 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..12083438e --- /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, salt, 16, 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..4bbc4e3e7 --- /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, 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..13835f210 --- /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(secret, 16, 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..b2524fbfd --- /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, 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..c93c49723 --- /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(2048, 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..4a8e56886 --- /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, '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..9d8c4dfa1 --- /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, '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..368980c82 --- /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, '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..6031b9e50 --- /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, '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..63ede1551 --- /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, '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..76189fac5 --- /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(public_key, '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..dadcc8f25 --- /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(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..c7c0ea214 --- /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(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..5f344b7f0 --- /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(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..7341f75d7 --- /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(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/aead/PycaAESGCMTest.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaAESGCMTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java index d3409b362..7811c3317 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaAESGCMTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaAESGCMTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.aead; +package com.ibm.plugin.rules.detection.pyca.aead; import static org.assertj.core.api.Assertions.assertThat; @@ -52,7 +52,7 @@ class PycaAESGCMTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/aead/PycaAESGCMTestFile.py", this); + "src/test/files/rules/detection/pyca/aead/PycaAESGCMTestFile.py", this); } @Override 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/pyca/aead/PycaChaCha20Poly1305Test.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaChaCha20Poly1305Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaChaCha20Poly1305Test.java index 004201c31..88c1b01f4 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/aead/PycaChaCha20Poly1305Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/aead/PycaChaCha20Poly1305Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.aead; +package com.ibm.plugin.rules.detection.pyca.aead; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaChaCha20Poly1305Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/aead/PycaChaCha20Poly1305TestFile.py", this); + "src/test/files/rules/detection/pyca/aead/PycaChaCha20Poly1305TestFile.py", this); } @Override 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/pyca/asymmetric/DSA/PycaDSANumbersTest.java similarity index 96% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSANumbersTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTest.java index 2178efc29..beebbd7c6 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSANumbersTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.DSA; +package com.ibm.plugin.rules.detection.pyca.asymmetric.DSA; import static org.assertj.core.api.Assertions.assertThat; @@ -47,7 +47,8 @@ class PycaDSANumbersTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DSA/PycaDSANumbersTestFile.py", this); + "src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSANumbersTestFile.py", + this); } @Override 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/pyca/asymmetric/DSA/PycaDSASignTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSASignTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSASignTest.java index 295196b08..1c4cfcafe 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DSA/PycaDSASignTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DSA/PycaDSASignTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.DSA; +package com.ibm.plugin.rules.detection.pyca.asymmetric.DSA; import static org.assertj.core.api.Assertions.assertThat; @@ -54,7 +54,7 @@ class PycaDSASignTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DSA/PycaDSASignTestFile.py", this); + "src/test/files/rules/detection/pyca/asymmetric/DSA/PycaDSASignTestFile.py", this); } @Override 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/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java index 06ce44018..4097dcf75 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.DiffieHellman; +package com.ibm.plugin.rules.detection.pyca.asymmetric.DiffieHellman; import static org.assertj.core.api.Assertions.assertThat; @@ -47,7 +47,7 @@ public final class PycaDiffieHellmanGenerateTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanGenerateTestFile.py", this); } 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/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java index bdd8bf7dc..26099a4a9 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.DiffieHellman; +package com.ibm.plugin.rules.detection.pyca.asymmetric.DiffieHellman; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ class PycaDiffieHellmanNumbersTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/DiffieHellman/PycaDiffieHellmanNumbersTestFile.py", this); } 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/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java index dde89a44a..d34d4fa91 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; import static org.assertj.core.api.Assertions.assertThat; @@ -46,7 +46,7 @@ class PycaEllipticCurveDeriveTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveDeriveTestFile.py", this); } 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/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java index ca421610e..c073991dc 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; import static org.assertj.core.api.Assertions.assertThat; @@ -57,7 +57,7 @@ class PycaEllipticCurveKeyExchangeTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveKeyExchangeTestFile.py", this); } 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/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java similarity index 90% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java index 2c95fc794..9c1f7d79c 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; import com.ibm.engine.detection.DetectionStore; import com.ibm.mapper.model.INode; @@ -38,7 +38,7 @@ class PycaEllipticCurveNumbersTest extends TestBase { @Test void test() { PythonCheckVerifier.verifyNoIssue( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveNumbersTestFile.py", this); } 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/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java index c6156730e..84e4928eb 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaEllipticCurveSign2Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSign2TestFile.py", this); } 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/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java index 42e6c8347..e70a1c3df 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; import static org.assertj.core.api.Assertions.assertThat; @@ -54,7 +54,7 @@ class PycaEllipticCurveSignTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveSignTestFile.py", this); } 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/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java similarity index 91% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java index ab38d7bcb..2941263c2 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.EllipticCurve; +package com.ibm.plugin.rules.detection.pyca.asymmetric.EllipticCurve; import com.ibm.engine.detection.DetectionStore; import com.ibm.mapper.model.INode; @@ -41,7 +41,7 @@ class PycaEllipticCurveVerifyTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py", + "src/test/files/rules/detection/pyca/asymmetric/EllipticCurve/PycaEllipticCurveVerifyTestFile.py", this); } 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/pyca/asymmetric/RSA/PycaRSADecryptTest.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSADecryptTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTest.java index 416e98ba3..274f3bcd8 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSADecryptTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; import static org.assertj.core.api.Assertions.assertThat; @@ -58,7 +58,8 @@ class PycaRSADecryptTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSADecryptTestFile.py", this); + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSADecryptTestFile.py", + this); } @Override 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/pyca/asymmetric/RSA/PycaRSANumbersTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSANumbersTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTest.java index c78a44f64..5321621ee 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSANumbersTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; import static org.assertj.core.api.Assertions.assertThat; @@ -47,7 +47,8 @@ class PycaRSANumbersTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSANumbersTestFile.py", this); + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSANumbersTestFile.py", + this); } @Override 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/pyca/asymmetric/RSA/PycaRSASign1Test.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign1Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign1Test.java index e57bdb78a..25c86fe9c 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign1Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign1Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; import static org.assertj.core.api.Assertions.assertThat; @@ -56,7 +56,7 @@ class PycaRSASign1Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSASign1TestFile.py", this); + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign1TestFile.py", this); } @Override 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/pyca/asymmetric/RSA/PycaRSASign2Test.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign2Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign2Test.java index bb5181e56..c39328fa6 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/asymmetric/RSA/PycaRSASign2Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/asymmetric/RSA/PycaRSASign2Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.asymmetric.RSA; +package com.ibm.plugin.rules.detection.pyca.asymmetric.RSA; import static org.assertj.core.api.Assertions.assertThat; @@ -55,7 +55,7 @@ class PycaRSASign2Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/asymmetric/RSA/PycaRSASign2TestFile.py", this); + "src/test/files/rules/detection/pyca/asymmetric/RSA/PycaRSASign2TestFile.py", this); } @Override 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/pyca/fernet/PycaFernetDecryptTest.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetDecryptTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetDecryptTest.java index 10119414d..19f7d28cd 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetDecryptTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetDecryptTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.fernet; +package com.ibm.plugin.rules.detection.pyca.fernet; import static org.assertj.core.api.Assertions.assertThat; @@ -58,7 +58,7 @@ class PycaFernetDecryptTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/fernet/PycaFernetDecryptTestFile.py", this); + "src/test/files/rules/detection/pyca/fernet/PycaFernetDecryptTestFile.py", this); } @Override 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/pyca/fernet/PycaFernetEncryptTest.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetEncryptTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetEncryptTest.java index 759916b45..5ad5aa112 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaFernetEncryptTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaFernetEncryptTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.fernet; +package com.ibm.plugin.rules.detection.pyca.fernet; import static org.assertj.core.api.Assertions.assertThat; @@ -58,7 +58,7 @@ class PycaFernetEncryptTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/fernet/PycaFernetEncryptTestFile.py", this); + "src/test/files/rules/detection/pyca/fernet/PycaFernetEncryptTestFile.py", this); } @Override 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/pyca/fernet/PycaMultiFernetTest.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaMultiFernetTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaMultiFernetTest.java index 36fe6893b..ac90ea5a5 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/fernet/PycaMultiFernetTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/fernet/PycaMultiFernetTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.fernet; +package com.ibm.plugin.rules.detection.pyca.fernet; import static org.assertj.core.api.Assertions.assertThat; @@ -59,7 +59,7 @@ class PycaMultiFernetTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/fernet/PycaMultiFernetTestFile.py", this); + "src/test/files/rules/detection/pyca/fernet/PycaMultiFernetTestFile.py", this); } @Override 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/pyca/hash/PycaHashDirectTest.java similarity index 96% rename from python/src/test/java/com/ibm/plugin/rules/detection/hash/PycaHashDirectTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashDirectTest.java index 73cef79d4..0e57b8a00 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/hash/PycaHashDirectTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/hash/PycaHashDirectTest.java @@ -1,6 +1,6 @@ /* * Sonar Cryptography Plugin - * Copyright (C) 2024 PQCA + * Copyright (C) 2026 PQCA * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.hash; +package com.ibm.plugin.rules.detection.pyca.hash; import static org.assertj.core.api.Assertions.assertThat; @@ -46,7 +46,7 @@ class PycaHashDirectTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/hash/PycaHashDirectTest.py", this); + "src/test/files/rules/detection/pyca/hash/PycaHashDirectTest.py", this); } @Override 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/pyca/kdf/PycaConcatKDFHMACTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHMACTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHMACTest.java index 4c72b5be8..2e7bd5711 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHMACTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHMACTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaConcatKDFHMACTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaConcatKDFHMACTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHMACTestFile.py", this); } @Override 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/pyca/kdf/PycaConcatKDFHashTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHashTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHashTest.java index 68a47dbc9..e10a0c5f2 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaConcatKDFHashTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaConcatKDFHashTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -49,7 +49,7 @@ class PycaConcatKDFHashTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaConcatKDFHashTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaConcatKDFHashTestFile.py", this); } @Override 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/pyca/kdf/PycaHKDFExpandTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFExpandTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFExpandTest.java index 0eade96a6..585f7448b 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFExpandTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFExpandTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaHKDFExpandTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaHKDFExpandTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaHKDFExpandTestFile.py", this); } @Override 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/pyca/kdf/PycaHKDFTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFTest.java index e7a4a5f16..1db1ba235 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaHKDFTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaHKDFTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -49,7 +49,8 @@ class PycaHKDFTest extends TestBase { @Test void test() { - PythonCheckVerifier.verify("src/test/files/rules/detection/kdf/PycaHKDFTestFile.py", this); + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/kdf/PycaHKDFTestFile.py", this); } @Override 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/pyca/kdf/PycaKBKDFCMACTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFCMACTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFCMACTest.java index 0436832c1..16e7df570 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFCMACTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFCMACTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaKBKDFCMACTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaKBKDFCMACTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaKBKDFCMACTestFile.py", this); } @Override 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/pyca/kdf/PycaKBKDFHMACTest.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFHMACTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFHMACTest.java index ef0fad6b9..828cbf21d 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaKBKDFHMACTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaKBKDFHMACTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -52,7 +52,7 @@ class PycaKBKDFHMACTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaKBKDFHMACTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaKBKDFHMACTestFile.py", this); } @Override 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/pyca/kdf/PycaPBKDF2Test.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaPBKDF2Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaPBKDF2Test.java index bdcc9af1f..a714802e5 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaPBKDF2Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaPBKDF2Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -51,7 +51,7 @@ class PycaPBKDF2Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaPBKDF2TestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaPBKDF2TestFile.py", this); } @Override 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/pyca/kdf/PycaScryptTest.java similarity index 96% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaScryptTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaScryptTest.java index b137aabb5..f1a85a3fb 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaScryptTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaScryptTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ class PycaScryptTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaScryptTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaScryptTestFile.py", this); } @Override 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/pyca/kdf/PycaX963KDFTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaX963KDFTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaX963KDFTest.java index 786fa46cb..9310d6380 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/kdf/PycaX963KDFTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/kdf/PycaX963KDFTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.kdf; +package com.ibm.plugin.rules.detection.pyca.kdf; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaX963KDFTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/kdf/PycaX963KDFTestFile.py", this); + "src/test/files/rules/detection/pyca/kdf/PycaX963KDFTestFile.py", this); } @Override 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/pyca/keyagreement/PycaKeyAgreementTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreementTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreementTest.java index bb88f0e77..9327a162f 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/keyagreement/PycaKeyAgreementTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/keyagreement/PycaKeyAgreementTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.keyagreement; +package com.ibm.plugin.rules.detection.pyca.keyagreement; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,8 @@ class PycaKeyAgreementTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/keyagreement/PycaKeyAgreementTestFile.py", this); + "src/test/files/rules/detection/pyca/keyagreement/PycaKeyAgreementTestFile.py", + this); } @Override 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/pyca/mac/PycaCMACTest.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaCMACTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaCMACTest.java index bbc0f3f2b..afeab14a3 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaCMACTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaCMACTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.mac; +package com.ibm.plugin.rules.detection.pyca.mac; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,8 @@ class PycaCMACTest extends TestBase { @Test void test() { - PythonCheckVerifier.verify("src/test/files/rules/detection/mac/PycaCMACTestFile.py", this); + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/mac/PycaCMACTestFile.py", this); } @Override 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/pyca/mac/PycaHMACTest.java similarity index 96% rename from python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaHMACTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaHMACTest.java index 9957f5381..374ee293d 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaHMACTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaHMACTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.mac; +package com.ibm.plugin.rules.detection.pyca.mac; import static org.assertj.core.api.Assertions.assertThat; @@ -47,7 +47,8 @@ class PycaHMACTest extends TestBase { @Test void test() { - PythonCheckVerifier.verify("src/test/files/rules/detection/mac/PycaHMACTestFile.py", this); + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/mac/PycaHMACTestFile.py", this); } @Override 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/pyca/mac/PycaMacDetectionInCustomFunctionTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaMacDetectionInCustomFunctionTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTest.java index 9a89f28eb..e7806b573 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaMacDetectionInCustomFunctionTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.mac; +package com.ibm.plugin.rules.detection.pyca.mac; import static org.assertj.core.api.Assertions.assertThat; @@ -53,7 +53,7 @@ class PycaMacDetectionInCustomFunctionTest extends TestBase { @Test void testCryptographicOperationInCustomFunction() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/mac/PycaMacDetectionInCustomFunctionTestFile.py", + "src/test/files/rules/detection/pyca/mac/PycaMacDetectionInCustomFunctionTestFile.py", this); } 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/pyca/mac/PycaPoly1305Test.java similarity index 74% rename from python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaPoly1305Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaPoly1305Test.java index fecc5bd37..b6e9d0d96 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/mac/PycaPoly1305Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/mac/PycaPoly1305Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.mac; +package com.ibm.plugin.rules.detection.pyca.mac; import static org.assertj.core.api.Assertions.assertThat; @@ -27,8 +27,6 @@ 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; @@ -45,7 +43,7 @@ class PycaPoly1305Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/mac/PycaPoly1305TestFile.py", this); + "src/test/files/rules/detection/pyca/mac/PycaPoly1305TestFile.py", this); } @Override @@ -70,25 +68,13 @@ public void asserts( // Mac INode macNode = nodes.get(0); assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(2); - assertThat(macNode.asString()).isEqualTo("HMAC-Poly1305"); + 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"); - - // 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/pyca/padding/PycaPaddingTest.java similarity index 97% rename from python/src/test/java/com/ibm/plugin/rules/detection/padding/PycaPaddingTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPaddingTest.java index a2f29030a..7d3a43556 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/padding/PycaPaddingTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/padding/PycaPaddingTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.padding; +package com.ibm.plugin.rules.detection.pyca.padding; import static org.assertj.core.api.Assertions.assertThat; @@ -46,7 +46,7 @@ class PycaPaddingTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/padding/PycaPaddingTestFile.py", this); + "src/test/files/rules/detection/pyca/padding/PycaPaddingTestFile.py", this); } @Override 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/pyca/symmetric/PycaCipher1Test.java similarity index 98% rename from python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher1Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher1Test.java index 73a1d6ebb..36834055c 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher1Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher1Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.symmetric; +package com.ibm.plugin.rules.detection.pyca.symmetric; import static org.assertj.core.api.Assertions.assertThat; @@ -50,7 +50,7 @@ class PycaCipher1Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/symmetric/PycaCipher1TestFile.py", this); + "src/test/files/rules/detection/pyca/symmetric/PycaCipher1TestFile.py", this); } @Override 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/pyca/symmetric/PycaCipher2Test.java similarity index 96% rename from python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher2Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher2Test.java index 74ac657a9..a69bca299 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaCipher2Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher2Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.symmetric; +package com.ibm.plugin.rules.detection.pyca.symmetric; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ class PycaCipher2Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/symmetric/PycaCipher2TestFile.py", this); + "src/test/files/rules/detection/pyca/symmetric/PycaCipher2TestFile.py", this); } @Override diff --git a/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher3Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher3Test.java new file mode 100644 index 000000000..0ad33a939 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaCipher3Test.java @@ -0,0 +1,115 @@ +/* + * 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.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; + +class PycaCipher3Test extends TestBase { + + @Test + void test() { + PythonCheckVerifier.verify( + "src/test/files/rules/detection/pyca/symmetric/PycaCipher3TestFile.py", this); + } + + @SuppressWarnings("null") + @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()); + assertThat(store_1).isNotNull(); + 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()).isEqualTo("ENCRYPT"); + } + + // /* + // * Translation + // */ + assertThat(nodes).hasSize(1); + + // BlockCipher + INode blockCipherNode = nodes.get(0); + assertThat(blockCipherNode.getKind()).isEqualTo(BlockCipher.class); + assertThat(blockCipherNode.getChildren()).hasSize(4); + assertThat(blockCipherNode.asString()).isEqualTo("AES-CBC"); + + // Mode under BlockCipher + INode modeNode = blockCipherNode.getChildren().get(Mode.class); + assertThat(modeNode).isNotNull(); + assertThat(modeNode.getChildren()).isEmpty(); + assertThat(modeNode.asString()).isEqualTo("CBC"); + + // 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"); + + // 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/PycaStreamCipher1Test.java b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaStreamCipher1Test.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java index 7c5b1f339..98776e4f0 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/symmetric/PycaStreamCipher1Test.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/symmetric/PycaStreamCipher1Test.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.symmetric; +package com.ibm.plugin.rules.detection.pyca.symmetric; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +44,7 @@ class PycaStreamCipher1Test extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/symmetric/PycaStreamCipher1TestFile.py", this); + "src/test/files/rules/detection/pyca/symmetric/PycaStreamCipher1TestFile.py", this); } @Override 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/pyca/wrapping/PycaWrappingTest.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingTest.java index 003eaaf9c..9c1a34897 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.wrapping; +package com.ibm.plugin.rules.detection.pyca.wrapping; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +44,7 @@ class PycaWrappingTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/wrapping/PycaWrappingTestFile.py", this); + "src/test/files/rules/detection/pyca/wrapping/PycaWrappingTestFile.py", this); } @Override 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/pyca/wrapping/PycaWrappingWithPaddingTest.java similarity index 95% rename from python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingWithPaddingTest.java rename to python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTest.java index 37d0bb788..bfeeec87f 100644 --- a/python/src/test/java/com/ibm/plugin/rules/detection/wrapping/PycaWrappingWithPaddingTest.java +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTest.java @@ -17,7 +17,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.ibm.plugin.rules.detection.wrapping; +package com.ibm.plugin.rules.detection.pyca.wrapping; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +44,8 @@ class PycaWrappingWithPaddingTest extends TestBase { @Test void test() { PythonCheckVerifier.verify( - "src/test/files/rules/detection/wrapping/PycaWrappingWithPaddingTestFile.py", this); + "src/test/files/rules/detection/pyca/wrapping/PycaWrappingWithPaddingTestFile.py", + this); } @Override 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/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..54e77578a --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/DSATest.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.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.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(PythonCryptoPublicKey.DSARules()); + } + + @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..4397a1e9f --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/ECCTest.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.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.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(PythonCryptoPublicKey.ECCRules()); + } + + @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..b761bf335 --- /dev/null +++ b/python/src/test/java/com/ibm/plugin/rules/detection/pycrypto/publickey/RSATest.java @@ -0,0 +1,145 @@ +/* + * 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.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(PythonCryptoPublicKey.RSARules()); + } + + @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"); + } + } +}