Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ 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] |


[^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.
Expand Down
5 changes: 5 additions & 0 deletions docs/DETECTION_RULE_STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ new DetectionRuleBuilder<T>()
]?
[.addDependingDetectionRules(detectionRules)]?
]+
[
.withOtherParameters()
]
.buildForContext(detectionValueContext)
.inBundle(bundle)
.withDependingDetectionRules(detectionRules) | .withoutDependingDetectionRules()
Expand Down Expand Up @@ -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".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class MethodMatcher<T> {
@Nonnull private final List<String> invokedObjectTypeStringsSerializable;
@Nonnull private final List<String> methodNamesSerializable;
@Nonnull private final List<String> parameterTypesSerializable;
private final boolean prefixMatch;

public MethodMatcher(
@Nonnull String invokedObjectTypeString,
Expand All @@ -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))));
Expand All @@ -73,10 +75,19 @@ public MethodMatcher(
@Nonnull String[] invokedObjectTypeStrings,
@Nonnull String[] methodNames,
@Nonnull List<String> parameterTypes) {
this(invokedObjectTypeStrings, methodNames, parameterTypes, false);
}

public MethodMatcher(
@Nonnull String[] invokedObjectTypeStrings,
@Nonnull String[] methodNames,
@Nonnull List<String> parameterTypes,
boolean prefixMatch) {

this.invokedObjectTypeStringsSerializable = Arrays.asList(invokedObjectTypeStrings);
this.methodNamesSerializable = Arrays.asList(methodNames);
this.parameterTypesSerializable = parameterTypes;
this.prefixMatch = prefixMatch;

this.invokedObjectTypeString =
createPredicate(
Expand All @@ -95,7 +106,9 @@ public MethodMatcher(
type -> type.is(parameterType), parameterType))
.toList();
this.parameterTypes =
(List<IType> actualTypes) -> exactMatchesParameters(types, actualTypes);
prefixMatch
? (List<IType> actualTypes) -> prefixMatchesParameters(types, actualTypes)
: (List<IType> actualTypes) -> exactMatchesParameters(types, actualTypes);
}

public MethodMatcher(
Expand All @@ -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(
Expand Down Expand Up @@ -146,6 +160,13 @@ private boolean exactMatchesParameters(
&& matchesParameters(expectedTypes, actualTypes);
}

private boolean prefixMatchesParameters(
@Nonnull List<Predicate<IType>> expectedTypes, @Nonnull List<IType> actualTypes) {
return !expectedTypes.isEmpty()
&& actualTypes.size() >= expectedTypes.size()
&& matchesParameters(expectedTypes, actualTypes);
}

private boolean matchesParameters(
@Nonnull List<Predicate<IType>> expectedTypes, @Nonnull List<IType> actualTypes) {
for (int i = 0; i < expectedTypes.size(); i++) {
Expand Down Expand Up @@ -244,4 +265,8 @@ public List<String> getMethodNamesSerializable() {
public List<String> getParameterTypesSerializable() {
return this.parameterTypesSerializable;
}

public boolean isPrefixMatch() {
return this.prefixMatch;
}
}
12 changes: 12 additions & 0 deletions engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ interface ParametersFactoryBuilder<T> {
ParametersFinalDetectionRuleBuilder<T> addDependingDetectionRules(
@Nonnull List<IDetectionRule<T>> detectionRules);

@Nonnull
FinalDetectionRuleBuilder<T> withOtherParameters();

@Nonnull
AddBundleDetectionRuleBuilder<T> buildForContext(
@Nonnull IDetectionContext detectionValueContext);
Expand All @@ -121,6 +124,9 @@ interface PositionBuilder<T> {
ParametersFinalDetectionRuleBuilder<T> addDependingDetectionRules(
@Nonnull List<IDetectionRule<T>> detectionRules);

@Nonnull
FinalDetectionRuleBuilder<T> withOtherParameters();

@Nonnull
AddBundleDetectionRuleBuilder<T> buildForContext(
@Nonnull IDetectionContext detectionValueContext);
Expand All @@ -137,6 +143,9 @@ interface ParametersDependingRulesBuilder<T> {
ParametersFinalDetectionRuleBuilder<T> addDependingDetectionRules(
@Nonnull List<IDetectionRule<T>> detectionRules);

@Nonnull
FinalDetectionRuleBuilder<T> withOtherParameters();

@Nonnull
AddBundleDetectionRuleBuilder<T> buildForContext(
@Nonnull IDetectionContext detectionValueContext);
Expand All @@ -149,6 +158,9 @@ interface ParametersFinalDetectionRuleBuilder<T> {
@Nonnull
ParametersFactoryBuilder<T> withMethodParameterMatchExactType(@Nonnull String type);

@Nonnull
FinalDetectionRuleBuilder<T> withOtherParameters();

@Nonnull
AddBundleDetectionRuleBuilder<T> buildForContext(
@Nonnull IDetectionContext detectionValueContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,29 @@ public IDetectionRule.FinalDetectionRuleBuilder<T> withAnyParameters() {
bundle);
}

@Nonnull
@Override
public IDetectionRule.FinalDetectionRuleBuilder<T> 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<T> asChildOfParameterWithId(int id) {
Expand Down Expand Up @@ -489,7 +512,8 @@ private IDetectionRule<T> 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,
Expand Down Expand Up @@ -543,6 +567,7 @@ private void checkDetectionParameterState() {

enum CapturedParameterScope {
SOME,
SOME_WITH_REMAINDER,
ANY,
NONE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public static <T> String getMatcherID(MethodMatcher<T> methodMatcher) {
for (String parameterType : methodMatcher.getParameterTypesSerializable()) {
stringID += parameterType + " ";
}
stringID += "| " + methodMatcher.isPrefixMatch();
return stringID;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public void serialize(MethodMatcher matcher, JsonGenerator jgen, SerializerProvi
}
jgen.writeEndArray();

jgen.writeBooleanField("prefixMatch", matcher.isPrefixMatch());

jgen.writeEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
};
}
Expand Down
113 changes: 113 additions & 0 deletions mapper/src/main/java/com/ibm/mapper/mapper/pyca/PycaCurveMapper.java
Original file line number Diff line number Diff line change
@@ -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<EllipticCurveAlgorithm> 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();
};
}
}
Loading
Loading