diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 1f9a84af2..452b68c76 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -20,7 +20,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v6 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Deploy to GitHub Packages run: mvn --batch-mode deploy diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 83ef9ae63..49b17b6fa 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -10,7 +10,6 @@ name: Java CI with Maven permissions: contents: write - packages: write on: push: @@ -23,16 +22,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v6 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B clean package - name: Verify analyzer redistribution materials run: python3 sonar-cryptography-plugin/src/test/python/verify_analyzer_packaging.py sonar-cryptography-plugin/target/sonar-cryptography-plugin-*.jar + - name: Upload JaCoCo coverage reports + uses: actions/upload-artifact@v7 + with: + name: "jacoco-coverage-reports" + path: "**/target/site/jacoco/" + if-no-files-found: warn # The build must not write into the source tree. It used to: the mapper # module regenerated ciphersuites.json and JsonCipherSuites.java from a live # API on every compile. Fail here if that ever comes back. diff --git a/.github/workflows/update-cipher-suites.yml b/.github/workflows/update-cipher-suites.yml index 67fd35e81..aff2d65e9 100644 --- a/.github/workflows/update-cipher-suites.yml +++ b/.github/workflows/update-cipher-suites.yml @@ -26,10 +26,10 @@ jobs: steps: - uses: actions/checkout@v7 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v6 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..a4ff3e10b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is the **Sonar Cryptography Plugin (CBOMkit-hyperion)** - a SonarQube plugin that detects cryptographic assets in source code and generates Cryptographic Bill of Materials (CBOM) in CycloneDX format. Part of the [CBOMKit](https://github.com/cbomkit) toolset. + +**Supported languages/libraries:** + +- Java: JCA (100%), BouncyCastle light-weight API (100%) +- Python: pyca/cryptography (100%) +- Go: `crypto` standard library (100%, except `crypto/x509`), `golang.org/x/crypto` (partial: `hkdf`, `pbkdf2`, `sha3`) +- C/C++: OpenSSL (EVP, legacy API, SSL/TLS, PRNG), analyzed via the bundled sonar-cxx plugin +- C#: `System.Security.Cryptography` - in development, ANTLR-based parser, only engine-verification rules; not for active use + +## Build Commands + +```bash +# Build with tests +mvn clean package + +# Build without tests +mvn clean package -DskipTests + +# Build specific module +mvn clean package -pl java + +# Format code (Google Java Format, AOSP style) +mvn spotless:apply + +# Check formatting +mvn spotless:check + +# Check code style +mvn checkstyle:check +``` + +**Cipher-suite data:** `mapper/ciphersuites.json` and the generated +`mapper/src/main/java/.../ssl/json/JsonCipherSuites.java` are checked into git. The build does +not touch them. To refresh them from ciphersuite.info, run `mapper/download-cipher-suites.sh` +and then `mvn -pl mapper spotless:apply`, or trigger the `Update cipher suites` workflow, which +opens a pull request with the change. + +## Testing + +```bash +# Run all tests +mvn test + +# Run tests for specific module +mvn test -pl java +mvn test -pl python +mvn test -pl go +mvn test -pl cpp +mvn test -pl csharp +mvn test -pl mapper +mvn test -pl engine + +# Run specific test class +mvn test -Dtest=SimpleGuidelineTest + +# Run specific test method +mvn test -Dtest=SimpleGuidelineTest#testBasicAssertion +``` + +**Testing Framework:** JUnit 5 + AssertJ + SonarQube Test Fixtures + +**Detection rule tests:** + +- Extend the `TestBase` of the language module you're testing (`java`, `python`, `go`, `cpp` and `csharp` each have their own at `/src/test/java/com/ibm/plugin/TestBase.java`) +- Use the verifier for that language: `CheckVerifier` (Java), `PythonCheckVerifier` (Python), `GoVerifier` (Go), `CxxVerifier` (C/C++), `CSharpVerifier` (C#) - the last two live in the module's own test sources +- Test files (actual code to analyze) go in `src/test/files/`, not `src/test/java/` +- Implement `asserts()` method to verify detection store values and translated nodes + +## Architecture + +Multi-module Maven project (Java 21): + +``` +sonar-cryptography-plugin/ # Main SonarQube plugin entry point +├── CryptographyPlugin.java # Plugin registration +├── OutputFileJob.java # CBOM output handler +engine/ # Core detection engine +├── detection/ # DetectionStore, Finding classes +├── rule/ # IDetectionRule interface +├── language// # Per-language ILanguageSupport/ILanguageTranslation (java, python, go, cxx, csharp) +java/ # Java language support (JCA, BouncyCastle) +├── rules/detection/ # Java detection rules +python/ # Python language support (pyca/cryptography) +go/ # Go language support (crypto stdlib, golang.org/x/crypto) +├── rules/detection/gocrypto/ # Go detection rules +cpp/ # C/C++ language support (OpenSSL, via sonar-cxx) +├── rules/detection/openssl/ # OpenSSL detection rules +csharp/ # C# language support (ANTLR grammar, in development) +mapper/ # Translation layer to CBOM model +├── model/ # Core data model (Algorithm, Key, Protocol, etc.) +├── ITranslator.java # Main translation interface +enricher/ # Adds algorithm details to findings +output/ # CBOM/CycloneDX generation +common/ # Shared utilities +rules/ # Shared rule definitions +bom/ # Sample CBOM output (bom.json) +``` + +**Data flow:** Source code → Language module (AST) → Engine (detection) → Mapper (translation) → Enricher → Output (CBOM) + +## Code Style + +- **Formatting:** Google Java Format (AOSP style) via Spotless - runs on `mvn package` +- **License:** Apache 2.0 header required in all Java files (applied by Spotless) +- **Checkstyle rules:** No unused imports, camelCase lambda params, max 5 boolean operators, private utility constructors, @Override required + +## Running with SonarQube + +```bash +docker-compose up # Starts PostgreSQL + SonarQube +``` + +Plugin JAR is built to `sonar-cryptography-plugin/target/` and copied to `.SonarQube/plugins/`. + +## Key Documentation + +- `docs/LANGUAGE_SUPPORT.md` - Extending for new languages/libraries +- `docs/DETECTION_RULE_STRUCTURE.md` - Writing detection rules +- `docs/TROUBLESHOOTING.md` - Testing configuration guide +- `docs/PERFORMANCE_TESTING.md` - Runtime/heap measurement, including full Keycloak scans +- `README.md` (Build section) - Adding packages to `sonar-go-to-slang` when Go type resolution fails diff --git a/README.md b/README.md index 87bf85ebc..7f2f6e8aa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![License](https://img.shields.io/github/license/cbomkit/sonar-cryptography.svg?)](https://opensource.org/licenses/Apache-2.0) [![Current Release](https://img.shields.io/github/release/cbomkit/sonar-cryptography.svg?logo=IBM)](https://github.com/cbomkit/sonar-cryptography/releases) - This repository contains a SonarQube Plugin that detects cryptographic assets in source code and generates [CBOM](https://cyclonedx.org/capabilities/cbom/). It is part of **the [CBOMKit](https://github.com/cbomkit) toolset**. @@ -22,52 +21,70 @@ It is part of **the [CBOMKit](https://github.com/cbomkit) toolset**. ## Version compatibility -| Plugin Version | SonarQube Version | -|-----------------|--------------------------------| -| 1.3.7 and up | SonarQube 9.9 (LTS) and up | -| 1.3.2 and 1.3.6 | SonarQube 9.8 (LTS) up to 10.8 | -| 1.2.0 to 1.3.1 | SonarQube 9.8 (LTS) up to 10.4 | - +| Plugin Version | SonarQube Version | Requires Java | +| -------------- | --------------------------------------- | ------------- | +| 2.0.0 and up | SonarQube 2025.1 (LTA) and up | 21 | +| 1.3.2 to 1.3.x | SonarQube 9.14 (LTS) up to 2025.1 (LTA) | 17 | +| 1.2.0 to 1.3.1 | SonarQube 9.14 (LTS) up to 10.4 | 17 | ## Supported languages and libraries -| Language | Cryptographic Library | Coverage | -|----------|-----------------------------------------------------------------------------------------------|------------------| -| 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% | -| 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] | +| Language | Cryptographic Library | Coverage | +| -------- | --------------------------------------------------------------------------------------------------------- | ------------------ | +| 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% | +| 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] | | C# | [System.Security.Cryptography](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography) | In development[^4] | +| C/C++ | [OpenSSL](https://www.openssl.org/) | Partial[^5] | + +[^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) -[^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]: C# support uses an [ANTLR v7 grammar](https://github.com/antlr/grammars-v4/tree/master/csharp) to parse source files directly. The current csharp support only covers the language support and does not contain detection rules other than the rules used for verifying the detection engine. **This is not yet meant for active usage!** **Known limitations of the detection engine:** no cross-method variable tracking (only single-method scope), only works for c# v7, string-based matching (no type resolution) +[^5]: Covers the OpenSSL EVP API (ciphers, digests, MACs, KDFs, key agreement, key encapsulation, key generation, signatures, envelope encryption), the legacy (pre-EVP) API, SSL/TLS configuration, X.509 certificate, request and CRL signatures, CMS/PKCS#7/OCSP signatures and encryption, encrypted private keys and password-based encryption, and random number generation. An operation is reported with the key it uses when the key is generated in the scanned code, a signature made with a key from elsewhere as a signature with its digest, and a digest or cipher where it is selected (e.g. the digest of `EVP_DigestInit` or `EVP_Digest`). ENGINE, provider and FIPS configuration (`ENGINE_by_id`, `OSSL_PROVIDER_load`, `EVP_default_properties_enable_fips`) is not reported: it configures the whole process rather than naming an algorithm, as the JCA provider of a Java call is not reported either. Uses the [sonar-cxx](https://github.com/SonarOpenCommunity/sonar-cxx) plugin, which is bundled (see [Installation](#installation)). + > [!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. -> - To add support for another language or cryptography library, see [*Extending the Sonar Cryptography Plugin to add support for another language or cryptography library*](./docs/LANGUAGE_SUPPORT.md) -> - If you just want to know more about the syntax for writing new detection rules, see [*Writing new detection rules for the Sonar Cryptography Plugin*](./docs/DETECTION_RULE_STRUCTURE.md) +> +> - To add support for another language or cryptography library, see [_Extending the Sonar Cryptography Plugin to add support for another language or cryptography library_](./docs/LANGUAGE_SUPPORT.md) +> - If you just want to know more about the syntax for writing new detection rules, see [_Writing new detection rules for the Sonar Cryptography Plugin_](./docs/DETECTION_RULE_STRUCTURE.md) ## Installation -> [!NOTE] -> To run the plugin, you need a running SonarQube instance with one of the supported -> versions. If you don't have one but want to try the plugin, you can use the -> included Docker Compose to set up a development environment. See +> [!NOTE] +> To run the plugin, you need a running SonarQube instance with one of the supported +> versions (see [Version compatibility](#version-compatibility) above), and that +> instance's own JVM must be Java 21 or newer — the plugin JAR is built for Java 21 +> and a SonarQube server running on Java 17 will fail to load it. If you don't have +> a suitable instance but want to try the plugin, you can use the included Docker +> Compose to set up a development environment. See > [here](CONTRIBUTING.md#build) for instructions. Copy the plugin (the JAR file from the [latest releases](https://github.com/cbomkit/sonar-cryptography/releases)) -to `$SONARQUBE_HOME/extensions/plugins` and restart +to `$SONARQUBE_HOME/extensions/plugins` and restart SonarQube ([more](https://docs.sonarqube.org/latest/setup-and-upgrade/install-a-plugin/)). +> [!IMPORTANT] +> C/C++ support comes from [sonar-cxx](https://github.com/SonarOpenCommunity/sonar-cxx), which is +> bundled inside this plugin's JAR, the same way Java, Python and Go parsing are bundled. You do +> not need to install sonar-cxx separately. +> +> If sonar-cxx is already installed on your SonarQube instance, remove it. Both plugins would +> register the same C/C++ language, rules and configuration properties, and SonarQube will fail +> to start. + ## Using The plugin provides new rules regarding the use of cryptography for the supported languages. They are grouped in the **Sonar Cryptography** rule repositories, one per language -(`sonar-java-crypto`, `sonar-python-crypto` and `sonar-go-crypto`). -If you enable the *Cryptographic Inventory (CBOM)* rule, a source code scan creates a cryptographic +(`sonar-java-crypto`, `sonar-python-crypto`, `sonar-go-crypto` and `sonar-cpp-crypto`). +If you enable the _Cryptographic Inventory (CBOM)_ rule, a source code scan creates a cryptographic inventory by creating a [CBOM](https://cyclonedx.org/capabilities/cbom/) with all cryptographic assets and writing a `cbom.json` to the scan directory. @@ -76,32 +93,37 @@ assets and writing a `cbom.json` to the scan directory. This plugin incorporates rules specifically focused on cryptography. > To generate a Cryptography Bill of Materials (CBOM), it is mandatory to activate the -> *Cryptographic Inventory (CBOM)* rule. +> _Cryptographic Inventory (CBOM)_ rule. ![Activate Rules Crypto Rules](docs/images/rules.png) The plugin currently ships these rules: -| Rule | Languages | Contributes to the CBOM | -|----------------------------------------------------------|------------------|-------------------------| -| *Cryptographic Inventory (CBOM)* | Java, Python, Go | yes | -| *Do not use MD5 for cryptographic purposes like hashing* | Java, Python | no | +| Rule | Languages | Contributes to the CBOM | +| -------------------------------------------------------- | ----------------------- | ----------------------- | +| _Cryptographic Inventory (CBOM)_ | Java, Python, Go, C/C++ | yes | +| _Do not use MD5 for cryptographic purposes like hashing_ | Java, Python, C/C++ | no | -Only the *Cryptographic Inventory (CBOM)* rule writes a `cbom.json`; the other rules just raise +Only the _Cryptographic Inventory (CBOM)_ rule writes a `cbom.json`; the other rules just raise issues on the scanned code. Future updates may introduce additional rules to expand functionality. ### Scan Source Code -Now you can follow the [SonarQube documentation](https://docs.sonarqube.org/latest/analyzing-source-code/overview/) -to start your first scan. +Now you can follow the [SonarQube documentation](https://docs.sonarqube.org/latest/analyzing-source-code/overview/) +to start your first scan. See [_Running the Plugin with SonarQube_](./docs/RUNNING_WITH_SONARQUBE.md) +for a local setup and the scan settings of each language. + +> [!IMPORTANT] +> The scanner must run on Java 21 (see [Prerequisites](./docs/RUNNING_WITH_SONARQUBE.md#prerequisites)). +> A C/C++ scan requires `sonar.cxx.file.suffixes`. ### Configuration | Property | Default | Scope | Description | -|----------------------------|---------|---------|-------------------------------------------------------------------------------| +| -------------------------- | ------- | ------- | ----------------------------------------------------------------------------- | | `sonar.cryptoScanner.cbom` | `cbom` | Project | Filename (without extension) of the generated CBOM, written as `.json`. | -The property can be set in the SonarQube UI under *Project Settings → General*, or passed to the +The property can be set in the SonarQube UI under _Project Settings → General_, or passed to the scanner directly: ```bash @@ -208,6 +230,7 @@ The plugin generates a `cbom.json` file in [CycloneDX CBOM format](https://cyclo ``` The CBOM includes: + - **Algorithms**: Hash functions, ciphers, key exchange mechanisms with their parameters - **Keys and secrets**: Private keys, secret keys, and other cryptographic materials - **Evidence**: Source file locations where each asset was detected @@ -287,10 +310,10 @@ Run with `go run gen_package.go`, then delete the script. 2. **Check for dependencies**: Some packages depend on types from other packages. Common dependencies: -| Package | May require | -|---------|-------------| -| `crypto/hmac` | `hash` | -| `crypto/cipher` | `io` | +| Package | May require | +| ----------------- | ------------ | +| `crypto/hmac` | `hash` | +| `crypto/cipher` | `io` | | `crypto/*` (most) | `io`, `hash` | 3. **Add mapping entry** to `mapping_generated.go` in alphabetical order: @@ -303,19 +326,19 @@ Run with `go run gen_package.go`, then delete the script. ### File naming convention -| Package Path | Export Data File | -|--------------|------------------| -| `crypto/hmac` | `crypto_hmac.o` | -| `crypto/elliptic` | `crypto_elliptic.o` | +| Package Path | Export Data File | +| ---------------------------- | ------------------- | +| `crypto/hmac` | `crypto_hmac.o` | +| `crypto/elliptic` | `crypto_elliptic.o` | | `golang.org/x/crypto/bcrypt` | `x_crypto_bcrypt.o` | ## Help and troubleshooting -If you encounter difficulties or unexpected results while installing the plugin with SonarQube, or when trying to scan a repository, please check out our guide [*Testing your configuration and troubleshooting*](docs/TROUBLESHOOTING.md) to run our plugin with step-by-step instructions. +If you encounter difficulties or unexpected results while installing the plugin with SonarQube, or when trying to scan a repository, please check out our guide [_Testing your configuration and troubleshooting_](docs/TROUBLESHOOTING.md) to run our plugin with step-by-step instructions. -To measure the plugin's runtime performance and heap usage — including a full end-to-end scan of a large project (Keycloak) — see [*Performance & Heap Testing*](docs/PERFORMANCE_TESTING.md). +To measure the plugin's runtime performance and heap usage — including a full end-to-end scan of a large project (Keycloak) — see [_Performance & Heap Testing_](docs/PERFORMANCE_TESTING.md). ## Contribution Guidelines @@ -345,3 +368,7 @@ The notices describe the versions, source locations and packaging transformation These packaging materials do not establish that every use is permitted by SSAL. The permitted-purpose restrictions and the provenance and licence notices of any adapted implementation code still require review before release. + +The plugin JAR bundles [sonar-cxx](https://github.com/SonarOpenCommunity/sonar-cxx) for C/C++ +analysis, which is licensed under the +[GNU Lesser General Public License v3.0](https://github.com/SonarOpenCommunity/sonar-cxx/blob/master/LICENSE). diff --git a/common/pom.xml b/common/pom.xml index 29a10f02c..e5226adcb 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -12,8 +12,6 @@ common - 17 - 17 UTF-8 diff --git a/cpp/pom.xml b/cpp/pom.xml new file mode 100644 index 000000000..c2f4251cf --- /dev/null +++ b/cpp/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + com.ibm + sonar-cryptography + 2.0.0-SNAPSHOT + + + cpp + + + UTF-8 + + + + + com.ibm + engine + 2.0.0-SNAPSHOT + + + com.ibm + output + 2.0.0-SNAPSHOT + + + com.ibm + enricher + 2.0.0-SNAPSHOT + + + com.ibm + rules + 2.0.0-SNAPSHOT + compile + + + org.sonarsource.analyzer-commons + sonar-analyzer-test-commons + test + + + diff --git a/cpp/src/main/java/com/ibm/plugin/CxxAggregator.java b/cpp/src/main/java/com/ibm/plugin/CxxAggregator.java new file mode 100644 index 000000000..09e403ebc --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/CxxAggregator.java @@ -0,0 +1,68 @@ +/* + * 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; + +import com.ibm.engine.language.ILanguageSupport; +import com.ibm.engine.language.cxx.CxxLanguageSupporter; +import com.ibm.mapper.model.INode; +import com.ibm.output.IAggregator; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +public final class CxxAggregator implements IAggregator { + + private static ILanguageSupport< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + cxxLanguageSupport = CxxLanguageSupporter.cxxLanguageSupporter(); + private static List detectedNodes = new ArrayList<>(); + + private CxxAggregator() { + // private + } + + public static void addNodes(@Nonnull List newNodes) { + detectedNodes.addAll(newNodes); + IAggregator.log(newNodes); + } + + @Nonnull + public static List getDetectedNodes() { + return Collections.unmodifiableList(detectedNodes); + } + + @Nonnull + public static ILanguageSupport< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + getLanguageSupport() { + return cxxLanguageSupport; + } + + public static void reset() { + cxxLanguageSupport = CxxLanguageSupporter.cxxLanguageSupporter(); + detectedNodes = new ArrayList<>(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/CxxCheckRegistrar.java b/cpp/src/main/java/com/ibm/plugin/CxxCheckRegistrar.java new file mode 100644 index 000000000..98877cfd1 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/CxxCheckRegistrar.java @@ -0,0 +1,48 @@ +/* + * 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; + +import java.util.List; +import org.sonar.cxx.squidbridge.api.CxxCustomRuleRepository; +import org.sonarsource.api.sonarlint.SonarLintSide; + +/** + * Registers C++ cryptography detection rules with the sonar-cxx analysis framework. + * + *

This class implements the {@link CxxCustomRuleRepository} interface provided by sonar-cxx, + * which allows external plugins to register custom checks that will be executed during C++ code + * analysis. + * + *

The sonar-cxx plugin will discover this class via service loading and invoke the {@link + * #checkClasses()} method to obtain the list of check classes to instantiate and run. + */ +@SonarLintSide +public class CxxCheckRegistrar implements CxxCustomRuleRepository { + + @Override + public String repositoryKey() { + return CxxScannerRuleDefinition.REPOSITORY_KEY; + } + + @Override + public List> checkClasses() { + return CxxRuleList.getChecks(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/CxxRuleList.java b/cpp/src/main/java/com/ibm/plugin/CxxRuleList.java new file mode 100644 index 000000000..8c5912868 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/CxxRuleList.java @@ -0,0 +1,49 @@ +/* + * 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; + +import com.ibm.plugin.rules.CxxInventoryRule; +import com.ibm.plugin.rules.CxxNoMD5UseRule; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +public final class CxxRuleList { + + private CxxRuleList() {} + + public static @Nonnull List> getChecks() { + List> checks = new ArrayList<>(); + checks.addAll(getCxxChecks()); + checks.addAll(getCxxTestChecks()); + return Collections.unmodifiableList(checks); + } + + /** These rules are going to target MAIN code only */ + public static @Nonnull List> getCxxChecks() { + return List.of(CxxInventoryRule.class, CxxNoMD5UseRule.class); + } + + /** These rules are going to target TEST code only */ + public static @Nonnull List> getCxxTestChecks() { + return List.of(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/CxxScannerRuleDefinition.java b/cpp/src/main/java/com/ibm/plugin/CxxScannerRuleDefinition.java new file mode 100644 index 000000000..53bc02af4 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/CxxScannerRuleDefinition.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import org.sonar.api.SonarRuntime; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonarsource.analyzer.commons.RuleMetadataLoader; + +public class CxxScannerRuleDefinition implements RulesDefinition { + public static final String REPOSITORY_KEY = "sonar-cpp-crypto"; + public static final String REPOSITORY_NAME = "Sonar Cryptography"; + public static final String LANGUAGE_KEY = "cxx"; + + // Add the rule keys of the rules which need to be considered as template-rules + private static final Set RULE_TEMPLATES_KEY = Collections.emptySet(); + + private static final String RESOURCE_BASE_PATH = "/org/sonar/l10n/cpp/rules/cpp"; + + private final SonarRuntime sonarRuntime; + + public CxxScannerRuleDefinition(SonarRuntime sonarRuntime) { + this.sonarRuntime = sonarRuntime; + } + + @Override + public void define(Context context) { + NewRepository repository = + context.createRepository(REPOSITORY_KEY, LANGUAGE_KEY).setName(REPOSITORY_NAME); + + RuleMetadataLoader ruleMetadataLoader = + new RuleMetadataLoader(RESOURCE_BASE_PATH, this.sonarRuntime); + ruleMetadataLoader.addRulesByAnnotatedClass(repository, CxxRuleList.getChecks()); + + setTemplates(repository); + + repository.done(); + } + + private static void setTemplates(NewRepository repository) { + RULE_TEMPLATES_KEY.stream() + .map(repository::rule) + .filter(Objects::nonNull) + .forEach(rule -> rule.setTemplate(true)); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/package-info.java b/cpp/src/main/java/com/ibm/plugin/package-info.java new file mode 100644 index 000000000..aa6059345 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/package-info.java @@ -0,0 +1,5 @@ +@ParametersAreNonnullByDefault +@javax.annotation.CheckReturnValue +package com.ibm.plugin; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/cpp/src/main/java/com/ibm/plugin/rules/CxxInventoryRule.java b/cpp/src/main/java/com/ibm/plugin/rules/CxxInventoryRule.java new file mode 100644 index 000000000..1edd1e4a0 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/CxxInventoryRule.java @@ -0,0 +1,61 @@ +/* + * 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; + +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.rules.detection.CxxBaseDetectionRule; +import com.ibm.plugin.rules.detection.CxxDetectionRules; +import com.ibm.plugin.translation.reorganizer.CxxReorganizerRules; +import com.ibm.rules.InventoryRule; +import com.ibm.rules.issue.Issue; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.check.Rule; +import org.sonar.java.annotations.VisibleForTesting; + +/** + * C++ Inventory Rule for detecting cryptographic assets. + * + *

This rule scans C++ source files for cryptographic API usage and reports findings for + * inventory purposes. Detected assets are exported to the Cryptographic Bill of Materials (CBOM) + * output. + */ +@Rule(key = "Inventory") +public class CxxInventoryRule extends CxxBaseDetectionRule { + + public CxxInventoryRule() { + super(true, RuleSets.rulesOf(CxxDetectionRules.class), CxxReorganizerRules.rules()); + } + + @VisibleForTesting + protected CxxInventoryRule(@Nonnull List> detectionRules) { + super(true, detectionRules, CxxReorganizerRules.rules()); + } + + @Override + @Nonnull + public List> report( + @Nonnull AstNode markerTree, @Nonnull List translatedNodes) { + return new InventoryRule().report(markerTree, translatedNodes); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/CxxNoMD5UseRule.java b/cpp/src/main/java/com/ibm/plugin/rules/CxxNoMD5UseRule.java new file mode 100644 index 000000000..4ce6228dd --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/CxxNoMD5UseRule.java @@ -0,0 +1,40 @@ +/* + * 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; + +import com.ibm.mapper.model.INode; +import com.ibm.plugin.rules.detection.CxxBaseDetectionRule; +import com.ibm.rules.NoMD5UseForMessageDigestRule; +import com.ibm.rules.issue.Issue; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.check.Rule; + +@Rule(key = "CxxNoMD5use") +public class CxxNoMD5UseRule extends CxxBaseDetectionRule { + + @Nonnull + @Override + public List> report( + @Nonnull AstNode markerTree, @Nonnull List translatedNodes) { + return new NoMD5UseForMessageDigestRule().report(markerTree, translatedNodes); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRule.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRule.java new file mode 100644 index 000000000..e0d6ff9ee --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRule.java @@ -0,0 +1,407 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import com.ibm.common.IObserver; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.Finding; +import com.ibm.engine.executive.DetectionExecutive; +import com.ibm.engine.language.cxx.CxxConstructorCalls; +import com.ibm.engine.language.cxx.CxxScanContext; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.IAsset; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.IAssetCollection; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.translation.CxxTranslationProcess; +import com.ibm.plugin.translation.reorganizer.CxxReorganizerRules; +import com.ibm.rules.IReportableDetectionRule; +import com.ibm.rules.issue.Issue; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.AstNodeType; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.IntStream; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.AstNodeTraversal; +import org.sonar.cxx.squidbridge.api.AstNodeTypeExtension; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * Base class for C++ cryptography detection rules. + * + *

This class extends {@link SquidCheck} to integrate with the sonar-cxx analysis framework and + * implements the observer pattern to receive detection findings from the engine. + * + *

The detection flow works as follows: + * + *

    + *
  1. sonar-cxx finishes its shared node-by-node walk of the file (symbol resolution included) + * and calls {@link #leaveFile(AstNode)} + *
  2. We traverse the AST looking for function calls and constructor invocations + *
  3. For each relevant node, we create a {@link DetectionExecutive} and start detection + *
  4. When a finding is detected, {@link #update(Finding)} collects it + *
  5. Once the file is traversed, each finding that no other finding reports (see {@link + * #leaveFile}) is translated, reorganized, enriched, and optionally reported + *
+ */ +public abstract class CxxBaseDetectionRule extends SquidCheck + implements IObserver< + Finding< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>>, + IReportableDetectionRule { + + private static final Logger LOGGER = LoggerFactory.getLogger(CxxBaseDetectionRule.class); + + /** Node types to detect: function calls, new expressions, and enum specifiers. */ + private static final AstNodeType[] DETECTION_NODE_TYPES = { + CxxGrammarImpl.postfixExpression, + CxxGrammarImpl.newExpression, + CxxGrammarImpl.initDeclarator, + CxxGrammarImpl.enumSpecifier + }; + + private final boolean isInventory; + @Nonnull protected final CxxTranslationProcess cxxTranslationProcess; + @Nonnull protected final List> detectionRules; + + /** The findings of the file, handled once the file is traversed, see {@link #leaveFile}. */ + @Nonnull + private final List< + Finding< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + fileFindings = new ArrayList<>(); + + protected CxxBaseDetectionRule() { + this.isInventory = false; + this.detectionRules = RuleSets.rulesOf(CxxDetectionRules.class); + this.cxxTranslationProcess = new CxxTranslationProcess(CxxReorganizerRules.rules()); + } + + protected CxxBaseDetectionRule( + final boolean isInventory, + @Nonnull List> detectionRules, + @Nonnull List reorganizerRules) { + this.isInventory = isInventory; + this.detectionRules = detectionRules; + this.cxxTranslationProcess = new CxxTranslationProcess(reorganizerRules); + } + + @Override + public void init() { + CxxSymbolExtensionRelease.register(getContext(), this); + } + + /** + * Called when a file finishes analysis. Traverses the AST to find detection targets. + * + *

{@code leaveFile} runs after sonar-cxx's shared node-by-node walk of the file, over every + * registered visitor - including {@code CxxSymbolResolverVisitor}, which populates {@link + * AstNodeSymbolExtension}/{@link AstNodeTypeExtension} during that walk - so symbol resolution + * for the whole file is already complete by the time this runs. The file's symbol and type + * entries are removed by {@link CxxSymbolExtensionRelease} once every rule of the scan has left + * the file. + * + *

A call can be detected on its own and also be reported by the finding of another call, + * e.g. {@code EVP_aes_256_gcm()} passed to {@code EVP_EncryptInit_ex}, a cipher fetched into a + * variable that is passed to it, or {@code EVP_EncryptInit_ex} made on a context created by + * {@code EVP_CIPHER_CTX_new()}. The findings of the file are therefore collected first, and a + * finding is handled only if its call is not reported by another finding of the file, and if it + * describes a cryptographic asset. + * + * @param astNode the root AST node of the file that finished analysis, or {@code null} on a + * parse error + */ + @Override + public void leaveFile(@Nullable AstNode astNode) { + try { + if (astNode != null) { + detectIn(astNode); + } + } catch (RuntimeException e) { + // the other files of the scan are analyzed regardless, as sonar-java and the Go + // sensor do for a file whose analysis fails + LOGGER.error( + "Unable to detect cryptographic assets in file '{}'", + getContext().getInputFile(), + e); + } finally { + fileFindings.clear(); + if (astNode != null) { + CxxSymbolExtensionRelease.leaveFile(getContext(), this, astNode); + } + CxxAggregator.getLanguageSupport().notifyLeaveFile(getContext().getInputFile()); + } + } + + /** Detects, translates and reports the findings of the file rooted at {@code astNode}. */ + private void detectIn(@Nonnull AstNode astNode) { + fileFindings.clear(); + AstNodeTraversal.traverse(astNode, DETECTION_NODE_TYPES, this::processNode); + final List< + Finding< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + findings = List.copyOf(fileFindings); + fileFindings.clear(); + for (Finding, AstNode, Symbol, SquidAstVisitorContext> + finding : withoutFindingsReportedByOthers(findings)) { + final List nodes = cxxTranslationProcess.initiate(finding.detectionStore()); + // an operation whose algorithm is not known, e.g. the key set on a cipher context + // initialized elsewhere, describes no cryptographic asset + if (describesAnAsset(nodes)) { + onFinding(finding, nodes); + } + } + } + + /** + * Processes a single AST node for potential cryptographic detection. + * + * @param node The AST node to process + */ + private void processNode(@Nonnull AstNode node) { + // Only process actual function calls and constructor calls, not all postfix expressions + // and declarations + if (node.is(CxxGrammarImpl.postfixExpression, CxxGrammarImpl.initDeclarator)) { + if (!CxxAstNodeHelper.isFunctionCall(node) + && !CxxConstructorCalls.isConstructorCall(node)) { + return; + } + } + + detectionRules.forEach( + rule -> { + DetectionExecutive< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionExecutive = + CxxAggregator.getLanguageSupport() + .createDetectionExecutive( + node, + rule, + new CxxScanContext(this.getContext())); + detectionExecutive.subscribe(this); + detectionExecutive.start(); + }); + } + + /** + * The findings whose detection (a rule matched on a call) is not reported below the root of + * another finding. A finding is kept when every finding reporting its detection is itself + * reported by it, so that two findings never remove each other. A finding with the same + * detection as a kept one, reached through another call (e.g. a key passed to a setter of a + * context created elsewhere), is the same finding and is not kept again. A call reported below + * another finding by another rule is another detection, e.g. {@code EVP_SealInit} is both the + * encryption with its cipher and the encryption of the session key with the key given to it. + * + *

A finding of a rule derived from another one (see {@link DerivedDetectionRules}) is also + * reported by another finding when everything detected below it is detected below that finding: + * the rule it is derived from detects no call of its own when it has no action, e.g. a setting + * of a key generation context reported with the key. + */ + @Nonnull + private static > List withoutFindingsReportedByOthers( + @Nonnull List findings) { + final List roots = new ArrayList<>(findings.size()); + final List derived = new ArrayList<>(findings.size()); + final List> nestedDetections = new ArrayList<>(findings.size()); + final Map> reportedBy = new HashMap<>(); + for (int i = 0; i < findings.size(); i++) { + final DetectionStore store = findings.get(i).detectionStore(); + derived.add( + DerivedDetectionRules.origin(store.getDetectionRule()) + != store.getDetectionRule()); + roots.add( + new Detection( + DerivedDetectionRules.origin(store.getDetectionRule()), + position( + store.getDetectedExpression() + .orElse(findings.get(i).getMarkerTree())))); + final Set nested = new HashSet<>(); + store.getChildren().forEach(child -> collectDetections(child, nested)); + nestedDetections.add(nested); + for (Detection detection : nested) { + reportedBy.computeIfAbsent(detection, d -> new ArrayList<>()).add(i); + } + } + final List kept = new ArrayList<>(findings.size()); + final Set keptRoots = new HashSet<>(); + for (int i = 0; i < findings.size(); i++) { + final int finding = i; + final boolean reportedByAnother = + reportedBy.getOrDefault(roots.get(i), List.of()).stream() + .anyMatch( + other -> + other != finding + && !nestedDetections + .get(finding) + .contains(roots.get(other))); + final boolean derivedAndReportedByAnother = + derived.get(i) + && !nestedDetections.get(i).isEmpty() + && IntStream.range(0, findings.size()) + .anyMatch( + other -> + other != finding + && nestedDetections + .get(other) + .containsAll( + nestedDetections.get( + finding))); + if (!reportedByAnother && !derivedAndReportedByAnother && keptRoots.add(roots.get(i))) { + kept.add(findings.get(i)); + } + } + return kept; + } + + /** + * The detections of a detection store and of the stores below it: the calls the rules matched, + * and the values they found, which stand for a value found through a hook at the call of the + * function it is passed to, e.g. {@code "SHA256"} in {@code sign("SHA256", key)} for the digest + * of a signature made in {@code sign}, as no call of the store's rule is there. + */ + private static void collectDetections( + @Nonnull DetectionStore store, @Nonnull Set detections) { + final IDetectionRule rule = DerivedDetectionRules.origin(store.getDetectionRule()); + store.getDetectedExpressions() + .forEach(expression -> detections.add(new Detection(rule, position(expression)))); + store.getDetectionValues() + .forEach( + value -> + detections.add(new Detection(rule, position(value.getLocation())))); + store.getChildren().forEach(child -> collectDetections(child, detections)); + } + + /** + * A detection rule matched on the call at a position; the rule is compared by identity, a rule + * derived from another one as the rule it is derived from (see {@link DerivedDetectionRules}). + */ + private record Detection(@Nonnull IDetectionRule rule, @Nonnull String position) { + @Override + public boolean equals(Object other) { + return other instanceof Detection detection + && detection.rule == rule + && detection.position.equals(position); + } + + @Override + public int hashCode() { + return 31 * System.identityHashCode(rule) + position.hashCode(); + } + } + + /** Whether the translated nodes hold an asset (an algorithm, key, protocol or cipher suite). */ + private static boolean describesAnAsset(@Nonnull List nodes) { + return nodes.stream() + .anyMatch( + node -> + node instanceof IAsset + || node instanceof IAssetCollection + || describesAnAsset( + List.copyOf(node.getChildren().values()))); + } + + /** A node's position, by its first token: the matched calls may be detached copies. */ + @Nonnull + private static String position(@Nonnull AstNode node) { + return node.getToken().getLine() + ":" + node.getToken().getColumn(); + } + + /** + * Called when a finding is detected by the engine. The finding is handled by {@link #onFinding} + * once the file is traversed, see {@link #leaveFile}. + * + * @param finding A finding containing detection store information. + */ + @Override + public final void update( + @Nonnull + Finding< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + finding) { + fileFindings.add(finding); + } + + /** + * Handles a finding: adds its translation to the inventory and reports its issues. + * + * @param finding A finding containing detection store information. + * @param nodes The translation of the finding. + */ + protected void onFinding( + @Nonnull + Finding< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + finding, + @Nonnull List nodes) { + if (isInventory) { + CxxAggregator.addNodes(nodes); + } + // report + this.report(finding.getMarkerTree(), nodes) + .forEach( + issue -> + finding.detectionStore() + .getScanContext() + .reportIssue(this, issue.tree(), issue.message())); + } + + @Override + @Nonnull + public List> report( + @Nonnull AstNode markerTree, @Nonnull List translatedNodes) { + // override by higher level rule, to report an issue + return Collections.emptyList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxDetectionRules.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxDetectionRules.java new file mode 100644 index 000000000..0e5ab50ea --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxDetectionRules.java @@ -0,0 +1,55 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.plugin.rules.detection.openssl.OpenSSLDetectionRules; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Registry of C++ cryptography detection rules. + * + *

This class aggregates all detection rules for C++ cryptographic libraries. Detection rules are + * organized by library (e.g., OpenSSL, BoringSSL, libsodium). + * + *

To add new detection rules: + * + *

    + *
  1. Create a new package under {@code rules.detection} for the library + *
  2. Create detection rule classes following the pattern in Java module + *
  3. Create a {@code *DetectionRules} class that returns all rules for the library + *
  4. Add the rules to the stream in {@link #buildRules()} + *
+ */ +public final class CxxDetectionRules extends DetectionRuleSet { + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of(RuleSets.rulesOf(OpenSSLDetectionRules.class).stream()) + .flatMap(i -> i) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxSymbolExtensionRelease.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxSymbolExtensionRelease.java new file mode 100644 index 000000000..38af71927 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/CxxSymbolExtensionRelease.java @@ -0,0 +1,104 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.AstNodeTraversal; +import org.sonar.cxx.squidbridge.api.AstNodeTypeExtension; + +/** + * Removes a file's entries from the {@link AstNodeSymbolExtension} and {@link AstNodeTypeExtension} + * maps once every detection rule of the scan has finished with that file. + * + *

Both maps are process-wide and shared by all checks. sonar-cxx calls {@code leaveFile} on each + * check in turn, so the entries of a file can only be removed after the last of our rules has + * processed it; removing them earlier leaves the remaining rules without symbol information. + * + *

Rules register in {@code init()}, which sonar-cxx calls once per scan, and report in {@code + * leaveFile}. Rules are grouped by the {@link SquidAstVisitorContext} of their scan, since all + * checks of one scan share that context. + */ +final class CxxSymbolExtensionRelease { + + private static final Map, CxxSymbolExtensionRelease> BY_CONTEXT = + new WeakHashMap<>(); + + private final Set registeredRules = + Collections.newSetFromMap(new IdentityHashMap<>()); + private final Set rulesDoneWithFile = + Collections.newSetFromMap(new IdentityHashMap<>()); + @Nullable private AstNode currentFile; + + private CxxSymbolExtensionRelease() { + // created per scan context + } + + static synchronized void register( + @Nonnull SquidAstVisitorContext context, @Nonnull CxxBaseDetectionRule rule) { + BY_CONTEXT + .computeIfAbsent(context, c -> new CxxSymbolExtensionRelease()) + .registeredRules + .add(rule); + } + + /** + * Records that {@code rule} has finished with the file rooted at {@code fileRoot}, and removes + * the file's symbol and type entries if it was the last registered rule to do so. + */ + static synchronized void leaveFile( + @Nonnull SquidAstVisitorContext context, + @Nonnull CxxBaseDetectionRule rule, + @Nonnull AstNode fileRoot) { + final CxxSymbolExtensionRelease release = BY_CONTEXT.get(context); + if (release == null || !release.registeredRules.contains(rule)) { + // rule was not initialized by a scanner, so no other rule depends on this file + removeEntries(fileRoot); + return; + } + if (release.currentFile != fileRoot) { + release.currentFile = fileRoot; + release.rulesDoneWithFile.clear(); + } + release.rulesDoneWithFile.add(rule); + if (release.rulesDoneWithFile.containsAll(release.registeredRules)) { + removeEntries(fileRoot); + release.currentFile = null; + release.rulesDoneWithFile.clear(); + } + } + + private static void removeEntries(@Nonnull AstNode fileRoot) { + AstNodeTraversal.traverse( + fileRoot, + node -> { + AstNodeSymbolExtension.removeSymbol(node); + AstNodeTypeExtension.removeType(node); + }); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/DerivedDetectionRules.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/DerivedDetectionRules.java new file mode 100644 index 000000000..38282a99a --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/DerivedDetectionRules.java @@ -0,0 +1,125 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.model.factory.IActionFactory; +import com.ibm.engine.rule.DetectableParameter; +import com.ibm.engine.rule.DetectionRule; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.Parameter; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules derived from others that match the same calls with the same parameters and report + * their values in another context, e.g. the digest getters ({@code EVP_sha256()}, ...) in the + * context of the digest of MGF1. A derived rule detects the same as the rule it is derived from, so + * a call reported through a derived rule is not reported again through the original rule, see + * {@link #origin}. + */ +public final class DerivedDetectionRules { + + private static final Map, IDetectionRule> ORIGINS = + Collections.synchronizedMap(new IdentityHashMap<>()); + + private DerivedDetectionRules() { + // utility + } + + /** + * The rule reporting its values in the given context, derived from the given rule. A rule that + * is not a {@link DetectionRule} is kept as it is. + */ + @Nonnull + public static IDetectionRule withContext( + @Nonnull IDetectionRule rule, @Nonnull DetectionContext context) { + if (!(rule instanceof DetectionRule detectionRule)) { + return rule; + } + final IDetectionRule derived = + new DetectionRule<>( + detectionRule.matchers(), + detectionRule.shouldMatchExactTypes(), + detectionRule.parameters(), + detectionRule.actionFactory(), + context, + detectionRule.bundle(), + detectionRule.nextDetectionRules()); + ORIGINS.put(derived, origin(rule)); + return derived; + } + + /** + * The rule detecting the given action, with its values in the given context, derived from the + * given rule, e.g. a setting of a key generation context that is a detection of its own when + * the context is created elsewhere. The values the rule detects at its parameters are reported + * below the action. The given rule must be a {@link DetectionRule}. + */ + @Nonnull + public static IDetectionRule withAction( + @Nonnull IDetectionRule rule, + @Nonnull IActionFactory actionFactory, + @Nonnull DetectionContext context) { + final DetectionRule detectionRule = (DetectionRule) rule; + final List> parameters = + detectionRule.parameters().stream() + .map(DerivedDetectionRules::belowTheAction) + .toList(); + final IDetectionRule derived = + new DetectionRule<>( + detectionRule.matchers(), + detectionRule.shouldMatchExactTypes(), + parameters, + actionFactory, + context, + detectionRule.bundle(), + detectionRule.nextDetectionRules()); + ORIGINS.put(derived, origin(rule)); + return derived; + } + + /** The parameter, whose detected value, if it detects one, is reported below the action. */ + @Nonnull + private static Parameter belowTheAction(@Nonnull Parameter parameter) { + if (!(parameter instanceof DetectableParameter detectable) + || detectable.getShouldBeMovedUnder().isPresent()) { + return parameter; + } + return new DetectableParameter<>( + detectable.getParameterType(), + detectable.getIndex(), + detectable.shouldMatchExactTypes(), + detectable.getiValueFactory(), + detectable.getDetectionRules(), + -1); + } + + /** The rule a rule is derived from, or the rule itself when it is not derived. */ + @Nonnull + public static IDetectionRule origin(@Nonnull IDetectionRule rule) { + final IDetectionRule origin = ORIGINS.get(rule); + return origin == null ? rule : origin; + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/OpenSSLDetectionRules.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/OpenSSLDetectionRules.java new file mode 100644 index 000000000..f4ab000fb --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/OpenSSLDetectionRules.java @@ -0,0 +1,93 @@ +/* + * 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.openssl; + +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherFetch; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.kdf.OpenSSLEvpKdf; +import com.ibm.plugin.rules.detection.openssl.keyagreement.OpenSSLEvpKeyAgreement; +import com.ibm.plugin.rules.detection.openssl.keygen.OpenSSLEvpKeyGen; +import com.ibm.plugin.rules.detection.openssl.keygen.OpenSSLEvpKeyGenRsa; +import com.ibm.plugin.rules.detection.openssl.keygen.OpenSSLEvpKeyUsage; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyCipher; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDh; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDigest; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDsa; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyEc; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyMac; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyRsa; +import com.ibm.plugin.rules.detection.openssl.mac.OpenSSLEvpMac; +import com.ibm.plugin.rules.detection.openssl.rand.OpenSSLRand; +import com.ibm.plugin.rules.detection.openssl.signature.OpenSSLEvpSignature; +import com.ibm.plugin.rules.detection.openssl.ssl.OpenSSLLibssl; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Aggregates all OpenSSL cryptography detection rules. + * + *

This class collects detection rules from all OpenSSL API categories: + * + *

    + *
  • EVP message digest functions ({@code EVP_sha256}, {@code EVP_md5}, etc.) + *
  • EVP cipher functions ({@code EVP_aes_256_gcm}, {@code EVP_chacha20_poly1305}, etc.) + *
  • EVP MAC functions ({@code EVP_MAC_fetch} with HMAC, CMAC, GMAC, etc.) + *
  • SSL/TLS protocol functions ({@code TLS_method}, {@code SSL_CTX_new}, etc.) + *
+ */ +public final class OpenSSLDetectionRules extends DetectionRuleSet { + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + // EVP API - Modern OpenSSL 3.x functions + RuleSets.rulesOf(OpenSSLEvpCipher.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherFetch.class).stream(), + RuleSets.rulesOf(OpenSSLEvpMessageDigest.class).stream(), + RuleSets.rulesOf(OpenSSLEvpMac.class).stream(), + RuleSets.rulesOf(OpenSSLEvpSignature.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKeyGen.class).stream(), + // signatures with a key given to them, e.g. loaded from a file + RuleSets.rulesOf(OpenSSLEvpKeyUsage.Signatures.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKeyGenRsa.KeyGenerationSettings.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKdf.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKeyAgreement.class).stream(), + RuleSets.rulesOf(OpenSSLRand.class).stream(), + // Legacy API - Deprecated but widely used + RuleSets.rulesOf(OpenSSLLegacyCipher.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyDigest.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyMac.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyRsa.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyDsa.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyEc.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyDh.class).stream(), + // SSL/TLS Protocol API + RuleSets.rulesOf(OpenSSLLibssl.class).stream()) + .flatMap(i -> i) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/OpenSSLSizeFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/OpenSSLSizeFactory.java new file mode 100644 index 000000000..2eddd1350 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/OpenSSLSizeFactory.java @@ -0,0 +1,55 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * The value of a size argument, e.g. the key length in bits of {@code + * EVP_PKEY_CTX_set_rsa_keygen_bits} or in bytes of {@code PKCS5_PBKDF2_HMAC}. OpenSSL takes sizes + * as integers, so only a positive integer is a size: a name whose value is not known, such as a + * macro defined in a header that is not analyzed, and a value that is not positive give no size. + */ +public final class OpenSSLSizeFactory implements IValueFactory { + + @Nonnull private final IValueFactory sizeFactory; + + /** + * @param sizeFactory the factory creating the size from a positive integer, e.g. a key size + * factory interpreting it as bits or bytes + */ + public OpenSSLSizeFactory(@Nonnull IValueFactory sizeFactory) { + this.sizeFactory = sizeFactory; + } + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (resolvedValue.value() instanceof Integer size && size > 0) { + return sizeFactory.apply(resolvedValue); + } + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherCtrlFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherCtrlFactory.java new file mode 100644 index 000000000..eb4029b73 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherCtrlFactory.java @@ -0,0 +1,129 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.language.cxx.CxxSemantic; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.factory.IValueFactory; +import com.ibm.engine.model.factory.InitializationVectorSizeFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.TagSizeFactory; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * Resolves the cipher parameter set by {@code EVP_CIPHER_CTX_ctrl(ctx, type, arg, ptr)}. The + * factory is applied to {@code arg}; the command {@code type} of the same call selects what {@code + * arg} is: the key length ({@code EVP_CTRL_SET_KEY_LENGTH}, in bytes), the effective key bits of + * RC2 ({@code EVP_CTRL_SET_RC2_KEY_BITS}), the IV length ({@code EVP_CTRL_AEAD_SET_IVLEN} and its + * GCM and CCM names, in bytes) or the tag length ({@code EVP_CTRL_AEAD_SET_TAG} / {@code + * EVP_CTRL_AEAD_GET_TAG} and their GCM and CCM names, in bytes). Other commands resolve to nothing. + */ +public final class OpenSSLCipherCtrlFactory implements IValueFactory { + + private static final int TYPE_ARGUMENT = 1; + + private enum Parameter { + KEY_LENGTH, + RC2_KEY_BITS, + IV_LENGTH, + TAG_LENGTH + } + + /** The {@code EVP_CTRL_*} commands (evp.h), by name and by value. */ + private static final Map COMMANDS = + Map.ofEntries( + Map.entry("EVP_CTRL_SET_KEY_LENGTH", Parameter.KEY_LENGTH), + Map.entry(0x1, Parameter.KEY_LENGTH), + Map.entry("EVP_CTRL_SET_RC2_KEY_BITS", Parameter.RC2_KEY_BITS), + Map.entry(0x3, Parameter.RC2_KEY_BITS), + Map.entry("EVP_CTRL_AEAD_SET_IVLEN", Parameter.IV_LENGTH), + Map.entry("EVP_CTRL_GCM_SET_IVLEN", Parameter.IV_LENGTH), + Map.entry("EVP_CTRL_CCM_SET_IVLEN", Parameter.IV_LENGTH), + Map.entry(0x9, Parameter.IV_LENGTH), + Map.entry("EVP_CTRL_AEAD_GET_TAG", Parameter.TAG_LENGTH), + Map.entry("EVP_CTRL_GCM_GET_TAG", Parameter.TAG_LENGTH), + Map.entry("EVP_CTRL_CCM_GET_TAG", Parameter.TAG_LENGTH), + Map.entry(0x10, Parameter.TAG_LENGTH), + Map.entry("EVP_CTRL_AEAD_SET_TAG", Parameter.TAG_LENGTH), + Map.entry("EVP_CTRL_GCM_SET_TAG", Parameter.TAG_LENGTH), + Map.entry("EVP_CTRL_CCM_SET_TAG", Parameter.TAG_LENGTH), + Map.entry(0x11, Parameter.TAG_LENGTH)); + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + final AstNode call = enclosingCall(resolvedValue.tree()); + if (call == null) { + return Optional.empty(); + } + final List arguments = CxxAstNodeHelper.getFunctionCallArguments(call); + if (arguments.size() <= TYPE_ARGUMENT) { + return Optional.empty(); + } + for (ResolvedValue type : + CxxSemantic.resolveValues( + Object.class, + arguments.get(TYPE_ARGUMENT), + new LinkedList<>(), + null, + false, + null)) { + final Parameter parameter = COMMANDS.get(type.value()); + if (parameter != null) { + return factoryOf(parameter).apply(resolvedValue); + } + } + return Optional.empty(); + } + + @Nonnull + private static IValueFactory factoryOf(@Nonnull Parameter parameter) { + return switch (parameter) { + case KEY_LENGTH -> new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE)); + case RC2_KEY_BITS -> new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT)); + case IV_LENGTH -> + new OpenSSLSizeFactory( + new InitializationVectorSizeFactory<>(Size.UnitType.BYTE)); + case TAG_LENGTH -> new OpenSSLSizeFactory(new TagSizeFactory<>(Size.UnitType.BYTE)); + }; + } + + /** The {@code EVP_CIPHER_CTX_ctrl} call the resolved argument is written in. */ + @Nullable private static AstNode enclosingCall(@Nonnull AstNode tree) { + for (AstNode node = tree; node != null; node = node.getParent()) { + if (CxxAstNodeHelper.isFunctionCall(node)) { + return "EVP_CIPHER_CTX_ctrl".equals(CxxAstNodeHelper.getFunctionCallName(node)) + ? node + : null; + } + } + return null; + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherOperationFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherOperationFactory.java new file mode 100644 index 000000000..b9fad4c28 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherOperationFactory.java @@ -0,0 +1,53 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Resolves the {@code enc} argument of {@code EVP_CipherInit} to the operation the cipher is + * initialized for: 1 encrypts and 0 decrypts. Any other value (-1) keeps the operation of an + * earlier initialization and yields no operation. + */ +public final class OpenSSLCipherOperationFactory implements IValueFactory { + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (!(resolvedValue.value() instanceof Number enc)) { + return Optional.empty(); + } + return switch (enc.intValue()) { + case 1 -> + Optional.of( + new CipherAction<>(CipherAction.Action.ENCRYPT, resolvedValue.tree())); + case 0 -> + Optional.of( + new CipherAction<>(CipherAction.Action.DECRYPT, resolvedValue.tree())); + default -> Optional.empty(); + }; + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherPaddingFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherPaddingFactory.java new file mode 100644 index 000000000..2e0cfdc96 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLCipherPaddingFactory.java @@ -0,0 +1,47 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.Padding; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Resolves the {@code pad} argument of {@code EVP_CIPHER_CTX_set_padding(ctx, pad)}. A non-zero + * value enables the standard block padding of OpenSSL, PKCS#7; zero disables padding, which, as the + * JCA {@code NoPadding}, is no padding to report. + */ +public final class OpenSSLCipherPaddingFactory implements IValueFactory { + + private static final String STANDARD_BLOCK_PADDING = "PKCS7"; + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (resolvedValue.value() instanceof Number pad && pad.longValue() != 0) { + return Optional.of(new Padding<>(STANDARD_BLOCK_PADDING, resolvedValue.tree())); + } + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipher.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipher.java new file mode 100644 index 000000000..53e38acb8 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipher.java @@ -0,0 +1,480 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.ibm.plugin.translation.translator.contexts.CxxDigestContextTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP cipher algorithm specifiers. + * + *

These rules detect calls to OpenSSL functions that return EVP_CIPHER pointers, identifying the + * specific cipher algorithm, key size, and mode of operation. Each function (e.g., {@code + * EVP_aes_256_gcm()}) maps to a known cipher specification. + * + *

Per-family cipher specifiers live in their own {@code OpenSSLEvpCipher} classes (AES, + * Camellia, ARIA, SM4, DES/3DES, Blowfish, CAST5, RC2, RC4, RC5, IDEA, SEED, ChaCha20); this class + * holds the generic EVP cipher infrastructure (fetch, RSA padding and OAEP setters, and the CMS and + * PKCS#7 functions that take a content encryption cipher or a key wrap algorithm) and aggregates + * every family's rules in {@link #buildRules()}. A cipher context and the operations on it are in + * {@link OpenSSLEvpCipherContext}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipher extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_ENC_NULL = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_enc_null") + .shouldBeDetectedAs(new ValueActionFactory<>("NULL")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_GET_CIPHERBYNAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_get_cipherbyname") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.CIPHER_NAMES)) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_get_cipherbynid(nid) (evp.h): EVP_get_cipherbyname for the cipher of the NID + private static final IDetectionRule EVP_GET_CIPHERBYNID = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_get_cipherbynid") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.CIPHER_BY_CODE, + OpenSSLNidLookupFactory.CIPHER_BY_NAME)) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + /** + * The calls that select a cipher: the EVP_* cipher functions, EVP_CIPHER_fetch, + * EVP_get_cipherbyname and EVP_get_cipherbynid. The cipher argument of the init functions + * ({@link OpenSSLEvpCipherInit}) is traced back to one of them. + */ + private static final List> CIPHER_SELECTION = + Stream.of( + cipherFamilyRules().stream(), + RuleSets.rulesOf(OpenSSLEvpCipherFetch.class).stream(), + Stream.of(EVP_ENC_NULL, EVP_GET_CIPHERBYNAME, EVP_GET_CIPHERBYNID)) + .flatMap(i -> i) + .toList(); + + private static final IDetectionRule EVP_ASYM_CIPHER_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_ASYM_CIPHER_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + /** RSA padding mode (rsa.h) → the RSA scheme it selects. */ + private static final Map RSA_PADDING_BY_CODE = + Map.ofEntries( + Map.entry(1, "RSA-PKCS1-TYPE2"), // RSA_PKCS1_PADDING + Map.entry(3, "RSA-NO-PADDING"), // RSA_NO_PADDING + Map.entry(4, "RSA-OAEP"), // RSA_PKCS1_OAEP_PADDING + Map.entry(5, "RSA-X931"), // RSA_X931_PADDING + Map.entry(6, "RSA-PSS"), // RSA_PKCS1_PSS_PADDING + Map.entry(7, "RSA-PKCS1-TYPE2"), // RSA_PKCS1_WITH_TLS_PADDING + Map.entry(8, "RSA-PKCS1-TYPE2")); // RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING + + private static final Map RSA_PADDING_BY_NAME = + Map.ofEntries( + Map.entry("RSA_PKCS1_PADDING", "RSA-PKCS1-TYPE2"), + Map.entry("RSA_NO_PADDING", "RSA-NO-PADDING"), + Map.entry("RSA_PKCS1_OAEP_PADDING", "RSA-OAEP"), + Map.entry("RSA_X931_PADDING", "RSA-X931"), + Map.entry("RSA_PKCS1_PSS_PADDING", "RSA-PSS"), + Map.entry("RSA_PKCS1_WITH_TLS_PADDING", "RSA-PKCS1-TYPE2"), + Map.entry("RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING", "RSA-PKCS1-TYPE2")); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PADDING = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_padding") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory(RSA_PADDING_BY_CODE, RSA_PADDING_BY_NAME)) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_OAEP_MD_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_oaep_md_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .buildForContext( + new DigestContext( + Map.of( + CxxDigestContextTranslator.KIND, + CxxDigestContextTranslator.OAEP_KIND))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // CMS and PKCS#7 encryption: the content encryption cipher is an argument, e.g. + // CMS_encrypt(certs, in, cipher, flags) + + private static final IDetectionRule CMS_ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_ENCRYPT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_encrypt_ex") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_ENVELOPED_DATA_CREATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_EnvelopedData_create") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_ENVELOPED_DATA_CREATE_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_EnvelopedData_create_ex") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_AUTH_ENVELOPED_DATA_CREATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_AuthEnvelopedData_create") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_AUTH_ENVELOPED_DATA_CREATE_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_AuthEnvelopedData_create_ex") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_ENCRYPTED_DATA_ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_EncryptedData_encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_ENCRYPTED_DATA_ENCRYPT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_EncryptedData_encrypt_ex") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule CMS_ENCRYPTED_DATA_SET1_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_EncryptedData_set1_key") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS7_ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS7_encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS7_ENCRYPT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS7_encrypt_ex") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS7_SET_CIPHER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS7_set_cipher") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(CIPHER_SELECTION) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // CMS_add0_recipient_key(cms, nid, key, keylen, id, idlen, date, otherTypeId, otherType): the + // key wrap algorithm of a KEK recipient + + private static final IDetectionRule CMS_ADD0_RECIPIENT_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMS_add0_recipient_key") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.KEY_WRAP_BY_CODE, + OpenSSLNidLookupFactory.KEY_WRAP_BY_NAME)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + cipherFamilyRules().stream(), + directRules().stream(), + // the operations on a context, detected with the creation of the context + // when it is created in the scanned code and on their own otherwise + RuleSets.rulesOf(OpenSSLEvpCipherContext.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherInit.class).stream(), + // private keys written encrypted + RuleSets.rulesOf(OpenSSLPrivateKeyEncryption.class).stream()) + .flatMap(i -> i) + .toList(); + } + + @Nonnull + private static List> cipherFamilyRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLEvpCipherAes.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherCamellia.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherAria.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherSm4.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherDes.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherBlowfish.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherCast5.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherRc2.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherRc4.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherRc5.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherIdea.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherSeed.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherChacha20.class).stream()) + .flatMap(i -> i) + .toList(); + } + + @Nonnull + private static List> directRules() { + return List.of( + // NULL Cipher + EVP_ENC_NULL, + // Legacy lookup + EVP_GET_CIPHERBYNAME, + EVP_GET_CIPHERBYNID, + // EVP_ASYM_CIPHER_fetch - Asymmetric cipher algorithm fetch + EVP_ASYM_CIPHER_FETCH, + // RSA OAEP context setters + EVP_PKEY_CTX_SET_RSA_PADDING, + EVP_PKEY_CTX_SET_RSA_OAEP_MD_NAME, + // CMS - Cryptographic Message Syntax (enveloped / encrypted data) + CMS_ENCRYPT, + CMS_ENCRYPT_EX, + CMS_ENVELOPED_DATA_CREATE, + CMS_ENVELOPED_DATA_CREATE_EX, + CMS_AUTH_ENVELOPED_DATA_CREATE, + CMS_AUTH_ENVELOPED_DATA_CREATE_EX, + CMS_ENCRYPTED_DATA_ENCRYPT, + CMS_ENCRYPTED_DATA_ENCRYPT_EX, + CMS_ENCRYPTED_DATA_SET1_KEY, + CMS_ADD0_RECIPIENT_KEY, + // PKCS#7 encryption functions + PKCS7_ENCRYPT, + PKCS7_ENCRYPT_EX, + PKCS7_SET_CIPHER); + } + + /** + * The rule for the digest of RSA-OAEP given by name ({@code + * EVP_PKEY_CTX_set_rsa_oaep_md_name}), for the encryptions performed with an RSA key. + */ + @Nonnull + public static IDetectionRule rsaOaepDigestNameRule() { + return EVP_PKEY_CTX_SET_RSA_OAEP_MD_NAME; + } + + /** + * The rule for the RSA padding set on a context ({@code EVP_PKEY_CTX_set_rsa_padding}), for the + * operations performed with an RSA key. + */ + @Nonnull + public static IDetectionRule rsaPaddingRule() { + return EVP_PKEY_CTX_SET_RSA_PADDING; + } + + /** + * The rules for the calls that select a cipher, for functions that take the cipher as an + * argument. + */ + public static final class CipherSelection extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return CIPHER_SELECTION; + } + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherAes.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherAes.java new file mode 100644 index 000000000..f0787aa3d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherAes.java @@ -0,0 +1,94 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.rule.IDetectionRule; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP AES cipher algorithm specifiers. + * + *

Covers AES-128/192/256 across all EVP modes (CBC, ECB, GCM, CTR, CCM, CFB variants, OFB, XTS, + * OCB, key wrap) plus the AES-CBC-HMAC combined TLS Encrypt-then-MAC ciphers. + */ +public final class OpenSSLEvpCipherAes extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + private static final List ENTRIES = + List.of( + // AES-128 + new Entry("EVP_aes_128_cbc", "AES-128-CBC"), + new Entry("EVP_aes_128_ecb", "AES-128-ECB"), + new Entry("EVP_aes_128_gcm", "AES-128-GCM"), + new Entry("EVP_aes_128_ctr", "AES-128-CTR"), + new Entry("EVP_aes_128_ccm", "AES-128-CCM"), + new Entry("EVP_aes_128_cfb128", "AES-128-CFB"), + new Entry("EVP_aes_128_cfb1", "AES-128-CFB1"), + new Entry("EVP_aes_128_cfb8", "AES-128-CFB8"), + new Entry("EVP_aes_128_cfb", "AES-128-CFB"), + new Entry("EVP_aes_128_ofb", "AES-128-OFB"), + new Entry("EVP_aes_128_xts", "AES-128-XTS"), + new Entry("EVP_aes_128_ocb", "AES-128-OCB"), + new Entry("EVP_aes_128_wrap", "AES-128-WRAP"), + new Entry("EVP_aes_128_wrap_pad", "AES-128-WRAP-PAD"), + // AES-192 + new Entry("EVP_aes_192_cbc", "AES-192-CBC"), + new Entry("EVP_aes_192_ecb", "AES-192-ECB"), + new Entry("EVP_aes_192_gcm", "AES-192-GCM"), + new Entry("EVP_aes_192_ctr", "AES-192-CTR"), + new Entry("EVP_aes_192_ccm", "AES-192-CCM"), + new Entry("EVP_aes_192_cfb128", "AES-192-CFB"), + new Entry("EVP_aes_192_cfb1", "AES-192-CFB1"), + new Entry("EVP_aes_192_cfb8", "AES-192-CFB8"), + new Entry("EVP_aes_192_cfb", "AES-192-CFB"), + new Entry("EVP_aes_192_ofb", "AES-192-OFB"), + new Entry("EVP_aes_192_ocb", "AES-192-OCB"), + new Entry("EVP_aes_192_wrap", "AES-192-WRAP"), + new Entry("EVP_aes_192_wrap_pad", "AES-192-WRAP-PAD"), + // AES-256 + new Entry("EVP_aes_256_cbc", "AES-256-CBC"), + new Entry("EVP_aes_256_ecb", "AES-256-ECB"), + new Entry("EVP_aes_256_gcm", "AES-256-GCM"), + new Entry("EVP_aes_256_ctr", "AES-256-CTR"), + new Entry("EVP_aes_256_ccm", "AES-256-CCM"), + new Entry("EVP_aes_256_cfb128", "AES-256-CFB"), + new Entry("EVP_aes_256_cfb1", "AES-256-CFB1"), + new Entry("EVP_aes_256_cfb8", "AES-256-CFB8"), + new Entry("EVP_aes_256_cfb", "AES-256-CFB"), + new Entry("EVP_aes_256_ofb", "AES-256-OFB"), + new Entry("EVP_aes_256_xts", "AES-256-XTS"), + new Entry("EVP_aes_256_ocb", "AES-256-OCB"), + new Entry("EVP_aes_256_wrap", "AES-256-WRAP"), + new Entry("EVP_aes_256_wrap_pad", "AES-256-WRAP-PAD"), + // AES CBC-HMAC Combined Mode - TLS Encrypt-then-MAC + new Entry("EVP_aes_128_cbc_hmac_sha1", "AES-128-CBC-HMAC-SHA1"), + new Entry("EVP_aes_256_cbc_hmac_sha1", "AES-256-CBC-HMAC-SHA1"), + new Entry("EVP_aes_128_cbc_hmac_sha256", "AES-128-CBC-HMAC-SHA256"), + new Entry("EVP_aes_256_cbc_hmac_sha256", "AES-256-CBC-HMAC-SHA256")); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.build(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherAria.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherAria.java new file mode 100644 index 000000000..200c0a4c5 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherAria.java @@ -0,0 +1,78 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.rule.IDetectionRule; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP ARIA cipher algorithm specifiers. + * + *

Covers ARIA-128/192/256 (the Korean national standard cipher, RFC 5794) across all EVP modes + * (ECB, CBC, CFB variants, OFB, CTR, GCM, CCM). + */ +public final class OpenSSLEvpCipherAria extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + private static final List ENTRIES = + List.of( + // ARIA-128 + new Entry("EVP_aria_128_ecb", "ARIA-128-ECB"), + new Entry("EVP_aria_128_cbc", "ARIA-128-CBC"), + new Entry("EVP_aria_128_cfb128", "ARIA-128-CFB"), + new Entry("EVP_aria_128_cfb1", "ARIA-128-CFB1"), + new Entry("EVP_aria_128_cfb8", "ARIA-128-CFB8"), + new Entry("EVP_aria_128_cfb", "ARIA-128-CFB"), + new Entry("EVP_aria_128_ofb", "ARIA-128-OFB"), + new Entry("EVP_aria_128_ctr", "ARIA-128-CTR"), + new Entry("EVP_aria_128_gcm", "ARIA-128-GCM"), + new Entry("EVP_aria_128_ccm", "ARIA-128-CCM"), + // ARIA-192 + new Entry("EVP_aria_192_ecb", "ARIA-192-ECB"), + new Entry("EVP_aria_192_cbc", "ARIA-192-CBC"), + new Entry("EVP_aria_192_cfb128", "ARIA-192-CFB"), + new Entry("EVP_aria_192_cfb1", "ARIA-192-CFB1"), + new Entry("EVP_aria_192_cfb8", "ARIA-192-CFB8"), + new Entry("EVP_aria_192_cfb", "ARIA-192-CFB"), + new Entry("EVP_aria_192_ofb", "ARIA-192-OFB"), + new Entry("EVP_aria_192_ctr", "ARIA-192-CTR"), + new Entry("EVP_aria_192_gcm", "ARIA-192-GCM"), + new Entry("EVP_aria_192_ccm", "ARIA-192-CCM"), + // ARIA-256 + new Entry("EVP_aria_256_ecb", "ARIA-256-ECB"), + new Entry("EVP_aria_256_cbc", "ARIA-256-CBC"), + new Entry("EVP_aria_256_cfb128", "ARIA-256-CFB"), + new Entry("EVP_aria_256_cfb1", "ARIA-256-CFB1"), + new Entry("EVP_aria_256_cfb8", "ARIA-256-CFB8"), + new Entry("EVP_aria_256_cfb", "ARIA-256-CFB"), + new Entry("EVP_aria_256_ofb", "ARIA-256-OFB"), + new Entry("EVP_aria_256_ctr", "ARIA-256-CTR"), + new Entry("EVP_aria_256_gcm", "ARIA-256-GCM"), + new Entry("EVP_aria_256_ccm", "ARIA-256-CCM")); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.build(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherBlowfish.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherBlowfish.java new file mode 100644 index 000000000..6955ad6ca --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherBlowfish.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP Blowfish cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherBlowfish extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_BF_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_bf_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("BLOWFISH-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_BF_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_bf_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("BLOWFISH-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_BF_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_bf_cfb64", "EVP_bf_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("BLOWFISH-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_BF_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_bf_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("BLOWFISH-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_BF_ECB, EVP_BF_CBC, EVP_BF_CFB, EVP_BF_OFB); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherCamellia.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherCamellia.java new file mode 100644 index 000000000..c8d20cc30 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherCamellia.java @@ -0,0 +1,71 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.rule.IDetectionRule; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP Camellia cipher algorithm specifiers. + * + *

Covers Camellia-128/192/256 across all EVP modes (ECB, CBC, CFB variants, OFB, CTR). + */ +public final class OpenSSLEvpCipherCamellia extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + private static final List ENTRIES = + List.of( + // Camellia-128 + new Entry("EVP_camellia_128_ecb", "CAMELLIA-128-ECB"), + new Entry("EVP_camellia_128_cbc", "CAMELLIA-128-CBC"), + new Entry("EVP_camellia_128_cfb128", "CAMELLIA-128-CFB"), + new Entry("EVP_camellia_128_cfb1", "CAMELLIA-128-CFB1"), + new Entry("EVP_camellia_128_cfb8", "CAMELLIA-128-CFB8"), + new Entry("EVP_camellia_128_cfb", "CAMELLIA-128-CFB"), + new Entry("EVP_camellia_128_ofb", "CAMELLIA-128-OFB"), + new Entry("EVP_camellia_128_ctr", "CAMELLIA-128-CTR"), + // Camellia-192 + new Entry("EVP_camellia_192_ecb", "CAMELLIA-192-ECB"), + new Entry("EVP_camellia_192_cbc", "CAMELLIA-192-CBC"), + new Entry("EVP_camellia_192_cfb128", "CAMELLIA-192-CFB"), + new Entry("EVP_camellia_192_cfb1", "CAMELLIA-192-CFB1"), + new Entry("EVP_camellia_192_cfb8", "CAMELLIA-192-CFB8"), + new Entry("EVP_camellia_192_cfb", "CAMELLIA-192-CFB"), + new Entry("EVP_camellia_192_ofb", "CAMELLIA-192-OFB"), + new Entry("EVP_camellia_192_ctr", "CAMELLIA-192-CTR"), + // Camellia-256 + new Entry("EVP_camellia_256_ecb", "CAMELLIA-256-ECB"), + new Entry("EVP_camellia_256_cbc", "CAMELLIA-256-CBC"), + new Entry("EVP_camellia_256_cfb128", "CAMELLIA-256-CFB"), + new Entry("EVP_camellia_256_cfb1", "CAMELLIA-256-CFB1"), + new Entry("EVP_camellia_256_cfb8", "CAMELLIA-256-CFB8"), + new Entry("EVP_camellia_256_cfb", "CAMELLIA-256-CFB"), + new Entry("EVP_camellia_256_ofb", "CAMELLIA-256-OFB"), + new Entry("EVP_camellia_256_ctr", "CAMELLIA-256-CTR")); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.build(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherCast5.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherCast5.java new file mode 100644 index 000000000..413a3a7c1 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherCast5.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP CAST5 cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherCast5 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_CAST5_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_cast5_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("CAST5-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_CAST5_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_cast5_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("CAST5-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_CAST5_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_cast5_cfb64", "EVP_cast5_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("CAST5-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_CAST5_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_cast5_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("CAST5-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_CAST5_ECB, EVP_CAST5_CBC, EVP_CAST5_CFB, EVP_CAST5_OFB); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherChacha20.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherChacha20.java new file mode 100644 index 000000000..163c2b46d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherChacha20.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP ChaCha20 cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherChacha20 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_CHACHA20 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_chacha20") + .shouldBeDetectedAs(new ValueActionFactory<>("ChaCha20")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_CHACHA20_POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_chacha20_poly1305") + .shouldBeDetectedAs(new ValueActionFactory<>("ChaCha20-Poly1305")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_CHACHA20, EVP_CHACHA20_POLY1305); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContext.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContext.java new file mode 100644 index 000000000..58e6dc6b2 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContext.java @@ -0,0 +1,71 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rule for the creation of an OpenSSL cipher context, {@code EVP_CIPHER_CTX_new()}. The + * context is followed to the calls made on it: its initialization with a cipher ({@link + * OpenSSLEvpCipherInit}) and the parameters set on it ({@link OpenSSLEvpCipherParameters}), which + * are reported together, as the JCA {@code Cipher.getInstance} rules report the {@code Cipher.init} + * made on the cipher: + * + *

{@code
+ * EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ * EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
+ * EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL);
+ * EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv);
+ * }
+ */ +public final class OpenSSLEvpCipherContext extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_CIPHER_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CIPHER_CTX_new") + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules( + Stream.concat( + RuleSets.rulesOf(OpenSSLEvpCipherInit.class).stream(), + RuleSets.rulesOf(OpenSSLEvpCipherParameters.class) + .stream()) + .toList()); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_CIPHER_CTX_NEW); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherDes.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherDes.java new file mode 100644 index 000000000..cece1dc71 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherDes.java @@ -0,0 +1,74 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.rule.IDetectionRule; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP DES and 3DES cipher algorithm specifiers. + * + *

Covers single DES, 3DES EDE (2-key) and EDE3 (3-key) across all EVP modes, plus DESX and the + * EDE3 key-wrap variant. + */ +public final class OpenSSLEvpCipherDes extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + private static final List ENTRIES = + List.of( + // DES/3DES + new Entry("EVP_des_cbc", "DES-CBC"), + new Entry("EVP_des_ecb", "DES-ECB"), + new Entry("EVP_des_ede3_cbc", "DESede3-CBC"), + // Single DES additional modes + new Entry("EVP_des_cfb64", "DES-CFB"), + new Entry("EVP_des_cfb1", "DES-CFB1"), + new Entry("EVP_des_cfb8", "DES-CFB8"), + new Entry("EVP_des_cfb", "DES-CFB"), + new Entry("EVP_des_ofb", "DES-OFB"), + // 3DES EDE (2-key) + new Entry("EVP_des_ede", "DESede"), + new Entry("EVP_des_ede_ecb", "DESede-ECB"), + new Entry("EVP_des_ede_cbc", "DESede-CBC"), + new Entry("EVP_des_ede_cfb64", "DESede-CFB64"), + new Entry("EVP_des_ede_cfb", "DESede-CFB64"), + new Entry("EVP_des_ede_ofb", "DESede-OFB"), + // 3DES EDE3 (3-key) additional modes + new Entry("EVP_des_ede3", "DESede3"), + new Entry("EVP_des_ede3_ecb", "DESede3-ECB"), + new Entry("EVP_des_ede3_cfb1", "DESede3-CFB1"), + new Entry("EVP_des_ede3_cfb8", "DESede3-CFB8"), + new Entry("EVP_des_ede3_cfb64", "DESede3-CFB64"), + new Entry("EVP_des_ede3_cfb", "DESede3-CFB64"), + new Entry("EVP_des_ede3_ofb", "DESede3-OFB"), + // DESX + new Entry("EVP_desx_cbc", "DESX-CBC"), + // EVP_des_ede3_wrap + new Entry("EVP_des_ede3_wrap", "DES-EDE3-WRAP")); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.build(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetch.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetch.java new file mode 100644 index 000000000..933876b85 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetch.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rule for {@code EVP_CIPHER_fetch}, which selects a cipher by name through the OpenSSL + * 3.0 provider API, e.g. {@code EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL)}. + * + *

The name argument is resolved to its string value, whether it is a literal or a variable, and + * normalized with {@link OpenSSLNameCanonicalizerFactory#CIPHER_NAMES}, since OpenSSL matches + * cipher names case-insensitively and accepts aliases such as {@code "DES3"} or {@code + * "id-aes256-GCM"}. + */ +public final class OpenSSLEvpCipherFetch extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_CIPHER_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CIPHER_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.CIPHER_NAMES)) + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_CIPHER_FETCH); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherIdea.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherIdea.java new file mode 100644 index 000000000..d5cc31186 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherIdea.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP IDEA cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherIdea extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_IDEA_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_idea_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("IDEA-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_IDEA_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_idea_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("IDEA-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_IDEA_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_idea_cfb64", "EVP_idea_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("IDEA-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_IDEA_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_idea_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("IDEA-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_IDEA_ECB, EVP_IDEA_CBC, EVP_IDEA_CFB, EVP_IDEA_OFB); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherInit.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherInit.java new file mode 100644 index 000000000..c7e5f7d9d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherInit.java @@ -0,0 +1,227 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the initialization of an OpenSSL cipher context: {@code EVP_EncryptInit}, + * {@code EVP_DecryptInit} and {@code EVP_CipherInit} with their {@code _ex} / {@code _ex2} + * variants, and the envelope encryption and decryption {@code EVP_SealInit} / {@code EVP_OpenInit} + * (whose session key is encrypted with the keys given to them, see {@code OpenSSLEvpKeyUsage}). The + * cipher argument is traced back to the call that selects it. These are the depending rules of the + * creation of the context ({@link OpenSSLEvpCipherContext}), as the JCA {@code Cipher.init} rules + * are depending rules of {@code Cipher.getInstance}. A context is often created by the caller of + * the function that initializes it, so they are detection rules on their own as well; an + * initialization reported with the creation of its context is not reported again. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherInit extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Cipher initialization: (ctx, cipher, ...) for encryption or decryption + + private static final IDetectionRule EVP_ENCRYPT_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_EncryptInit") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_ENCRYPT_INIT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_EncryptInit_ex", "EVP_EncryptInit_ex2") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_DECRYPT_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_DecryptInit") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_DECRYPT_INIT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_DecryptInit_ex", "EVP_DecryptInit_ex2") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_CipherInit: the operation is given by the enc argument (1 encrypts, 0 decrypts) + + private static final IDetectionRule EVP_CIPHER_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CipherInit") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLCipherOperationFactory()) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_CIPHER_INIT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CipherInit_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLCipherOperationFactory()) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_CIPHER_INIT_EX2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CipherInit_ex2") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLCipherOperationFactory()) + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Envelope encryption: EVP_SealInit(ctx, type, ek, ekl, iv, pubk, npubk) encrypts with a random + // session key, EVP_OpenInit(ctx, type, ek, ekl, iv, priv) decrypts + + private static final IDetectionRule EVP_SEAL_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_SealInit") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_OPEN_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_OpenInit") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_ENCRYPT_INIT, + EVP_ENCRYPT_INIT_EX, + EVP_DECRYPT_INIT, + EVP_DECRYPT_INIT_EX, + EVP_CIPHER_INIT, + EVP_CIPHER_INIT_EX, + EVP_CIPHER_INIT_EX2, + EVP_SEAL_INIT, + EVP_OPEN_INIT); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherParameters.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherParameters.java new file mode 100644 index 000000000..1fc7277a9 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherParameters.java @@ -0,0 +1,93 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.AlgorithmParameterContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the parameters set on an OpenSSL cipher context: the key length ({@code + * EVP_CIPHER_CTX_set_key_length}), the padding ({@code EVP_CIPHER_CTX_set_padding}) and the key + * length, IV length and tag length set with {@code EVP_CIPHER_CTX_ctrl}. These are depending rules + * of the creation of the context ({@link OpenSSLEvpCipherContext}), as the JCA parameter + * specifications are given to {@code Cipher.init}. + */ +public final class OpenSSLEvpCipherParameters extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // EVP_CIPHER_CTX_set_key_length(ctx, keylen): the key length in bytes + private static final IDetectionRule EVP_CIPHER_CTX_SET_KEY_LENGTH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CIPHER_CTX_set_key_length") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .buildForContext(new AlgorithmParameterContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_CIPHER_CTX_set_padding(ctx, pad): the standard block padding unless pad is 0 + private static final IDetectionRule EVP_CIPHER_CTX_SET_PADDING = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CIPHER_CTX_set_padding") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLCipherPaddingFactory()) + .buildForContext(new AlgorithmParameterContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_CIPHER_CTX_ctrl(ctx, type, arg, ptr): the parameter the command type sets, given by arg + private static final IDetectionRule EVP_CIPHER_CTX_CTRL = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_CIPHER_CTX_ctrl") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLCipherCtrlFactory()) + .withMethodParameter("*") + .buildForContext(new AlgorithmParameterContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_CIPHER_CTX_SET_KEY_LENGTH, EVP_CIPHER_CTX_SET_PADDING, EVP_CIPHER_CTX_CTRL); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc2.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc2.java new file mode 100644 index 000000000..77b3e9f05 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc2.java @@ -0,0 +1,110 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP RC2 cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherRc2 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_RC2_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc2_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC2_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc2_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC2_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc2_cfb64", "EVP_rc2_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC2_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc2_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC2_40_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc2_40_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2-40-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC2_64_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc2_64_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("RC2-64-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_RC2_ECB, EVP_RC2_CBC, EVP_RC2_CFB, EVP_RC2_OFB, EVP_RC2_40_CBC, EVP_RC2_64_CBC); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc4.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc4.java new file mode 100644 index 000000000..fb077bddd --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc4.java @@ -0,0 +1,76 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP RC4 stream cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherRc4 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_RC4 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc4") + .shouldBeDetectedAs(new ValueActionFactory<>("RC4")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC4_40 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc4_40") + .shouldBeDetectedAs(new ValueActionFactory<>("RC4-40")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC4_HMAC_MD5 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc4_hmac_md5") + .shouldBeDetectedAs(new ValueActionFactory<>("RC4-HMAC-MD5")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_RC4, EVP_RC4_40, EVP_RC4_HMAC_MD5); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc5.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc5.java new file mode 100644 index 000000000..89c18e79f --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRc5.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP RC5 cipher algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherRc5 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_RC5_32_12_16_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc5_32_12_16_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("RC5-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC5_32_12_16_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc5_32_12_16_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("RC5-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC5_32_12_16_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc5_32_12_16_cfb64", "EVP_rc5_32_12_16_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("RC5-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RC5_32_12_16_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_rc5_32_12_16_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("RC5-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_RC5_32_12_16_ECB, + EVP_RC5_32_12_16_CBC, + EVP_RC5_32_12_16_CFB, + EVP_RC5_32_12_16_OFB); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRuleFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRuleFactory.java new file mode 100644 index 000000000..47e83aae6 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherRuleFactory.java @@ -0,0 +1,268 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * The rule set of an OpenSSL cipher family, built by the builders shared by every family: the + * {@code EVP_CIPHER}-returning functions (AES, ARIA, Camellia, DES, ...), which take no argument + * and always resolve to a fixed cipher-spec label, and the legacy (pre-EVP) key setup and + * encryption functions of each cipher. + */ +public abstract class OpenSSLEvpCipherRuleFactory extends DetectionRuleSet { + + /** One {@code functionName -> label} entry, e.g. {@code "EVP_aes_128_cbc" -> "AES-128-CBC"}. */ + public record Entry(@Nonnull String functionName, @Nonnull String label) {} + + /** Builds one detection rule per entry, in list order. */ + @Nonnull + protected static List> build( + @Nonnull String bundle, @Nonnull List entries) { + List> rules = new ArrayList<>(entries.size()); + for (Entry entry : entries) { + rules.add( + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(entry.functionName()) + .shouldBeDetectedAs(new ValueActionFactory<>(entry.label())) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> bundle) + .withoutDependingDetectionRules()); + } + return rules; + } + + /** + * The context kind of the legacy cipher rules: their labels name a cipher and its mode, while + * the key length is set by the key setup function of the key they use. + */ + public static final String LEGACY = "LEGACY"; + + /** + * One {@code functionNames -> label} entry for a legacy (pre-EVP) cipher call, e.g. {@code + * ["DES_ncbc_encrypt", "DES_cbc_encrypt"] -> "DES-CBC"}, with the number of arguments of the + * functions (which share one prototype) and: + * + *

    + *
  • for a key setup function, the argument that gives the key size and its unit (e.g. the + * {@code bits} of {@code AES_set_encrypt_key}, the {@code len} in bytes of {@code + * BF_set_key}); {@code keySizeParameter} is -1 when there is none, as for a cipher whose + * key length is fixed, which is marked as a key setup by {@link #setsUpKey()}; + *
  • for an encryption function, the argument that gives the key schedule set up by a key + * setup function of the same family (e.g. the {@code key} of {@code AES_cbc_encrypt}); + * {@code keyParameter} is -1 when the encryption takes no key schedule. + *
+ */ + public record LegacyEntry( + @Nonnull List functionNames, + @Nonnull String label, + int parameterCount, + int keySizeParameter, + @Nullable Size.UnitType keySizeUnit, + int keyParameter, + boolean keySetup) { + + public LegacyEntry( + @Nonnull String functionName, @Nonnull String label, int parameterCount) { + this(List.of(functionName), label, parameterCount); + } + + public LegacyEntry( + @Nonnull List functionNames, @Nonnull String label, int parameterCount) { + this(functionNames, label, parameterCount, -1, null, -1, false); + } + + public LegacyEntry( + @Nonnull String functionName, + @Nonnull String label, + int parameterCount, + int keySizeParameter, + @Nonnull Size.UnitType keySizeUnit) { + this( + List.of(functionName), + label, + parameterCount, + keySizeParameter, + keySizeUnit, + -1, + true); + } + + /** The same entry, whose key schedule is the argument at the given index. */ + @Nonnull + public LegacyEntry keyAt(int parameter) { + return new LegacyEntry( + functionNames, + label, + parameterCount, + keySizeParameter, + keySizeUnit, + parameter, + keySetup); + } + + /** The same entry, a key setup function that gives no key size. */ + @Nonnull + public LegacyEntry setsUpKey() { + return new LegacyEntry( + functionNames, + label, + parameterCount, + keySizeParameter, + keySizeUnit, + keyParameter, + true); + } + + private boolean isKeySetup() { + return keySetup; + } + } + + /** + * Builds one detection rule per legacy entry, in list order. The key size, when the entry has + * one, is reported as a child of the detected cipher. The key schedule argument of an + * encryption function is followed to the key setup functions of the same entries, so that the + * encryption reports the key size its key was set up with: + * + *
{@code
+     * AES_set_encrypt_key(userKey, 256, &key);
+     * AES_cbc_encrypt(in, out, length, &key, iv, AES_ENCRYPT); // AES-256-CBC
+     * }
+ */ + @Nonnull + protected static List> buildLegacy( + @Nonnull String bundle, @Nonnull List entries) { + final List> keySetups = + entries.stream() + .filter(LegacyEntry::isKeySetup) + .map(entry -> buildLegacy(bundle, entry, List.of())) + .toList(); + final List> rules = new ArrayList<>(entries.size()); + int keySetup = 0; + for (LegacyEntry entry : entries) { + rules.add( + entry.isKeySetup() + ? keySetups.get(keySetup++) + : buildLegacy(bundle, entry, keySetups)); + } + return rules; + } + + @Nonnull + private static IDetectionRule buildLegacy( + @Nonnull String bundle, + @Nonnull LegacyEntry entry, + @Nonnull List> keySetups) { + final IDetectionRule.ParametersTypeBuilder call = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(entry.functionNames().toArray(new String[0])) + .shouldBeDetectedAs(new ValueActionFactory<>(entry.label())); + final CipherContext context = new CipherContext(Map.of("kind", LEGACY)); + if (entry.parameterCount() == 0) { + return call.withoutParameters() + .buildForContext(context) + .inBundle(() -> bundle) + .withoutDependingDetectionRules(); + } + Chain chain = Chain.of(call.withMethodParameter("*")); + for (int i = 0; i < entry.parameterCount(); i++) { + if (i > 0) { + chain = Chain.of(chain.next().get()); + } + if (i == entry.keySizeParameter()) { + chain = + Chain.of( + chain.current() + .shouldBeDetectedAs( + new OpenSSLSizeFactory( + new KeySizeFactory<>( + Objects.requireNonNull( + entry.keySizeUnit())))) + .asChildOfParameterWithId(-1)); + } else if (i == entry.keyParameter() && !keySetups.isEmpty()) { + chain = Chain.of(chain.current().addDependingDetectionRules(keySetups)); + } + } + return chain.build().apply(context).inBundle(() -> bundle).withoutDependingDetectionRules(); + } + + /** + * The rule being built after one of its parameters: the parameter itself, or the parameter with + * a value or depending rules, from which the next parameter is added or the rule built. + */ + private record Chain( + @Nullable IDetectionRule.ParametersFactoryBuilder current, + @Nonnull Supplier> next, + @Nonnull + Function> + build) { + + @Nonnull + static Chain of(@Nonnull IDetectionRule.ParametersFactoryBuilder parameter) { + return new Chain( + parameter, + () -> parameter.withMethodParameter("*"), + parameter::buildForContext); + } + + @Nonnull + static Chain of( + @Nonnull IDetectionRule.ParametersDependingRulesBuilder parameter) { + return new Chain( + null, () -> parameter.withMethodParameter("*"), parameter::buildForContext); + } + + @Nonnull + static Chain of( + @Nonnull IDetectionRule.ParametersFinalDetectionRuleBuilder parameter) { + return new Chain( + null, () -> parameter.withMethodParameter("*"), parameter::buildForContext); + } + + @Nonnull + @Override + public IDetectionRule.ParametersFactoryBuilder current() { + return Objects.requireNonNull(current, "a parameter holds one value or rule list"); + } + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherSeed.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherSeed.java new file mode 100644 index 000000000..9841d7b2b --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherSeed.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP SEED cipher algorithm specifiers. + * + *

Covers SEED (the Korean national standard cipher, RFC 4269). + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherSeed extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_SEED_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_seed_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("SEED-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SEED_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_seed_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("SEED-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SEED_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_seed_cfb128", "EVP_seed_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("SEED-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SEED_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_seed_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("SEED-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_SEED_ECB, EVP_SEED_CBC, EVP_SEED_CFB, EVP_SEED_OFB); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherSm4.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherSm4.java new file mode 100644 index 000000000..eaceb0cd8 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherSm4.java @@ -0,0 +1,105 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP SM4 cipher algorithm specifiers. + * + *

Covers SM4 (the Chinese national standard cipher, GB/T 32907-2016) across all EVP modes (ECB, + * CBC, CFB variants, OFB, CTR). + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpCipherSm4 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_SM4_ECB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sm4_ecb") + .shouldBeDetectedAs(new ValueActionFactory<>("SM4-ECB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SM4_CBC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sm4_cbc") + .shouldBeDetectedAs(new ValueActionFactory<>("SM4-CBC")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SM4_CFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sm4_cfb128", "EVP_sm4_cfb") + .shouldBeDetectedAs(new ValueActionFactory<>("SM4-CFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SM4_OFB = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sm4_ofb") + .shouldBeDetectedAs(new ValueActionFactory<>("SM4-OFB")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SM4_CTR = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sm4_ctr") + .shouldBeDetectedAs(new ValueActionFactory<>("SM4-CTR")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // SM4 + EVP_SM4_ECB, EVP_SM4_CBC, EVP_SM4_CFB, EVP_SM4_OFB, EVP_SM4_CTR); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryption.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryption.java new file mode 100644 index 000000000..fa9a3889d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryption.java @@ -0,0 +1,93 @@ +/* + * 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.openssl.cipher; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for the encryption of a private key when it is written: {@code + * PEM_write_bio_PrivateKey} and the other PEM, PKCS#8 and DER writers encrypt the key with the + * cipher given to them ({@code enc}) under a key derived from a password. A key written without a + * cipher is not encrypted, and not reported. + */ +public final class OpenSSLPrivateKeyEncryption extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + /** The writers taking (out, pkey, enc, kstr, klen, cb, u). */ + private static final List WRITERS = + List.of( + "PEM_write_bio_PrivateKey", + "PEM_write_PrivateKey", + "PEM_write_bio_PrivateKey_traditional", + "PEM_write_bio_PKCS8PrivateKey", + "PEM_write_PKCS8PrivateKey", + "i2d_PKCS8PrivateKey_bio", + "i2d_PKCS8PrivateKey_fp"); + + /** The writers taking (out, pkey, enc, kstr, klen, cb, u, libctx, propq). */ + private static final List WRITERS_EX = List.of("PEM_write_bio_PrivateKey_ex"); + + private static final IDetectionRule WRITE_ENCRYPTED = writer(WRITERS, 7); + + private static final IDetectionRule WRITE_ENCRYPTED_EX = writer(WRITERS_EX, 9); + + /** A writer whose third argument, {@code enc}, is the cipher that encrypts the key. */ + @Nonnull + private static IDetectionRule writer( + @Nonnull List functions, int parameterCount) { + IDetectionRule.ParametersFactoryBuilder parameters = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(functions.toArray(new String[0])) + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*"); + for (int i = 4; i < parameterCount; i++) { + parameters = parameters.withMethodParameter("*"); + } + return parameters + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of(WRITE_ENCRYPTED, WRITE_ENCRYPTED_EX).toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigest.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigest.java new file mode 100644 index 000000000..4d1d1fc24 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigest.java @@ -0,0 +1,256 @@ +/* + * 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.openssl.digest; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.DerivedDetectionRules; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.ibm.plugin.translation.translator.contexts.CxxDigestContextTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP message digest algorithm specifiers. + * + *

These rules detect calls to OpenSSL functions that return EVP_MD pointers, identifying the + * specific hash algorithm being used. Each function (e.g., {@code EVP_sha256()}) maps to a known + * digest algorithm name. + * + *

Per-family digest specifiers with multiple variants live in their own {@code + * OpenSSLEvpMessageDigest} classes (MD, SHA-2, SHA-3/SHAKE, BLAKE2); this class holds the + * remaining single-variant digests (SHA-1, RIPEMD, Whirlpool, SM3, combined/special digests) and + * the digests selected by name or NID ({@code EVP_MD_fetch}, {@code EVP_get_digestbyname}, {@code + * EVP_get_digestbynid}, {@code EVP_Q_digest}), and aggregates every family's rules in {@link + * #buildRules()}. The digest given to {@code EVP_DigestInit} and the other functions that take an + * {@code EVP_MD} is reported where it is created. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpMessageDigest extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_SHA1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha1") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-1")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RIPEMD160 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_ripemd160") + .shouldBeDetectedAs(new ValueActionFactory<>("RIPEMD160")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_WHIRLPOOL = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_whirlpool") + .shouldBeDetectedAs(new ValueActionFactory<>("WHIRLPOOL")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SM3 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sm3") + .shouldBeDetectedAs(new ValueActionFactory<>("SM3")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MD5_SHA1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_md5_sha1") + .shouldBeDetectedAs(new ValueActionFactory<>("MD5-SHA1")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MD_NULL = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_md_null") + .shouldBeDetectedAs(new ValueActionFactory<>("NULL")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MD_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_MD_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_GET_DIGESTBYNAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_get_digestbyname") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_get_digestbynid(nid) (evp.h): EVP_get_digestbyname for the digest of the NID + private static final IDetectionRule EVP_GET_DIGESTBYNID = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_get_digestbynid") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.DIGEST_BY_CODE, + OpenSSLNidLookupFactory.DIGEST_BY_NAME)) + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_Q_digest(libctx, name, propq, data, datalen, md, mdlen): one-shot digest by name + private static final IDetectionRule EVP_Q_DIGEST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_Q_digest") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLEvpMessageDigestMd.class).stream(), + RuleSets.rulesOf(OpenSSLEvpMessageDigestSha2.class).stream(), + RuleSets.rulesOf(OpenSSLEvpMessageDigestSha3.class).stream(), + RuleSets.rulesOf(OpenSSLEvpMessageDigestBlake2.class).stream(), + directRules().stream()) + .flatMap(i -> i) + .toList(); + } + + @Nonnull + private static List> directRules() { + return List.of( + // SHA-1 + EVP_SHA1, + // RIPEMD + EVP_RIPEMD160, + // Whirlpool + EVP_WHIRLPOOL, + // SM3 (Chinese National Standard) + EVP_SM3, + // Combined and Special Digests + EVP_MD5_SHA1, + EVP_MD_NULL, + // Digest selected by name: fetch, legacy lookup and one-shot digest + EVP_MD_FETCH, + EVP_GET_DIGESTBYNAME, + EVP_GET_DIGESTBYNID, + EVP_Q_DIGEST); + } + + /** + * The digest rules for the digest of MGF1, the mask generation function of RSA-PSS and + * RSA-OAEP, e.g. the {@code mgf1Hash} argument of {@code RSA_padding_add_PKCS1_PSS_mgf1}: each + * digest is reported as MGF1 with that digest. + */ + public static final class Mgf1 extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return inDigestContext(CxxDigestContextTranslator.MGF1_KIND); + } + } + + /** + * The digest rules for the digest of RSA-OAEP, e.g. the {@code md} argument of {@code + * RSA_padding_add_PKCS1_OAEP_mgf1}: each digest is reported as the OAEP padding with that + * digest. + */ + public static final class Oaep extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return inDigestContext(CxxDigestContextTranslator.OAEP_KIND); + } + } + + /** The digest rules, each derived to report its digest in the digest context of the kind. */ + @Nonnull + private static List> inDigestContext(@Nonnull String kind) { + final DigestContext context = + new DigestContext(Map.of(CxxDigestContextTranslator.KIND, kind)); + return RuleSets.rulesOf(OpenSSLEvpMessageDigest.class).stream() + .map(rule -> DerivedDetectionRules.withContext(rule, context)) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestBlake2.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestBlake2.java new file mode 100644 index 000000000..c92371547 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestBlake2.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.digest; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP BLAKE2 message digest algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpMessageDigestBlake2 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_BLAKE2B512 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_blake2b512") + .shouldBeDetectedAs(new ValueActionFactory<>("BLAKE2B-512")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_BLAKE2S256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_blake2s256") + .shouldBeDetectedAs(new ValueActionFactory<>("BLAKE2S-256")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_BLAKE2B512, EVP_BLAKE2S256); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestMd.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestMd.java new file mode 100644 index 000000000..ba5c3a1b5 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestMd.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.digest; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL EVP MD2/MD4/MD5/MDC2 message digest algorithm specifiers. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpMessageDigestMd extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_MD2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_md2") + .shouldBeDetectedAs(new ValueActionFactory<>("MD2")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MD4 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_md4") + .shouldBeDetectedAs(new ValueActionFactory<>("MD4")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MD5 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_md5") + .shouldBeDetectedAs(new ValueActionFactory<>("MD5")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MDC2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_mdc2") + .shouldBeDetectedAs(new ValueActionFactory<>("MDC2")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_MD2, EVP_MD4, EVP_MD5, EVP_MDC2); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestSha2.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestSha2.java new file mode 100644 index 000000000..431104954 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestSha2.java @@ -0,0 +1,114 @@ +/* + * 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.openssl.digest; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP SHA-2 family message digest algorithm specifiers. + * + *

Covers SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, and SHA-512/256. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpMessageDigestSha2 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_SHA224 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha224") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-224")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha256") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-256")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA384 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha384") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-384")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA512 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha512") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-512")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA512_224 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha512_224") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-512/224")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA512_256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha512_256") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-512/256")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_SHA224, EVP_SHA256, EVP_SHA384, EVP_SHA512, EVP_SHA512_224, EVP_SHA512_256); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestSha3.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestSha3.java new file mode 100644 index 000000000..4024a531a --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestSha3.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.openssl.digest; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP SHA-3 family message digest algorithm specifiers. + * + *

Covers SHA3-224/256/384/512 and the SHAKE128/SHAKE256 extendable-output functions (all built + * on the same Keccak sponge construction). + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpMessageDigestSha3 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_SHA3_224 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha3_224") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA3-224")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA3_256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha3_256") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA3-256")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA3_384 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha3_384") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA3-384")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHA3_512 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_sha3_512") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA3-512")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHAKE128 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_shake128") + .shouldBeDetectedAs(new ValueActionFactory<>("SHAKE128")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_SHAKE256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_shake256") + .shouldBeDetectedAs(new ValueActionFactory<>("SHAKE256")) + .withoutParameters() + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_SHA3_224, EVP_SHA3_256, EVP_SHA3_384, EVP_SHA3_512, EVP_SHAKE128, EVP_SHAKE256); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLNameCanonicalizerFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLNameCanonicalizerFactory.java new file mode 100644 index 000000000..cda7e653f --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLNameCanonicalizerFactory.java @@ -0,0 +1,343 @@ +/* + * 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.openssl.digest; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Resolves an OpenSSL bare-name string argument (e.g. a digest {@code md_name} like {@code + * "SHA256"}, or a cipher name like {@code "DES3"}) to the identifier string the corresponding + * {@code Cxx*ContextTranslator} switches on, using a caller-supplied canonicalization table. Names + * already in a recognized form, or not recognized at all, pass through unchanged, so the + * translator's own {@code default -> Optional.empty()} decides whether they surface. + */ +public final class OpenSSLNameCanonicalizerFactory implements IValueFactory { + + /** + * {@code md_name} argument (e.g. {@code EVP_PKEY_CTX_set_rsa_mgf1_md_name}) → {@code + * "SHA-256"}. + */ + public static final Map DIGEST_NAMES = + Map.ofEntries( + Map.entry("SHA1", "SHA-1"), + Map.entry("SHA224", "SHA-224"), + Map.entry("SHA256", "SHA-256"), + Map.entry("SHA384", "SHA-384"), + Map.entry("SHA512", "SHA-512"), + Map.entry("SHA512-224", "SHA-512/224"), + Map.entry("SHA512-256", "SHA-512/256"), + // OpenSSL 3.x provider fetch names (OSSL_DIGEST_NAME_*) + Map.entry("SHA2-224", "SHA-224"), + Map.entry("SHA2-256", "SHA-256"), + Map.entry("SHA2-384", "SHA-384"), + Map.entry("SHA2-512", "SHA-512"), + Map.entry("SHA2-512/224", "SHA-512/224"), + Map.entry("SHA2-512/256", "SHA-512/256"), + // SHAKE (extendable-output functions) + Map.entry("SHAKE128", "SHAKE128"), + Map.entry("SHAKE256", "SHAKE256"), + // RIPEMD-160 (OpenSSL's short_name is "RMD160", the long/EVP name is + // "RIPEMD160") + Map.entry("RIPEMD160", "RIPEMD160"), + Map.entry("RMD160", "RIPEMD160"), + // BLAKE2 (OpenSSL's EVP_MD names have no hyphen; the translator's canonical + // form does) + Map.entry("BLAKE2B512", "BLAKE2B-512"), + Map.entry("BLAKE2S256", "BLAKE2S-256"), + // MDC-2 (ISO/IEC 10118-2, built on DES) + Map.entry("MDC2", "MDC2"), + // SSLv3 MAC digest names (OBJ_sn_ssl3_sha1 / OBJ_sn_ssl3_md5): the same + // SHA-1/MD5 algorithms, just under their SSLv3 cipher-suite object name + Map.entry("SSL3-SHA1", "SHA-1"), + Map.entry("SSL3-MD5", "MD5"), + // OpenSSL 3.x name macros (core_names.h), e.g. OSSL_DIGEST_NAME_SHA2_256 + Map.entry("OSSL_DIGEST_NAME_MD5", "MD5"), + Map.entry("OSSL_DIGEST_NAME_MD5_SHA1", "MD5-SHA1"), + Map.entry("OSSL_DIGEST_NAME_SHA1", "SHA-1"), + Map.entry("OSSL_DIGEST_NAME_SHA2_224", "SHA-224"), + Map.entry("OSSL_DIGEST_NAME_SHA2_256", "SHA-256"), + Map.entry("OSSL_DIGEST_NAME_SHA2_384", "SHA-384"), + Map.entry("OSSL_DIGEST_NAME_SHA2_512", "SHA-512"), + Map.entry("OSSL_DIGEST_NAME_SHA2_512_224", "SHA-512/224"), + Map.entry("OSSL_DIGEST_NAME_SHA2_512_256", "SHA-512/256"), + Map.entry("OSSL_DIGEST_NAME_MD2", "MD2"), + Map.entry("OSSL_DIGEST_NAME_MD4", "MD4"), + Map.entry("OSSL_DIGEST_NAME_MDC2", "MDC2"), + Map.entry("OSSL_DIGEST_NAME_RIPEMD160", "RIPEMD160"), + Map.entry("OSSL_DIGEST_NAME_SHA3_224", "SHA3-224"), + Map.entry("OSSL_DIGEST_NAME_SHA3_256", "SHA3-256"), + Map.entry("OSSL_DIGEST_NAME_SHA3_384", "SHA3-384"), + Map.entry("OSSL_DIGEST_NAME_SHA3_512", "SHA3-512"), + Map.entry("OSSL_DIGEST_NAME_SM3", "SM3"), + // short-name macros (obj_mac.h), e.g. SN_sha256 + Map.entry("SN_MD5", "MD5"), + Map.entry("SN_SHA1", "SHA-1"), + Map.entry("SN_SHA224", "SHA-224"), + Map.entry("SN_SHA256", "SHA-256"), + Map.entry("SN_SHA384", "SHA-384"), + Map.entry("SN_SHA512", "SHA-512"), + Map.entry("SN_SHA3_224", "SHA3-224"), + Map.entry("SN_SHA3_256", "SHA3-256"), + Map.entry("SN_SHA3_384", "SHA3-384"), + Map.entry("SN_SHA3_512", "SHA3-512"), + Map.entry("SN_SM3", "SM3"), + Map.entry("SN_RIPEMD160", "RIPEMD160")); + + /** + * Cipher name or alias accepted by {@code EVP_CIPHER_fetch}/{@code EVP_get_cipherbyname} (e.g. + * {@code "DES3"}) → the name the cipher translator maps (e.g. {@code "DESede3-CBC"}). Names + * that already are in that form, such as {@code "AES-256-GCM"}, are not listed. + */ + public static final Map CIPHER_NAMES = + Map.ofEntries( + // AES: size-only aliases select CBC; "id-aes*" names are the ASN.1 names + Map.entry("AES128", "AES-128-CBC"), + Map.entry("AES128-WRAP", "AES-128-WRAP"), + Map.entry("AES128-WRAP-INV", "AES-128-WRAP-INV"), + Map.entry("AES128-WRAP-PAD", "AES-128-WRAP-PAD"), + Map.entry("AES128-WRAP-PAD-INV", "AES-128-WRAP-PAD-INV"), + Map.entry("ID-AES128-CCM", "AES-128-CCM"), + Map.entry("ID-AES128-GCM", "AES-128-GCM"), + Map.entry("ID-AES128-WRAP", "AES-128-WRAP"), + Map.entry("ID-AES128-WRAP-PAD", "AES-128-WRAP-PAD"), + Map.entry("AES192", "AES-192-CBC"), + Map.entry("AES192-WRAP", "AES-192-WRAP"), + Map.entry("AES192-WRAP-INV", "AES-192-WRAP-INV"), + Map.entry("AES192-WRAP-PAD", "AES-192-WRAP-PAD"), + Map.entry("AES192-WRAP-PAD-INV", "AES-192-WRAP-PAD-INV"), + Map.entry("ID-AES192-CCM", "AES-192-CCM"), + Map.entry("ID-AES192-GCM", "AES-192-GCM"), + Map.entry("ID-AES192-WRAP", "AES-192-WRAP"), + Map.entry("ID-AES192-WRAP-PAD", "AES-192-WRAP-PAD"), + Map.entry("AES256", "AES-256-CBC"), + Map.entry("AES256-WRAP", "AES-256-WRAP"), + Map.entry("AES256-WRAP-INV", "AES-256-WRAP-INV"), + Map.entry("AES256-WRAP-PAD", "AES-256-WRAP-PAD"), + Map.entry("AES256-WRAP-PAD-INV", "AES-256-WRAP-PAD-INV"), + Map.entry("ID-AES256-CCM", "AES-256-CCM"), + Map.entry("ID-AES256-GCM", "AES-256-GCM"), + Map.entry("ID-AES256-WRAP", "AES-256-WRAP"), + Map.entry("ID-AES256-WRAP-PAD", "AES-256-WRAP-PAD"), + // ARIA and Camellia: size-only aliases select CBC + Map.entry("ARIA128", "ARIA-128-CBC"), + Map.entry("ARIA192", "ARIA-192-CBC"), + Map.entry("ARIA256", "ARIA-256-CBC"), + Map.entry("CAMELLIA128", "CAMELLIA-128-CBC"), + Map.entry("CAMELLIA192", "CAMELLIA-192-CBC"), + Map.entry("CAMELLIA256", "CAMELLIA-256-CBC"), + // Blowfish + Map.entry("BF", "BLOWFISH-CBC"), + Map.entry("BLOWFISH", "BLOWFISH-CBC"), + Map.entry("BF-CBC", "BLOWFISH-CBC"), + Map.entry("BF-ECB", "BLOWFISH-ECB"), + Map.entry("BF-CFB", "BLOWFISH-CFB"), + Map.entry("BF-OFB", "BLOWFISH-OFB"), + // CAST5 + Map.entry("CAST", "CAST5-CBC"), + Map.entry("CAST-CBC", "CAST5-CBC"), + // DES, two-key (DES-EDE) and three-key (DES-EDE3) Triple DES; CFB is CFB64 + Map.entry("DES", "DES-CBC"), + Map.entry("DESX", "DESX-CBC"), + Map.entry("DES-EDE", "DESede"), + Map.entry("DES-EDE-ECB", "DESede-ECB"), + Map.entry("DES-EDE-CBC", "DESede-CBC"), + Map.entry("DES-EDE-CFB", "DESede-CFB64"), + Map.entry("DES-EDE-OFB", "DESede-OFB"), + Map.entry("DES-EDE3", "DESede3"), + Map.entry("DES-EDE3-ECB", "DESede3-ECB"), + Map.entry("DES-EDE3-CBC", "DESede3-CBC"), + Map.entry("DES3", "DESede3-CBC"), + Map.entry("DES-EDE3-CFB", "DESede3-CFB64"), + Map.entry("DES-EDE3-CFB1", "DESede3-CFB1"), + Map.entry("DES-EDE3-CFB8", "DESede3-CFB8"), + Map.entry("DES-EDE3-OFB", "DESede3-OFB"), + Map.entry("DES3-WRAP", "DES-EDE3-WRAP"), + Map.entry("ID-SMIME-ALG-CMS3DESWRAP", "DES-EDE3-WRAP"), + // IDEA, RC2, RC5, SEED, SM4: bare names select CBC + Map.entry("IDEA", "IDEA-CBC"), + Map.entry("RC2", "RC2-CBC"), + Map.entry("RC2-128", "RC2-CBC"), + Map.entry("RC2-40", "RC2-40-CBC"), + Map.entry("RC2-64", "RC2-64-CBC"), + Map.entry("RC5", "RC5-CBC"), + Map.entry("SEED", "SEED-CBC"), + Map.entry("SEED-OFB128", "SEED-OFB"), + Map.entry("SM4", "SM4-CBC"), + Map.entry("SM4-OFB128", "SM4-OFB"), + // OpenSSL 3.x name macros (core_names.h) + Map.entry("OSSL_CIPHER_NAME_AES_128_GCM_SIV", "AES-128-GCM-SIV"), + Map.entry("OSSL_CIPHER_NAME_AES_192_GCM_SIV", "AES-192-GCM-SIV"), + Map.entry("OSSL_CIPHER_NAME_AES_256_GCM_SIV", "AES-256-GCM-SIV")); + + /** + * Key type name accepted by {@code EVP_PKEY_CTX_new_from_name}, {@code EVP_PKEY_Q_keygen} and + * {@code EVP_KEYMGMT_fetch} (e.g. {@code "RSA"}, {@code "ML-KEM-768"}) → the key type name. + */ + public static final Map KEY_TYPE_NAMES = + Map.ofEntries( + Map.entry("RSA", "RSA"), + Map.entry("RSA-PSS", "RSA-PSS"), + Map.entry("DSA", "DSA"), + Map.entry("DH", "DH"), + Map.entry("EC", "EC"), + Map.entry("SM2", "SM2"), + Map.entry("X25519", "X25519"), + Map.entry("X448", "X448"), + Map.entry("ED25519", "ED25519"), + Map.entry("ED448", "ED448"), + Map.entry("ML-KEM-512", "ML-KEM-512"), + Map.entry("ML-KEM-768", "ML-KEM-768"), + Map.entry("ML-KEM-1024", "ML-KEM-1024"), + Map.entry("ML-DSA-44", "ML-DSA-44"), + Map.entry("ML-DSA-65", "ML-DSA-65"), + Map.entry("ML-DSA-87", "ML-DSA-87"), + Map.entry("X25519MLKEM768", "X25519MLKEM768"), + Map.entry("X448MLKEM1024", "X448MLKEM1024"), + Map.entry("SECP256R1MLKEM768", "SECP256R1MLKEM768"), + Map.entry("SECP384R1MLKEM1024", "SECP384R1MLKEM1024"), + Map.entry("SLH-DSA-SHA2-128F", "SLH-DSA-SHA2-128F"), + Map.entry("SLH-DSA-SHA2-128S", "SLH-DSA-SHA2-128S"), + Map.entry("SLH-DSA-SHA2-192F", "SLH-DSA-SHA2-192F"), + Map.entry("SLH-DSA-SHA2-192S", "SLH-DSA-SHA2-192S"), + Map.entry("SLH-DSA-SHA2-256F", "SLH-DSA-SHA2-256F"), + Map.entry("SLH-DSA-SHA2-256S", "SLH-DSA-SHA2-256S"), + Map.entry("SLH-DSA-SHAKE-128F", "SLH-DSA-SHAKE-128F"), + Map.entry("SLH-DSA-SHAKE-128S", "SLH-DSA-SHAKE-128S"), + Map.entry("SLH-DSA-SHAKE-192F", "SLH-DSA-SHAKE-192F"), + Map.entry("SLH-DSA-SHAKE-192S", "SLH-DSA-SHAKE-192S"), + Map.entry("SLH-DSA-SHAKE-256F", "SLH-DSA-SHAKE-256F"), + Map.entry("SLH-DSA-SHAKE-256S", "SLH-DSA-SHAKE-256S"), + // aliases + Map.entry("MLKEM512", "ML-KEM-512"), + Map.entry("MLKEM768", "ML-KEM-768"), + Map.entry("MLKEM1024", "ML-KEM-1024"), + Map.entry("MLDSA44", "ML-DSA-44"), + Map.entry("MLDSA65", "ML-DSA-65"), + Map.entry("MLDSA87", "ML-DSA-87"), + Map.entry("RSAENCRYPTION", "RSA"), + Map.entry("RSASSA-PSS", "RSA-PSS"), + Map.entry("DHKEYAGREEMENT", "DH"), + Map.entry("DHX", "DH"), + Map.entry("X9.42 DH", "DH"), + Map.entry("ID-ECPUBLICKEY", "EC")); + + /** KDF name macro (e.g. {@code OSSL_KDF_NAME_HKDF}) → the KDF name it stands for. */ + public static final Map KDF_NAMES = + Map.ofEntries( + Map.entry("OSSL_KDF_NAME_HKDF", "HKDF"), + Map.entry("OSSL_KDF_NAME_TLS1_3_KDF", "TLS13-KDF"), + Map.entry("OSSL_KDF_NAME_PBKDF1", "PBKDF1"), + Map.entry("OSSL_KDF_NAME_PBKDF2", "PBKDF2"), + Map.entry("OSSL_KDF_NAME_SCRYPT", "SCRYPT"), + Map.entry("OSSL_KDF_NAME_SSHKDF", "SSHKDF"), + Map.entry("OSSL_KDF_NAME_SSKDF", "SSKDF"), + Map.entry("OSSL_KDF_NAME_TLS1_PRF", "TLS1-PRF"), + Map.entry("OSSL_KDF_NAME_X942KDF_ASN1", "X942KDF-ASN1"), + Map.entry("OSSL_KDF_NAME_X942KDF_CONCAT", "X942KDF-CONCAT"), + Map.entry("OSSL_KDF_NAME_X963KDF", "X963KDF"), + Map.entry("OSSL_KDF_NAME_KBKDF", "KBKDF"), + Map.entry("OSSL_KDF_NAME_KRB5KDF", "KRB5KDF"), + Map.entry("OSSL_KDF_NAME_HMACDRBGKDF", "HMAC-DRBG-KDF")); + + /** + * Names of the KDFs available through the EVP_PKEY interface, and the KDF name macros that + * stand for them → KDF names. + */ + public static final Map PKEY_KDF_NAMES = + Map.ofEntries( + Map.entry("HKDF", "HKDF"), + Map.entry("TLS1-PRF", "TLS1-PRF"), + Map.entry("SCRYPT", "SCRYPT"), + Map.entry("ID-SCRYPT", "SCRYPT"), + Map.entry("OSSL_KDF_NAME_HKDF", "HKDF"), + Map.entry("OSSL_KDF_NAME_TLS1_PRF", "TLS1-PRF"), + Map.entry("OSSL_KDF_NAME_SCRYPT", "SCRYPT")); + + /** Key type names accepted by EVP_PKEY_new_raw_private_key_ex → key type names. */ + public static final Map RAW_KEY_TYPE_NAMES = + Map.ofEntries( + Map.entry("HMAC", "HMAC"), + Map.entry("CMAC", "CMAC"), + Map.entry("POLY1305", "POLY1305"), + Map.entry("SIPHASH", "SIPHASH"), + Map.entry("X25519", "X25519"), + Map.entry("X448", "X448"), + Map.entry("ED25519", "ED25519"), + Map.entry("ED448", "ED448")); + + /** MAC name macro (e.g. {@code OSSL_MAC_NAME_HMAC}) → the MAC name it stands for. */ + public static final Map MAC_NAMES = + Map.ofEntries( + Map.entry("OSSL_MAC_NAME_BLAKE2BMAC", "BLAKE2BMAC"), + Map.entry("OSSL_MAC_NAME_BLAKE2SMAC", "BLAKE2SMAC"), + Map.entry("OSSL_MAC_NAME_CMAC", "CMAC"), + Map.entry("OSSL_MAC_NAME_GMAC", "GMAC"), + Map.entry("OSSL_MAC_NAME_HMAC", "HMAC"), + Map.entry("OSSL_MAC_NAME_KMAC128", "KMAC128"), + Map.entry("OSSL_MAC_NAME_KMAC256", "KMAC256"), + Map.entry("OSSL_MAC_NAME_POLY1305", "POLY1305"), + Map.entry("OSSL_MAC_NAME_SIPHASH", "SIPHASH")); + + @Nonnull private final Map table; + private final boolean knownNamesOnly; + + public OpenSSLNameCanonicalizerFactory(@Nonnull Map table) { + this(table, false); + } + + /** + * @param knownNamesOnly true to resolve only the names listed in {@code table}, for arguments + * that select among several kinds of algorithms, e.g. a key type or a KDF + */ + public OpenSSLNameCanonicalizerFactory( + @Nonnull Map table, boolean knownNamesOnly) { + this.table = table; + this.knownNamesOnly = knownNamesOnly; + } + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + final Object value = resolvedValue.value(); + if (!(value instanceof String str)) { + return Optional.empty(); + } + if (knownNamesOnly && !table.containsKey(str.toUpperCase().trim())) { + return Optional.empty(); + } + return Optional.of(new Algorithm<>(canonicalize(table, str), resolvedValue.tree())); + } + + /** + * Normalizes {@code name} against {@code table} (uppercased, trimmed lookup). A name already in + * a recognized form, or not recognized at all, passes through unchanged. + */ + @Nonnull + public static String canonicalize(@Nonnull Map table, @Nonnull String name) { + return table.getOrDefault(name.toUpperCase().trim(), name); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdf.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdf.java new file mode 100644 index 000000000..eb41b6271 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdf.java @@ -0,0 +1,180 @@ +/* + * 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.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.ibm.plugin.rules.detection.openssl.params.OpenSSLParams; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL Key Derivation Functions (KDFs). + * + *

The EVP_KDF API introduced in OpenSSL 3.0 selects the KDF by name in {@code EVP_KDF_fetch} + * (PBKDF2, HKDF, SCRYPT, TLS1-PRF, ARGON2ID, ...). The digest used by the KDF is set on the context + * created from the fetched KDF, through the {@code "digest"} entry of an {@code OSSL_PARAM} array + * passed to {@code EVP_KDF_CTX_set_params} or {@code EVP_KDF_derive}: + * + *

{@code
+ * EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL);
+ * EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+ * EVP_KDF_CTX_set_params(kctx, params); // params contains {"digest", "SHA256"}
+ * }
+ * + *

HKDF, TLS1-PRF and scrypt can also be selected through the EVP_PKEY interface, by the key type + * passed to {@code EVP_PKEY_CTX_new_id} or {@code EVP_PKEY_CTX_new_from_name}. The digest is then + * set on that context by a KDF specific setter, found in {@link OpenSSLEvpKdfHkdf} and {@link + * OpenSSLEvpKdfTls}: + * + *

{@code
+ * EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ * EVP_PKEY_derive_init(pctx);
+ * EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256());
+ * }
+ * + *

The PKCS#5 PBKDF2 functions are in {@link OpenSSLEvpKdfPbkdf2}, the scrypt functions in {@link + * OpenSSLEvpKdfScrypt}, the PKCS#12 and PKCS#5 password-based functions in {@link + * OpenSSLEvpKdfPkcs12}, and the password-based encryption selected by its algorithm identifier in + * {@link OpenSSLPasswordBasedEncryption}; {@link #buildRules()} includes them. + */ +public final class OpenSSLEvpKdf extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_KDF_CTX_SET_PARAMS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KDF_CTX_set_params") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLParams.Digests.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_KDF_derive(ctx, key, keylen, params): the length of the derived key, in bytes, and the + // parameters set for the derivation + private static final IDetectionRule EVP_KDF_DERIVE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KDF_derive") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLParams.Digests.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_KDF_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KDF_CTX_new") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(EVP_KDF_CTX_SET_PARAMS, EVP_KDF_DERIVE)); + + private static final IDetectionRule EVP_KDF_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KDF_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.KDF_NAMES)) + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(EVP_KDF_CTX_NEW)); + + // HKDF, TLS1-PRF and scrypt through the EVP_PKEY interface: the KDF is selected when the + // context is created, and its digest is set on that context + + private static final List> EVP_PKEY_KDF_CONTEXT_RULES = + Stream.of( + RuleSets.rulesOf(OpenSSLEvpKdfHkdf.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKdfTls.class).stream()) + .flatMap(i -> i) + .toList(); + + private static final IDetectionRule EVP_PKEY_CTX_NEW_ID = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_new_id") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.PKEY_KDF_BY_CODE, + OpenSSLNidLookupFactory.PKEY_KDF_BY_NAME)) + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(EVP_PKEY_KDF_CONTEXT_RULES); + + private static final IDetectionRule EVP_PKEY_CTX_NEW_FROM_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_new_from_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.PKEY_KDF_NAMES, true)) + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(EVP_PKEY_KDF_CONTEXT_RULES); + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLEvpKdfPbkdf2.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKdfScrypt.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKdfPkcs12.class).stream(), + RuleSets.rulesOf(OpenSSLPasswordBasedEncryption.class).stream(), + Stream.of(EVP_KDF_FETCH, EVP_PKEY_CTX_NEW_ID, EVP_PKEY_CTX_NEW_FROM_NAME)) + .flatMap(i -> i) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfHkdf.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfHkdf.java new file mode 100644 index 000000000..96dd2287d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfHkdf.java @@ -0,0 +1,63 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the calls made on an HKDF (HMAC-based Key Derivation Function, RFC 5869) + * context of the {@code EVP_PKEY} interface: the digest setter. The context itself is created by + * the {@code EVP_PKEY_CTX_new_id} and {@code EVP_PKEY_CTX_new_from_name} rules of {@link + * OpenSSLEvpKdf}, which also covers HKDF fetched by name through {@code EVP_KDF_fetch}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpKdfHkdf extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_PKEY_CTX_SET_HKDF_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_hkdf_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // HKDF setters + EVP_PKEY_CTX_SET_HKDF_MD); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2.java new file mode 100644 index 000000000..76b9b2359 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2.java @@ -0,0 +1,109 @@ +/* + * 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.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.IterationCountFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.SaltSizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the OpenSSL PKCS#5 PBKDF2 functions {@code PKCS5_PBKDF2_HMAC} and {@code + * PKCS5_PBKDF2_HMAC_SHA1}. The salt length, the iteration count and the length of the derived key + * are reported with the KDF, and the digest of {@code PKCS5_PBKDF2_HMAC} is traced back to where it + * is selected. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpKdfPbkdf2 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // PKCS5_PBKDF2_HMAC(pass, passlen, salt, saltlen, iter, digest, keylen, out) + private static final IDetectionRule PKCS5_PBKDF2_HMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS5_PBKDF2_HMAC") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2-HMAC")) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // salt + .withMethodParameter("*") // saltlen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new SaltSizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // iter + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // digest + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") // keylen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // out + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS5_PBKDF2_HMAC_SHA1(pass, passlen, salt, saltlen, iter, keylen, out) + private static final IDetectionRule PKCS5_PBKDF2_HMAC_SHA1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS5_PBKDF2_HMAC_SHA1") + .shouldBeDetectedAs(new ValueActionFactory<>("PBKDF2-HMAC-SHA1")) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // salt + .withMethodParameter("*") // saltlen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new SaltSizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // iter + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // keylen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // out + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(PKCS5_PBKDF2_HMAC, PKCS5_PBKDF2_HMAC_SHA1); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPkcs12.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPkcs12.java new file mode 100644 index 000000000..fdbc8a15c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPkcs12.java @@ -0,0 +1,384 @@ +/* + * 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.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the OpenSSL PKCS#12 and PKCS#5 password-based functions: the PKCS#12 key + * derivation function (PKCS12_key_gen_*), the PKCS#12 and PKCS#5 v1.5 password-based encryption + * schemes with the cipher and digest passed to PKCS12_PBE_keyivgen and PKCS5_PBE_keyivgen, the HMAC + * set on a PKCS#12 container by PKCS12_set_mac, and the schemes that encrypt the key and the + * certificates of a container created by PKCS12_create. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpKdfPkcs12 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // PKCS12_create(pass, name, pkey, cert, ca, nid_key, nid_cert, iter, mac_iter, keytype, ...): + // nid_key and nid_cert select how the private key and the certificates are encrypted + + private static final OpenSSLNidLookupFactory PKCS12_ENCRYPTION = + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.PKCS12_ENCRYPTION_BY_CODE, + OpenSSLNidLookupFactory.PKCS12_ENCRYPTION_BY_NAME); + + private static final IDetectionRule PKCS12_CREATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_create") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PKCS12_ENCRYPTION) + .withMethodParameter("*") + .shouldBeDetectedAs(PKCS12_ENCRYPTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_CREATE_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_create_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PKCS12_ENCRYPTION) + .withMethodParameter("*") + .shouldBeDetectedAs(PKCS12_ENCRYPTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_CREATE_EX2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_create_ex2") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PKCS12_ENCRYPTION) + .withMethodParameter("*") + .shouldBeDetectedAs(PKCS12_ENCRYPTION) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS12_set_mac(p12, pass, passlen, salt, saltlen, iter, md_type): the PKCS#12 MAC is an + // HMAC with md_type + + private static final IDetectionRule PKCS12_SET_MAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_set_mac") + .shouldBeDetectedAs(new ValueActionFactory<>("HMAC")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS12_PBE_keyivgen(ctx, pass, passlen, param, cipher, md_type, en_de, ...) and + // PKCS5_PBE_keyivgen(ctx, pass, passlen, param, cipher, md, en_de, ...) + + private static final IDetectionRule PKCS12_PBE_KEYIVGEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_PBE_keyivgen") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12-PBE")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_PBE_KEYIVGEN_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_PBE_keyivgen_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12-PBE")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS5_PBE_KEYIVGEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS5_PBE_keyivgen") + .shouldBeDetectedAs(new ValueActionFactory<>("PBES1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS5_PBE_KEYIVGEN_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS5_PBE_keyivgen_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("PBES1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS12_key_gen_*(pass, passlen, salt, saltlen, id, iter, n, out, md_type, ...) + + private static final IDetectionRule PKCS12_KEY_GEN_ASC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_key_gen_asc") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12KDF")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_KEY_GEN_ASC_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_key_gen_asc_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12KDF")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_KEY_GEN_UNI = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_key_gen_uni") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12KDF")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_KEY_GEN_UNI_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_key_gen_uni_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12KDF")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_KEY_GEN_UTF8 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_key_gen_utf8") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12KDF")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule PKCS12_KEY_GEN_UTF8_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS12_key_gen_utf8_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("PKCS12KDF")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // PKCS#12 containers + PKCS12_CREATE, + PKCS12_CREATE_EX, + PKCS12_CREATE_EX2, + PKCS12_SET_MAC, + // PKCS#12 and PKCS#5 password-based encryption + PKCS12_PBE_KEYIVGEN, + PKCS12_PBE_KEYIVGEN_EX, + PKCS5_PBE_KEYIVGEN, + PKCS5_PBE_KEYIVGEN_EX, + // PKCS#12 key derivation + PKCS12_KEY_GEN_ASC, + PKCS12_KEY_GEN_ASC_EX, + PKCS12_KEY_GEN_UNI, + PKCS12_KEY_GEN_UNI_EX, + PKCS12_KEY_GEN_UTF8, + PKCS12_KEY_GEN_UTF8_EX); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfScrypt.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfScrypt.java new file mode 100644 index 000000000..b3560594c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfScrypt.java @@ -0,0 +1,108 @@ +/* + * 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.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.SaltSizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the OpenSSL scrypt function {@code EVP_PBE_scrypt} and its {@code + * EVP_PBE_scrypt_ex} variant (evp.h). The salt length and the length of the derived key are + * reported with the KDF. Scrypt selected by name through {@code EVP_KDF_fetch} or by key type + * through {@code EVP_PKEY_CTX_new_id} is covered by {@link OpenSSLEvpKdf}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpKdfScrypt extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // EVP_PBE_scrypt(pass, passlen, salt, saltlen, N, r, p, maxmem, key, keylen) + private static final IDetectionRule EVP_PBE_SCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PBE_scrypt") + .shouldBeDetectedAs(new ValueActionFactory<>("SCRYPT")) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // salt + .withMethodParameter("*") // saltlen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new SaltSizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // N + .withMethodParameter("*") // r + .withMethodParameter("*") // p + .withMethodParameter("*") // maxmem + .withMethodParameter("*") // key + .withMethodParameter("*") // keylen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_PBE_scrypt_ex(pass, passlen, salt, saltlen, N, r, p, maxmem, key, keylen, ctx, propq) + private static final IDetectionRule EVP_PBE_SCRYPT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PBE_scrypt_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("SCRYPT")) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // salt + .withMethodParameter("*") // saltlen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new SaltSizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // N + .withMethodParameter("*") // r + .withMethodParameter("*") // p + .withMethodParameter("*") // maxmem + .withMethodParameter("*") // key + .withMethodParameter("*") // keylen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") // ctx + .withMethodParameter("*") // propq + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_PBE_SCRYPT, EVP_PBE_SCRYPT_EX); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfTls.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfTls.java new file mode 100644 index 000000000..68ffc05c3 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfTls.java @@ -0,0 +1,63 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the calls made on a TLS1-PRF (TLS 1.0/1.1/1.2) context of the {@code + * EVP_PKEY} interface: the digest setter. The context itself is created by the {@code + * EVP_PKEY_CTX_new_id} and {@code EVP_PKEY_CTX_new_from_name} rules of {@link OpenSSLEvpKdf}, which + * also covers the TLS1-PRF and TLS13-KDF fetched by name through {@code EVP_KDF_fetch}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpKdfTls extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_PKEY_CTX_SET_TLS1_PRF_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_tls1_prf_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // TLS1-PRF setters + EVP_PKEY_CTX_SET_TLS1_PRF_MD); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryption.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryption.java new file mode 100644 index 000000000..a001888a4 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryption.java @@ -0,0 +1,168 @@ +/* + * 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.openssl.kdf; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.IterationCountFactory; +import com.ibm.engine.model.factory.SaltSizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLCipherOperationFactory; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for password-based encryption selected by its algorithm identifier: {@code + * PKCS8_encrypt}, which encrypts a PKCS#8 private key with the scheme of {@code pbe_nid} (PBES2 + * with {@code cipher} for -1), and {@code EVP_PBE_CipherInit}, which initializes a cipher context + * with the scheme of an {@code OBJ_nid2obj} object, for the operation given by {@code en_de}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLPasswordBasedEncryption extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final OpenSSLNidLookupFactory PBE_ALGORITHM = + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.PBE_ALGORITHM_BY_CODE, + OpenSSLNidLookupFactory.PBE_ALGORITHM_BY_NAME); + + // PKCS8_encrypt(pbe_nid, cipher, pass, passlen, salt, saltlen, iter, p8inf) + private static final IDetectionRule PKCS8_ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS8_encrypt") + .withMethodParameter("*") // pbe_nid + .shouldBeDetectedAs(PBE_ALGORITHM) + .withMethodParameter("*") // cipher + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // salt + .withMethodParameter("*") // saltlen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new SaltSizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(0) + .withMethodParameter("*") // iter + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(0) + .withMethodParameter("*") // p8inf + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS8_encrypt_ex(pbe_nid, cipher, pass, passlen, salt, saltlen, iter, p8inf, libctx, propq) + private static final IDetectionRule PKCS8_ENCRYPT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("PKCS8_encrypt_ex") + .withMethodParameter("*") // pbe_nid + .shouldBeDetectedAs(PBE_ALGORITHM) + .withMethodParameter("*") // cipher + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // salt + .withMethodParameter("*") // saltlen + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new SaltSizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(0) + .withMethodParameter("*") // iter + .shouldBeDetectedAs(new IterationCountFactory<>()) + .asChildOfParameterWithId(0) + .withMethodParameter("*") // p8inf + .withMethodParameter("*") // libctx + .withMethodParameter("*") // propq + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // OBJ_nid2obj(nid): the object identifying the scheme given to EVP_PBE_CipherInit + private static final IDetectionRule OBJ_NID2OBJ = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OBJ_nid2obj") + .withMethodParameter("*") + .shouldBeDetectedAs(PBE_ALGORITHM) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_PBE_CipherInit(pbe_obj, pass, passlen, param, ctx, en_de): initializes the cipher context + // with the cipher of the scheme and the key derived from the password; en_de 1 encrypts, 0 + // decrypts + private static final IDetectionRule EVP_PBE_CIPHER_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PBE_CipherInit") + .withMethodParameter("*") // pbe_obj + .addDependingDetectionRules(List.of(OBJ_NID2OBJ)) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // param + .withMethodParameter("*") // ctx + .withMethodParameter("*") // en_de + .shouldBeDetectedAs(new OpenSSLCipherOperationFactory()) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_PBE_CipherInit_ex(pbe_obj, pass, passlen, param, ctx, en_de, libctx, propq) + private static final IDetectionRule EVP_PBE_CIPHER_INIT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PBE_CipherInit_ex") + .withMethodParameter("*") // pbe_obj + .addDependingDetectionRules(List.of(OBJ_NID2OBJ)) + .withMethodParameter("*") // pass + .withMethodParameter("*") // passlen + .withMethodParameter("*") // param + .withMethodParameter("*") // ctx + .withMethodParameter("*") // en_de + .shouldBeDetectedAs(new OpenSSLCipherOperationFactory()) + .withMethodParameter("*") // libctx + .withMethodParameter("*") // propq + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + PKCS8_ENCRYPT, PKCS8_ENCRYPT_EX, EVP_PBE_CIPHER_INIT, EVP_PBE_CIPHER_INIT_EX); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreement.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreement.java new file mode 100644 index 000000000..becf5db1e --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreement.java @@ -0,0 +1,215 @@ +/* + * 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.openssl.keyagreement; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL key agreement and key encapsulation. + * + *

    + *
  • {@code EVP_KEYEXCH_fetch} and {@code EVP_KEM_fetch} select a key exchange (DH, ECDH, + * X25519, X448) or a key encapsulation mechanism (RSA, EC, X25519, X448, ML-KEM and the + * hybrid ML-KEM groups) by name. + *
  • {@code EVP_PKEY_CTX_set_ecdh_kdf_type} and {@code EVP_PKEY_CTX_set_dh_kdf_type} select the + * KDF applied to the shared secret of an ECDH (ANSI X9.63) or DH (ANSI X9.42) derivation. Its + * digest is set on the same context: + *
    {@code
    + * EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, EVP_PKEY_ECDH_KDF_X9_63);
    + * EVP_PKEY_CTX_set_ecdh_kdf_md(ctx, EVP_sha256());
    + * }
    + *
  • The HPKE (RFC 9180) functions take the suite, the KEM, KDF and AEAD, as a string ({@code + * OSSL_HPKE_str2suite}) or as an {@code OSSL_HPKE_SUITE} ({@code OSSL_HPKE_CTX_new}, {@code + * OSSL_HPKE_keygen}). + *
+ * + * The key type of an {@code EVP_PKEY_derive} or {@code EVP_PKEY_encapsulate} context comes from a + * key created elsewhere, so the derivation and encapsulation calls themselves are not reported. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpKeyAgreement extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Key exchange and KEM fetch + + private static final IDetectionRule EVP_KEYEXCH_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KEYEXCH_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_KEM_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KEM_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext(Map.of("kind", "KEM"))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // KDF applied to the shared secret of an ECDH or DH derivation, with the digest set on the + // same context + + private static final IDetectionRule EVP_PKEY_CTX_SET_ECDH_KDF_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_ecdh_kdf_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_ECDH_KDF_TYPE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_ecdh_kdf_type") + .withMethodParameter("*") + .addDependingDetectionRules(List.of(EVP_PKEY_CTX_SET_ECDH_KDF_MD)) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.ECDH_KDF_TYPE_BY_CODE, + OpenSSLNidLookupFactory.ECDH_KDF_TYPE_BY_NAME)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DH_KDF_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dh_kdf_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DH_KDF_TYPE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dh_kdf_type") + .withMethodParameter("*") + .addDependingDetectionRules(List.of(EVP_PKEY_CTX_SET_DH_KDF_MD)) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.DH_KDF_TYPE_BY_CODE, + OpenSSLNidLookupFactory.DH_KDF_TYPE_BY_NAME)) + .buildForContext(new KeyDerivationFunctionContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // HPKE (Hybrid Public Key Encryption, RFC 9180) + + private static final IDetectionRule OSSL_HPKE_STR2SUITE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_HPKE_str2suite") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLHpkeSuiteFactory()) + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext(Map.of("kind", "HPKE"))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // OSSL_HPKE_CTX_new(mode, suite, role, libctx, propq) + private static final IDetectionRule OSSL_HPKE_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_HPKE_CTX_new") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLHpkeSuiteFactory()) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext(Map.of("kind", "HPKE"))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // OSSL_HPKE_keygen(suite, pub, publen, priv, ikm, ikmlen, libctx, propq) + private static final IDetectionRule OSSL_HPKE_KEYGEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_HPKE_keygen") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLHpkeSuiteFactory()) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext(Map.of("kind", "HPKE"))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // Key exchange and KEM fetch + EVP_KEYEXCH_FETCH, + EVP_KEM_FETCH, + // KDF of an ECDH or DH derivation + EVP_PKEY_CTX_SET_ECDH_KDF_TYPE, + EVP_PKEY_CTX_SET_DH_KDF_TYPE, + // HPKE + OSSL_HPKE_STR2SUITE, + OSSL_HPKE_CTX_NEW, + OSSL_HPKE_KEYGEN); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLHpkeSuiteFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLHpkeSuiteFactory.java new file mode 100644 index 000000000..7803c8882 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLHpkeSuiteFactory.java @@ -0,0 +1,146 @@ +/* + * 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.openssl.keyagreement; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Resolves an HPKE suite (RFC 9180) to the identifier {@code ",,"}, e.g. {@code + * "X25519,HKDF-SHA256,AES-128-GCM"}, that {@code CxxKeyAgreementContextTranslator} turns into an + * HPKE node. The suite can be given as: + * + *
    + *
  • the string passed to {@code OSSL_HPKE_str2suite}, e.g. {@code "x25519,hkdf-sha256, + * aes-128-gcm"}, where each part is a name or its numeric identifier ({@code "0x20"}, {@code + * "32"}); + *
  • an {@code OSSL_HPKE_SUITE} brace initializer, e.g. {@code {OSSL_HPKE_KEM_ID_P256, + * OSSL_HPKE_KDF_ID_HKDF_SHA256, OSSL_HPKE_AEAD_ID_AES_GCM_256}}; + *
  • the {@code OSSL_HPKE_SUITE_DEFAULT} macro. + *
+ * + * A suite with an unknown part resolves to nothing. + */ +public final class OpenSSLHpkeSuiteFactory implements IValueFactory { + + /** The suite {@code OSSL_HPKE_SUITE_DEFAULT} stands for. */ + private static final String DEFAULT_SUITE = "X25519,HKDF-SHA256,AES-128-GCM"; + + private static final Map KEM_BY_ID = + Map.of(0x10, "P-256", 0x11, "P-384", 0x12, "P-521", 0x20, "X25519", 0x21, "X448"); + + private static final Map KEM_BY_NAME = + Map.ofEntries( + Map.entry("P-256", "P-256"), + Map.entry("P-384", "P-384"), + Map.entry("P-521", "P-521"), + Map.entry("X25519", "X25519"), + Map.entry("X448", "X448"), + Map.entry("OSSL_HPKE_KEM_ID_P256", "P-256"), + Map.entry("OSSL_HPKE_KEM_ID_P384", "P-384"), + Map.entry("OSSL_HPKE_KEM_ID_P521", "P-521"), + Map.entry("OSSL_HPKE_KEM_ID_X25519", "X25519"), + Map.entry("OSSL_HPKE_KEM_ID_X448", "X448")); + + private static final Map KDF_BY_ID = + Map.of(1, "HKDF-SHA256", 2, "HKDF-SHA384", 3, "HKDF-SHA512"); + + private static final Map KDF_BY_NAME = + Map.ofEntries( + Map.entry("HKDF-SHA256", "HKDF-SHA256"), + Map.entry("HKDF-SHA384", "HKDF-SHA384"), + Map.entry("HKDF-SHA512", "HKDF-SHA512"), + Map.entry("OSSL_HPKE_KDF_ID_HKDF_SHA256", "HKDF-SHA256"), + Map.entry("OSSL_HPKE_KDF_ID_HKDF_SHA384", "HKDF-SHA384"), + Map.entry("OSSL_HPKE_KDF_ID_HKDF_SHA512", "HKDF-SHA512")); + + /** The export-only AEAD is 0xff in a suite string and 0xFFFF in an {@code OSSL_HPKE_SUITE}. */ + private static final Map AEAD_BY_ID = + Map.of( + 1, "AES-128-GCM", + 2, "AES-256-GCM", + 3, "CHACHA20-POLY1305", + 0xFF, "EXPORTER", + 0xFFFF, "EXPORTER"); + + private static final Map AEAD_BY_NAME = + Map.ofEntries( + Map.entry("AES-128-GCM", "AES-128-GCM"), + Map.entry("AES-256-GCM", "AES-256-GCM"), + Map.entry("CHACHA20-POLY1305", "CHACHA20-POLY1305"), + Map.entry("EXPORTER", "EXPORTER"), + Map.entry("OSSL_HPKE_AEAD_ID_AES_GCM_128", "AES-128-GCM"), + Map.entry("OSSL_HPKE_AEAD_ID_AES_GCM_256", "AES-256-GCM"), + Map.entry("OSSL_HPKE_AEAD_ID_CHACHA_POLY1305", "CHACHA20-POLY1305"), + Map.entry("OSSL_HPKE_AEAD_ID_EXPORTONLY", "EXPORTER")); + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + final List parts; + if (resolvedValue.value() instanceof List elements) { + // a brace initializer, resolved to the source text of each of its elements + parts = elements.stream().map(String::valueOf).toList(); + } else if (resolvedValue.value() instanceof String str) { + if ("OSSL_HPKE_SUITE_DEFAULT".equals(str.trim())) { + return Optional.of(new ValueAction<>(DEFAULT_SUITE, resolvedValue.tree())); + } + parts = List.of(str.split(",", -1)); + } else { + return Optional.empty(); + } + if (parts.size() != 3) { + return Optional.empty(); + } + final String kem = lookup(parts.get(0), KEM_BY_NAME, KEM_BY_ID); + final String kdf = lookup(parts.get(1), KDF_BY_NAME, KDF_BY_ID); + final String aead = lookup(parts.get(2), AEAD_BY_NAME, AEAD_BY_ID); + if (kem == null || kdf == null || aead == null) { + return Optional.empty(); + } + return Optional.of( + new ValueAction<>(String.join(",", kem, kdf, aead), resolvedValue.tree())); + } + + @Nullable private static String lookup( + @Nonnull String part, + @Nonnull Map byName, + @Nonnull Map byId) { + final String trimmed = part.trim(); + final String name = byName.get(trimmed.toUpperCase(Locale.ROOT)); + if (name != null) { + return name; + } + try { + return byId.get(Integer.decode(trimmed)); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGen.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGen.java new file mode 100644 index 000000000..b492377bb --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGen.java @@ -0,0 +1,257 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.factory.KeyActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP key and parameter generation. + * + *

The key type is selected when the key generation context is created, by {@code + * EVP_PKEY_CTX_new_id} or {@code EVP_PKEY_CTX_new_from_name}, and the calls made on that context + * configure and run the generation: + * + *

{@code
+ * EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ * EVP_PKEY_keygen_init(ctx);
+ * EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 3072);
+ * EVP_PKEY_keygen(ctx, &pkey);
+ * }
+ * + *

{@code EVP_PKEY_Q_keygen} does all of this in one call. A generated key is reported as a + * private key holding its algorithm. Domain parameters generated on such a context ({@code + * EVP_PKEY_paramgen}) are reported as the algorithm with the settings made on the context. The + * operations performed with a generated key are reported on it (see {@link OpenSSLEvpKeyUsage}). + * The settings specific to RSA, DSA and Diffie-Hellman are in their own {@code + * OpenSSLEvpKeyGen} classes. + */ +public final class OpenSSLEvpKeyGen extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Calls made on a key generation context + + private static final IDetectionRule EVP_PKEY_CTX_SET_GROUP_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_group_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLKeyParameterFactory()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_EC_PARAMGEN_CURVE_NID = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_ec_paramgen_curve_nid") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.CURVE_BY_CODE, + OpenSSLNidLookupFactory.CURVE_BY_NAME, + code -> code, + Curve::new)) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_PKEY_keygen(ctx, &pkey) / EVP_PKEY_generate(ctx, &pkey): the key is returned in pkey, + // whose uses are followed + private static final IDetectionRule EVP_PKEY_KEYGEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_keygen", "EVP_PKEY_generate") + .shouldBeDetectedAs( + new KeyActionFactory<>(KeyAction.Action.PRIVATE_KEY_GENERATION)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + private static final List> KEY_GENERATION_CONTEXT_RULES = + Stream.of( + RuleSets.rulesOf(OpenSSLEvpKeyGenRsa.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKeyGenDsa.class).stream(), + RuleSets.rulesOf(OpenSSLEvpKeyGenDh.class).stream(), + Stream.of( + EVP_PKEY_CTX_SET_GROUP_NAME, + EVP_PKEY_CTX_SET_EC_PARAMGEN_CURVE_NID, + EVP_PKEY_KEYGEN)) + .flatMap(i -> i) + .toList(); + + // Key type selection + + private static final IDetectionRule EVP_PKEY_CTX_NEW_ID = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_new_id") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.PKEY_TYPE_BY_CODE, + OpenSSLNidLookupFactory.PKEY_TYPE_BY_NAME)) + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_GENERATION_CONTEXT_RULES); + + private static final IDetectionRule EVP_PKEY_CTX_NEW_FROM_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_new_from_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.KEY_TYPE_NAMES, true)) + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_GENERATION_CONTEXT_RULES); + + // EVP_PKEY_Q_keygen(libctx, propq, type, ...): the size of an RSA key or the curve of an EC + // key follows the type + + private static final IDetectionRule EVP_PKEY_Q_KEYGEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_Q_keygen") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.KEY_TYPE_NAMES, true)) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + private static final IDetectionRule EVP_PKEY_Q_KEYGEN_WITH_PARAMETER = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_Q_keygen") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.KEY_TYPE_NAMES, true)) + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLKeyParameterFactory()) + .asChildOfParameterWithId(2) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + // EVP_RSA_gen(bits) and EVP_EC_gen(curve) (rsa.h, ec.h): EVP_PKEY_Q_keygen for an RSA key of + // the given size and an EC key on the given curve + + private static final IDetectionRule EVP_RSA_GEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_RSA_gen") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA")) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .asChildOfParameterWithId(-1) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + private static final IDetectionRule EVP_EC_GEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_EC_gen") + .shouldBeDetectedAs(new ValueActionFactory<>("EC")) + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLKeyParameterFactory()) + .asChildOfParameterWithId(-1) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + private static final IDetectionRule EVP_KEYMGMT_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_KEYMGMT_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.KEY_TYPE_NAMES, true)) + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + Stream.of( + EVP_PKEY_CTX_NEW_ID, + EVP_PKEY_CTX_NEW_FROM_NAME, + EVP_PKEY_Q_KEYGEN, + EVP_PKEY_Q_KEYGEN_WITH_PARAMETER, + EVP_RSA_GEN, + EVP_EC_GEN, + EVP_KEYMGMT_FETCH), + // keys created from raw bytes + RuleSets.rulesOf(OpenSSLEvpRawKey.class).stream()) + .flatMap(i -> i) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenDh.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenDh.java new file mode 100644 index 000000000..0ad43edf4 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenDh.java @@ -0,0 +1,118 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.factory.IValueFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Detection rules for the Diffie-Hellman settings of an OpenSSL key generation context: the size of + * the prime, given directly or through a named group. They apply to the calls made on a context + * created by one of the rules of {@link OpenSSLEvpKeyGen}. + */ +public final class OpenSSLEvpKeyGenDh extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + /** RFC 5114 group number (1, 2 or 3) → size of its prime. */ + private static final Map RFC5114_PRIME_BITS = + Map.of(1, 1024, 2, 2048, 3, 2048); + + private static final IValueFactory RFC5114_GROUP_SIZE = + resolvedValue -> + resolvedValue.value() instanceof Number group + && RFC5114_PRIME_BITS.containsKey(group.intValue()) + ? Optional.of( + new KeySize<>( + RFC5114_PRIME_BITS.get(group.intValue()), + Size.UnitType.BIT, + resolvedValue.tree())) + : Optional.empty(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DH_PARAMGEN_PRIME_LEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dh_paramgen_prime_len") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DH_NID = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dh_nid") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.DH_GROUP_BY_CODE, + OpenSSLNidLookupFactory.DH_GROUP_BY_NAME, + code -> code, + // "DH-2048" → the size of the group's prime + (label, tree) -> + new KeySize<>( + Integer.parseInt(label.substring(3)), + Size.UnitType.BIT, + tree))) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DH_RFC5114 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dh_rfc5114", "EVP_PKEY_CTX_set_dhx_rfc5114") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(RFC5114_GROUP_SIZE) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_PKEY_CTX_SET_DH_PARAMGEN_PRIME_LEN, + EVP_PKEY_CTX_SET_DH_NID, + EVP_PKEY_CTX_SET_DH_RFC5114); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenDsa.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenDsa.java new file mode 100644 index 000000000..5a393a569 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenDsa.java @@ -0,0 +1,95 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for the DSA settings of an OpenSSL key generation context: the size of the + * parameters and the digest used to generate them. They apply to the calls made on a context + * created by one of the rules of {@link OpenSSLEvpKeyGen}. + */ +public final class OpenSSLEvpKeyGenDsa extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_DSA_PARAMGEN_BITS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dsa_paramgen_bits") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DSA_PARAMGEN_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dsa_paramgen_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_DSA_PARAMGEN_MD_PROPS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_dsa_paramgen_md_props") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_DSA_PARAMGEN_BITS, + EVP_PKEY_CTX_SET_DSA_PARAMGEN_MD, + EVP_PKEY_CTX_SET_DSA_PARAMGEN_MD_PROPS); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenRsa.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenRsa.java new file mode 100644 index 000000000..323ac2a9c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenRsa.java @@ -0,0 +1,186 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.DerivedDetectionRules; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.signature.OpenSSLSaltLengthFactory; +import com.ibm.plugin.translation.translator.contexts.CxxDigestContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxSignatureContextTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for the RSA settings of an OpenSSL key generation context: the size of the key + * and, for an RSA-PSS key, the digests and salt length it is restricted to. They apply to the calls + * made on a context created by one of the rules of {@link OpenSSLEvpKeyGen}. + */ +public final class OpenSSLEvpKeyGenRsa extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule EVP_RSA_KEYGEN_BITS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_keygen_bits") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // RSA-PSS keys: the digest, MGF1 digest and salt length the key is restricted to; the MGF1 + // digest is reported as MGF1 with that digest + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_pss_keygen_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MGF1_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Mgf1.class)) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MD_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_pss_keygen_md_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MGF1_MD_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .buildForContext( + new DigestContext( + Map.of( + CxxDigestContextTranslator.KIND, + CxxDigestContextTranslator.MGF1_KIND))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_SALTLEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLSaltLengthFactory()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + /** + * The RSA-PSS key generation settings as detections of their own, for a key generation context + * created elsewhere: each setting is only accepted on an RSA-PSS key generation context, so it + * reports RSA-PSS with the digest, MGF1 digest or salt length it restricts the key to. A + * setting reported with the key generated in the analyzed code is not reported again (see + * {@link DerivedDetectionRules}). + */ + public static final class KeyGenerationSettings extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return List.of( + rsaPss(EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MD, Map.of()), + rsaPss(EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MGF1_MD, Map.of()), + rsaPss( + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MD_NAME, + Map.of( + CxxSignatureContextTranslator.KIND, + CxxSignatureContextTranslator.DIGEST_NAME_KIND)), + rsaPss( + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MGF1_MD_NAME, + Map.of( + CxxSignatureContextTranslator.KIND, + CxxSignatureContextTranslator.MGF1_DIGEST_NAME_KIND)), + rsaPss(EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_SALTLEN, Map.of())); + } + } + + @Nonnull + private static IDetectionRule rsaPss( + @Nonnull IDetectionRule setting, @Nonnull Map properties) { + return DerivedDetectionRules.withAction( + setting, new ValueActionFactory<>("RSA-PSS"), new SignatureContext(properties)); + } + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_RSA_KEYGEN_BITS, + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MD, + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MGF1_MD, + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MD_NAME, + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_MGF1_MD_NAME, + EVP_PKEY_CTX_SET_RSA_PSS_KEYGEN_SALTLEN); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyUsage.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyUsage.java new file mode 100644 index 000000000..857d5d383 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyUsage.java @@ -0,0 +1,523 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.context.AlgorithmParameterContext; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.IActionFactory; +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.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.signature.OpenSSLEvpSignature; +import com.ibm.plugin.translation.translator.contexts.CxxAlgorithmParameterContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxSignatureContextTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for the operations performed with an OpenSSL key ({@code EVP_PKEY}). They are + * depending rules of the calls that generate the key (see {@link OpenSSLEvpKeyGen}), so that each + * operation is reported on the key it uses, as the key's algorithm is what selects the operation's + * algorithm: + * + *

{@code
+ * EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256");
+ * EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey); // ECDSA with SHA-256
+ *
+ * EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ * EVP_PKEY_derive_init(ctx);                                 // ECDH
+ * }
+ * + *

The operations are the signature and its verification, key agreement ({@code + * EVP_PKEY_derive}), public-key encryption and decryption, and key encapsulation, together with the + * digest and the RSA padding they use; the signature of certificates, certificate requests and CRLs + * ({@code X509_sign}, ...) and their verification, the CMS, PKCS#7 and OCSP signatures ({@code + * CMS_sign}, {@code PKCS7_sign}, {@code OCSP_basic_sign}, ...), and the encryption and decryption + * of the session key of an envelope ({@code EVP_SealInit} / {@code EVP_OpenInit}) and of the + * content encryption key of a CMS or PKCS#7 message ({@code CMS_decrypt}, {@code PKCS7_decrypt}, + * ...). The {@code X509_sign_ctx} forms sign with a context initialized by {@code + * EVP_DigestSignInit}, which is where the key and the digest are reported. + */ +public final class OpenSSLEvpKeyUsage extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final SignatureAction.Action SIGN = SignatureAction.Action.SIGN; + private static final SignatureAction.Action VERIFY = SignatureAction.Action.VERIFY; + + /** The digest index of an operation given no digest. */ + private static final int NO_DIGEST = -1; + + // Settings of the operation initialized on a context created for the key: the digest of a + // signature, and the digests of RSA-OAEP and of its MGF1 mask generation function, each given + // as the argument of the setter, e.g. EVP_PKEY_CTX_set_signature_md(ctx, md) + + private static final IDetectionRule EVP_PKEY_CTX_SET_SIGNATURE_MD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_signature_md") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_OAEP_MD = + digestSetting( + "EVP_PKEY_CTX_set_rsa_oaep_md", + CxxAlgorithmParameterContextTranslator.OAEP_SETTING); + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_MGF1_MD = + digestSetting( + "EVP_PKEY_CTX_set_rsa_mgf1_md", + CxxAlgorithmParameterContextTranslator.MGF1_SETTING); + + /** The setting {@code setting} of a digest given as the argument of {@code function}. */ + @Nonnull + private static IDetectionRule digestSetting( + @Nonnull String function, @Nonnull String setting) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(function) + .shouldBeDetectedAs(new ValueActionFactory<>(setting)) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new AlgorithmParameterContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + private static final List> OPERATION_SETTINGS = + Stream.concat( + Stream.of( + EVP_PKEY_CTX_SET_SIGNATURE_MD, + EVP_PKEY_CTX_SET_RSA_OAEP_MD, + EVP_PKEY_CTX_SET_RSA_MGF1_MD, + OpenSSLEvpCipher.rsaOaepDigestNameRule()), + RuleSets.rulesOf(OpenSSLEvpSignature.SigningContext.class).stream()) + .toList(); + + // Operations on a context created for the key: the init function of each operation, one rule + // per number of arguments, e.g. EVP_PKEY_sign_init(ctx), EVP_PKEY_sign_init_ex(ctx, params) and + // EVP_PKEY_sign_init_ex2(ctx, algo, params) + + private static final List> EVP_PKEY_SIGN = + operationInit( + new SignatureActionFactory<>(SignatureAction.Action.SIGN), + SignatureContext::new, + new InitFunctions(1, "EVP_PKEY_sign_init"), + new InitFunctions(2, "EVP_PKEY_sign_init_ex"), + new InitFunctions(3, "EVP_PKEY_sign_init_ex2", "EVP_PKEY_sign_message_init")); + + private static final List> EVP_PKEY_VERIFY = + operationInit( + new SignatureActionFactory<>(SignatureAction.Action.VERIFY), + SignatureContext::new, + new InitFunctions(1, "EVP_PKEY_verify_init", "EVP_PKEY_verify_recover_init"), + new InitFunctions( + 2, "EVP_PKEY_verify_init_ex", "EVP_PKEY_verify_recover_init_ex"), + new InitFunctions( + 3, + "EVP_PKEY_verify_init_ex2", + "EVP_PKEY_verify_message_init", + "EVP_PKEY_verify_recover_init_ex2")); + + private static final List> EVP_PKEY_ENCRYPT = + operationInit( + new CipherActionFactory<>(CipherAction.Action.ENCRYPT), + CipherContext::new, + new InitFunctions(1, "EVP_PKEY_encrypt_init"), + new InitFunctions(2, "EVP_PKEY_encrypt_init_ex")); + + private static final List> EVP_PKEY_DECRYPT = + operationInit( + new CipherActionFactory<>(CipherAction.Action.DECRYPT), + CipherContext::new, + new InitFunctions(1, "EVP_PKEY_decrypt_init"), + new InitFunctions(2, "EVP_PKEY_decrypt_init_ex")); + + private static final List> EVP_PKEY_DERIVE = + operationInit( + new KeyActionFactory<>(KeyAction.Action.KDF), + KeyContext::new, + new InitFunctions(1, "EVP_PKEY_derive_init"), + new InitFunctions(2, "EVP_PKEY_derive_init_ex")); + + // EVP_PKEY_encapsulate_init(ctx, params), EVP_PKEY_auth_encapsulate_init(ctx, authpriv, params) + private static final List> EVP_PKEY_ENCAPSULATE = + operationInit( + new KeyActionFactory<>(KeyAction.Action.ENCAPSULATION), + KeyContext::new, + new InitFunctions(2, "EVP_PKEY_encapsulate_init"), + new InitFunctions(3, "EVP_PKEY_auth_encapsulate_init")); + + private static final List> EVP_PKEY_DECAPSULATE = + operationInit( + new KeyActionFactory<>(KeyAction.Action.DECAPSULATION), + KeyContext::new, + new InitFunctions(2, "EVP_PKEY_decapsulate_init"), + new InitFunctions(3, "EVP_PKEY_auth_decapsulate_init")); + + /** Init functions of an operation that take the same number of arguments. */ + private record InitFunctions(int parameterCount, @Nonnull List names) { + InitFunctions(int parameterCount, @Nonnull String... names) { + this(parameterCount, List.of(names)); + } + } + + @Nonnull + private static List> operationInit( + @Nonnull IActionFactory operation, + @Nonnull Supplier context, + @Nonnull InitFunctions... initFunctions) { + return Stream.of(initFunctions) + .map( + functions -> { + IDetectionRule.ParametersFactoryBuilder parameters = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(functions.names().toArray(new String[0])) + .shouldBeDetectedAs(operation) + .withMethodParameter("*"); + for (int i = 1; i < functions.parameterCount(); i++) { + parameters = parameters.withMethodParameter("*"); + } + return parameters + .buildForContext(context.get()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(OPERATION_SETTINGS); + }) + .toList(); + } + + /** The operations, and the RSA padding, set on a context created for the key. */ + private static final List> KEY_CONTEXT_OPERATIONS = + Stream.of( + EVP_PKEY_SIGN, + EVP_PKEY_VERIFY, + EVP_PKEY_ENCRYPT, + EVP_PKEY_DECRYPT, + EVP_PKEY_DERIVE, + EVP_PKEY_ENCAPSULATE, + EVP_PKEY_DECAPSULATE, + List.of(OpenSSLEvpCipher.rsaPaddingRule())) + .flatMap(List::stream) + .toList(); + + // Uses of the key: a context created for it, or a digest sign or verify operation with it + + // EVP_PKEY_CTX_new(pkey, e) + private static final IDetectionRule EVP_PKEY_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_new") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_CONTEXT_OPERATIONS); + + // EVP_PKEY_CTX_new_from_pkey(libctx, pkey, propq) + private static final IDetectionRule EVP_PKEY_CTX_NEW_FROM_PKEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_new_from_pkey") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_CONTEXT_OPERATIONS); + + /** + * The RSA padding, the RSA-PSS settings and the MGF1 digest set on the context a digest sign or + * verify operation returns in pctx. + */ + private static final List> SIGNING_CONTEXT_SETTINGS = + Stream.concat( + Stream.of( + OpenSSLEvpCipher.rsaPaddingRule(), + EVP_PKEY_CTX_SET_RSA_MGF1_MD), + RuleSets.rulesOf(OpenSSLEvpSignature.SigningContext.class).stream()) + .toList(); + + // EVP_DigestSignInit(mdctx, pctx, md, e, pkey) / EVP_DigestVerifyInit(...) + private static final IDetectionRule EVP_DIGEST_SIGN_INIT = + digestSignInit("EVP_DigestSignInit", SignatureAction.Action.SIGN); + + private static final IDetectionRule EVP_DIGEST_VERIFY_INIT = + digestSignInit("EVP_DigestVerifyInit", SignatureAction.Action.VERIFY); + + // EVP_DigestSignInit_ex(mdctx, pctx, mdname, libctx, props, pkey, params) / + // EVP_DigestVerifyInit_ex(...): the digest is given by name + private static final IDetectionRule EVP_DIGEST_SIGN_INIT_EX = + digestSignInitEx("EVP_DigestSignInit_ex", SignatureAction.Action.SIGN); + + private static final IDetectionRule EVP_DIGEST_VERIFY_INIT_EX = + digestSignInitEx("EVP_DigestVerifyInit_ex", SignatureAction.Action.VERIFY); + + @Nonnull + private static IDetectionRule digestSignInit( + @Nonnull String function, @Nonnull SignatureAction.Action action) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(function) + .shouldBeDetectedAs(new SignatureActionFactory<>(action)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(SIGNING_CONTEXT_SETTINGS); + } + + @Nonnull + private static IDetectionRule digestSignInitEx( + @Nonnull String function, @Nonnull SignatureAction.Action action) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(function) + .shouldBeDetectedAs(new SignatureActionFactory<>(action)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext( + new SignatureContext( + Map.of( + CxxSignatureContextTranslator.KIND, + CxxSignatureContextTranslator.DIGEST_NAME_KIND))) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(SIGNING_CONTEXT_SETTINGS); + } + + // Operations taking the key as an argument, with the digest they use where they are given one + + // X509_sign(x, pkey, md), X509_REQ_sign(req, pkey, md), X509_CRL_sign(crl, pkey, md) + private static final IDetectionRule X509_SIGN = + keyOperation(SIGN, 3, 2, "X509_sign", "X509_REQ_sign", "X509_CRL_sign"); + + // X509_verify(x, pkey), X509_REQ_verify(req, pkey), X509_CRL_verify(crl, pkey) + private static final IDetectionRule X509_VERIFY = + keyOperation(VERIFY, 2, NO_DIGEST, "X509_verify", "X509_REQ_verify", "X509_CRL_verify"); + + // X509_REQ_verify_ex(req, pkey, libctx, propq) + private static final IDetectionRule X509_REQ_VERIFY_EX = + keyOperation(VERIFY, 4, NO_DIGEST, "X509_REQ_verify_ex"); + + // CMS_sign(signcert, pkey, certs, data, flags), PKCS7_sign(...): the default digest + private static final IDetectionRule CMS_SIGN = + keyOperation(SIGN, 5, NO_DIGEST, "CMS_sign", "PKCS7_sign"); + + // CMS_sign_ex(signcert, pkey, certs, data, flags, libctx, propq), PKCS7_sign_ex(...) + private static final IDetectionRule CMS_SIGN_EX = + keyOperation(SIGN, 7, NO_DIGEST, "CMS_sign_ex", "PKCS7_sign_ex"); + + // CMS_add1_signer(cms, signer, pkey, md, flags), PKCS7_sign_add_signer(p7, signcert, pkey, + // md, flags) + private static final IDetectionRule CMS_ADD_SIGNER = + keyOperation(SIGN, 5, 3, "CMS_add1_signer", "PKCS7_sign_add_signer"); + + // OCSP_basic_sign(brsp, signer, key, dgst, certs, flags) + private static final IDetectionRule OCSP_BASIC_SIGN = + keyOperation(SIGN, 6, 3, "OCSP_basic_sign"); + + // EVP_SealInit(ctx, type, ek, ekl, iv, pubk, npubk): the session key is encrypted with the + // public keys; EVP_OpenInit(ctx, type, ek, ekl, iv, priv) decrypts it with the private key + private static final IDetectionRule EVP_SEAL_INIT = + keyOperation( + new CipherActionFactory<>(CipherAction.Action.ENCRYPT), + new CipherContext(), + 7, + NO_DIGEST, + "EVP_SealInit"); + + private static final IDetectionRule EVP_OPEN_INIT = + keyOperation( + new CipherActionFactory<>(CipherAction.Action.DECRYPT), + new CipherContext(), + 6, + NO_DIGEST, + "EVP_OpenInit"); + + // CMS_decrypt(cms, pkey, cert, dcont, out, flags), CMS_decrypt_set1_pkey(cms, pk, cert), + // CMS_decrypt_set1_pkey_and_peer(cms, pk, cert, peer), PKCS7_decrypt(p7, pkey, cert, data, + // flags): the content encryption key of the message is decrypted with the private key, or + // agreed with it for a key agreement recipient + private static final IDetectionRule CMS_DECRYPT = keyDecryption(6, "CMS_decrypt"); + + private static final IDetectionRule CMS_DECRYPT_SET1_PKEY = + keyDecryption(3, "CMS_decrypt_set1_pkey"); + + private static final IDetectionRule CMS_DECRYPT_SET1_PKEY_AND_PEER = + keyDecryption(4, "CMS_decrypt_set1_pkey_and_peer"); + + private static final IDetectionRule PKCS7_DECRYPT = keyDecryption(5, "PKCS7_decrypt"); + + @Nonnull + private static IDetectionRule keyDecryption( + int parameterCount, @Nonnull String function) { + return keyOperation( + new CipherActionFactory<>(CipherAction.Action.DECRYPT), + new CipherContext(), + parameterCount, + NO_DIGEST, + function); + } + + @Nonnull + private static IDetectionRule keyOperation( + @Nonnull SignatureAction.Action action, + int parameterCount, + int digestIndex, + @Nonnull String... functions) { + return keyOperation( + new SignatureActionFactory<>(action), + new SignatureContext(), + parameterCount, + digestIndex, + functions); + } + + /** + * An operation taking the key as an argument, detected as {@code operation}; the digest given + * at {@code digestIndex} ({@link #NO_DIGEST} for none) is traced back to where it is selected. + */ + @Nonnull + private static IDetectionRule keyOperation( + @Nonnull IActionFactory operation, + @Nonnull DetectionContext context, + int parameterCount, + int digestIndex, + @Nonnull String... functions) { + IDetectionRule.ParametersFactoryBuilder parameters = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(functions) + .shouldBeDetectedAs(operation) + .withMethodParameter("*"); + IDetectionRule.ParametersFinalDetectionRuleBuilder withDigest = null; + for (int i = 1; i < parameterCount; i++) { + parameters = + withDigest == null + ? parameters.withMethodParameter("*") + : withDigest.withMethodParameter("*"); + withDigest = + i == digestIndex + ? parameters.addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + : null; + } + return (withDigest == null + ? parameters.buildForContext(context) + : withDigest.buildForContext(context)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + /** + * The signatures and their verifications made with a key given as an argument: detection rules + * on their own as well, for a key that is not generated in the analyzed code, e.g. loaded from + * a file. A signature reported with the generation of its key is not reported again. + */ + public static final class Signatures extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_DIGEST_SIGN_INIT, + EVP_DIGEST_VERIFY_INIT, + EVP_DIGEST_SIGN_INIT_EX, + EVP_DIGEST_VERIFY_INIT_EX, + X509_SIGN, + X509_VERIFY, + X509_REQ_VERIFY_EX, + CMS_SIGN, + CMS_SIGN_EX, + CMS_ADD_SIGNER, + OCSP_BASIC_SIGN); + } + } + + /** The uses of a key, followed from the variable holding it. */ + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_PKEY_CTX_NEW, + EVP_PKEY_CTX_NEW_FROM_PKEY, + EVP_DIGEST_SIGN_INIT, + EVP_DIGEST_VERIFY_INIT, + EVP_DIGEST_SIGN_INIT_EX, + EVP_DIGEST_VERIFY_INIT_EX, + X509_SIGN, + X509_VERIFY, + X509_REQ_VERIFY_EX, + CMS_SIGN, + CMS_SIGN_EX, + CMS_ADD_SIGNER, + OCSP_BASIC_SIGN, + EVP_SEAL_INIT, + EVP_OPEN_INIT, + CMS_DECRYPT, + CMS_DECRYPT_SET1_PKEY, + CMS_DECRYPT_SET1_PKEY_AND_PEER, + PKCS7_DECRYPT); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpRawKey.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpRawKey.java new file mode 100644 index 000000000..9ec5badc1 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpRawKey.java @@ -0,0 +1,167 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL keys created from raw bytes: MAC keys (HMAC, CMAC, Poly1305, SipHash) + * and X25519, X448, Ed25519 and Ed448 private and public keys. The key type and the key length are + * reported, and the operations performed with the key are followed as for a generated key (see + * {@link OpenSSLEvpKeyUsage}), e.g. the HMAC computed with {@code EVP_DigestSign}. + */ +public final class OpenSSLEvpRawKey extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + /** The context of a key created from raw bytes, a secret key or a private key by its type. */ + private static final Map RAW_KEY = Map.of("kind", "RAW_KEY"); + + private static final OpenSSLNidLookupFactory RAW_KEY_TYPE = + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.RAW_KEY_TYPE_BY_CODE, + OpenSSLNidLookupFactory.RAW_KEY_TYPE_BY_NAME); + + // EVP_PKEY_new_mac_key(type, e, key, keylen) / EVP_PKEY_new_raw_private_key(type, e, priv, len) + private static final IDetectionRule EVP_PKEY_NEW_RAW_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_new_mac_key", "EVP_PKEY_new_raw_private_key") + .withMethodParameter("*") + .shouldBeDetectedAs(RAW_KEY_TYPE) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(0) + .buildForContext(new KeyContext(RAW_KEY)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + // EVP_PKEY_new_raw_private_key_ex(libctx, keytype, propq, priv, len) + private static final IDetectionRule EVP_PKEY_NEW_RAW_PRIVATE_KEY_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_new_raw_private_key_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.RAW_KEY_TYPE_NAMES, true)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(1) + .buildForContext(new KeyContext(RAW_KEY)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + // EVP_PKEY_new_raw_public_key(type, e, pub, len) + private static final IDetectionRule EVP_PKEY_NEW_RAW_PUBLIC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_new_raw_public_key") + .withMethodParameter("*") + .shouldBeDetectedAs(RAW_KEY_TYPE) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(0) + .buildForContext(new PublicKeyContext(RAW_KEY)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + // EVP_PKEY_new_raw_public_key_ex(libctx, keytype, propq, pub, len) + private static final IDetectionRule EVP_PKEY_NEW_RAW_PUBLIC_KEY_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_new_raw_public_key_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.RAW_KEY_TYPE_NAMES, true)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(1) + .buildForContext(new PublicKeyContext(RAW_KEY)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + // EVP_PKEY_new_CMAC_key(e, priv, len, cipher) + private static final IDetectionRule EVP_PKEY_NEW_CMAC_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_new_CMAC_key") + .shouldBeDetectedAs(new ValueActionFactory<>("CMAC")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BYTE))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpCipher.CipherSelection.class)) + .buildForContext(new KeyContext(RAW_KEY)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpKeyUsage.class)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + EVP_PKEY_NEW_RAW_KEY, + EVP_PKEY_NEW_RAW_PRIVATE_KEY_EX, + EVP_PKEY_NEW_RAW_PUBLIC_KEY, + EVP_PKEY_NEW_RAW_PUBLIC_KEY_EX, + EVP_PKEY_NEW_CMAC_KEY); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyParameterFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyParameterFactory.java new file mode 100644 index 000000000..129dd8605 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyParameterFactory.java @@ -0,0 +1,73 @@ +/* + * 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.openssl.keygen; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Resolves a key generation parameter: a positive key size in bits (e.g. the bits of {@code + * EVP_PKEY_Q_keygen(NULL, NULL, "RSA", 2048)}) or a group name (e.g. {@code + * EVP_PKEY_CTX_set_group_name(ctx, "P-256")}). A finite-field group name ({@code "ffdhe2048"}) + * resolves to the size of its prime; any other group name resolves to a {@link Curve} of that name + * (e.g. {@code "EC-P-256"}), which the key mapper recognizes by any of its OpenSSL names. + */ +public final class OpenSSLKeyParameterFactory implements IValueFactory { + + private static final String FFDHE = "FFDHE"; + + /** The prefix of a curve, by which the key mapper tells it from other algorithm names. */ + private static final String EC_CURVE_PREFIX = "EC-"; + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + final Object value = resolvedValue.value(); + if (value instanceof Number) { + // only a positive integer is a key size + return value instanceof Integer bits && bits > 0 + ? Optional.of(new KeySize<>(bits, Size.UnitType.BIT, resolvedValue.tree())) + : Optional.empty(); + } + if (!(value instanceof String name)) { + return Optional.empty(); + } + final String group = name.toUpperCase().trim(); + if (group.startsWith(FFDHE)) { + try { + return Optional.of( + new KeySize<>( + Integer.parseInt(group.substring(FFDHE.length())), + Size.UnitType.BIT, + resolvedValue.tree())); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + return Optional.of(new Curve<>(EC_CURVE_PREFIX + name.trim(), resolvedValue.tree())); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipher.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipher.java new file mode 100644 index 000000000..c556557f5 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipher.java @@ -0,0 +1,59 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy cipher APIs. + * + *

These rules detect direct cipher operations using the legacy (pre-EVP) APIs. These APIs are + * deprecated but still widely used in existing codebases. + * + *

Per-family cipher rules live in their own {@code OpenSSLLegacyCipher} classes (AES, + * DES/3DES, Blowfish, RC4, RC2, CAST, IDEA, Camellia, RC5, SEED); this class aggregates all of them + * in {@link #buildRules()}. + */ +public final class OpenSSLLegacyCipher extends DetectionRuleSet { + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLLegacyCipherAes.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherDes.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherBlowfish.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherRc4.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherRc2.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherCast.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherIdea.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherCamellia.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherRc5.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyCipherSeed.class).stream()) + .flatMap(i -> i) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherAes.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherAes.java new file mode 100644 index 000000000..52a99c289 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherAes.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.rules.detection.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) AES cipher APIs. */ +public final class OpenSSLLegacyCipherAes extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("AES_set_encrypt_key", "AES", 3, 1, Size.UnitType.BIT), + new LegacyEntry("AES_set_decrypt_key", "AES", 3, 1, Size.UnitType.BIT), + new LegacyEntry("AES_ecb_encrypt", "AES-ECB", 4).keyAt(2), + new LegacyEntry("AES_cbc_encrypt", "AES-CBC", 6).keyAt(3), + new LegacyEntry("AES_cfb128_encrypt", "AES-CFB128", 7).keyAt(3), + new LegacyEntry("AES_ofb128_encrypt", "AES-OFB", 6).keyAt(3), + new LegacyEntry("AES_ige_encrypt", "AES-IGE", 6).keyAt(3), + new LegacyEntry("AES_cfb1_encrypt", "AES-CFB1", 7).keyAt(3), + new LegacyEntry("AES_cfb8_encrypt", "AES-CFB8", 7).keyAt(3), + new LegacyEntry("AES_bi_ige_encrypt", "AES-BI-IGE", 7).keyAt(3), + // AES_wrap_key(key, iv, out, in, inlen) / AES_unwrap_key(...): RFC 3394 + new LegacyEntry("AES_wrap_key", "AES-WRAP", 5).keyAt(0), + new LegacyEntry("AES_unwrap_key", "AES-WRAP", 5).keyAt(0)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherBlowfish.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherBlowfish.java new file mode 100644 index 000000000..11aa3527d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherBlowfish.java @@ -0,0 +1,49 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) Blowfish cipher APIs. */ +public final class OpenSSLLegacyCipherBlowfish extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("BF_set_key", "BLOWFISH", 3, 1, Size.UnitType.BYTE), + new LegacyEntry("BF_ecb_encrypt", "BLOWFISH-ECB", 4).keyAt(2), + new LegacyEntry("BF_cbc_encrypt", "BLOWFISH-CBC", 6).keyAt(3), + new LegacyEntry("BF_cfb64_encrypt", "BLOWFISH-CFB", 7).keyAt(3), + new LegacyEntry("BF_ofb64_encrypt", "BLOWFISH-OFB", 6).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherCamellia.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherCamellia.java new file mode 100644 index 000000000..e237f3d61 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherCamellia.java @@ -0,0 +1,52 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) Camellia cipher APIs. */ +public final class OpenSSLLegacyCipherCamellia extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("Camellia_set_key", "CAMELLIA", 3, 1, Size.UnitType.BIT), + new LegacyEntry("Camellia_ecb_encrypt", "CAMELLIA-ECB", 4).keyAt(2), + new LegacyEntry("Camellia_cbc_encrypt", "CAMELLIA-CBC", 6).keyAt(3), + new LegacyEntry("Camellia_cfb128_encrypt", "CAMELLIA-CFB128", 7).keyAt(3), + new LegacyEntry("Camellia_cfb1_encrypt", "CAMELLIA-CFB1", 7).keyAt(3), + new LegacyEntry("Camellia_cfb8_encrypt", "CAMELLIA-CFB8", 7).keyAt(3), + new LegacyEntry("Camellia_ofb128_encrypt", "CAMELLIA-OFB", 6).keyAt(3), + new LegacyEntry("Camellia_ctr128_encrypt", "CAMELLIA-CTR", 7).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherCast.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherCast.java new file mode 100644 index 000000000..8a3f16390 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherCast.java @@ -0,0 +1,49 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) CAST5 cipher APIs. */ +public final class OpenSSLLegacyCipherCast extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("CAST_set_key", "CAST5", 3, 1, Size.UnitType.BYTE), + new LegacyEntry("CAST_ecb_encrypt", "CAST5-ECB", 4).keyAt(2), + new LegacyEntry("CAST_cbc_encrypt", "CAST5-CBC", 6).keyAt(3), + new LegacyEntry("CAST_cfb64_encrypt", "CAST5-CFB", 7).keyAt(3), + new LegacyEntry("CAST_ofb64_encrypt", "CAST5-OFB", 6).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherDes.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherDes.java new file mode 100644 index 000000000..ceceb3313 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherDes.java @@ -0,0 +1,63 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) DES and 3DES cipher APIs. */ +public final class OpenSSLLegacyCipherDes extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the (first) key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry( + List.of( + "DES_set_key", + "DES_set_key_checked", + "DES_set_key_unchecked"), + "DES", + 2) + .setsUpKey(), + new LegacyEntry("DES_ecb_encrypt", "DES-ECB", 4).keyAt(2), + new LegacyEntry(List.of("DES_ncbc_encrypt", "DES_cbc_encrypt"), "DES-CBC", 6) + .keyAt(3), + new LegacyEntry("DES_cfb64_encrypt", "DES-CFB", 7).keyAt(3), + new LegacyEntry("DES_cfb_encrypt", "DES-CFB", 7).keyAt(4), + new LegacyEntry("DES_ofb64_encrypt", "DES-OFB", 6).keyAt(3), + new LegacyEntry("DES_ede3_cbc_encrypt", "3DES-CBC", 8).keyAt(3), + new LegacyEntry("DES_ecb3_encrypt", "3DES-ECB", 6).keyAt(2), + new LegacyEntry("DES_ede3_cfb64_encrypt", "3DES-CFB", 9).keyAt(3), + new LegacyEntry("DES_ede3_cfb_encrypt", "3DES-CFB", 9).keyAt(4), + new LegacyEntry("DES_ede3_ofb64_encrypt", "3DES-OFB", 8).keyAt(3), + new LegacyEntry("DES_xcbc_encrypt", "DES-XCBC", 8).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherIdea.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherIdea.java new file mode 100644 index 000000000..9d610411e --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherIdea.java @@ -0,0 +1,49 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) IDEA cipher APIs. */ +public final class OpenSSLLegacyCipherIdea extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("IDEA_set_encrypt_key", "IDEA", 2).setsUpKey(), + new LegacyEntry("IDEA_set_decrypt_key", "IDEA", 2).setsUpKey(), + new LegacyEntry("IDEA_ecb_encrypt", "IDEA-ECB", 3).keyAt(2), + new LegacyEntry("IDEA_cbc_encrypt", "IDEA-CBC", 6).keyAt(3), + new LegacyEntry("IDEA_cfb64_encrypt", "IDEA-CFB", 7).keyAt(3), + new LegacyEntry("IDEA_ofb64_encrypt", "IDEA-OFB", 6).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc2.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc2.java new file mode 100644 index 000000000..733d4b8f8 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc2.java @@ -0,0 +1,49 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) RC2 cipher APIs. */ +public final class OpenSSLLegacyCipherRc2 extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("RC2_set_key", "RC2", 4, 1, Size.UnitType.BYTE), + new LegacyEntry("RC2_ecb_encrypt", "RC2-ECB", 4).keyAt(2), + new LegacyEntry("RC2_cbc_encrypt", "RC2-CBC", 6).keyAt(3), + new LegacyEntry("RC2_cfb64_encrypt", "RC2-CFB", 7).keyAt(3), + new LegacyEntry("RC2_ofb64_encrypt", "RC2-OFB", 6).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc4.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc4.java new file mode 100644 index 000000000..3c32e5c35 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc4.java @@ -0,0 +1,46 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) RC4 stream cipher APIs. */ +public final class OpenSSLLegacyCipherRc4 extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("RC4_set_key", "RC4", 3, 1, Size.UnitType.BYTE), + new LegacyEntry("RC4", "RC4", 4).keyAt(0)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc5.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc5.java new file mode 100644 index 000000000..658844861 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherRc5.java @@ -0,0 +1,49 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.model.Size; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) RC5 cipher APIs. */ +public final class OpenSSLLegacyCipherRc5 extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("RC5_32_set_key", "RC5", 4, 1, Size.UnitType.BYTE), + new LegacyEntry("RC5_32_ecb_encrypt", "RC5-ECB", 4).keyAt(2), + new LegacyEntry("RC5_32_cbc_encrypt", "RC5-CBC", 6).keyAt(3), + new LegacyEntry("RC5_32_cfb64_encrypt", "RC5-CFB", 7).keyAt(3), + new LegacyEntry("RC5_32_ofb64_encrypt", "RC5-OFB", 6).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherSeed.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherSeed.java new file mode 100644 index 000000000..989be088d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherSeed.java @@ -0,0 +1,48 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherRuleFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) SEED cipher APIs. */ +public final class OpenSSLLegacyCipherSeed extends OpenSSLEvpCipherRuleFactory { + + private static final String BUNDLE = "OpenSSL"; + + // function(s), label, number of arguments[, argument giving the key size, its unit]; + // keyAt: the argument giving the key schedule, followed to its key setup + private static final List ENTRIES = + List.of( + new LegacyEntry("SEED_set_key", "SEED", 2).setsUpKey(), + new LegacyEntry("SEED_ecb_encrypt", "SEED-ECB", 4).keyAt(2), + new LegacyEntry("SEED_cbc_encrypt", "SEED-CBC", 6).keyAt(3), + new LegacyEntry("SEED_cfb128_encrypt", "SEED-CFB", 7).keyAt(3), + new LegacyEntry("SEED_ofb128_encrypt", "SEED-OFB", 6).keyAt(3)); + + @Nonnull + @Override + protected List> buildRules() { + return OpenSSLEvpCipherRuleFactory.buildLegacy(BUNDLE, ENTRIES); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDh.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDh.java new file mode 100644 index 000000000..f7dbb0177 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDh.java @@ -0,0 +1,154 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy DH (Diffie-Hellman) APIs. + * + *

These rules detect direct DH operations using the legacy (pre-EVP) APIs from dh.h. These APIs + * are deprecated but still widely used in existing codebases. + * + *

Covers: Key/Parameter Generation, Predefined Groups (RFC 5114), Key Agreement + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyDh extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Parameter generation: DH_generate_parameters_ex(dh, prime_len, generator, cb) + + private static final IDetectionRule DH_GENERATE_PARAMETERS_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DH_generate_parameters_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("DH")) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Predefined Groups (RFC 5114) functions + + private static final IDetectionRule DH_GET_1024_160 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DH_get_1024_160") + .shouldBeDetectedAs(new ValueActionFactory<>("DH-1024-160")) + .withoutParameters() + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DH_GET_2048_224 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DH_get_2048_224") + .shouldBeDetectedAs(new ValueActionFactory<>("DH-2048-224")) + .withoutParameters() + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DH_GET_2048_256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DH_get_2048_256") + .shouldBeDetectedAs(new ValueActionFactory<>("DH-2048-256")) + .withoutParameters() + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Key Agreement functions + + private static final IDetectionRule DH_COMPUTE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DH_compute_key") + .shouldBeDetectedAs(new ValueActionFactory<>("DH")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // DH_generate_key(dh): a key is generated for the parameters of dh, a named group or generated + // parameters, followed by the key agreement made with it + + private static final IDetectionRule DH_GENERATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DH_generate_key") + .shouldBeDetectedAs(new ValueActionFactory<>("DH")) + .withMethodParameter("*") + .addDependingDetectionRules( + List.of( + DH_GET_1024_160, + DH_GET_2048_224, + DH_GET_2048_256, + DH_GENERATE_PARAMETERS_EX)) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(DH_COMPUTE_KEY)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // Key/Parameter Generation + DH_GENERATE_PARAMETERS_EX, + DH_GENERATE_KEY, + // Predefined Groups (RFC 5114) + DH_GET_1024_160, + DH_GET_2048_224, + DH_GET_2048_256, + // Key Agreement + DH_COMPUTE_KEY); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigest.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigest.java new file mode 100644 index 000000000..a7b264a90 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigest.java @@ -0,0 +1,146 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy digest/hash APIs. + * + *

These rules detect direct hash operations using the legacy (pre-EVP) APIs. These APIs are + * deprecated but still widely used in existing codebases. + * + *

The MD (MD2/MD4/MD5/MDC2) and SHA-2 (SHA-224/256/384/512) families live in their own {@code + * OpenSSLLegacyDigest} classes; this class holds the remaining single-algorithm legacy + * digests (SHA-1, RIPEMD-160, WHIRLPOOL) and aggregates every family's rules in {@link + * #buildRules()}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyDigest extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule SHA1_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA1_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-1")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA1") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RIPEMD160_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RIPEMD160_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("RIPEMD160")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RIPEMD160 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RIPEMD160") + .shouldBeDetectedAs(new ValueActionFactory<>("RIPEMD160")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule WHIRLPOOL = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("WHIRLPOOL") + .shouldBeDetectedAs(new ValueActionFactory<>("WHIRLPOOL")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule WHIRLPOOL_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("WHIRLPOOL_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("WHIRLPOOL")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLLegacyDigestMd.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyDigestSha2.class).stream(), + directRules().stream()) + .flatMap(i -> i) + .toList(); + } + + @Nonnull + private static List> directRules() { + return List.of( + // Legacy SHA-1 functions + SHA1_INIT, + SHA1, + // Legacy RIPEMD-160 functions + RIPEMD160_INIT, + RIPEMD160, + // WHIRLPOOL (deprecated, legacy provider) + WHIRLPOOL, + WHIRLPOOL_INIT); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestMd.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestMd.java new file mode 100644 index 000000000..2e040a5eb --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestMd.java @@ -0,0 +1,151 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** Detection rules for OpenSSL legacy (pre-EVP) MD2/MD4/MD5/MDC2 digest APIs. */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyDigestMd extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule MD2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MD2") + .shouldBeDetectedAs(new ValueActionFactory<>("MD2")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MD2_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MD2_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("MD2")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MD4 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MD4") + .shouldBeDetectedAs(new ValueActionFactory<>("MD4")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MD4_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MD4_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("MD4")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MD5_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MD5_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("MD5")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MD5 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MD5") + .shouldBeDetectedAs(new ValueActionFactory<>("MD5")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MDC2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MDC2") + .shouldBeDetectedAs(new ValueActionFactory<>("MDC2")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule MDC2_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("MDC2_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("MDC2")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // MD2 + MD2, + MD2_INIT, + // MD4 + MD4, + MD4_INIT, + // MD5 + MD5_INIT, + MD5, + // MDC2 + MDC2, + MDC2_INIT); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestSha2.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestSha2.java new file mode 100644 index 000000000..fbcc6a955 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestSha2.java @@ -0,0 +1,155 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy (pre-EVP) SHA-2 family digest APIs. + * + *

Covers SHA-224, SHA-256, SHA-384, SHA-512. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyDigestSha2 extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule SHA224_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA224_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-224")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA224 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA224") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-224")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA256_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA256_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-256")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA256 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA256") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-256")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA384_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA384_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-384")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA384 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA384") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-384")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA512_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA512_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-512")) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SHA512 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SHA512") + .shouldBeDetectedAs(new ValueActionFactory<>("SHA-512")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // SHA-224 + SHA224_INIT, + SHA224, + // SHA-256 + SHA256_INIT, + SHA256, + // SHA-384 + SHA384_INIT, + SHA384, + // SHA-512 + SHA512_INIT, + SHA512); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDsa.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDsa.java new file mode 100644 index 000000000..9ed053851 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDsa.java @@ -0,0 +1,131 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +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.SignatureContext; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy DSA APIs. + * + *

These rules detect direct DSA operations using the legacy (pre-EVP) APIs from dsa.h. These + * APIs are deprecated but still widely used in existing codebases. + * + *

Covers: key generation, signing, verification, size/utility, and conversion functions. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyDsa extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Signature functions + + private static final IDetectionRule DSA_SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DSA_sign") + .shouldBeDetectedAs(new ValueActionFactory<>("DSA-SIGN")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DSA_DO_SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DSA_do_sign") + .shouldBeDetectedAs(new ValueActionFactory<>("DSA-SIGN")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Parameter generation: DSA_generate_parameters_ex(dsa, bits, seed, seed_len, counter_ret, + // h_ret, cb) + + private static final IDetectionRule DSA_GENERATE_PARAMETERS_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DSA_generate_parameters_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("DSA")) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // DSA_generate_key(dsa): a key is generated for the parameters of dsa, followed by the + // signatures made with it + + private static final IDetectionRule DSA_GENERATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DSA_generate_key") + .shouldBeDetectedAs(new ValueActionFactory<>("DSA")) + .withMethodParameter("*") + .addDependingDetectionRules(List.of(DSA_GENERATE_PARAMETERS_EX)) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(DSA_SIGN, DSA_DO_SIGN)); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // Signatures + DSA_SIGN, + DSA_DO_SIGN, + // Key Generation + DSA_GENERATE_KEY, + DSA_GENERATE_PARAMETERS_EX); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyEc.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyEc.java new file mode 100644 index 000000000..a26de80b9 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyEc.java @@ -0,0 +1,279 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy EC APIs. + * + *

These rules detect direct EC operations using the legacy (pre-EVP) APIs from ec.h. These APIs + * are deprecated but still widely used in existing codebases. + * + *

Covers: EC key management, ECDSA signatures, ECDH key agreement, EC group/curve, EC point + * operations + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyEc extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // ECDSA Signature functions + + private static final IDetectionRule ECDSA_SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("ECDSA_sign") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDSA-SIGN")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule ECDSA_DO_SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("ECDSA_do_sign") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDSA-SIGN")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule ECDSA_SIGN_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("ECDSA_sign_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDSA-SIGN")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule ECDSA_DO_SIGN_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("ECDSA_do_sign_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDSA-SIGN")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Key Generation + + private static final IDetectionRule EC_KEY_NEW_BY_CURVE_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EC_KEY_new_by_curve_name") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLNidLookupFactory()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EC_KEY_NEW_BY_CURVE_NAME_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EC_KEY_new_by_curve_name_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLNidLookupFactory()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EC_GROUP_NEW_BY_CURVE_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EC_GROUP_new_by_curve_name") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLNidLookupFactory()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EC_GROUP_NEW_BY_CURVE_NAME_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EC_GROUP_new_by_curve_name_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLNidLookupFactory()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // A group on a curve given by its parameters, not by name: EC_GROUP_new_curve_GFp(p, a, b, ctx) + // and EC_GROUP_new_curve_GF2m(p, a, b, ctx), EC_GROUP_new_from_params(params, libctx, propq), + // EC_GROUP_new_from_ecparameters(params) and EC_GROUP_new_from_ecpkparameters(params) + private static final List> EC_GROUP_NEW_CUSTOM_CURVE = + List.of( + customCurve(4, "EC_GROUP_new_curve_GFp", "EC_GROUP_new_curve_GF2m"), + customCurve(3, "EC_GROUP_new_from_params"), + customCurve( + 1, + "EC_GROUP_new_from_ecparameters", + "EC_GROUP_new_from_ecpkparameters")); + + @Nonnull + private static IDetectionRule customCurve( + int parameterCount, @Nonnull String... functions) { + IDetectionRule.ParametersFactoryBuilder parameters = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(functions) + .shouldBeDetectedAs(new ValueActionFactory<>("EC")) + .withMethodParameter("*"); + for (int i = 1; i < parameterCount; i++) { + parameters = parameters.withMethodParameter("*"); + } + return parameters + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + // EC_KEY_set_group(key, group): the curve of the key is the curve of the group + private static final IDetectionRule EC_KEY_SET_GROUP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EC_KEY_set_group") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + Stream.concat( + Stream.of( + EC_GROUP_NEW_BY_CURVE_NAME, + EC_GROUP_NEW_BY_CURVE_NAME_EX), + EC_GROUP_NEW_CUSTOM_CURVE.stream()) + .toList()) + .buildForContext(new KeyContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Key Agreement functions + + private static final IDetectionRule ECDH_COMPUTE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("ECDH_compute_key") + .shouldBeDetectedAs(new ValueActionFactory<>("ECDH")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new KeyAgreementContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EC_KEY_generate_key(key): a key is generated on the curve of key, set when the key is + // created for a named curve or by EC_KEY_set_group, followed by the signatures and the key + // agreement made with it + private static final IDetectionRule EC_KEY_GENERATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EC_KEY_generate_key") + .shouldBeDetectedAs(new ValueActionFactory<>("EC")) + .withMethodParameter("*") + .addDependingDetectionRules( + List.of( + EC_KEY_NEW_BY_CURVE_NAME, + EC_KEY_NEW_BY_CURVE_NAME_EX, + EC_KEY_SET_GROUP)) + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules( + List.of( + ECDSA_SIGN, + ECDSA_DO_SIGN, + ECDSA_SIGN_EX, + ECDSA_DO_SIGN_EX, + ECDH_COMPUTE_KEY)); + + @Nonnull + @Override + protected List> buildRules() { + return Stream.concat( + Stream.of( + // ECDSA Signatures + ECDSA_SIGN, + ECDSA_SIGN_EX, + ECDSA_DO_SIGN, + ECDSA_DO_SIGN_EX, + // Keys and groups + EC_KEY_NEW_BY_CURVE_NAME, + EC_KEY_NEW_BY_CURVE_NAME_EX, + EC_GROUP_NEW_BY_CURVE_NAME, + EC_GROUP_NEW_BY_CURVE_NAME_EX, + EC_KEY_GENERATE_KEY, + // Key Agreement + ECDH_COMPUTE_KEY), + // Groups on custom curves + EC_GROUP_NEW_CUSTOM_CURVE.stream()) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMac.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMac.java new file mode 100644 index 000000000..93d3954e8 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMac.java @@ -0,0 +1,54 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy MAC (Message Authentication Code) APIs. + * + *

These rules detect MAC operations using the legacy (pre-EVP) APIs. These APIs are deprecated + * but still widely used in existing codebases. + * + *

HMAC and CMAC each live in their own {@code OpenSSLLegacyMac} class; this class + * aggregates both in {@link #buildRules()}. Poly1305 is intentionally excluded — its {@code + * Poly1305_Init/Update/Final} symbols are OpenSSL-internal (declared in {@code + * include/crypto/poly1305.h}, not {@code include/openssl/}). Public Poly1305 access in OpenSSL 3.x + * is via {@code EVP_MAC_fetch(..., "POLY1305", ...)}, handled by {@link + * com.ibm.plugin.rules.detection.openssl.mac.OpenSSLEvpMac}. + */ +public final class OpenSSLLegacyMac extends DetectionRuleSet { + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLLegacyMacHmac.class).stream(), + RuleSets.rulesOf(OpenSSLLegacyMacCmac.class).stream()) + .flatMap(i -> i) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacCmac.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacCmac.java new file mode 100644 index 000000000..7f03e3b9c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacCmac.java @@ -0,0 +1,64 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy (pre-EVP) CMAC APIs: {@code CMAC_Init}, which selects the + * cipher of the CMAC. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyMacCmac extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule CMAC_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("CMAC_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("CMAC")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpCipher.class)) + .withMethodParameter("*") + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(CMAC_INIT); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacHmac.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacHmac.java new file mode 100644 index 000000000..20f61dfd7 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacHmac.java @@ -0,0 +1,97 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy (pre-EVP) HMAC APIs: the calls that select the digest of the + * HMAC ({@code HMAC_Init_ex}, {@code HMAC_Init} and the one-shot {@code HMAC}). + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyMacHmac extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final IDetectionRule HMAC_INIT_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("HMAC_Init_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("HMAC")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule HMAC_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("HMAC_Init") + .shouldBeDetectedAs(new ValueActionFactory<>("HMAC")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule HMAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("HMAC") + .shouldBeDetectedAs(new ValueActionFactory<>("HMAC")) + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(HMAC_INIT_EX, HMAC_INIT, HMAC); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsa.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsa.java new file mode 100644 index 000000000..37b081207 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsa.java @@ -0,0 +1,599 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.PrivateKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.KeySizeFactory; +import com.ibm.engine.model.factory.SignatureActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.OpenSSLSizeFactory; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.signature.OpenSSLSaltLengthFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL legacy RSA Direct API functions (from rsa.h). + * + *

These rules detect RSA operations using the legacy (pre-EVP) APIs. These APIs are deprecated + * but still widely used in existing codebases. + * + *

Covers: RSA key management, encryption/decryption, signing/verification, PSS, OAEP + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLegacyRsa extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Signatures + + /** + * int RSA_sign/RSA_verify(int type, ...): {@code type} is the NID (obj_mac.h) of the digest the + * signature is computed or verified over, resolved via {@link OpenSSLNidLookupFactory} to the + * name of the signature scheme with that digest (e.g. {@code RSA-SHA256}). + */ + private static final Map RSA_DIGEST_BY_CODE = + Map.ofEntries( + Map.entry(64, "RSA-SHA1"), // NID_sha1 + Map.entry(675, "RSA-SHA224"), // NID_sha224 + Map.entry(672, "RSA-SHA256"), // NID_sha256 + Map.entry(673, "RSA-SHA384"), // NID_sha384 + Map.entry(674, "RSA-SHA512"), // NID_sha512 + Map.entry(4, "RSA-MD5"), // NID_md5 + Map.entry(114, "RSA-MD5-SHA1")); // NID_md5_sha1 + + private static final Map RSA_DIGEST_BY_NAME = + Map.ofEntries( + Map.entry("NID_sha1", "RSA-SHA1"), + Map.entry("NID_sha224", "RSA-SHA224"), + Map.entry("NID_sha256", "RSA-SHA256"), + Map.entry("NID_sha384", "RSA-SHA384"), + Map.entry("NID_sha512", "RSA-SHA512"), + Map.entry("NID_md5", "RSA-MD5"), + Map.entry("NID_md5_sha1", "RSA-MD5-SHA1")); + + private static final IDetectionRule RSA_SIGN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_sign") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory(RSA_DIGEST_BY_CODE, RSA_DIGEST_BY_NAME)) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_VERIFY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_verify") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.VERIFY)) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory(RSA_DIGEST_BY_CODE, RSA_DIGEST_BY_NAME)) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PSS + + private static final IDetectionRule RSA_PADDING_ADD_PKCS1_PSS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_PKCS1_PSS") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLSaltLengthFactory()) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_ADD_PKCS1_PSS_MGF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_PKCS1_PSS_mgf1") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Mgf1.class)) + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLSaltLengthFactory()) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_VERIFY_PKCS1_PSS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_verify_PKCS1_PSS") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLSaltLengthFactory()) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_VERIFY_PKCS1_PSS_MGF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_verify_PKCS1_PSS_mgf1") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLEvpMessageDigest.class)) + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Mgf1.class)) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLSaltLengthFactory()) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS1 type_1 padding (legacy direct) + + private static final IDetectionRule RSA_PADDING_ADD_PKCS1_TYPE_1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_PKCS1_type_1") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PKCS1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_CHECK_PKCS1_TYPE_1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_check_PKCS1_type_1") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PKCS1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // X9.31 padding + + private static final IDetectionRule RSA_PADDING_ADD_X931 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_X931") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-X931")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_CHECK_X931 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_check_X931") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-X931")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Raw RSA operations: RSA_*_encrypt / RSA_*_decrypt(flen, from, to, rsa, padding). The + // public-key encryption and private-key decryption use an encryption scheme; the private-key + // "encryption" and public-key "decryption" are the signature primitive and its verification. + + private static final IDetectionRule RSA_PUBLIC_ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_public_encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.RSA_ENCRYPTION_PADDING_BY_CODE, + OpenSSLNidLookupFactory.RSA_ENCRYPTION_PADDING_BY_NAME)) + .asChildOfParameterWithId(-1) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PRIVATE_ENCRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_private_encrypt") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.SIGN)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.RSA_SIGNATURE_PADDING_BY_CODE, + OpenSSLNidLookupFactory.RSA_SIGNATURE_PADDING_BY_NAME)) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PUBLIC_DECRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_public_decrypt") + .shouldBeDetectedAs(new SignatureActionFactory<>(SignatureAction.Action.VERIFY)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.RSA_SIGNATURE_PADDING_BY_CODE, + OpenSSLNidLookupFactory.RSA_SIGNATURE_PADDING_BY_NAME)) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PRIVATE_DECRYPT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_private_decrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.RSA_ENCRYPTION_PADDING_BY_CODE, + OpenSSLNidLookupFactory.RSA_ENCRYPTION_PADDING_BY_NAME)) + .asChildOfParameterWithId(-1) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // PKCS1 type_2 padding (encryption padding) + + private static final IDetectionRule RSA_PADDING_ADD_PKCS1_TYPE_2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_PKCS1_type_2") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PKCS1-TYPE2")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_CHECK_PKCS1_TYPE_2 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_check_PKCS1_type_2") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PKCS1-TYPE2")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // No-padding (raw RSA) + + private static final IDetectionRule RSA_PADDING_ADD_NONE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_none") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-NO-PADDING")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_CHECK_NONE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_check_none") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-NO-PADDING")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // OAEP padding (encryption padding) + + private static final IDetectionRule RSA_PADDING_ADD_PKCS1_OAEP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_PKCS1_OAEP") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-OAEP")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_CHECK_PKCS1_OAEP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_check_PKCS1_OAEP") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-OAEP")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_ADD_PKCS1_OAEP_MGF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_add_PKCS1_OAEP_mgf1") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-OAEP-MGF1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Oaep.class)) + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Mgf1.class)) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RSA_PADDING_CHECK_PKCS1_OAEP_MGF1 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_padding_check_PKCS1_OAEP_mgf1") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-OAEP-MGF1")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Oaep.class)) + .withMethodParameter("*") + .addDependingDetectionRules( + RuleSets.rulesOf(OpenSSLEvpMessageDigest.Mgf1.class)) + .buildForContext(new CipherContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // The operations made with a generated key, given as an argument of each of them: the + // signatures, the PSS encodings, and the raw RSA operations + private static final List> KEY_OPERATIONS = + List.of( + RSA_SIGN, + RSA_VERIFY, + RSA_PADDING_ADD_PKCS1_PSS, + RSA_PADDING_ADD_PKCS1_PSS_MGF1, + RSA_VERIFY_PKCS1_PSS, + RSA_VERIFY_PKCS1_PSS_MGF1, + RSA_PUBLIC_ENCRYPT, + RSA_PRIVATE_ENCRYPT, + RSA_PUBLIC_DECRYPT, + RSA_PRIVATE_DECRYPT); + + // Key Generation, followed by the operations made with the key + + private static final IDetectionRule RSA_GENERATE_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_generate_key") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA")) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_OPERATIONS); + + private static final IDetectionRule RSA_GENERATE_KEY_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_generate_key_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA")) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_OPERATIONS); + + private static final IDetectionRule RSA_GENERATE_MULTI_PRIME_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RSA_generate_multi_prime_key") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA")) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLSizeFactory(new KeySizeFactory<>(Size.UnitType.BIT))) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PrivateKeyContext(Map.of())) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(KEY_OPERATIONS); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // Signatures + RSA_SIGN, + RSA_VERIFY, + // PSS + RSA_PADDING_ADD_PKCS1_PSS, + RSA_PADDING_ADD_PKCS1_PSS_MGF1, + RSA_VERIFY_PKCS1_PSS, + RSA_VERIFY_PKCS1_PSS_MGF1, + // PKCS1 type_1 padding + RSA_PADDING_ADD_PKCS1_TYPE_1, + RSA_PADDING_CHECK_PKCS1_TYPE_1, + // X9.31 padding + RSA_PADDING_ADD_X931, + RSA_PADDING_CHECK_X931, + // Key Generation + RSA_GENERATE_KEY, + RSA_GENERATE_KEY_EX, + RSA_GENERATE_MULTI_PRIME_KEY, + // Encrypt / Decrypt (raw RSA) + RSA_PUBLIC_ENCRYPT, + RSA_PRIVATE_ENCRYPT, + RSA_PUBLIC_DECRYPT, + RSA_PRIVATE_DECRYPT, + // PKCS1 type_2 padding + RSA_PADDING_ADD_PKCS1_TYPE_2, + RSA_PADDING_CHECK_PKCS1_TYPE_2, + // No-padding + RSA_PADDING_ADD_NONE, + RSA_PADDING_CHECK_NONE, + // OAEP padding + RSA_PADDING_ADD_PKCS1_OAEP, + RSA_PADDING_CHECK_PKCS1_OAEP, + RSA_PADDING_ADD_PKCS1_OAEP_MGF1, + RSA_PADDING_CHECK_PKCS1_OAEP_MGF1); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLNidLookupFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLNidLookupFactory.java new file mode 100644 index 000000000..168c0cd9c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLNidLookupFactory.java @@ -0,0 +1,715 @@ +/* + * 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.openssl.legacy; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.Protocol; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.IntUnaryOperator; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Resolves an OpenSSL numeric-or-bare-name argument (e.g. the curve NID of {@code + * EC_KEY_new_by_curve_name(_ex)} / {@code EC_GROUP_new_by_curve_name(_ex)} / {@code + * EVP_PKEY_CTX_set_ec_paramgen_curve_nid}, the DH group NID of {@code EVP_PKEY_CTX_set_dh_nid}, or + * the protocol version of {@code SSL_(CTX_)set_min/max_proto_version}) to the identifier string the + * corresponding {@code Cxx*ContextTranslator} switches on, using caller-supplied lookup tables. + * + *

The engine hands this factory a numeric code (e.g. {@code 415}) when the argument is a numeric + * literal or an enum constant of the analyzed code, and a name (e.g. {@code + * "NID_X9_62_prime256v1"}), looked up in {@code byName}, for an OpenSSL macro constant that is not + * expanded because no include directories are configured (see {@code CxxSemantic}). A code or name + * outside both tables resolves to nothing. + */ +public final class OpenSSLNidLookupFactory implements IValueFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(OpenSSLNidLookupFactory.class); + + /** + * Curve NIDs (obj_mac.h) → {@code "EC-"} and the curve's short name, for every named curve of + * OpenSSL (see {@code OpenSslCurveMapper#NAMED_CURVES}). + */ + public static final Map CURVE_BY_CODE = + Map.ofEntries( + Map.entry(409, "EC-prime192v1"), + Map.entry(410, "EC-prime192v2"), + Map.entry(411, "EC-prime192v3"), + Map.entry(412, "EC-prime239v1"), + Map.entry(413, "EC-prime239v2"), + Map.entry(414, "EC-prime239v3"), + Map.entry(415, "EC-prime256v1"), + Map.entry(684, "EC-c2pnb163v1"), + Map.entry(685, "EC-c2pnb163v2"), + Map.entry(686, "EC-c2pnb163v3"), + Map.entry(687, "EC-c2pnb176v1"), + Map.entry(688, "EC-c2tnb191v1"), + Map.entry(689, "EC-c2tnb191v2"), + Map.entry(690, "EC-c2tnb191v3"), + Map.entry(693, "EC-c2pnb208w1"), + Map.entry(694, "EC-c2tnb239v1"), + Map.entry(695, "EC-c2tnb239v2"), + Map.entry(696, "EC-c2tnb239v3"), + Map.entry(699, "EC-c2pnb272w1"), + Map.entry(700, "EC-c2pnb304w1"), + Map.entry(701, "EC-c2tnb359v1"), + Map.entry(702, "EC-c2pnb368w1"), + Map.entry(703, "EC-c2tnb431r1"), + Map.entry(704, "EC-secp112r1"), + Map.entry(705, "EC-secp112r2"), + Map.entry(706, "EC-secp128r1"), + Map.entry(707, "EC-secp128r2"), + Map.entry(708, "EC-secp160k1"), + Map.entry(709, "EC-secp160r1"), + Map.entry(710, "EC-secp160r2"), + Map.entry(711, "EC-secp192k1"), + Map.entry(712, "EC-secp224k1"), + Map.entry(713, "EC-secp224r1"), + Map.entry(714, "EC-secp256k1"), + Map.entry(715, "EC-secp384r1"), + Map.entry(716, "EC-secp521r1"), + Map.entry(717, "EC-sect113r1"), + Map.entry(718, "EC-sect113r2"), + Map.entry(719, "EC-sect131r1"), + Map.entry(720, "EC-sect131r2"), + Map.entry(721, "EC-sect163k1"), + Map.entry(722, "EC-sect163r1"), + Map.entry(723, "EC-sect163r2"), + Map.entry(724, "EC-sect193r1"), + Map.entry(725, "EC-sect193r2"), + Map.entry(726, "EC-sect233k1"), + Map.entry(727, "EC-sect233r1"), + Map.entry(728, "EC-sect239k1"), + Map.entry(729, "EC-sect283k1"), + Map.entry(730, "EC-sect283r1"), + Map.entry(731, "EC-sect409k1"), + Map.entry(732, "EC-sect409r1"), + Map.entry(733, "EC-sect571k1"), + Map.entry(734, "EC-sect571r1"), + Map.entry(735, "EC-wap-wsg-idm-ecid-wtls1"), + Map.entry(736, "EC-wap-wsg-idm-ecid-wtls3"), + Map.entry(737, "EC-wap-wsg-idm-ecid-wtls4"), + Map.entry(738, "EC-wap-wsg-idm-ecid-wtls5"), + Map.entry(739, "EC-wap-wsg-idm-ecid-wtls6"), + Map.entry(740, "EC-wap-wsg-idm-ecid-wtls7"), + Map.entry(741, "EC-wap-wsg-idm-ecid-wtls8"), + Map.entry(742, "EC-wap-wsg-idm-ecid-wtls9"), + Map.entry(743, "EC-wap-wsg-idm-ecid-wtls10"), + Map.entry(744, "EC-wap-wsg-idm-ecid-wtls11"), + Map.entry(745, "EC-wap-wsg-idm-ecid-wtls12"), + Map.entry(749, "EC-Oakley-EC2N-3"), + Map.entry(750, "EC-Oakley-EC2N-4"), + Map.entry(921, "EC-brainpoolP160r1"), + Map.entry(922, "EC-brainpoolP160t1"), + Map.entry(923, "EC-brainpoolP192r1"), + Map.entry(924, "EC-brainpoolP192t1"), + Map.entry(925, "EC-brainpoolP224r1"), + Map.entry(926, "EC-brainpoolP224t1"), + Map.entry(927, "EC-brainpoolP256r1"), + Map.entry(928, "EC-brainpoolP256t1"), + Map.entry(929, "EC-brainpoolP320r1"), + Map.entry(930, "EC-brainpoolP320t1"), + Map.entry(931, "EC-brainpoolP384r1"), + Map.entry(932, "EC-brainpoolP384t1"), + Map.entry(933, "EC-brainpoolP512r1"), + Map.entry(934, "EC-brainpoolP512t1"), + Map.entry(1172, "EC-SM2")); + + /** Curve NID constant names → {@code "EC-"} and the curve's short name. */ + public static final Map CURVE_BY_NAME = + Map.ofEntries( + Map.entry("NID_X9_62_prime192v1", "EC-prime192v1"), + Map.entry("NID_X9_62_prime192v2", "EC-prime192v2"), + Map.entry("NID_X9_62_prime192v3", "EC-prime192v3"), + Map.entry("NID_X9_62_prime239v1", "EC-prime239v1"), + Map.entry("NID_X9_62_prime239v2", "EC-prime239v2"), + Map.entry("NID_X9_62_prime239v3", "EC-prime239v3"), + Map.entry("NID_X9_62_prime256v1", "EC-prime256v1"), + Map.entry("NID_X9_62_c2pnb163v1", "EC-c2pnb163v1"), + Map.entry("NID_X9_62_c2pnb163v2", "EC-c2pnb163v2"), + Map.entry("NID_X9_62_c2pnb163v3", "EC-c2pnb163v3"), + Map.entry("NID_X9_62_c2pnb176v1", "EC-c2pnb176v1"), + Map.entry("NID_X9_62_c2tnb191v1", "EC-c2tnb191v1"), + Map.entry("NID_X9_62_c2tnb191v2", "EC-c2tnb191v2"), + Map.entry("NID_X9_62_c2tnb191v3", "EC-c2tnb191v3"), + Map.entry("NID_X9_62_c2pnb208w1", "EC-c2pnb208w1"), + Map.entry("NID_X9_62_c2tnb239v1", "EC-c2tnb239v1"), + Map.entry("NID_X9_62_c2tnb239v2", "EC-c2tnb239v2"), + Map.entry("NID_X9_62_c2tnb239v3", "EC-c2tnb239v3"), + Map.entry("NID_X9_62_c2pnb272w1", "EC-c2pnb272w1"), + Map.entry("NID_X9_62_c2pnb304w1", "EC-c2pnb304w1"), + Map.entry("NID_X9_62_c2tnb359v1", "EC-c2tnb359v1"), + Map.entry("NID_X9_62_c2pnb368w1", "EC-c2pnb368w1"), + Map.entry("NID_X9_62_c2tnb431r1", "EC-c2tnb431r1"), + Map.entry("NID_secp112r1", "EC-secp112r1"), + Map.entry("NID_secp112r2", "EC-secp112r2"), + Map.entry("NID_secp128r1", "EC-secp128r1"), + Map.entry("NID_secp128r2", "EC-secp128r2"), + Map.entry("NID_secp160k1", "EC-secp160k1"), + Map.entry("NID_secp160r1", "EC-secp160r1"), + Map.entry("NID_secp160r2", "EC-secp160r2"), + Map.entry("NID_secp192k1", "EC-secp192k1"), + Map.entry("NID_secp224k1", "EC-secp224k1"), + Map.entry("NID_secp224r1", "EC-secp224r1"), + Map.entry("NID_secp256k1", "EC-secp256k1"), + Map.entry("NID_secp384r1", "EC-secp384r1"), + Map.entry("NID_secp521r1", "EC-secp521r1"), + Map.entry("NID_sect113r1", "EC-sect113r1"), + Map.entry("NID_sect113r2", "EC-sect113r2"), + Map.entry("NID_sect131r1", "EC-sect131r1"), + Map.entry("NID_sect131r2", "EC-sect131r2"), + Map.entry("NID_sect163k1", "EC-sect163k1"), + Map.entry("NID_sect163r1", "EC-sect163r1"), + Map.entry("NID_sect163r2", "EC-sect163r2"), + Map.entry("NID_sect193r1", "EC-sect193r1"), + Map.entry("NID_sect193r2", "EC-sect193r2"), + Map.entry("NID_sect233k1", "EC-sect233k1"), + Map.entry("NID_sect233r1", "EC-sect233r1"), + Map.entry("NID_sect239k1", "EC-sect239k1"), + Map.entry("NID_sect283k1", "EC-sect283k1"), + Map.entry("NID_sect283r1", "EC-sect283r1"), + Map.entry("NID_sect409k1", "EC-sect409k1"), + Map.entry("NID_sect409r1", "EC-sect409r1"), + Map.entry("NID_sect571k1", "EC-sect571k1"), + Map.entry("NID_sect571r1", "EC-sect571r1"), + Map.entry("NID_wap_wsg_idm_ecid_wtls1", "EC-wap-wsg-idm-ecid-wtls1"), + Map.entry("NID_wap_wsg_idm_ecid_wtls3", "EC-wap-wsg-idm-ecid-wtls3"), + Map.entry("NID_wap_wsg_idm_ecid_wtls4", "EC-wap-wsg-idm-ecid-wtls4"), + Map.entry("NID_wap_wsg_idm_ecid_wtls5", "EC-wap-wsg-idm-ecid-wtls5"), + Map.entry("NID_wap_wsg_idm_ecid_wtls6", "EC-wap-wsg-idm-ecid-wtls6"), + Map.entry("NID_wap_wsg_idm_ecid_wtls7", "EC-wap-wsg-idm-ecid-wtls7"), + Map.entry("NID_wap_wsg_idm_ecid_wtls8", "EC-wap-wsg-idm-ecid-wtls8"), + Map.entry("NID_wap_wsg_idm_ecid_wtls9", "EC-wap-wsg-idm-ecid-wtls9"), + Map.entry("NID_wap_wsg_idm_ecid_wtls10", "EC-wap-wsg-idm-ecid-wtls10"), + Map.entry("NID_wap_wsg_idm_ecid_wtls11", "EC-wap-wsg-idm-ecid-wtls11"), + Map.entry("NID_wap_wsg_idm_ecid_wtls12", "EC-wap-wsg-idm-ecid-wtls12"), + Map.entry("NID_ipsec3", "EC-Oakley-EC2N-3"), + Map.entry("NID_ipsec4", "EC-Oakley-EC2N-4"), + Map.entry("NID_brainpoolP160r1", "EC-brainpoolP160r1"), + Map.entry("NID_brainpoolP160t1", "EC-brainpoolP160t1"), + Map.entry("NID_brainpoolP192r1", "EC-brainpoolP192r1"), + Map.entry("NID_brainpoolP192t1", "EC-brainpoolP192t1"), + Map.entry("NID_brainpoolP224r1", "EC-brainpoolP224r1"), + Map.entry("NID_brainpoolP224t1", "EC-brainpoolP224t1"), + Map.entry("NID_brainpoolP256r1", "EC-brainpoolP256r1"), + Map.entry("NID_brainpoolP256t1", "EC-brainpoolP256t1"), + Map.entry("NID_brainpoolP320r1", "EC-brainpoolP320r1"), + Map.entry("NID_brainpoolP320t1", "EC-brainpoolP320t1"), + Map.entry("NID_brainpoolP384r1", "EC-brainpoolP384r1"), + Map.entry("NID_brainpoolP384t1", "EC-brainpoolP384t1"), + Map.entry("NID_brainpoolP512r1", "EC-brainpoolP512r1"), + Map.entry("NID_brainpoolP512t1", "EC-brainpoolP512t1"), + Map.entry("NID_sm2", "EC-SM2")); + + /** + * OpenSSL key type identifiers of the KDFs available through the EVP_PKEY interface (evp.h / + * obj_mac.h) → KDF names. + */ + public static final Map PKEY_KDF_BY_CODE = + Map.ofEntries( + Map.entry(1036, "HKDF"), // EVP_PKEY_HKDF + Map.entry(1021, "TLS1-PRF"), // EVP_PKEY_TLS1_PRF + Map.entry(973, "SCRYPT")); // EVP_PKEY_SCRYPT + + /** OpenSSL KDF key type constant names → KDF names. */ + public static final Map PKEY_KDF_BY_NAME = + Map.ofEntries( + Map.entry("EVP_PKEY_HKDF", "HKDF"), + Map.entry("EVP_PKEY_TLS1_PRF", "TLS1-PRF"), + Map.entry("EVP_PKEY_SCRYPT", "SCRYPT")); + + /** + * Encryption NIDs accepted by {@code PKCS12_create} for the private key and the certificates + * (obj_mac.h) → password-based encryption scheme identifiers. A PKCS#12 PBE NID selects that + * scheme; a cipher NID selects PBES2 with that cipher, and 0 selects the default, PBES2 with + * AES-256-CBC. -1 (no encryption) is not listed. + */ + public static final Map PKCS12_ENCRYPTION_BY_CODE = + Map.ofEntries( + Map.entry(144, "PBE-SHA1-RC4-128"), + Map.entry(145, "PBE-SHA1-RC4-40"), + Map.entry(146, "PBE-SHA1-3DES"), + Map.entry(147, "PBE-SHA1-2DES"), + Map.entry(148, "PBE-SHA1-RC2-128"), + Map.entry(149, "PBE-SHA1-RC2-40"), + Map.entry(419, "PBES2-AES-128-CBC"), + Map.entry(423, "PBES2-AES-192-CBC"), + Map.entry(427, "PBES2-AES-256-CBC"), + Map.entry(44, "PBES2-DES-EDE3-CBC"), + Map.entry(0, "PBES2-AES-256-CBC")); + + /** + * Password-based encryption NIDs (obj_mac.h) → scheme identifiers, for the {@code pbe_nid} of + * {@code PKCS8_encrypt} and the {@code OBJ_nid2obj} object given to {@code EVP_PBE_CipherInit}: + * the PKCS#5 v1.5 (PBES1) and PKCS#12 schemes, and PBES2, whose cipher is given with it. -1 + * selects PBES2 in {@code PKCS8_encrypt}. + */ + public static final Map PBE_ALGORITHM_BY_CODE = + Map.ofEntries( + Map.entry(9, "PBE-MD2-DES"), + Map.entry(10, "PBE-MD5-DES"), + Map.entry(168, "PBE-MD2-RC2-64"), + Map.entry(169, "PBE-MD5-RC2-64"), + Map.entry(170, "PBE-SHA1-DES"), + Map.entry(68, "PBE-SHA1-RC2-64"), + Map.entry(144, "PBE-SHA1-RC4-128"), + Map.entry(145, "PBE-SHA1-RC4-40"), + Map.entry(146, "PBE-SHA1-3DES"), + Map.entry(147, "PBE-SHA1-2DES"), + Map.entry(148, "PBE-SHA1-RC2-128"), + Map.entry(149, "PBE-SHA1-RC2-40"), + Map.entry(161, "PBES2"), + Map.entry(-1, "PBES2")); + + /** Password-based encryption NID constant names → scheme identifiers. */ + public static final Map PBE_ALGORITHM_BY_NAME = + Map.ofEntries( + Map.entry("NID_pbeWithMD2AndDES_CBC", "PBE-MD2-DES"), + Map.entry("NID_pbeWithMD5AndDES_CBC", "PBE-MD5-DES"), + Map.entry("NID_pbeWithMD2AndRC2_CBC", "PBE-MD2-RC2-64"), + Map.entry("NID_pbeWithMD5AndRC2_CBC", "PBE-MD5-RC2-64"), + Map.entry("NID_pbeWithSHA1AndDES_CBC", "PBE-SHA1-DES"), + Map.entry("NID_pbeWithSHA1AndRC2_CBC", "PBE-SHA1-RC2-64"), + Map.entry("NID_pbe_WithSHA1And128BitRC4", "PBE-SHA1-RC4-128"), + Map.entry("NID_pbe_WithSHA1And40BitRC4", "PBE-SHA1-RC4-40"), + Map.entry("NID_pbe_WithSHA1And3_Key_TripleDES_CBC", "PBE-SHA1-3DES"), + Map.entry("NID_pbe_WithSHA1And2_Key_TripleDES_CBC", "PBE-SHA1-2DES"), + Map.entry("NID_pbe_WithSHA1And128BitRC2_CBC", "PBE-SHA1-RC2-128"), + Map.entry("NID_pbe_WithSHA1And40BitRC2_CBC", "PBE-SHA1-RC2-40"), + Map.entry("NID_pbes2", "PBES2")); + + /** Encryption NID constant names accepted by {@code PKCS12_create} → scheme identifiers. */ + public static final Map PKCS12_ENCRYPTION_BY_NAME = + Map.ofEntries( + Map.entry("NID_pbe_WithSHA1And128BitRC4", "PBE-SHA1-RC4-128"), + Map.entry("NID_pbe_WithSHA1And40BitRC4", "PBE-SHA1-RC4-40"), + Map.entry("NID_pbe_WithSHA1And3_Key_TripleDES_CBC", "PBE-SHA1-3DES"), + Map.entry("NID_pbe_WithSHA1And2_Key_TripleDES_CBC", "PBE-SHA1-2DES"), + Map.entry("NID_pbe_WithSHA1And128BitRC2_CBC", "PBE-SHA1-RC2-128"), + Map.entry("NID_pbe_WithSHA1And40BitRC2_CBC", "PBE-SHA1-RC2-40"), + Map.entry("NID_aes_128_cbc", "PBES2-AES-128-CBC"), + Map.entry("NID_aes_192_cbc", "PBES2-AES-192-CBC"), + Map.entry("NID_aes_256_cbc", "PBES2-AES-256-CBC"), + Map.entry("NID_des_ede3_cbc", "PBES2-DES-EDE3-CBC")); + + /** + * OpenSSL ECDH KDF types (EVP_PKEY_ECDH_KDF_*, ec.h) → KDF names; {@code "NONE"} when the + * shared secret is used without a KDF. + */ + public static final Map ECDH_KDF_TYPE_BY_CODE = + Map.of(1, "NONE", 2, "X963KDF"); + + /** OpenSSL ECDH KDF type constant names → KDF names. */ + public static final Map ECDH_KDF_TYPE_BY_NAME = + Map.of( + "EVP_PKEY_ECDH_KDF_NONE", "NONE", + "EVP_PKEY_ECDH_KDF_X9_63", "X963KDF", + "EVP_PKEY_ECDH_KDF_X9_62", "X963KDF"); + + /** + * OpenSSL DH KDF types (EVP_PKEY_DH_KDF_*, dh.h) → KDF names; {@code "NONE"} when the shared + * secret is used without a KDF. + */ + public static final Map DH_KDF_TYPE_BY_CODE = + Map.of(1, "NONE", 2, "X942KDF-ASN1"); + + /** OpenSSL DH KDF type constant names → KDF names. */ + public static final Map DH_KDF_TYPE_BY_NAME = + Map.of("EVP_PKEY_DH_KDF_NONE", "NONE", "EVP_PKEY_DH_KDF_X9_42", "X942KDF-ASN1"); + + /** Key wrap algorithm NIDs of a CMS KEK recipient (obj_mac.h) → cipher names. */ + public static final Map KEY_WRAP_BY_CODE = + Map.ofEntries( + Map.entry(788, "AES-128-WRAP"), + Map.entry(789, "AES-192-WRAP"), + Map.entry(790, "AES-256-WRAP"), + Map.entry(897, "AES-128-WRAP-PAD"), + Map.entry(900, "AES-192-WRAP-PAD"), + Map.entry(903, "AES-256-WRAP-PAD"), + Map.entry(246, "DES-EDE3-WRAP")); + + /** Key wrap algorithm NID constant names → cipher names. */ + public static final Map KEY_WRAP_BY_NAME = + Map.ofEntries( + Map.entry("NID_id_aes128_wrap", "AES-128-WRAP"), + Map.entry("NID_id_aes192_wrap", "AES-192-WRAP"), + Map.entry("NID_id_aes256_wrap", "AES-256-WRAP"), + Map.entry("NID_id_aes128_wrap_pad", "AES-128-WRAP-PAD"), + Map.entry("NID_id_aes192_wrap_pad", "AES-192-WRAP-PAD"), + Map.entry("NID_id_aes256_wrap_pad", "AES-256-WRAP-PAD"), + Map.entry("NID_id_smime_alg_CMS3DESwrap", "DES-EDE3-WRAP")); + + /** Digest NIDs (obj_mac.h) → digest names. */ + public static final Map DIGEST_BY_CODE = + Map.ofEntries( + Map.entry(4, "MD5"), + Map.entry(64, "SHA-1"), + Map.entry(95, "MDC2"), + Map.entry(117, "RIPEMD160"), + Map.entry(257, "MD4"), + Map.entry(672, "SHA-256"), + Map.entry(673, "SHA-384"), + Map.entry(674, "SHA-512"), + Map.entry(675, "SHA-224"), + Map.entry(1056, "BLAKE2B-512"), + Map.entry(1057, "BLAKE2S-256"), + Map.entry(1094, "SHA-512/224"), + Map.entry(1095, "SHA-512/256"), + Map.entry(1096, "SHA3-224"), + Map.entry(1097, "SHA3-256"), + Map.entry(1098, "SHA3-384"), + Map.entry(1099, "SHA3-512"), + Map.entry(1100, "SHAKE128"), + Map.entry(1101, "SHAKE256"), + Map.entry(1143, "SM3")); + + /** Digest NID constant names → digest names. */ + public static final Map DIGEST_BY_NAME = + Map.ofEntries( + Map.entry("NID_md4", "MD4"), + Map.entry("NID_md5", "MD5"), + Map.entry("NID_sha1", "SHA-1"), + Map.entry("NID_sha224", "SHA-224"), + Map.entry("NID_sha256", "SHA-256"), + Map.entry("NID_sha384", "SHA-384"), + Map.entry("NID_sha512", "SHA-512"), + Map.entry("NID_sha512_224", "SHA-512/224"), + Map.entry("NID_sha512_256", "SHA-512/256"), + Map.entry("NID_sha3_224", "SHA3-224"), + Map.entry("NID_sha3_256", "SHA3-256"), + Map.entry("NID_sha3_384", "SHA3-384"), + Map.entry("NID_sha3_512", "SHA3-512"), + Map.entry("NID_shake128", "SHAKE128"), + Map.entry("NID_shake256", "SHAKE256"), + Map.entry("NID_ripemd160", "RIPEMD160"), + Map.entry("NID_sm3", "SM3"), + Map.entry("NID_mdc2", "MDC2"), + Map.entry("NID_blake2b512", "BLAKE2B-512"), + Map.entry("NID_blake2s256", "BLAKE2S-256")); + + /** Cipher NIDs (obj_mac.h) → cipher names. */ + public static final Map CIPHER_BY_CODE = + Map.ofEntries( + Map.entry(5, "RC4"), + Map.entry(29, "DES-ECB"), + Map.entry(31, "DES-CBC"), + Map.entry(33, "DESede3-ECB"), + Map.entry(44, "DESede3-CBC"), + Map.entry(91, "BLOWFISH-CBC"), + Map.entry(418, "AES-128-ECB"), + Map.entry(419, "AES-128-CBC"), + Map.entry(420, "AES-128-OFB"), + Map.entry(421, "AES-128-CFB"), + Map.entry(422, "AES-192-ECB"), + Map.entry(423, "AES-192-CBC"), + Map.entry(426, "AES-256-ECB"), + Map.entry(427, "AES-256-CBC"), + Map.entry(428, "AES-256-OFB"), + Map.entry(429, "AES-256-CFB"), + Map.entry(751, "CAMELLIA-128-CBC"), + Map.entry(753, "CAMELLIA-256-CBC"), + Map.entry(895, "AES-128-GCM"), + Map.entry(896, "AES-128-CCM"), + Map.entry(898, "AES-192-GCM"), + Map.entry(901, "AES-256-GCM"), + Map.entry(902, "AES-256-CCM"), + Map.entry(904, "AES-128-CTR"), + Map.entry(906, "AES-256-CTR"), + Map.entry(1018, "CHACHA20-POLY1305"), + Map.entry(1019, "CHACHA20"), + Map.entry(1134, "SM4-CBC")); + + /** Cipher NID constant names → cipher names. */ + public static final Map CIPHER_BY_NAME = + Map.ofEntries( + Map.entry("NID_aes_128_ecb", "AES-128-ECB"), + Map.entry("NID_aes_128_cbc", "AES-128-CBC"), + Map.entry("NID_aes_128_ofb128", "AES-128-OFB"), + Map.entry("NID_aes_128_cfb128", "AES-128-CFB"), + Map.entry("NID_aes_128_ctr", "AES-128-CTR"), + Map.entry("NID_aes_128_gcm", "AES-128-GCM"), + Map.entry("NID_aes_128_ccm", "AES-128-CCM"), + Map.entry("NID_aes_192_ecb", "AES-192-ECB"), + Map.entry("NID_aes_192_cbc", "AES-192-CBC"), + Map.entry("NID_aes_192_gcm", "AES-192-GCM"), + Map.entry("NID_aes_256_ecb", "AES-256-ECB"), + Map.entry("NID_aes_256_cbc", "AES-256-CBC"), + Map.entry("NID_aes_256_ofb128", "AES-256-OFB"), + Map.entry("NID_aes_256_cfb128", "AES-256-CFB"), + Map.entry("NID_aes_256_ctr", "AES-256-CTR"), + Map.entry("NID_aes_256_gcm", "AES-256-GCM"), + Map.entry("NID_aes_256_ccm", "AES-256-CCM"), + Map.entry("NID_chacha20", "CHACHA20"), + Map.entry("NID_chacha20_poly1305", "CHACHA20-POLY1305"), + Map.entry("NID_des_ecb", "DES-ECB"), + Map.entry("NID_des_cbc", "DES-CBC"), + Map.entry("NID_des_ede3_ecb", "DESede3-ECB"), + Map.entry("NID_des_ede3_cbc", "DESede3-CBC"), + Map.entry("NID_camellia_128_cbc", "CAMELLIA-128-CBC"), + Map.entry("NID_camellia_256_cbc", "CAMELLIA-256-CBC"), + Map.entry("NID_sm4_cbc", "SM4-CBC"), + Map.entry("NID_bf_cbc", "BLOWFISH-CBC"), + Map.entry("NID_rc4", "RC4")); + + /** HMAC NIDs (obj_mac.h) → MAC names. */ + public static final Map HMAC_BY_CODE = + Map.ofEntries( + Map.entry(780, "HMAC-MD5"), + Map.entry(781, "HMAC-SHA1"), + Map.entry(163, "HMAC-SHA1"), + Map.entry(798, "HMAC-SHA224"), + Map.entry(799, "HMAC-SHA256"), + Map.entry(800, "HMAC-SHA384"), + Map.entry(801, "HMAC-SHA512")); + + /** HMAC NID constant names → MAC names. */ + public static final Map HMAC_BY_NAME = + Map.ofEntries( + Map.entry("NID_hmac_md5", "HMAC-MD5"), + Map.entry("NID_hmac_sha1", "HMAC-SHA1"), + Map.entry("NID_hmacWithSHA1", "HMAC-SHA1"), + Map.entry("NID_hmacWithSHA224", "HMAC-SHA224"), + Map.entry("NID_hmacWithSHA256", "HMAC-SHA256"), + Map.entry("NID_hmacWithSHA384", "HMAC-SHA384"), + Map.entry("NID_hmacWithSHA512", "HMAC-SHA512")); + + /** + * RSA paddings accepted by RSA_public_encrypt and RSA_private_decrypt (rsa.h) → the RSA + * encryption scheme they select. + */ + public static final Map RSA_ENCRYPTION_PADDING_BY_CODE = + Map.ofEntries( + Map.entry(1, "RSA-PKCS1-TYPE2"), // RSA_PKCS1_PADDING + Map.entry(3, "RSA-NO-PADDING"), // RSA_NO_PADDING + Map.entry(4, "RSA-OAEP"), // RSA_PKCS1_OAEP_PADDING + Map.entry(7, "RSA-PKCS1-TYPE2"), // RSA_PKCS1_WITH_TLS_PADDING + Map.entry(8, "RSA-PKCS1-TYPE2")); // RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING + + /** RSA encryption padding constant names → the RSA encryption scheme they select. */ + public static final Map RSA_ENCRYPTION_PADDING_BY_NAME = + Map.ofEntries( + Map.entry("RSA_PKCS1_PADDING", "RSA-PKCS1-TYPE2"), + Map.entry("RSA_NO_PADDING", "RSA-NO-PADDING"), + Map.entry("RSA_PKCS1_OAEP_PADDING", "RSA-OAEP"), + Map.entry("RSA_PKCS1_WITH_TLS_PADDING", "RSA-PKCS1-TYPE2"), + Map.entry("RSA_PKCS1_NO_IMPLICIT_REJECT_PADDING", "RSA-PKCS1-TYPE2")); + + /** + * RSA paddings accepted by RSA_private_encrypt and RSA_public_decrypt (rsa.h) → the RSA + * signature scheme they select. + */ + public static final Map RSA_SIGNATURE_PADDING_BY_CODE = + Map.ofEntries( + Map.entry(1, "RSA-PKCS1"), // RSA_PKCS1_PADDING + Map.entry(3, "RSA-NO-PADDING"), // RSA_NO_PADDING + Map.entry(5, "RSA-X931")); // RSA_X931_PADDING + + /** RSA signature padding constant names → the RSA signature scheme they select. */ + public static final Map RSA_SIGNATURE_PADDING_BY_NAME = + Map.ofEntries( + Map.entry("RSA_PKCS1_PADDING", "RSA-PKCS1"), + Map.entry("RSA_NO_PADDING", "RSA-NO-PADDING"), + Map.entry("RSA_X931_PADDING", "RSA-X931")); + + /** + * Key types a key can be created for from raw bytes (EVP_PKEY_new_raw_private_key, + * EVP_PKEY_new_mac_key; evp.h / obj_mac.h) → key type names. + */ + public static final Map RAW_KEY_TYPE_BY_CODE = + Map.ofEntries( + Map.entry(855, "HMAC"), // EVP_PKEY_HMAC + Map.entry(894, "CMAC"), // EVP_PKEY_CMAC + Map.entry(1061, "POLY1305"), // EVP_PKEY_POLY1305 + Map.entry(1062, "SIPHASH"), // EVP_PKEY_SIPHASH + Map.entry(1034, "X25519"), // EVP_PKEY_X25519 + Map.entry(1035, "X448"), // EVP_PKEY_X448 + Map.entry(1087, "ED25519"), // EVP_PKEY_ED25519 + Map.entry(1088, "ED448")); // EVP_PKEY_ED448 + + /** Raw key type constant names → key type names. */ + public static final Map RAW_KEY_TYPE_BY_NAME = + Map.ofEntries( + Map.entry("EVP_PKEY_HMAC", "HMAC"), + Map.entry("EVP_PKEY_CMAC", "CMAC"), + Map.entry("EVP_PKEY_POLY1305", "POLY1305"), + Map.entry("EVP_PKEY_SIPHASH", "SIPHASH"), + Map.entry("EVP_PKEY_X25519", "X25519"), + Map.entry("EVP_PKEY_X448", "X448"), + Map.entry("EVP_PKEY_ED25519", "ED25519"), + Map.entry("EVP_PKEY_ED448", "ED448")); + + /** OpenSSL key type identifiers (EVP_PKEY_*, evp.h / obj_mac.h) → key type names. */ + public static final Map PKEY_TYPE_BY_CODE = + Map.ofEntries( + Map.entry(6, "RSA"), // EVP_PKEY_RSA + Map.entry(912, "RSA-PSS"), // EVP_PKEY_RSA_PSS + Map.entry(116, "DSA"), // EVP_PKEY_DSA + Map.entry(28, "DH"), // EVP_PKEY_DH + Map.entry(920, "DH"), // EVP_PKEY_DHX + Map.entry(408, "EC"), // EVP_PKEY_EC + Map.entry(1172, "SM2"), // EVP_PKEY_SM2 + Map.entry(1034, "X25519"), // EVP_PKEY_X25519 + Map.entry(1035, "X448"), // EVP_PKEY_X448 + Map.entry(1087, "ED25519"), // EVP_PKEY_ED25519 + Map.entry(1088, "ED448")); // EVP_PKEY_ED448 + + /** OpenSSL key type macro names → key type names. */ + public static final Map PKEY_TYPE_BY_NAME = + Map.ofEntries( + Map.entry("EVP_PKEY_RSA", "RSA"), + Map.entry("EVP_PKEY_RSA_PSS", "RSA-PSS"), + Map.entry("EVP_PKEY_DSA", "DSA"), + Map.entry("EVP_PKEY_DH", "DH"), + Map.entry("EVP_PKEY_DHX", "DH"), + Map.entry("EVP_PKEY_EC", "EC"), + Map.entry("EVP_PKEY_SM2", "SM2"), + Map.entry("EVP_PKEY_X25519", "X25519"), + Map.entry("EVP_PKEY_X448", "X448"), + Map.entry("EVP_PKEY_ED25519", "ED25519"), + Map.entry("EVP_PKEY_ED448", "ED448")); + + /** OpenSSL named DH group NID codes (obj_mac.h) → key-length identifier strings. */ + public static final Map DH_GROUP_BY_CODE = + Map.ofEntries( + Map.entry(1126, "DH-2048"), // NID_ffdhe2048 + Map.entry(1127, "DH-3072"), // NID_ffdhe3072 + Map.entry(1128, "DH-4096")); // NID_ffdhe4096 + + /** OpenSSL named DH group NID constant names → key-length identifier strings. */ + public static final Map DH_GROUP_BY_NAME = + Map.ofEntries( + Map.entry("NID_ffdhe2048", "DH-2048"), + Map.entry("NID_ffdhe3072", "DH-3072"), + Map.entry("NID_ffdhe4096", "DH-4096")); + + /** Numeric OpenSSL protocol version codes → version strings. */ + public static final Map PROTO_VERSION_BY_CODE = + Map.ofEntries( + Map.entry(0x0300, "SSLv3.0"), + Map.entry(0x0301, "TLSv1.0"), + Map.entry(0x0302, "TLSv1.1"), + Map.entry(0x0303, "TLSv1.2"), + Map.entry(0x0304, "TLSv1.3"), + Map.entry(0xFEFF, "DTLSv1.0"), + Map.entry(0xFEFD, "DTLSv1.2"), + Map.entry(0x0100, "DTLSv1.0")); // DTLS1_BAD_VER + + /** + * OpenSSL version constant names → version strings, for a version macro that is not expanded + * and resolves to its own name instead of a numeric code. + */ + public static final Map PROTO_VERSION_BY_NAME = + Map.ofEntries( + Map.entry("SSL3_VERSION", "SSLv3.0"), + Map.entry("TLS1_VERSION", "TLSv1.0"), + Map.entry("TLS1_1_VERSION", "TLSv1.1"), + Map.entry("TLS1_2_VERSION", "TLSv1.2"), + Map.entry("TLS1_3_VERSION", "TLSv1.3"), + Map.entry("DTLS1_VERSION", "DTLSv1.0"), + Map.entry("DTLS1_2_VERSION", "DTLSv1.2"), + Map.entry("DTLS1_BAD_VER", "DTLSv1.0")); + + /** Widens an {@code int} unchanged; the default {@code codeMask}. */ + private static final IntUnaryOperator NO_MASK = code -> code; + + @Nonnull private final Map byCode; + @Nonnull private final Map byName; + @Nonnull private final IntUnaryOperator codeMask; + @Nonnull private final BiFunction> valueConstructor; + + public OpenSSLNidLookupFactory() { + this(CURVE_BY_CODE, CURVE_BY_NAME); + } + + public OpenSSLNidLookupFactory( + @Nonnull Map byCode, @Nonnull Map byName) { + this(byCode, byName, NO_MASK, ValueAction::new); + } + + /** + * @param codeMask applied to a numeric argument's {@code int} value before the {@code byCode} + * lookup (e.g. {@code code -> code & 0xFFFF} to drop width padding on a protocol-version + * code); {@link #NO_MASK} for callers with no such padding. + * @param valueConstructor builds the {@link IValue} the resolved string is wrapped in (e.g. + * {@link ValueAction} for a plain identifier, {@link Protocol} for a protocol version). + */ + public OpenSSLNidLookupFactory( + @Nonnull Map byCode, + @Nonnull Map byName, + @Nonnull IntUnaryOperator codeMask, + @Nonnull BiFunction> valueConstructor) { + this.byCode = byCode; + this.byName = byName; + this.codeMask = codeMask; + this.valueConstructor = valueConstructor; + } + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + final Object value = resolvedValue.value(); + String resolved = null; + + if (value instanceof Number number) { + resolved = byCode.get(codeMask.applyAsInt(number.intValue())); + } else if (value instanceof String str) { + resolved = byName.get(str); + if (resolved == null) { + final Integer code = parseNumeric(str); + if (code != null) { + resolved = byCode.get(codeMask.applyAsInt(code)); + } + } + } + + if (resolved != null) { + LOGGER.debug("Resolved NID argument {} → \"{}\"", value, resolved); + return Optional.of(valueConstructor.apply(resolved, resolvedValue.tree())); + } + LOGGER.debug( + "Could not map NID argument value: {} ({})", + value, + value == null ? "null" : value.getClass().getSimpleName()); + return Optional.empty(); + } + + private static final Pattern INTEGER_SUFFIX_PATTERN = Pattern.compile("[uUlL]+$"); + + /** Parses a decimal or hex ("0x19f") NID literal, tolerating integer suffixes. */ + private static Integer parseNumeric(@Nonnull String raw) { + String s = INTEGER_SUFFIX_PATTERN.matcher(raw.trim()).replaceAll(""); + try { + if (s.length() > 2 && (s.startsWith("0x") || s.startsWith("0X"))) { + return Integer.parseInt(s.substring(2), 16); + } + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMac.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMac.java new file mode 100644 index 000000000..48b487b05 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMac.java @@ -0,0 +1,191 @@ +/* + * 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.openssl.mac; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.MacContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.ibm.plugin.rules.detection.openssl.params.OpenSSLParams; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL EVP MAC (Message Authentication Code) algorithms. + * + *

The EVP_MAC API introduced in OpenSSL 3.0 selects the MAC by name in {@code EVP_MAC_fetch} + * (HMAC, CMAC, GMAC, KMAC-128, POLY1305, SIPHASH, ...). The digest of an HMAC and the cipher of a + * CMAC or GMAC are set on the context created from the fetched MAC, through the {@code "digest"} or + * {@code "cipher"} entry of an {@code OSSL_PARAM} array passed to {@code EVP_MAC_CTX_set_params} or + * {@code EVP_MAC_init}: + * + *

{@code
+ * EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
+ * EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac);
+ * EVP_MAC_init(ctx, key, keylen, params); // params contains {"digest", "SHA256"}
+ * }
+ * + *

The password-based MAC of CRMF is reported with the MAC and the one-way function given to + * {@code OSSL_CRMF_pbmp_new}. + */ +public final class OpenSSLEvpMac extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + private static final List> PARAMS_RULES = + Stream.of( + RuleSets.rulesOf(OpenSSLParams.Digests.class).stream(), + RuleSets.rulesOf(OpenSSLParams.Ciphers.class).stream()) + .flatMap(i -> i) + .toList(); + + private static final IDetectionRule EVP_MAC_CTX_SET_PARAMS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_MAC_CTX_set_params") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(PARAMS_RULES) + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_MAC_init(ctx, key, keylen, params) + private static final IDetectionRule EVP_MAC_INIT = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_MAC_init") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(PARAMS_RULES) + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_MAC_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_MAC_CTX_new") + .withMethodParameter("*") + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(EVP_MAC_CTX_SET_PARAMS, EVP_MAC_INIT)); + + private static final IDetectionRule EVP_MAC_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_MAC_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.MAC_NAMES)) + .withMethodParameter("*") + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(EVP_MAC_CTX_NEW)); + + // EVP_Q_mac(libctx, name, propq, subalg, params, key, keylen, data, datalen, out, outsize, + // outlen): the MAC, and the digest of an HMAC or the cipher of a CMAC or GMAC + private static final IDetectionRule EVP_Q_MAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_Q_mac") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.MAC_NAMES)) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLMacSubAlgorithmFactory()) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // OSSL_CRMF_pbmp_new(libctx, slen, owfnid, itercnt, macnid): the password-based MAC of + // CRMF (RFC 4211) derives its key with the one-way function owfnid and computes the MAC + // macnid + + private static final IDetectionRule OSSL_CRMF_PBMP_NEW_MAC = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_CRMF_pbmp_new") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.HMAC_BY_CODE, + OpenSSLNidLookupFactory.HMAC_BY_NAME)) + .buildForContext(new MacContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule OSSL_CRMF_PBMP_NEW_OWF = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_CRMF_pbmp_new") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.DIGEST_BY_CODE, + OpenSSLNidLookupFactory.DIGEST_BY_NAME)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_MAC_FETCH, EVP_Q_MAC, OSSL_CRMF_PBMP_NEW_MAC, OSSL_CRMF_PBMP_NEW_OWF); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLMacSubAlgorithmFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLMacSubAlgorithmFactory.java new file mode 100644 index 000000000..fb00a587c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLMacSubAlgorithmFactory.java @@ -0,0 +1,48 @@ +/* + * 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.openssl.mac; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.AlgorithmParameter; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Resolves the algorithm a MAC is computed with, the {@code subalg} argument of {@code EVP_Q_mac}: + * the name of the digest of an HMAC or of the cipher of a CMAC or GMAC, e.g. {@code "SHA256"}. The + * name is kept as an algorithm parameter of the MAC, which the MAC translation maps to the digest + * or the cipher it names. + */ +public final class OpenSSLMacSubAlgorithmFactory implements IValueFactory { + + @Nonnull + @Override + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (!(resolvedValue.value() instanceof String name) || name.isBlank()) { + return Optional.empty(); + } + return Optional.of( + new AlgorithmParameter<>( + name.trim(), AlgorithmParameter.Kind.ANY, resolvedValue.tree())); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/params/OpenSSLParams.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/params/OpenSSLParams.java new file mode 100644 index 000000000..d32c3e68d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/params/OpenSSLParams.java @@ -0,0 +1,198 @@ +/* + * 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.openssl.params; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for the entries of an {@code OSSL_PARAM} array that name an algorithm: the {@code + * "digest"} and {@code "cipher"} parameters of a KDF, MAC or DRBG context. + * + *

An entry is built by {@code OSSL_PARAM_construct_utf8_string(key, value, size)} or by the + * {@code OSSL_PARAM_utf8_string(key, value, size)} macro (params.h), with the key given as a string + * or as its name macro (core_names.h, e.g. {@code OSSL_KDF_PARAM_DIGEST} for {@code "digest"}). The + * rules match the entry by its key and detect its value, wherever the entry is written to the array + * passed to the call that takes the parameters: + * + *

{@code
+ * OSSL_PARAM params[] = {
+ *     OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, "SHA256", 0),
+ *     OSSL_PARAM_construct_end()
+ * };
+ * EVP_KDF_CTX_set_params(kctx, params);
+ * }
+ * + *

The parameters can also be built with an {@code OSSL_PARAM_BLD} (param_build.h), whose entries + * are pushed by {@code OSSL_PARAM_BLD_push_utf8_string(bld, key, value, size)} or {@code + * OSSL_PARAM_BLD_push_utf8_ptr(bld, key, value, size)} and turned into the array by {@code + * OSSL_PARAM_BLD_to_param(bld)}: + * + *

{@code
+ * OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
+ * OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, "SHA256", 0);
+ * OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld);
+ * EVP_KDF_CTX_set_params(kctx, params);
+ * }
+ * + *

The calls that take the parameters list {@link Digests} or {@link Ciphers} as the depending + * rules of their {@code OSSL_PARAM} argument. + */ +public final class OpenSSLParams { + + private static final String BUNDLE = "OpenSSL"; + + /** The functions and macros that build a UTF-8 string entry from a key, value and size. */ + private static final String[] UTF8_STRING_ENTRIES = { + "OSSL_PARAM_construct_utf8_string", "OSSL_PARAM_utf8_string" + }; + + /** The keys of the digest entry: the string and the macros that stand for it. */ + private static final List DIGEST_KEYS = + List.of( + "\"digest\"", + "OSSL_ALG_PARAM_DIGEST", + "OSSL_KDF_PARAM_DIGEST", + "OSSL_MAC_PARAM_DIGEST", + "OSSL_DRBG_PARAM_DIGEST"); + + /** The keys of the cipher entry: the string and the macros that stand for it. */ + private static final List CIPHER_KEYS = + List.of( + "\"cipher\"", + "OSSL_ALG_PARAM_CIPHER", + "OSSL_KDF_PARAM_CIPHER", + "OSSL_MAC_PARAM_CIPHER", + "OSSL_DRBG_PARAM_CIPHER"); + + /** The functions that push a UTF-8 string entry from a key, value and size to a builder. */ + private static final String[] BUILDER_UTF8_STRING_ENTRIES = { + "OSSL_PARAM_BLD_push_utf8_string", "OSSL_PARAM_BLD_push_utf8_ptr" + }; + + private OpenSSLParams() { + // private + } + + @Nonnull + private static List> entryRules( + @Nonnull List keys, + @Nonnull Map names, + @Nonnull DetectionContext context) { + return keys.stream() + .map( + key -> + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(UTF8_STRING_ENTRIES) + .withMethodParameter(key) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory(names)) + .withMethodParameter("*") + .buildForContext(context) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules()) + .toList(); + } + + /** + * The rules for the entries pushed to a builder, {@code OSSL_PARAM_BLD_push_utf8_string(bld, + * key, value, size)}, matched by their key. + */ + @Nonnull + private static List> builderEntryRules( + @Nonnull List keys, + @Nonnull Map names, + @Nonnull DetectionContext context) { + return keys.stream() + .map( + key -> + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(BUILDER_UTF8_STRING_ENTRIES) + .withMethodParameter("*") + .withMethodParameter(key) + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory(names)) + .withMethodParameter("*") + .buildForContext(context) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules()) + .toList(); + } + + /** + * The entry rules of an {@code OSSL_PARAM} array, and the rule for the array built by {@code + * OSSL_PARAM_BLD_to_param(bld)}, which follows the builder to the entries pushed to it. + */ + @Nonnull + private static List> rules( + @Nonnull List keys, + @Nonnull Map names, + @Nonnull DetectionContext context) { + final IDetectionRule toParam = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_PARAM_BLD_to_param") + .withMethodParameter("*") + .addDependingDetectionRules(builderEntryRules(keys, names, context)) + .buildForContext(context) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + return Stream.concat(entryRules(keys, names, context).stream(), Stream.of(toParam)) + .toList(); + } + + /** The rules for the {@code "digest"} entry, detecting the digest it names. */ + public static final class Digests extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return rules( + DIGEST_KEYS, OpenSSLNameCanonicalizerFactory.DIGEST_NAMES, new DigestContext()); + } + } + + /** The rules for the {@code "cipher"} entry, detecting the cipher it names. */ + public static final class Ciphers extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return rules( + CIPHER_KEYS, OpenSSLNameCanonicalizerFactory.CIPHER_NAMES, new CipherContext()); + } + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRand.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRand.java new file mode 100644 index 000000000..4f1e200a6 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRand.java @@ -0,0 +1,214 @@ +/* + * 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.openssl.rand; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.PRNGContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.params.OpenSSLParams; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL random number generation. + * + *

These rules detect random number generation through the RAND API and EVP_RAND API. Covers + * basic RAND_bytes operations and Deterministic Random Bit Generators (DRBG) using CTR, HASH, and + * HMAC modes. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLRand extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Legacy RAND API + + private static final IDetectionRule RAND_BYTES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RAND_bytes") + .shouldBeDetectedAs(new ValueActionFactory<>("RAND")) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RAND_PRIV_BYTES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RAND_priv_bytes") + .shouldBeDetectedAs(new ValueActionFactory<>("RAND")) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_RAND API: the DRBG is fetched by name; its cipher (CTR-DRBG) or digest (HASH-DRBG, + // HMAC-DRBG) is set through OSSL_PARAMs of the context created from it + + private static final List> PARAMS_RULES = + Stream.of( + RuleSets.rulesOf(OpenSSLParams.Ciphers.class).stream(), + RuleSets.rulesOf(OpenSSLParams.Digests.class).stream()) + .flatMap(i -> i) + .toList(); + + private static final IDetectionRule EVP_RAND_CTX_SET_PARAMS = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_RAND_CTX_set_params") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(PARAMS_RULES) + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_RAND_instantiate(ctx, strength, prediction_resistance, pstr, pstr_len, params) + private static final IDetectionRule EVP_RAND_INSTANTIATE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_RAND_instantiate") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(PARAMS_RULES) + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule EVP_RAND_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_RAND_CTX_new") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules( + List.of(EVP_RAND_CTX_SET_PARAMS, EVP_RAND_INSTANTIATE)); + + private static final IDetectionRule EVP_RAND_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_RAND_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(List.of(EVP_RAND_CTX_NEW)); + + // EVP_RAND seed source + + private static final IDetectionRule RAND_SET_SEED_SOURCE_TYPE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RAND_set_seed_source_type") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // 3.0+ ex-variants and DRBG type selector + + private static final IDetectionRule RAND_BYTES_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RAND_bytes_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("RAND")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RAND_PRIV_BYTES_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RAND_priv_bytes_ex") + .shouldBeDetectedAs(new ValueActionFactory<>("RAND")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule RAND_SET_DRBG_TYPE = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("RAND_set_DRBG_type") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new PRNGContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // Legacy RAND API + RAND_BYTES, + RAND_PRIV_BYTES, + // EVP_RAND API + EVP_RAND_FETCH, + // EVP_RAND seed source + RAND_SET_SEED_SOURCE_TYPE, + // 3.0+ ex-variants + DRBG type + RAND_BYTES_EX, + RAND_PRIV_BYTES_EX, + RAND_SET_DRBG_TYPE); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLEvpSignature.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLEvpSignature.java new file mode 100644 index 000000000..dbc1b89f3 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLEvpSignature.java @@ -0,0 +1,148 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.signature; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLNameCanonicalizerFactory; +import com.ibm.plugin.translation.translator.contexts.CxxDigestContextTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL signature operations. + * + *

These rules detect the signature algorithm fetched by name ({@code EVP_SIGNATURE_fetch}), the + * RSA-PSS and MGF1 settings of a signing context, and the time-stamping signer digest given by + * name. The signature algorithm of {@code EVP_DigestSign}/{@code EVP_PKEY_sign} and of the CMS, + * PKCS#7 and OCSP signing functions is the type of the key given to them; those operations and the + * digest they use are detected by {@code OpenSSLEvpKeyUsage}. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLEvpSignature extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // Signature algorithm fetched by name + + private static final IDetectionRule EVP_SIGNATURE_FETCH = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_SIGNATURE_fetch") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .withMethodParameter("*") + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // RSA setters (signature-related) + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_MGF1_MD_NAME = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_mgf1_md_name") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .buildForContext( + new DigestContext( + Map.of( + CxxDigestContextTranslator.KIND, + CxxDigestContextTranslator.MGF1_KIND))) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, saltlen) is only accepted on an RSA-PSS signing + // context + + private static final IDetectionRule EVP_PKEY_CTX_SET_RSA_PSS_SALTLEN = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("EVP_PKEY_CTX_set_rsa_pss_saltlen") + .shouldBeDetectedAs(new ValueActionFactory<>("RSA-PSS")) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLSaltLengthFactory()) + .asChildOfParameterWithId(-1) + .buildForContext(new SignatureContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // TS_CONF_set_signer_digest(conf, section, md, ctx): the time-stamping signer digest, given + // by name + + private static final IDetectionRule TS_CONF_SET_SIGNER_DIGEST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TS_CONF_set_signer_digest") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs( + new OpenSSLNameCanonicalizerFactory( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES)) + .withMethodParameter("*") + .buildForContext(new DigestContext()) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // Fetch + EVP_SIGNATURE_FETCH, + // RSA setters + EVP_PKEY_CTX_SET_RSA_MGF1_MD_NAME, + EVP_PKEY_CTX_SET_RSA_PSS_SALTLEN, + // RFC 3161 time-stamping + TS_CONF_SET_SIGNER_DIGEST); + } + + /** + * The rules for the RSA-PSS settings of a signing context: its salt length and the digest of + * its mask generation function, set on the context a digest sign or verify operation returns in + * {@code pctx}. + */ + public static final class SigningContext extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return List.of(EVP_PKEY_CTX_SET_RSA_PSS_SALTLEN, EVP_PKEY_CTX_SET_RSA_MGF1_MD_NAME); + } + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLSaltLengthFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLSaltLengthFactory.java new file mode 100644 index 000000000..ad0a8e480 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLSaltLengthFactory.java @@ -0,0 +1,49 @@ +/* + * 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.openssl.signature; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SaltSize; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Resolves the RSA-PSS salt length argument of {@code EVP_PKEY_CTX_set_rsa_pss_saltlen} and {@code + * EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen}, given in bytes, to a salt size in bits. The negative + * special values ({@code RSA_PSS_SALTLEN_DIGEST}, {@code RSA_PSS_SALTLEN_MAX}, ...) select a length + * derived from the digest or the key and resolve to nothing, as does an argument that is not an + * integer. + */ +public final class OpenSSLSaltLengthFactory implements IValueFactory { + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (resolvedValue.value() instanceof Number bytes && bytes.intValue() > 0) { + return Optional.of( + new SaltSize<>(bytes.intValue() * 8, Size.UnitType.BIT, resolvedValue.tree())); + } + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLConfCommandFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLConfCommandFactory.java new file mode 100644 index 000000000..621471184 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLConfCommandFactory.java @@ -0,0 +1,101 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.ssl; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.language.cxx.CxxSemantic; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * Resolves the setting made by {@code SSL_CONF_cmd(cctx, cmd, value)} when {@code cmd} is one of + * the given commands, e.g. {@code "CipherString"} and its command line form {@code "-cipher"}. The + * factory is applied to the {@code cmd} argument; the {@code value} argument of the same call is + * resolved and handed to {@code valueFactory}, which gives the result. Any other command, or a + * {@code cmd} that is not given at the call, resolves to nothing. + */ +public final class OpenSSLConfCommandFactory implements IValueFactory { + + private static final int VALUE_ARGUMENT = 2; + + @Nonnull private final Set commands; + @Nonnull private final IValueFactory valueFactory; + + public OpenSSLConfCommandFactory( + @Nonnull Set commands, @Nonnull IValueFactory valueFactory) { + this.commands = + commands.stream() + .map(command -> command.toLowerCase(Locale.ROOT)) + .collect(Collectors.toUnmodifiableSet()); + this.valueFactory = valueFactory; + } + + @Override + @Nonnull + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (!(resolvedValue.value() instanceof String command) + || !commands.contains(command.trim().toLowerCase(Locale.ROOT))) { + return Optional.empty(); + } + final AstNode call = enclosingCall(resolvedValue.tree()); + if (call == null) { + return Optional.empty(); + } + final List arguments = CxxAstNodeHelper.getFunctionCallArguments(call); + if (arguments.size() <= VALUE_ARGUMENT) { + return Optional.empty(); + } + final AstNode valueArgument = arguments.get(VALUE_ARGUMENT); + for (ResolvedValue value : + CxxSemantic.resolveValues( + Object.class, valueArgument, new LinkedList<>(), null, false, null)) { + final Optional> setting = valueFactory.apply(value); + if (setting.isPresent()) { + return setting; + } + } + return Optional.empty(); + } + + /** + * The {@code SSL_CONF_cmd} call of the command: the tree of a value resolved at the call is the + * call itself, otherwise the call the value is written in. + */ + @Nullable private static AstNode enclosingCall(@Nonnull AstNode tree) { + for (AstNode node = tree; node != null; node = node.getParent()) { + if (CxxAstNodeHelper.isFunctionCall(node)) { + return "SSL_CONF_cmd".equals(CxxAstNodeHelper.getFunctionCallName(node)) + ? node + : null; + } + } + return null; + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibssl.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibssl.java new file mode 100644 index 000000000..7204178c6 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibssl.java @@ -0,0 +1,842 @@ +/* + * 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.openssl.ssl; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Protocol; +import com.ibm.engine.model.context.ProtocolContext; +import com.ibm.engine.model.factory.AlgorithmFactory; +import com.ibm.engine.model.factory.CipherSuiteFactory; +import com.ibm.engine.model.factory.IValueFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDh; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyEc; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLNidLookupFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for OpenSSL libssl (SSL/TLS protocol) functions. + * + *

These rules detect the protocol selected by the {@code *_method()} functions (TLS 1.0-1.3, + * DTLS 1.0-1.2, QUIC and SSLv3), and the protocol versions, cipher suites, key exchange groups, + * signature algorithms, ephemeral DH groups and EC curves and SRTP protection profiles configured + * on a context ({@link ContextSettings}) or a connection ({@link ConnectionSettings}), directly or + * through {@code SSL_CONF_cmd}. A context created in the scanned code is reported with its method + * and its configuration ({@link OpenSSLSslContext}); the rules are detection rules on their own as + * well, for a context or connection created elsewhere. + */ +@SuppressWarnings("java:S1192") +public final class OpenSSLLibssl extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // TLS Generic (Version Negotiation); SSLv23_*method are their former names (ssl.h) + + private static final IDetectionRule TLS_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLS_method", "SSLv23_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLS")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLS_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLS_client_method", "SSLv23_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLS")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLS_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLS_server_method", "SSLv23_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLS")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // TLS 1.2 (RFC 5246) + + private static final IDetectionRule TLSV1_2_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_2_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.2")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLSV1_2_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_2_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.2")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLSV1_2_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_2_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.2")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // TLS 1.1 (Deprecated) + + private static final IDetectionRule TLSV1_1_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_1_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.1")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLSV1_1_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_1_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.1")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLSV1_1_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_1_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.1")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // TLS 1.0 (Deprecated) + + private static final IDetectionRule TLSV1_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLSV1_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule TLSV1_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("TLSv1_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("TLSv1.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // SSL 3.0 (Insecure - disabled by default) + + private static final IDetectionRule SSLV3_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSLv3_method") + .shouldBeDetectedAs(new ValueActionFactory<>("SSLv3.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSLV3_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSLv3_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("SSLv3.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSLV3_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSLv3_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("SSLv3.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // DTLS Generic + + private static final IDetectionRule DTLS_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLS_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLS")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DTLS_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLS_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLS")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DTLS_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLS_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLS")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // DTLS 1.2 (RFC 6347) + + private static final IDetectionRule DTLSV1_2_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLSv1_2_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLSv1.2")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DTLSV1_2_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLSv1_2_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLSv1.2")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DTLSV1_2_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLSv1_2_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLSv1.2")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // DTLS 1.0 (Deprecated) + + private static final IDetectionRule DTLSV1_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLSv1_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLSv1.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DTLSV1_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLSv1_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLSv1.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule DTLSV1_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("DTLSv1_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("DTLSv1.0")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // QUIC (RFC 9000 - OpenSSL 3.2+) + + private static final IDetectionRule OSSL_QUIC_CLIENT_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_QUIC_client_method") + .shouldBeDetectedAs(new ValueActionFactory<>("QUIC")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule OSSL_QUIC_CLIENT_THREAD_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_QUIC_client_thread_method") + .shouldBeDetectedAs(new ValueActionFactory<>("QUIC")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule OSSL_QUIC_SERVER_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("OSSL_QUIC_server_method") + .shouldBeDetectedAs(new ValueActionFactory<>("QUIC")) + .withoutParameters() + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Cipher suite configuration: the cipher string lists the enabled suites, separated by colons + // (TLS 1.2 and below use OpenSSL suite names, TLS 1.3 uses the standard names) + + private static final IDetectionRule SSL_CTX_SET_CIPHER_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set_cipher_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new CipherSuiteFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET_CIPHER_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set_cipher_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new CipherSuiteFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_CTX_SET_CIPHERSUITES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set_ciphersuites") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new CipherSuiteFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET_CIPHERSUITES = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set_ciphersuites") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new CipherSuiteFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Protocol Version Configuration + + // Detection matches the literal API call (no OpenSSL headers required, so the + // SSL_(CTX_)set_min/max_proto_version macros are not expanded). The version argument is + // captured and resolved by PROTO_VERSION_FACTORY, the IValueFactory pattern the Java + // plugin uses. + private static final OpenSSLNidLookupFactory PROTO_VERSION_FACTORY = + new OpenSSLNidLookupFactory( + OpenSSLNidLookupFactory.PROTO_VERSION_BY_CODE, + OpenSSLNidLookupFactory.PROTO_VERSION_BY_NAME, + code -> code & 0xFFFF, + Protocol::new); + + /** Protocol version names accepted by the SSL_CONF MinProtocol and MaxProtocol commands. */ + private static final Map CONF_PROTOCOL_VERSIONS = + Map.ofEntries( + Map.entry("SSLv3", "SSLv3.0"), + Map.entry("TLSv1", "TLSv1.0"), + Map.entry("TLSv1.1", "TLSv1.1"), + Map.entry("TLSv1.2", "TLSv1.2"), + Map.entry("TLSv1.3", "TLSv1.3"), + Map.entry("DTLSv1", "DTLSv1.0"), + Map.entry("DTLSv1.2", "DTLSv1.2")); + + private static final IDetectionRule SSL_CTX_SET_MIN_PROTO_VERSION = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set_min_proto_version") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PROTO_VERSION_FACTORY) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_MINIMUM_VERSION)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_CTX_SET_MAX_PROTO_VERSION = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set_max_proto_version") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PROTO_VERSION_FACTORY) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_MAXIMUM_VERSION)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET_MIN_PROTO_VERSION = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set_min_proto_version") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PROTO_VERSION_FACTORY) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_MINIMUM_VERSION)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET_MAX_PROTO_VERSION = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set_max_proto_version") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(PROTO_VERSION_FACTORY) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_MAXIMUM_VERSION)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // SSL_CTX_set_options(ctx, op) / SSL_set_options(ssl, op): the protocol versions disabled by + // the SSL_OP_NO_* options; the options set on a context add up, and the versions disabled by + // all of them bound the range of versions the context uses + + private static final IDetectionRule SSL_CTX_SET_OPTIONS = + protocolOptions("SSL_CTX_set_options"); + + private static final IDetectionRule SSL_SET_OPTIONS = + protocolOptions("SSL_set_options"); + + @Nonnull + private static IDetectionRule protocolOptions(@Nonnull String function) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(function) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLProtocolOptionsFactory()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_DISABLED_VERSIONS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + // KEX Group / Curve Configuration (literal API calls; headers not required) + // SSL_(CTX_)set1_curves* are #define aliases of the set1_groups* forms, matched by both names + // as the header defining the aliases is not part of the analyzed code. + + // SSL_CTX_set1_groups/SSL_set1_groups take a raw int* NID buffer, not a string or object to + // resolve an algorithm name from - no finding is raised for these, unlike their *_list + // siblings below which take a colon-separated name string. + + private static final IDetectionRule SSL_CTX_SET1_GROUPS_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set1_groups_list", "SSL_CTX_set1_curves_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_GROUPS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET1_GROUPS_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set1_groups_list", "SSL_set1_curves_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_GROUPS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Signature Algorithm Configuration (literal API calls; headers not required) + + // SSL_CTX_set1_sigalgs/SSL_set1_sigalgs/SSL_CTX_set1_client_sigalgs take a raw int* sigalg-ID + // buffer, not a string or object to resolve an algorithm name from - no finding is raised for + // these, unlike their *_list siblings which take a colon-separated name string. + + private static final IDetectionRule SSL_CTX_SET1_SIGALGS_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set1_sigalgs_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext( + new ProtocolContext(ProtocolContext.Kind.TLS_SIGNATURE_ALGORITHMS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET1_SIGALGS_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set1_sigalgs_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext( + new ProtocolContext(ProtocolContext.Kind.TLS_SIGNATURE_ALGORITHMS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_CTX_SET1_CLIENT_SIGALGS_LIST = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set1_client_sigalgs_list") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext( + new ProtocolContext(ProtocolContext.Kind.TLS_SIGNATURE_ALGORITHMS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // SSL_CONF (string-driven config) + + // SSL_CONF_cmd(cctx, cmd, value): the value is read according to the command it sets + + private static final IDetectionRule SSL_CONF_CMD_MINIMUM_PROTOCOL_VERSION = + sslConfCmd( + Set.of("MinProtocol", "-min_protocol"), + new OpenSSLNidLookupFactory( + Map.of(), CONF_PROTOCOL_VERSIONS, code -> code, Protocol::new), + ProtocolContext.Kind.TLS_MINIMUM_VERSION); + + private static final IDetectionRule SSL_CONF_CMD_MAXIMUM_PROTOCOL_VERSION = + sslConfCmd( + Set.of("MaxProtocol", "-max_protocol"), + new OpenSSLNidLookupFactory( + Map.of(), CONF_PROTOCOL_VERSIONS, code -> code, Protocol::new), + ProtocolContext.Kind.TLS_MAXIMUM_VERSION); + + private static final IDetectionRule SSL_CONF_CMD_CIPHERS = + sslConfCmd( + Set.of("CipherString", "Ciphersuites", "-cipher", "-ciphersuites"), + new CipherSuiteFactory<>(), + ProtocolContext.Kind.TLS); + + private static final IDetectionRule SSL_CONF_CMD_GROUPS = + sslConfCmd( + Set.of("Groups", "Curves", "-groups", "-curves"), + new AlgorithmFactory<>(), + ProtocolContext.Kind.TLS_GROUPS); + + private static final IDetectionRule SSL_CONF_CMD_SIGNATURE_ALGORITHMS = + sslConfCmd( + Set.of( + "SignatureAlgorithms", + "ClientSignatureAlgorithms", + "-sigalgs", + "-client_sigalgs"), + new AlgorithmFactory<>(), + ProtocolContext.Kind.TLS_SIGNATURE_ALGORITHMS); + + @Nonnull + private static IDetectionRule sslConfCmd( + @Nonnull Set commands, + @Nonnull IValueFactory valueFactory, + @Nonnull ProtocolContext.Kind kind) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CONF_cmd") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new OpenSSLConfCommandFactory(commands, valueFactory)) + .withMethodParameter("*") + .buildForContext(new ProtocolContext(kind)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + // SRTP protection profile selection: a colon-separated list of profile names + + private static final IDetectionRule SSL_CTX_SET_TLSEXT_USE_SRTP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set_tlsext_use_srtp") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.SRTP)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET_TLSEXT_USE_SRTP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set_tlsext_use_srtp") + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new AlgorithmFactory<>()) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.SRTP)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // The method, set on a context or a connection after its creation + + private static final IDetectionRule SSL_CTX_SET_SSL_VERSION = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_set_ssl_version") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLLibssl.Methods.class)) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + private static final IDetectionRule SSL_SET_SSL_METHOD = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_set_ssl_method") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLLibssl.Methods.class)) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + + // Ephemeral key exchange parameters: the DH group or the EC curve of the given key + + private static final IDetectionRule SSL_CTX_SET_TMP_DH = + ephemeralKeyExchange("SSL_CTX_set_tmp_dh", RuleSets.rulesOf(OpenSSLLegacyDh.class)); + + private static final IDetectionRule SSL_SET_TMP_DH = + ephemeralKeyExchange("SSL_set_tmp_dh", RuleSets.rulesOf(OpenSSLLegacyDh.class)); + + private static final IDetectionRule SSL_CTX_SET_TMP_ECDH = + ephemeralKeyExchange("SSL_CTX_set_tmp_ecdh", RuleSets.rulesOf(OpenSSLLegacyEc.class)); + + private static final IDetectionRule SSL_SET_TMP_ECDH = + ephemeralKeyExchange("SSL_set_tmp_ecdh", RuleSets.rulesOf(OpenSSLLegacyEc.class)); + + @Nonnull + private static IDetectionRule ephemeralKeyExchange( + @Nonnull String function, @Nonnull List> keyRules) { + return new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods(function) + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(keyRules) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS_GROUPS)) + .inBundle(() -> BUNDLE) + .withoutDependingDetectionRules(); + } + + /** The {@code *_method()} functions, each selecting a protocol. */ + public static final class Methods extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return List.of( + // TLS Generic + TLS_METHOD, + TLS_CLIENT_METHOD, + TLS_SERVER_METHOD, + // TLS 1.2 + TLSV1_2_METHOD, + TLSV1_2_CLIENT_METHOD, + TLSV1_2_SERVER_METHOD, + // TLS 1.1 (Deprecated) + TLSV1_1_METHOD, + TLSV1_1_CLIENT_METHOD, + TLSV1_1_SERVER_METHOD, + // TLS 1.0 (Deprecated) + TLSV1_METHOD, + TLSV1_CLIENT_METHOD, + TLSV1_SERVER_METHOD, + // SSL 3.0 (Insecure) + SSLV3_METHOD, + SSLV3_CLIENT_METHOD, + SSLV3_SERVER_METHOD, + // DTLS Generic + DTLS_METHOD, + DTLS_CLIENT_METHOD, + DTLS_SERVER_METHOD, + // DTLS 1.2 + DTLSV1_2_METHOD, + DTLSV1_2_CLIENT_METHOD, + DTLSV1_2_SERVER_METHOD, + // DTLS 1.0 (Deprecated) + DTLSV1_METHOD, + DTLSV1_CLIENT_METHOD, + DTLSV1_SERVER_METHOD, + // QUIC + OSSL_QUIC_CLIENT_METHOD, + OSSL_QUIC_CLIENT_THREAD_METHOD, + OSSL_QUIC_SERVER_METHOD); + } + } + + /** The configuration of a context, {@code SSL_CTX_*(ctx, ...)}. */ + public static final class ContextSettings extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return List.of( + SSL_CTX_SET_SSL_VERSION, + SSL_CTX_SET_CIPHER_LIST, + SSL_CTX_SET_CIPHERSUITES, + SSL_CTX_SET_MIN_PROTO_VERSION, + SSL_CTX_SET_MAX_PROTO_VERSION, + SSL_CTX_SET_OPTIONS, + SSL_CTX_SET1_GROUPS_LIST, + SSL_CTX_SET1_SIGALGS_LIST, + SSL_CTX_SET1_CLIENT_SIGALGS_LIST, + SSL_CTX_SET_TMP_DH, + SSL_CTX_SET_TMP_ECDH, + SSL_CTX_SET_TLSEXT_USE_SRTP); + } + } + + /** The configuration of a connection, {@code SSL_*(ssl, ...)}. */ + public static final class ConnectionSettings extends DetectionRuleSet { + @Nonnull + @Override + protected List> buildRules() { + return List.of( + SSL_SET_SSL_METHOD, + SSL_SET_CIPHER_LIST, + SSL_SET_CIPHERSUITES, + SSL_SET_MIN_PROTO_VERSION, + SSL_SET_MAX_PROTO_VERSION, + SSL_SET_OPTIONS, + SSL_SET1_GROUPS_LIST, + SSL_SET1_SIGALGS_LIST, + SSL_SET_TMP_DH, + SSL_SET_TMP_ECDH, + SSL_SET_TLSEXT_USE_SRTP); + } + } + + @Nonnull + @Override + protected List> buildRules() { + return Stream.of( + RuleSets.rulesOf(OpenSSLLibssl.Methods.class).stream(), + // contexts, reported with their method and configuration + RuleSets.rulesOf(OpenSSLSslContext.class).stream(), + RuleSets.rulesOf(OpenSSLLibssl.ContextSettings.class).stream(), + RuleSets.rulesOf(OpenSSLLibssl.ConnectionSettings.class).stream(), + // SSL_CONF (string-driven config) + Stream.of( + SSL_CONF_CMD_MINIMUM_PROTOCOL_VERSION, + SSL_CONF_CMD_MAXIMUM_PROTOCOL_VERSION, + SSL_CONF_CMD_CIPHERS, + SSL_CONF_CMD_GROUPS, + SSL_CONF_CMD_SIGNATURE_ALGORITHMS)) + .flatMap(rules -> rules) + .toList(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLProtocolOptionsFactory.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLProtocolOptionsFactory.java new file mode 100644 index 000000000..b691c6919 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLProtocolOptionsFactory.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.ssl; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.Protocol; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Resolves the protocol versions disabled by the options of {@code SSL_CTX_set_options(ctx, op)} or + * {@code SSL_set_options(ssl, op)}, e.g. {@code SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1}, to the + * colon-separated list of these versions, lowest first, e.g. {@code TLSv1.0:TLSv1.1}. The options + * set on a context add up, so the range of versions they leave is resolved from the versions + * disabled by all the options of the context (see {@code ProtocolVersionReorganizer}); options that + * disable no version resolve to nothing. + * + *

The versions are those of TLS, or of DTLS for the {@code SSL_OP_NO_DTLS*} options. SSL 3.0 is + * not built into OpenSSL by default since 1.1.0, so {@code SSL_OP_NO_SSLv3} disables no version. + * The options are read by their names: {@code SSL_OP_NO_DTLSv1} has the value of {@code + * SSL_OP_NO_TLSv1}, and which one is meant depends on the method of the context. + */ +public final class OpenSSLProtocolOptionsFactory implements IValueFactory { + + /** The option disabling each version, lowest version first. */ + private static final Map VERSION_OPTIONS = new LinkedHashMap<>(); + + static { + VERSION_OPTIONS.put("SSL_OP_NO_TLSv1", "TLSv1.0"); + VERSION_OPTIONS.put("SSL_OP_NO_TLSv1_1", "TLSv1.1"); + VERSION_OPTIONS.put("SSL_OP_NO_TLSv1_2", "TLSv1.2"); + VERSION_OPTIONS.put("SSL_OP_NO_TLSv1_3", "TLSv1.3"); + VERSION_OPTIONS.put("SSL_OP_NO_DTLSv1", "DTLSv1.0"); + VERSION_OPTIONS.put("SSL_OP_NO_DTLSv1_2", "DTLSv1.2"); + } + + @Nonnull + @Override + public Optional> apply(@Nonnull ResolvedValue resolvedValue) { + if (!(resolvedValue.value() instanceof String options)) { + return Optional.empty(); + } + final Set flags = + Stream.of(options.split("\\|")).map(String::trim).collect(Collectors.toSet()); + final String disabled = + VERSION_OPTIONS.entrySet().stream() + .filter(option -> flags.contains(option.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.joining(":")); + if (disabled.isEmpty()) { + return Optional.empty(); + } + return Optional.of(new Protocol<>(disabled, resolvedValue.tree())); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLSslContext.java b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLSslContext.java new file mode 100644 index 000000000..535a8720b --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLSslContext.java @@ -0,0 +1,104 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.ssl; + +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.context.ProtocolContext; +import com.ibm.engine.rule.DetectionRuleSet; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Detection rules for the creation of an OpenSSL TLS context, {@code SSL_CTX_new(method)} and + * {@code SSL_CTX_new_ex(libctx, propq, method)}. The protocol is given by the method; the context + * is followed to its configuration ({@link OpenSSLLibssl.ContextSettings}) and to the connections + * created from it with {@code SSL_new(ctx)}, followed to theirs ({@link + * OpenSSLLibssl.ConnectionSettings}). A TLS setup is so reported as one protocol, with its + * versions, cipher suites, groups and signature algorithms, as the JCA {@code Cipher.getInstance} + * rules report the operations made on the cipher: + * + *

{@code
+ * SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
+ * SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
+ * SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES256-GCM-SHA384");
+ * SSL *ssl = SSL_new(ctx);
+ * SSL_set1_groups_list(ssl, "X25519");
+ * }
+ */ +public final class OpenSSLSslContext extends DetectionRuleSet { + + private static final String BUNDLE = "OpenSSL"; + + // SSL_new(ctx): a connection, configured as its context and further + private static final IDetectionRule SSL_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_new") + .withMethodParameter("*") + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules( + RuleSets.rulesOf(OpenSSLLibssl.ConnectionSettings.class)); + + private static final IDetectionRule SSL_CTX_NEW = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_new") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLLibssl.Methods.class)) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(dependingRules()); + + private static final IDetectionRule SSL_CTX_NEW_EX = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("SSL_CTX_new_ex") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(RuleSets.rulesOf(OpenSSLLibssl.Methods.class)) + .buildForContext(new ProtocolContext(ProtocolContext.Kind.TLS)) + .inBundle(() -> BUNDLE) + .withDependingDetectionRules(dependingRules()); + + /** The configuration of a context and the connections created from it. */ + @Nonnull + private static List> dependingRules() { + return Stream.concat( + RuleSets.rulesOf(OpenSSLLibssl.ContextSettings.class).stream(), + Stream.of(SSL_NEW)) + .toList(); + } + + @Nonnull + @Override + protected List> buildRules() { + return List.of(SSL_CTX_NEW, SSL_CTX_NEW_EX); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/rules/package-info.java b/cpp/src/main/java/com/ibm/plugin/rules/package-info.java new file mode 100644 index 000000000..3d1a5189b --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/rules/package-info.java @@ -0,0 +1,5 @@ +@ParametersAreNonnullByDefault +@javax.annotation.CheckReturnValue +package com.ibm.plugin.rules; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/cpp/src/main/java/com/ibm/plugin/translation/CxxTranslationProcess.java b/cpp/src/main/java/com/ibm/plugin/translation/CxxTranslationProcess.java new file mode 100644 index 000000000..5e26158d0 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/CxxTranslationProcess.java @@ -0,0 +1,83 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.enricher.Enricher; +import com.ibm.mapper.ITranslationProcess; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.Reorganizer; +import com.ibm.mapper.utils.Utils; +import com.ibm.plugin.translation.translator.CxxTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * C++ translation process for converting detection findings to the mapper model. + * + *

This class implements the three-phase translation pipeline: + * + *

    + *
  1. Translate: Convert detection values to mapper model nodes + *
  2. Reorganize: Apply reorganizer rules to normalize the tree structure + *
  3. Enrich: Add additional algorithm details from the enricher + *
+ */ +public final class CxxTranslationProcess + extends ITranslationProcess< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> { + + public CxxTranslationProcess(@Nonnull List reorganizerRules) { + super(reorganizerRules); + } + + @Override + @Nonnull + public List initiate( + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + rootDetectionStore) { + // 1. Translate + final CxxTranslator cxxTranslator = new CxxTranslator(); + final List translatedValues = cxxTranslator.translate(rootDetectionStore); + Utils.printNodeTree("translated ", translatedValues); + + // 2. Reorganize + final Reorganizer cxxReorganizer = new Reorganizer(reorganizerRules); + final List reorganizedValues = cxxReorganizer.reorganize(translatedValues); + Utils.printNodeTree("reorganised", reorganizedValues); + + // 3. Enrich + final List enrichedValues = Enricher.enrich(reorganizedValues).stream().toList(); + Utils.printNodeTree("enriched ", enrichedValues); + + return enrichedValues; + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/reorganizer/CxxReorganizerRules.java b/cpp/src/main/java/com/ibm/plugin/translation/reorganizer/CxxReorganizerRules.java new file mode 100644 index 000000000..df73ac525 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/reorganizer/CxxReorganizerRules.java @@ -0,0 +1,152 @@ +/* + * 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.reorganizer; + +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.Protocol; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.StreamCipher; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.rules.AeadBlockCipherReorganizer; +import com.ibm.mapper.reorganizer.rules.AsymmetricBlockCipherReorganizer; +import com.ibm.mapper.reorganizer.rules.BlockCipherReorganizer; +import com.ibm.mapper.reorganizer.rules.CipherParameterReorganizer; +import com.ibm.mapper.reorganizer.rules.CipherSuiteReorganizer; +import com.ibm.mapper.reorganizer.rules.KeyReorgenizer; +import com.ibm.mapper.reorganizer.rules.KeyUsageReorganizer; +import com.ibm.mapper.reorganizer.rules.MacReorganizer; +import com.ibm.mapper.reorganizer.rules.ProtocolVersionReorganizer; +import com.ibm.mapper.reorganizer.rules.SignatureReorganizer; +import java.util.List; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Reorganizer rules for C++ detection results. + * + *

This class provides the reorganizer rules used to normalize the translation tree structure. + * The rules are the same as used in the Java module, as they operate on the language-agnostic + * mapper model. + */ +public final class CxxReorganizerRules { + private CxxReorganizerRules() { + // private + } + + @Nonnull + public static List rules() { + return Stream.concat( + // the range of versions of a TLS context, from the versions set on it, + // before the context takes the place of its version + Stream.concat( + Stream.of( + ProtocolVersionReorganizer.resolveVersionsSetOn(TLS.class), + ProtocolVersionReorganizer.resolveVersionsSetOn( + Protocol.class)), + ProtocolVersionReorganizer.resolveVersionsSetOnTheirOwn().stream()), + otherRules().stream()) + .toList(); + } + + @Nonnull + private static List otherRules() { + return List.of( + AeadBlockCipherReorganizer.MERGE_AE_PARENT_AND_CHILD, + AeadBlockCipherReorganizer.MOVE_TAG_LENGTH_UNDER_MAC, + AsymmetricBlockCipherReorganizer.INVERT_DIGEST_AND_ITS_SIZE, + AsymmetricBlockCipherReorganizer.MERGE_PKE_PARENT_AND_CHILD, + AsymmetricBlockCipherReorganizer.MOVE_MASK_GENERATION_FUNCTION_UNDER_OAEP, + AsymmetricBlockCipherReorganizer.MAKE_RSA_ENCRYPTION_OF_AN_OAEP_PADDING, + BlockCipherReorganizer.MERGE_BLOCK_CIPHER_CHILD_INTO_PARENT, + // a legacy stream cipher operation on the key set up for it, e.g. RC4 on + // RC4_set_key + AsymmetricBlockCipherReorganizer.mergeParentAndChildOfSameAlgorithm( + StreamCipher.class), + CipherParameterReorganizer.MOVE_KEY_LENGTH_UNDER_TAG_LENGTH_UP, + CipherParameterReorganizer.KEEP_THE_FIXED_KEY_LENGTH_OF_THE_CIPHER_OF_A_DECRYPTION, + CipherParameterReorganizer.KEEP_THE_FIXED_KEY_LENGTH_OF_THE_CIPHER_OF_AN_ENCRYPTION, + CipherParameterReorganizer.MOVE_NODES_UNDER_DECRYPT_UP, + CipherParameterReorganizer.MOVE_ENCRYPT_UNDER_ITS_CIPHER, + CipherParameterReorganizer.MOVE_DECRYPT_UNDER_ITS_CIPHER, + CipherParameterReorganizer.MOVE_NODES_UNDER_ENCRYPT_UP, + CipherSuiteReorganizer.ADD_TLS_PROTOCOL_AS_PARENT_NODE, + CipherSuiteReorganizer.ADD_TLS_PROTOCOL_AS_PARENT_OF_CIPHER_SUITES, + CipherSuiteReorganizer.REPLACE_TLS_WITH_VERSIONED_CHILD, + MacReorganizer.MERGE_UNKNOWN_MAC_PARENT_AND_CIPHER_CHILD, + MacReorganizer.MERGE_GMAC_PARENT_AND_GCM_CIPHER_CHILD, + MacReorganizer.MOVE_SOME_MAC_CHILDREN_UNDER_BLOCKCIPHER, + MacReorganizer.MOVE_TAG_LENGTH_UNDER_MAC, + SignatureReorganizer.MERGE_UNKNOWN_SIGNATURE_PARENT_AND_CHILD, + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + Sign.class, Signature.class), + SignatureReorganizer.MERGE_SIGNATURE_PARENT_AND_CHILD, + // a sign or verify operation on the signature scheme it was detected with + SignatureReorganizer.moveFunctionalityUnderChildNode(Sign.class, Signature.class), + SignatureReorganizer.moveFunctionalityUnderChildNode(Verify.class, Signature.class), + KeyReorgenizer.SPECIFY_KEY_TYPE_BY_LOOKING_AT_KEY_GENERATION, + // the operations performed with a key generated by EVP_PKEY_keygen are found under + // its key generation + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + KeyGeneration.class, PublicKeyEncryption.class), + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + KeyGeneration.class, Signature.class), + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + KeyGeneration.class, KeyAgreement.class), + SignatureReorganizer.moveNodesFromUnderFunctionalityUnderParent( + KeyGeneration.class, KeyEncapsulationMechanism.class), + // a key generated for parameters, a group or a curve described by a child + AsymmetricBlockCipherReorganizer.mergeParentAndChildOfSameAlgorithm( + PublicKeyEncryption.class), + AsymmetricBlockCipherReorganizer.mergeParentAndChildOfSameAlgorithm( + Signature.class), + // a generated asymmetric key is a private key holding its algorithm + KeyReorgenizer.makePrivateKeyOfGeneratedAlgorithm(PublicKeyEncryption.class), + KeyReorgenizer.makePrivateKeyOfGeneratedAlgorithm(Signature.class), + KeyReorgenizer.makePrivateKeyOfGeneratedAlgorithm( + ProbabilisticSignatureScheme.class), + KeyReorgenizer.makePrivateKeyOfGeneratedAlgorithm(KeyAgreement.class), + KeyReorgenizer.makePrivateKeyOfGeneratedAlgorithm(KeyEncapsulationMechanism.class), + // the operations performed with a key created from raw bytes are found on the key + KeyUsageReorganizer.moveOperationsOfImportedKeyUnderItsAlgorithm(PrivateKey.class), + KeyUsageReorganizer.moveOperationsOfImportedKeyUnderItsAlgorithm(PublicKey.class), + KeyUsageReorganizer.moveOperationsOfImportedKeyUnderItsAlgorithm(SecretKey.class), + // the operations performed with a private key, e.g. ECDSA with an EC key + KeyUsageReorganizer.MAKE_KEY_DERIVATION_OF_A_DECRYPTION_WITH_A_KEY_AGREEMENT_KEY, + KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS, + KeyUsageReorganizer.MOVE_OPERATION_ALGORITHMS_OF_PRIVATE_KEY_TO_THE_KEY, + // the MAC computed with a MAC key + KeyUsageReorganizer.MAKE_TAGS_OF_SECRET_KEY_OPERATIONS, + KeyReorgenizer.MOVE_KEY_UNDER_ALGORITHM_AND_REPLACE_INNER_ALGORITHM, + KeyReorgenizer.PROPAGATE_KEY_LENGTH_TO_BLOCK_CIPHER, + // a signature made with a key whose type is not known in the analyzed code + SignatureReorganizer.MAKE_SIGNATURE_OF_A_SIGNING_OPERATION_WITHOUT_SCHEME, + SignatureReorganizer.MAKE_SIGNATURE_OF_A_VERIFYING_OPERATION_WITHOUT_SCHEME); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/CxxTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/CxxTranslator.java new file mode 100644 index 000000000..81564d03d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/CxxTranslator.java @@ -0,0 +1,228 @@ +/* + * 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; + +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.context.AlgorithmParameterContext; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.model.context.DigestContext; +import com.ibm.engine.model.context.KeyAgreementContext; +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.ProtocolContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.model.context.SecretKeyContext; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.ITranslator; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.utils.DetectionLocation; +import com.ibm.plugin.translation.translator.contexts.CxxAlgorithmParameterContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxCipherContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxDigestContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxKeyAgreementContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxKeyContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxKeyDerivationFunctionContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxMacContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxPRNGContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxProtocolContextTranslator; +import com.ibm.plugin.translation.translator.contexts.CxxSignatureContextTranslator; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import com.sonar.cxx.sslr.api.Token; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * C++ translator for converting detection findings to the mapper model. + * + *

This class extends the base {@link ITranslator} and provides C++-specific translation logic + * for various cryptographic detection contexts (cipher, key, digest, signature, etc.). + * + *

The translator delegates to context-specific translators based on the detection context type. + */ +public final class CxxTranslator + extends ITranslator< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> { + + public CxxTranslator() { + // nothing + } + + @Override + @Nonnull + public Optional translate( + @Nonnull final IBundle bundleIdentifier, + @Nonnull final IValue value, + @Nonnull final DetectionContext detectionValueContext, + @Nonnull final String filePath) { + DetectionLocation detectionLocation = + getDetectionContextFrom(value.getLocation(), bundleIdentifier, filePath); + if (detectionLocation == null) { + return Optional.empty(); + } + + // cipher context + if (detectionValueContext.is(CipherContext.class)) { + CxxCipherContextTranslator cxxCipherContextTranslation = + new CxxCipherContextTranslator(); + return cxxCipherContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // key, private key, public key and secret key contexts + } else if (detectionValueContext.is(KeyContext.class) + || detectionValueContext.is(PublicKeyContext.class) + || detectionValueContext.is(PrivateKeyContext.class) + || detectionValueContext.is(SecretKeyContext.class)) { + CxxKeyContextTranslator cxxKeyContextTranslation = new CxxKeyContextTranslator(); + return cxxKeyContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // key agreement context + } else if (detectionValueContext.is(KeyAgreementContext.class)) { + CxxKeyAgreementContextTranslator cxxKeyAgreementContextTranslation = + new CxxKeyAgreementContextTranslator(); + return cxxKeyAgreementContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // PRNG context + } else if (detectionValueContext.is(PRNGContext.class)) { + CxxPRNGContextTranslator cxxPRNGContextTranslation = new CxxPRNGContextTranslator(); + return cxxPRNGContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // digest context + } else if (detectionValueContext.is(DigestContext.class)) { + CxxDigestContextTranslator cxxDigestContextTranslation = + new CxxDigestContextTranslator(); + return cxxDigestContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // signature context + } else if (detectionValueContext.is(SignatureContext.class)) { + CxxSignatureContextTranslator cxxSignatureContextTranslation = + new CxxSignatureContextTranslator(); + return cxxSignatureContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // mac context + } else if (detectionValueContext.is(MacContext.class)) { + CxxMacContextTranslator cxxMacContextTranslation = new CxxMacContextTranslator(); + return cxxMacContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // key derivation function context + } else if (detectionValueContext.is(KeyDerivationFunctionContext.class)) { + CxxKeyDerivationFunctionContextTranslator cxxKdfContextTranslation = + new CxxKeyDerivationFunctionContextTranslator(); + return cxxKdfContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // algorithm parameter context + } else if (detectionValueContext.is(AlgorithmParameterContext.class)) { + CxxAlgorithmParameterContextTranslator cxxAlgorithmParameterContextTranslation = + new CxxAlgorithmParameterContextTranslator(); + return cxxAlgorithmParameterContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + + // protocol + } else if (detectionValueContext.is(ProtocolContext.class)) { + CxxProtocolContextTranslator cxxProtocolContextTranslation = + new CxxProtocolContextTranslator(); + return cxxProtocolContextTranslation.translate( + bundleIdentifier, value, detectionValueContext, detectionLocation); + } + return Optional.empty(); + } + + /** A C function operates on the object it is given, which its other arguments configure. */ + @Override + protected boolean argumentsConfigureTheOperatedObject() { + return true; + } + + /** + * Gets a detection location from the specified AstNode location and file path. + * + *

This method extracts line number, column offset, and keywords from the AST node to create + * a DetectionLocation for reporting findings. + */ + @Override + @Nullable public DetectionLocation getDetectionContextFrom( + @Nonnull AstNode location, @Nonnull IBundle bundle, @Nonnull String filePath) { + Token token = location.getToken(); + if (token == null) { + return null; + } + + int lineNumber = token.getLine(); + int offset = token.getColumn(); + List keywords = extractKeywords(location); + + return new DetectionLocation(filePath, lineNumber, offset, keywords, bundle); + } + + /** + * Extracts keywords from the AST node for the detection location. + * + * @param node The AST node to extract keywords from + * @return List of keywords (function name, etc.) + */ + @Nonnull + private List extractKeywords(@Nonnull AstNode node) { + // Try to extract function name for function calls + if (node.is(CxxGrammarImpl.postfixExpression)) { + if (CxxAstNodeHelper.isFunctionCall(node)) { + String functionName = CxxAstNodeHelper.getFunctionCallName(node); + if (functionName != null) { + return List.of(functionName); + } + } + } else if (node.is(CxxGrammarImpl.newExpression)) { + // For new expressions, try to get the type name from the type specifier + AstNode typeSpecifier = node.getFirstDescendant(CxxGrammarImpl.typeSpecifier); + if (typeSpecifier != null) { + String typeName = CxxAstNodeHelper.getIdentifierName(typeSpecifier); + if (typeName != null) { + return List.of(typeName); + } + } + } + + // Fallback to token value + Token token = node.getToken(); + if (token != null) { + return List.of(token.getValue()); + } + + return List.of(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxAlgorithmParameterContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxAlgorithmParameterContextTranslator.java new file mode 100644 index 000000000..5ca94827c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxAlgorithmParameterContextTranslator.java @@ -0,0 +1,81 @@ +/* + * 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.InitializationVectorSize; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.Padding; +import com.ibm.engine.model.TagSize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.InitializationVectorLength; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.TagLength; +import com.ibm.mapper.model.algorithms.MGF1; +import com.ibm.mapper.model.padding.OAEP; +import com.ibm.mapper.model.padding.PKCS7; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +public final class CxxAlgorithmParameterContextTranslator implements IContextTranslation { + + /** The setting of the digest of RSA-OAEP. */ + public static final String OAEP_SETTING = "OAEP"; + + /** The setting of the digest of the MGF1 mask generation function. */ + public static final String MGF1_SETTING = "MGF1"; + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + // parameters set on a cipher context (see OpenSSLEvpCipherParameters) + if (value instanceof KeySize keySize) { + return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); + } else if (value instanceof TagSize tagSize) { + return Optional.of(new TagLength(tagSize.getValue(), detectionLocation)); + } else if (value instanceof InitializationVectorSize initializationVectorSize) { + return Optional.of( + new InitializationVectorLength( + initializationVectorSize.getValue(), detectionLocation)); + } else if (value instanceof Padding padding + && "PKCS7".equals(padding.asString())) { + // the standard block padding (EVP_CIPHER_CTX_set_padding) + return Optional.of(new PKCS7(detectionLocation)); + } else if (value instanceof ValueAction setting) { + // the setting of an RSA padding whose digest is given as the argument of the setter, + // e.g. EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) and EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md) + return switch (setting.asString()) { + case OAEP_SETTING -> Optional.of(new OAEP(detectionLocation)); + case MGF1_SETTING -> Optional.of(new MGF1(detectionLocation)); + default -> Optional.empty(); + }; + } + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxCipherContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxCipherContextTranslator.java new file mode 100644 index 000000000..f6717da6d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxCipherContextTranslator.java @@ -0,0 +1,76 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslCipherMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +public final class CxxCipherContextTranslator implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof CipherAction cipherAction) { + return switch (cipherAction.getAction()) { + case ENCRYPT -> Optional.of(new Encrypt(detectionLocation)); + case DECRYPT -> Optional.of(new Decrypt(detectionLocation)); + default -> Optional.empty(); + }; + } + + // the key size given to a legacy key setup function, e.g. the bits of AES_set_encrypt_key + if (value instanceof KeySize keySize) { + return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); + } + + if (value instanceof ValueAction || value instanceof Algorithm) { + // a legacy cipher function takes its key length from the key setup of its key + final boolean legacy = + detectionContext instanceof DetectionContext context + && context.get("kind").filter("LEGACY"::equals).isPresent(); + final OpenSslCipherMapper mapper = new OpenSslCipherMapper(); + return (legacy + ? mapper.parseLegacy(value.asString(), detectionLocation) + : mapper.parse(value.asString(), detectionLocation)) + .map(node -> node); + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxDigestContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxDigestContextTranslator.java new file mode 100644 index 000000000..7fe97f127 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxDigestContextTranslator.java @@ -0,0 +1,79 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.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; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslMessageDigestMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.algorithms.MGF1; +import com.ibm.mapper.model.padding.OAEP; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +public final class CxxDigestContextTranslator implements IContextTranslation { + + /** The property of a digest context that tells what the digest is used for. */ + public static final String KIND = "kind"; + + /** The kind of the digest of a mask generation function. */ + public static final String MGF1_KIND = "MGF1"; + + /** The kind of the digest of RSA-OAEP. */ + public static final String OAEP_KIND = "OAEP"; + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof ValueAction || value instanceof Algorithm) { + final Optional digest = + new OpenSslMessageDigestMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + // the digest of a mask generation function, e.g. the MGF1 digest of RSA-PSS or + // RSA-OAEP, is reported as that function + if (detectionContext instanceof DetectionContext context + && context.get(KIND).filter(MGF1_KIND::equals).isPresent()) { + return digest.filter(MessageDigest.class::isInstance) + .map(node -> new MGF1((MessageDigest) node)); + } + // the digest of RSA-OAEP, given by name to EVP_PKEY_CTX_set_rsa_oaep_md_name + if (detectionContext instanceof DetectionContext context + && context.get(KIND).filter(OAEP_KIND::equals).isPresent()) { + return digest.filter(MessageDigest.class::isInstance) + .map(node -> new OAEP((MessageDigest) node, detectionLocation)); + } + return digest; + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyAgreementContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyAgreementContextTranslator.java new file mode 100644 index 000000000..da4db01a9 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyAgreementContextTranslator.java @@ -0,0 +1,76 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslKemMapper; +import com.ibm.mapper.mapper.openssl.OpenSslKeyAgreementMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Translator for C++ key agreement detection contexts. + * + *

This translator handles the translation of key agreement-related detection values (Diffie- + * Hellman, ECDH, X25519/X448, ML-KEM, SM2) to the mapper model nodes. Values detected with the + * context kind {@code KEM} are key encapsulation mechanism names, and values with the kind {@code + * HPKE} are HPKE suites. + */ +public final class CxxKeyAgreementContextTranslator implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof ValueAction + || value instanceof com.ibm.engine.model.Algorithm) { + final String kind = + detectionContext instanceof DetectionContext context + ? context.get("kind").orElse("") + : ""; + return switch (kind) { + case "KEM" -> + new OpenSslKemMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + case "HPKE" -> + new OpenSslKemMapper() + .parseHpkeSuite(value.asString(), detectionLocation) + .map(node -> node); + default -> + new OpenSslKeyAgreementMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + }; + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyContextTranslator.java new file mode 100644 index 000000000..af5e12224 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyContextTranslator.java @@ -0,0 +1,170 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.Curve; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.KeyAction; +import com.ibm.engine.model.KeySize; +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.PrivateKeyContext; +import com.ibm.engine.model.context.PublicKeyContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslKeyMapper; +import com.ibm.mapper.mapper.openssl.OpenSslMacMapper; +import com.ibm.mapper.model.IAlgorithm; +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.PublicKey; +import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.functionality.Decapsulate; +import com.ibm.mapper.model.functionality.Encapsulate; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Translator for C++ key detection contexts. + * + *

This translator handles the translation of key-related detection values (public keys, private + * keys, secret keys) to the mapper model nodes. + */ +public final class CxxKeyContextTranslator implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + if (isRawKey(detectionContext) + && (value instanceof ValueAction || value instanceof Algorithm)) { + return rawKey(bundleIdentifier, value, detectionContext, detectionLocation); + } + final Optional translated = + translateValue(bundleIdentifier, value, detectionContext, detectionLocation); + // the algorithm of a key generated by the detected call (e.g. EVP_PKEY_Q_keygen) + if (detectionContext.is(PrivateKeyContext.class)) { + translated + .filter(IAlgorithm.class::isInstance) + .ifPresent( + algorithm -> + algorithm.put( + new KeyGeneration( + KeyGeneration.Specification.PRIVATE_KEY, + detectionLocation))); + } + return translated; + } + + /** Whether the value is the type of a key created from raw bytes (see OpenSSLEvpRawKey). */ + private static boolean isRawKey(@Nonnull DetectionContext detectionContext) { + return detectionContext instanceof DetectionContext context + && context.get("kind").filter("RAW_KEY"::equals).isPresent(); + } + + /** + * A key created from raw bytes: a secret key holding its MAC algorithm, or a private or public + * key (by the detection context) holding its asymmetric algorithm. The key is imported, not + * generated. + */ + @Nonnull + private Optional rawKey( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + final Optional mac = + new OpenSslMacMapper().parse(value.asString(), detectionLocation); + if (mac.isPresent() && mac.get() instanceof IAlgorithm macAlgorithm) { + return Optional.of(new SecretKey(new Key(macAlgorithm))); + } + return translateValue(bundleIdentifier, value, detectionContext, detectionLocation) + .filter(IAlgorithm.class::isInstance) + .map( + algorithm -> { + final Key key = new Key((IAlgorithm) algorithm); + return detectionContext.is(PublicKeyContext.class) + ? new PublicKey(key) + : new PrivateKey(key); + }); + } + + @Nonnull + private Optional translateValue( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + // settings made on a key generation context (see OpenSSLEvpKeyGen) + if (value instanceof KeySize keySize) { + return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); + } else if (value instanceof SaltSize saltSize) { + // a negative salt length selects a length derived from the digest or the key + return saltSize.getValue() > 0 + ? Optional.of(new SaltLength(saltSize.getValue(), detectionLocation)) + : Optional.empty(); + } else if (value instanceof Curve curve) { + return new OpenSslKeyMapper() + .parseCurve(curve.asString(), detectionLocation) + .map(node -> node); + } else if (value instanceof KeyAction keyAction) { + return switch (keyAction.getAction()) { + case PRIVATE_KEY_GENERATION -> + Optional.of( + new KeyGeneration( + KeyGeneration.Specification.PRIVATE_KEY, + detectionLocation)); + case PUBLIC_KEY_GENERATION -> + Optional.of( + new KeyGeneration( + KeyGeneration.Specification.PUBLIC_KEY, detectionLocation)); + case SECRET_KEY_GENERATION -> + Optional.of( + new KeyGeneration( + KeyGeneration.Specification.SECRET_KEY, detectionLocation)); + case GENERATION -> Optional.of(new KeyGeneration(detectionLocation)); + // operations performed with a key (see OpenSSLEvpKeyUsage) + case KDF -> Optional.of(new KeyDerivation(detectionLocation)); + case ENCAPSULATION -> Optional.of(new Encapsulate(detectionLocation)); + case DECAPSULATION -> Optional.of(new Decapsulate(detectionLocation)); + }; + } + + if (value instanceof ValueAction || value instanceof Algorithm) { + return new OpenSslKeyMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyDerivationFunctionContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyDerivationFunctionContextTranslator.java new file mode 100644 index 000000000..465e7318c --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxKeyDerivationFunctionContextTranslator.java @@ -0,0 +1,79 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.IterationCount; +import com.ibm.engine.model.KeySize; +import com.ibm.engine.model.SaltSize; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslKdfMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.NumberOfIterations; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +public final class CxxKeyDerivationFunctionContextTranslator + implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof ValueAction + || value instanceof com.ibm.engine.model.Algorithm) { + return new OpenSslKdfMapper() + .parse(value.asString(), detectionLocation) + .map(kdf -> withKeyDerivation(kdf, detectionLocation)); + } else if (value instanceof KeySize keySize) { + return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); + } else if (value instanceof SaltSize saltSize) { + return Optional.of(new SaltLength(saltSize.getValue(), detectionLocation)); + } else if (value instanceof IterationCount iterationCount) { + return Optional.of( + new NumberOfIterations(iterationCount.getValue(), detectionLocation)); + } + return Optional.empty(); + } + + /** A key derivation function is reported with the key derivation it performs. */ + @Nonnull + private static INode withKeyDerivation( + @Nonnull INode node, @Nonnull DetectionLocation detectionLocation) { + if (node.is(KeyDerivationFunction.class) + || node.is(PasswordBasedKeyDerivationFunction.class)) { + node.put(new KeyDerivation(detectionLocation)); + } + return node; + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxMacContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxMacContextTranslator.java new file mode 100644 index 000000000..6a41a20cc --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxMacContextTranslator.java @@ -0,0 +1,73 @@ +/* + * 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.AlgorithmParameter; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslCipherMapper; +import com.ibm.mapper.mapper.openssl.OpenSslMacMapper; +import com.ibm.mapper.mapper.openssl.OpenSslMessageDigestMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Translator for C++ MAC detection contexts. + * + *

This translator handles the translation of MAC-related detection values to the mapper model + * nodes. Supports HMAC, CMAC, GMAC, Poly1305, SipHash, KMAC, and BLAKE2 MAC variants. + */ +public final class CxxMacContextTranslator implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof ValueAction + || value instanceof com.ibm.engine.model.Algorithm) { + return new OpenSslMacMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + } + // the digest of an HMAC or the cipher of a CMAC or GMAC the MAC is computed with + if (value instanceof AlgorithmParameter) { + final Optional digest = + new OpenSslMessageDigestMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + return digest.isPresent() + ? digest + : new OpenSslCipherMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxPRNGContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxPRNGContextTranslator.java new file mode 100644 index 000000000..58014f91d --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxPRNGContextTranslator.java @@ -0,0 +1,58 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation.translator.contexts; + +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslPRNGMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Translator for C++ PRNG (Pseudo-Random Number Generator) detection contexts. + * + *

This translator handles the translation of PRNG-related detection values (RAND, DRBG variants) + * to the mapper model nodes. + */ +public final class CxxPRNGContextTranslator implements IContextTranslation { + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof ValueAction + || value instanceof com.ibm.engine.model.Algorithm) { + return new OpenSslPRNGMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxProtocolContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxProtocolContextTranslator.java new file mode 100644 index 000000000..213f77745 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxProtocolContextTranslator.java @@ -0,0 +1,210 @@ +/* + * 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.Algorithm; +import com.ibm.engine.model.CipherSuite; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.model.context.ProtocolContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.mapper.openssl.OpenSslCipherStringMapper; +import com.ibm.mapper.mapper.openssl.OpenSslSrtpProfileMapper; +import com.ibm.mapper.mapper.ssl.OpenSslGroupMapper; +import com.ibm.mapper.mapper.ssl.OpenSslSignatureMapper; +import com.ibm.mapper.mapper.ssl.SSLVersionMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.Protocol; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.Unknown; +import com.ibm.mapper.model.Version; +import com.ibm.mapper.model.collections.MergeableCollection; +import com.ibm.mapper.model.collections.ProtocolVersionSettings; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Translates OpenSSL libssl protocol contexts to CBOM model nodes. + * + *

Handles SSL/TLS protocol detection including version strings (TLS 1.2, TLS 1.3, DTLS, QUIC, + * etc.) and cipher suite configurations. + */ +public final class CxxProtocolContextTranslator implements IContextTranslation { + + @Nonnull + @Override + public Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + if (!bundleIdentifier.getIdentifier().equals("OpenSSL")) { + return Optional.empty(); + } + + final ProtocolContext.Kind kind = ((ProtocolContext) detectionContext).kind(); + + if (value instanceof com.ibm.engine.model.Protocol protocol) { + return switch (kind) { + case TLS_DISABLED_VERSIONS -> + versions(protocol.asString(), detectionLocation) + .map(ProtocolVersionSettings.Disabled::new); + case TLS_MINIMUM_VERSION -> + versions(protocol.asString(), detectionLocation) + .map(ProtocolVersionSettings.Minimum::new); + case TLS_MAXIMUM_VERSION -> + versions(protocol.asString(), detectionLocation) + .map(ProtocolVersionSettings.Maximum::new); + case TLS -> + Optional.of(protocol) + .map( + p -> { + final SSLVersionMapper sslVersionMapper = + new SSLVersionMapper(); + return sslVersionMapper + .parse(p.asString(), detectionLocation) + .map( + version -> + new TLS(p.asString(), version)) + .orElse(new TLS(detectionLocation)); + }); + default -> + Optional.of(protocol) + .map(p -> new Protocol(p.asString(), detectionLocation)); + }; + } else if (value instanceof CipherSuite cipherSuite) { + return switch (kind) { + case TLS -> + new OpenSslCipherStringMapper() + .parse(cipherSuite.get(), detectionLocation) + .map(node -> node); + default -> + Optional.of(cipherSuite) + .map( + suite -> + new com.ibm.mapper.model.CipherSuite( + suite.asString(), detectionLocation)); + }; + } else if (value instanceof Algorithm algorithm) { + return switch (kind) { + case TLS_SIGNATURE_ALGORITHMS -> + translateList( + algorithm.asString(), + new OpenSslSignatureMapper(), + Signature.class, + detectionLocation); + // a group is a key agreement or a key encapsulation mechanism + case TLS_GROUPS -> + translateList( + algorithm.asString(), + new OpenSslGroupMapper(), + Unknown.class, + detectionLocation); + case SRTP -> + new OpenSslSrtpProfileMapper() + .parse(algorithm.asString(), detectionLocation) + .map(node -> node); + default -> Optional.of(new Protocol(algorithm.asString(), detectionLocation)); + }; + } else if (value instanceof ValueAction valueAction) { + final String stringValue = valueAction.asString(); + if (kind == ProtocolContext.Kind.TLS) { + // TLS_method() and its client and server forms negotiate the version + if (stringValue.equals("TLS")) { + return Optional.of(new TLS(detectionLocation)); + } + final SSLVersionMapper sslVersionMapper = new SSLVersionMapper(); + final Optional parsedVersion = + sslVersionMapper.parse(stringValue, detectionLocation); + if (parsedVersion.isPresent()) { + return Optional.of(new TLS(stringValue, parsedVersion.get())); + } + } + return Optional.of(new Protocol(stringValue, detectionLocation)); + } + + return Optional.of(new Unknown(detectionLocation)); + } + + /** + * The colon-separated protocol versions set on a context, e.g. {@code TLSv1.0:TLSv1.1} disabled + * by options or the {@code TLSv1.2} minimum version, as a {@link TLS} node for each version: + * the settings made on a context are merged into the context, which uses the range of versions + * they leave (see {@link ProtocolVersionSettings}). + */ + @Nonnull + private static Optional> versions( + @Nonnull String versions, @Nonnull DetectionLocation detectionLocation) { + final SSLVersionMapper sslVersionMapper = new SSLVersionMapper(); + final List nodes = new ArrayList<>(); + for (String name : versions.split(":")) { + sslVersionMapper + .parse(name, detectionLocation) + .ifPresent(version -> nodes.add(new TLS(name, version))); + } + if (nodes.isEmpty()) { + return Optional.empty(); + } + return Optional.of(nodes); + } + + /** + * Splits a colon-separated OpenSSL algorithm list (e.g. {@code "MLKEM768:X25519"}) and maps + * each entry with the given per-library mapper. An entry the mapper doesn't recognize is an + * algorithm of the given kind named after the entry. The result is a single {@link + * MergeableCollection} node holding one child per entry: the groups and signature algorithms + * configured on a TLS context are merged into the algorithms the protocol uses. + */ + @Nonnull + private static Optional translateList( + @Nonnull String list, + @Nonnull IMapper mapper, + @Nonnull Class kindOfUnknownNames, + @Nonnull DetectionLocation detectionLocation) { + final List nodes = new ArrayList<>(); + for (String rawName : list.split(":")) { + final String name = rawName.trim(); + if (name.isEmpty()) { + continue; + } + final INode node = + mapper.parse(name, detectionLocation) + .map(n -> n) + .orElseGet( + () -> + new com.ibm.mapper.model.Algorithm( + name, kindOfUnknownNames, detectionLocation)); + nodes.add(node); + } + if (nodes.isEmpty()) { + return Optional.empty(); + } + return Optional.of(new MergeableCollection(nodes)); + } +} diff --git a/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxSignatureContextTranslator.java b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxSignatureContextTranslator.java new file mode 100644 index 000000000..de04bf9d4 --- /dev/null +++ b/cpp/src/main/java/com/ibm/plugin/translation/translator/contexts/CxxSignatureContextTranslator.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.plugin.translation.translator.contexts; + +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.SaltSize; +import com.ibm.engine.model.SignatureAction; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.IContextTranslation; +import com.ibm.mapper.mapper.openssl.OpenSslMessageDigestMapper; +import com.ibm.mapper.mapper.openssl.OpenSslSignatureAlgorithmMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.algorithms.MGF1; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Translator for C++ signature detection contexts. + * + *

This translator handles the translation of signature-related detection values to the mapper + * model nodes. Supports RSA, DSA, ECDSA, EdDSA, post-quantum, and SM2 signatures. + */ +public final class CxxSignatureContextTranslator implements IContextTranslation { + + /** The property of a signature context that tells what its values are. */ + public static final String KIND = "kind"; + + /** The kind of a signature context whose algorithm values are the digest names it uses. */ + public static final String DIGEST_NAME_KIND = "DIGEST_NAME"; + + /** + * The kind of a signature context whose algorithm values are the names of the digests of the + * MGF1 mask generation function it uses. + */ + public static final String MGF1_DIGEST_NAME_KIND = "MGF1_DIGEST_NAME"; + + @Override + public @Nonnull Optional translate( + @Nonnull IBundle bundleIdentifier, + @Nonnull IValue value, + @Nonnull DetectionContext detectionContext, + @Nonnull DetectionLocation detectionLocation) { + + if (value instanceof SignatureAction signatureAction) { + return switch (signatureAction.getAction()) { + case SIGN -> Optional.of(new Sign(detectionLocation)); + case VERIFY -> Optional.of(new Verify(detectionLocation)); + }; + } + + if (value instanceof SaltSize saltSize) { + // a negative salt length selects a length derived from the digest or the key + return saltSize.getValue() > 0 + ? Optional.of(new SaltLength(saltSize.getValue(), detectionLocation)) + : Optional.empty(); + } + + // the digest of a digest sign or verify operation given by name, e.g. the mdname of + // EVP_DigestSignInit_ex + if (value instanceof Algorithm + && detectionContext instanceof DetectionContext context + && context.get(KIND).filter(DIGEST_NAME_KIND::equals).isPresent()) { + return new OpenSslMessageDigestMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + } + + // the digest of the MGF1 mask generation function given by name, e.g. the mdname of + // EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name + if (value instanceof Algorithm + && detectionContext instanceof DetectionContext context + && context.get(KIND).filter(MGF1_DIGEST_NAME_KIND::equals).isPresent()) { + return new OpenSslMessageDigestMapper() + .parse(value.asString(), detectionLocation) + .filter(MessageDigest.class::isInstance) + .map(digest -> new MGF1((MessageDigest) digest)); + } + + if (value instanceof ValueAction || value instanceof Algorithm) { + return new OpenSslSignatureAlgorithmMapper() + .parse(value.asString(), detectionLocation) + .map(node -> node); + } + + return Optional.empty(); + } +} diff --git a/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/CxxNoMD5use.html b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/CxxNoMD5use.html new file mode 100644 index 000000000..5f40d7180 --- /dev/null +++ b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/CxxNoMD5use.html @@ -0,0 +1,3 @@ +

Cryptography Usage: Be careful

+ +

While MD5 is still used in some applications, it is no longer considered secure for cryptographic purposes like password hashing.

diff --git a/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/CxxNoMD5use.json b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/CxxNoMD5use.json new file mode 100644 index 000000000..223b2d131 --- /dev/null +++ b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/CxxNoMD5use.json @@ -0,0 +1,19 @@ +{ + "title": "Do not use MD5 for cryptographic purposes like hashing", + "type": "VULNERABILITY", + "code": { + "impacts": { + "SECURITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "tags": [ + "cryptography", + "cbom", + "cwe" + ], + "defaultSeverity": "Critical", + "scope": "Main", + "sqKey": "CxxNoMD5use" +} diff --git a/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/Inventory.html b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/Inventory.html new file mode 100644 index 000000000..6df507b88 --- /dev/null +++ b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/Inventory.html @@ -0,0 +1,10 @@ +

Cryptography Usage: Be careful

+ +

Cryptography is a critical component of modern digital security, protecting sensitive data and communications + from unauthorized access. However, implementing cryptographic systems correctly is notoriously challenging, + even for experienced developers. Therefore, caution is necessary when writing code related to + cryptography.

+ +

It is important that you read the documentation for the cryptographic library you are using and strictly + adhere to the specified implementation guidelines.

+ diff --git a/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/Inventory.json b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/Inventory.json new file mode 100644 index 000000000..5fbf60599 --- /dev/null +++ b/cpp/src/main/resources/org/sonar/l10n/cpp/rules/cpp/Inventory.json @@ -0,0 +1,19 @@ +{ + "title": "Cryptographic Inventory (CBOM)", + "type": "CODE_SMELL", + "code": { + "impacts": { + "SECURITY": "INFO" + }, + "attribute": "TRUSTWORTHY" + }, + "status": "ready", + "tags": [ + "cryptography", + "cbom", + "cwe" + ], + "defaultSeverity": "Minor", + "scope": "Main", + "sqKey": "Inventory" +} diff --git a/cpp/src/test/files/language/CxxConstructorCallsTestFile.cc b/cpp/src/test/files/language/CxxConstructorCallsTestFile.cc new file mode 100644 index 000000000..dfb91096c --- /dev/null +++ b/cpp/src/test/files/language/CxxConstructorCallsTestFile.cc @@ -0,0 +1,34 @@ +class Hasher { + public: + Hasher(const char *name); + Hasher(const char *name, int size); +}; + +class Config {}; + +namespace crypto { +class Digest { + public: + Digest(const char *name); +}; +} // namespace crypto + +void declarations(const char *name, int size, Hasher *other) { + Hasher parenthesized("SHA224"); + Hasher braced{"SHA384"}; + Hasher bracedTwo{"SHA256", 32}; + Hasher copyList = {"SHA512"}; + const Hasher qualified("SHA1", 32); + Hasher names(name, size); + crypto::Digest namespaced("MD5"); + Hasher function(Config); + Hasher noArguments; + Hasher copied = *other; + Hasher array[1]{"SHA256"}; + int builtIn(32); + Hasher("SHA3-256"); + Hasher{"SHA3-384"}; + crypto::Digest("SHA3-512"); + new Hasher("SHAKE128", 16); + other->reset("x"); +} diff --git a/cpp/src/test/files/language/CxxLanguageTranslationTestFile.cc b/cpp/src/test/files/language/CxxLanguageTranslationTestFile.cc new file mode 100644 index 000000000..6d0701084 --- /dev/null +++ b/cpp/src/test/files/language/CxxLanguageTranslationTestFile.cc @@ -0,0 +1,27 @@ +class Hasher { + public: + Hasher(const char *name); + static Hasher make(const char *name); + void reset(const char *name); +}; + +namespace crypto { +void helper(int value); +} + +struct Api { + const void *(*digest)(void); +}; + +enum Mode { FAST, SLOW }; + +void calls(Hasher &hasher, Hasher *pointer, Api *api, int count) { + EVP_sha256(); + hasher.reset("SHA1"); + pointer->reset("SHA1"); + api->digest(); + Hasher::make("SHA256"); + crypto::helper(count); + Hasher constructed("SHA224"); + use(32, "digest", 'c', count, count + 1, FAST); +} diff --git a/cpp/src/test/files/language/CxxSemanticTestFile.cc b/cpp/src/test/files/language/CxxSemanticTestFile.cc new file mode 100644 index 000000000..83abbc7e0 --- /dev/null +++ b/cpp/src/test/files/language/CxxSemanticTestFile.cc @@ -0,0 +1,47 @@ +enum Mode { FAST = 3, SLOW }; +enum class Strictness { STRICT, LENIENT }; + +struct Settings { + const char *name; +}; + +void values(int condition, struct Settings settings, const char *parameter) { + const char *local = "initial"; + local = "reassigned"; + const char *assigned; + v("plain"); + v(L"wide"); + v(u"utf16"); + v(U"utf32"); + v(u8"utf8"); + v(R"(raw)"); + v("con" "cat"); + v('c'); + v(42); + v(42u); + v(0x10); + v(0x80000000); + v(0b101); + v(010); + v(1.5f); + v(1'000); + v(5000000000); + v(0e0); + v(true); + v(false); + v(nullptr); + v((24)); + v(condition ? "either" : "or"); + v(1 ? "chosen" : "other"); + v((const char *) "cast"); + v(static_cast(7)); + v(2 * 1024); + v(TLS1_2_VERSION); + v(assigned = "assigned"); + v(FAST); + v(SLOW); + v(Strictness::LENIENT); + v(local); + v(settings.name); + v(parameter); +} diff --git a/cpp/src/test/files/rules/detection/CxxAddressArgumentTestFile.cc b/cpp/src/test/files/rules/detection/CxxAddressArgumentTestFile.cc new file mode 100644 index 000000000..087175bd7 --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxAddressArgumentTestFile.cc @@ -0,0 +1,19 @@ +#include + +void key_set_up_in_the_function(const unsigned char *k, unsigned char *buf, unsigned char *iv) { + AES_KEY ak; + AES_set_encrypt_key(k, 128, &ak); + AES_cbc_encrypt(buf, buf, 64, &ak, iv, 1); // Noncompliant {{(BlockCipher) AES-128-CBC}} +} + +void two_keys(const unsigned char *k, unsigned char *buf, unsigned char *iv) { + AES_KEY k1; + AES_KEY k2; + AES_set_encrypt_key(k, 256, &k2); + AES_set_encrypt_key(k, 192, &k1); + AES_cbc_encrypt(buf, buf, 64, &k2, iv, 1); // Noncompliant {{(BlockCipher) AES-256-CBC}} +} + +void key_given_to_the_function(const AES_KEY *key, unsigned char *buf, unsigned char *iv) { + AES_cbc_encrypt(buf, buf, 64, key, iv, 1); // Noncompliant {{(BlockCipher) AES-CBC}} +} diff --git a/cpp/src/test/files/rules/detection/CxxArrayElementTestFile.c b/cpp/src/test/files/rules/detection/CxxArrayElementTestFile.c new file mode 100644 index 000000000..1c0ff59d1 --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxArrayElementTestFile.c @@ -0,0 +1,47 @@ +#include + +static const char *const GLOBAL_NAMES[] = {"SHA3-224", "SHA3-256"}; + +void every_element_for_a_variable_index(void) { + const char *names[] = {"SHA1", "SHA256", "MD5"}; + for (int i = 0; i < 3; i++) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(names[i]), NULL); + } +} + +void the_element_at_a_constant_index(void) { + const char *names[] = {"SHA384", "SHA512"}; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(names[1]), NULL); +} + +void an_element_assigned_after_the_declaration(void) { + const char *names[2] = {"SHA224", "SHA512-224"}; + names[1] = "SHA512-256"; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(names[1]), NULL); +} + +void a_designated_element(void) { + const char *names[3] = {[2] = "SM3", [0] = "MD4"}; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(names[2]), NULL); +} + +void a_column_of_a_two_dimensional_array(int row) { + const char *names[2][2] = {{"RIPEMD160", "BLAKE2b512"}, {"WHIRLPOOL", "BLAKE2s256"}}; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(names[row][1]), NULL); +} + +void an_element_of_a_global_array(int i) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(GLOBAL_NAMES[i]), NULL); +} + +void an_index_out_of_range(void) { + const char *names[] = {"MDC2"}; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(names[1]), NULL); +} diff --git a/cpp/src/test/files/rules/detection/CxxBaseDetectionRuleSharedSymbolsTestFile.cc b/cpp/src/test/files/rules/detection/CxxBaseDetectionRuleSharedSymbolsTestFile.cc new file mode 100644 index 000000000..4e2babc8c --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxBaseDetectionRuleSharedSymbolsTestFile.cc @@ -0,0 +1,6 @@ +#include + +void digest_by_variable_name() { + const char *name = "MD5"; + const EVP_MD *md = EVP_get_digestbyname(name); +} diff --git a/cpp/src/test/files/rules/detection/CxxChainedCallTestFile.cc b/cpp/src/test/files/rules/detection/CxxChainedCallTestFile.cc new file mode 100644 index 000000000..6915bf05f --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxChainedCallTestFile.cc @@ -0,0 +1,13 @@ +#include + +struct DigestApi { + const EVP_MD *(*EVP_get_digestbyname)(const char *name); +}; + +struct Library { + DigestApi digests(const char *provider); +}; + +void digest_through_chained_call(Library &library) { + library.digests("default").EVP_get_digestbyname("SHA512"); // Noncompliant {{(MessageDigest) SHA-512}} +} diff --git a/cpp/src/test/files/rules/detection/CxxConstructorArgumentTestFile.cc b/cpp/src/test/files/rules/detection/CxxConstructorArgumentTestFile.cc new file mode 100644 index 000000000..d4571419d --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxConstructorArgumentTestFile.cc @@ -0,0 +1,104 @@ +#include + +class Hasher { + EVP_MD_CTX *ctx_; + + public: + Hasher(const char *name) { + ctx_ = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx_, EVP_get_digestbyname(name), NULL); + } + + void reset(const char *name) { + EVP_DigestInit_ex(ctx_, EVP_get_digestbyname(name), NULL); + } + + void restart(const char *name); + + static void select(const char *name); +}; + +void Hasher::restart(const char *name) { + EVP_DigestInit_ex(ctx_, EVP_get_digestbyname(name), NULL); +} + +void Hasher::select(const char *name) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(name), NULL); +} + +class Digester { + EVP_MD_CTX *ctx_; + + public: + Digester(EVP_MD_CTX *ctx, const char *name); +}; + +Digester::Digester(EVP_MD_CTX *ctx, const char *name) { + ctx_ = ctx; + EVP_DigestInit_ex(ctx_, EVP_get_digestbyname(name), NULL); +} + +class Config {}; + +namespace crypto { +class Digest { + public: + explicit Digest(const char *name) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(name), NULL); + } +}; +} // namespace crypto + +void object_with_arguments() { + Hasher hasher("SHA224"); // Noncompliant {{(MessageDigest) SHA-224}} +} + +void object_with_braced_arguments() { + Hasher hasher{"SHA384"}; // Noncompliant {{(MessageDigest) SHA-384}} +} + +void object_with_a_variable_argument() { + const char *name = "SHA512"; + Hasher hasher(name); // Noncompliant {{(MessageDigest) SHA-512}} +} + +void object_created_with_new() { + Hasher *hasher = new Hasher("SHA1"); // Noncompliant {{(MessageDigest) SHA-1}} +} + +void temporary_object() { + Hasher("MD5"); // Noncompliant {{(MessageDigest) MD5}} +} + +void object_initialized_from_a_temporary() { + auto hasher = Hasher("SHA3-256"); // Noncompliant {{(MessageDigest) SHA3-256}} +} + +void constructor_defined_outside_its_class(EVP_MD_CTX *ctx) { + Digester digester(ctx, "SHA256"); // Noncompliant {{(MessageDigest) SHA-256}} +} + +void function_declared_in_a_block() { + Hasher make(Config); +} + +void object_of_a_class_in_a_namespace() { + crypto::Digest digest("SHA3-512"); // Noncompliant {{(MessageDigest) SHA3-512}} +} + +void member_function_calls(Hasher &hasher, Hasher *pointer) { + hasher.reset("SHA512-256"); // Noncompliant {{(MessageDigest) SHA-512/256}} + pointer->restart("SHA512-224"); // Noncompliant {{(MessageDigest) SHA-512/224}} +} + +void objects_without_a_constructor_call(Hasher *other) { + int size(32); + Hasher *alias(other); + Hasher &reference(*other); +} + +void static_member_function_call() { + Hasher::select("SM3"); // Noncompliant {{(MessageDigest) SM3}} +} diff --git a/cpp/src/test/files/rules/detection/CxxEnclosingCallArgumentTestFile.cc b/cpp/src/test/files/rules/detection/CxxEnclosingCallArgumentTestFile.cc new file mode 100644 index 000000000..4a2c9ec6c --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxEnclosingCallArgumentTestFile.cc @@ -0,0 +1,15 @@ +#include + +void selector_passed_to_a_detected_call(EVP_CIPHER_CTX *ctx, const unsigned char *key, + const unsigned char *iv) { + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} +} + +void selector_used_on_its_own(EVP_MD_CTX *ctx) { + const EVP_MD *md = EVP_sha256(); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_DigestInit_ex(ctx, md, NULL); +} + +void selector_passed_to_another_call(void) { + use_digest(EVP_sha384()); // Noncompliant {{(MessageDigest) SHA-384}} +} diff --git a/cpp/src/test/files/rules/detection/CxxFunctionPointerTestFile.c b/cpp/src/test/files/rules/detection/CxxFunctionPointerTestFile.c new file mode 100644 index 000000000..8717ca105 --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxFunctionPointerTestFile.c @@ -0,0 +1,23 @@ +#include + +typedef const EVP_MD *(*md_fn)(void); + +void pointer_to_a_getter(EVP_MD_CTX *ctx) { + md_fn get = EVP_sha384; + EVP_DigestInit_ex(ctx, get(), NULL); +} + +void pointer_assigned_the_address_of_a_getter(EVP_MD_CTX *ctx) { + const EVP_MD *(*get)(void); + get = &EVP_sha512; + EVP_DigestInit_ex(ctx, (*get)(), NULL); +} + +void table_of_getters(EVP_MD_CTX *ctx, int i) { + static const md_fn getters[] = {EVP_sha1, EVP_sha256}; + EVP_DigestInit_ex(ctx, getters[i](), NULL); +} + +void pointer_given_to_the_function(EVP_MD_CTX *ctx, md_fn get) { + EVP_DigestInit_ex(ctx, get(), NULL); +} diff --git a/cpp/src/test/files/rules/detection/CxxHookValueTestFile.cc b/cpp/src/test/files/rules/detection/CxxHookValueTestFile.cc new file mode 100644 index 000000000..675511004 --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxHookValueTestFile.cc @@ -0,0 +1,10 @@ +#include + +void sign(const char *name, EVP_PKEY *key) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(ctx, NULL, EVP_get_digestbyname(name), NULL, key); +} + +void sign_with_sha256(EVP_PKEY *key) { + sign("SHA256", key); +} diff --git a/cpp/src/test/files/rules/detection/CxxMemberFunctionCallTestFile.cc b/cpp/src/test/files/rules/detection/CxxMemberFunctionCallTestFile.cc new file mode 100644 index 000000000..26f86ce2e --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxMemberFunctionCallTestFile.cc @@ -0,0 +1,25 @@ +#include + +class Hasher { +public: + unsigned char *MD5(const unsigned char *d, unsigned long n, unsigned char *md) { return md; } +}; + +struct DigestApi { + unsigned char *(*MD5)(const unsigned char *d, size_t n, unsigned char *md); +}; + +void member_function(Hasher &reference, Hasher *pointer, const unsigned char *d, unsigned long n, unsigned char *md) { + Hasher local; + local.MD5(d, n, md); + reference.MD5(d, n, md); + pointer->MD5(d, n, md); +} + +void function_pointer_member(DigestApi *api, const unsigned char *d, size_t n, unsigned char *md) { + api->MD5(d, n, md); // Noncompliant {{(MessageDigest) MD5}} +} + +void standalone_function(const unsigned char *d, size_t n, unsigned char *md) { + MD5(d, n, md); // Noncompliant {{(MessageDigest) MD5}} +} diff --git a/cpp/src/test/files/rules/detection/CxxReturnValueTestFile.c b/cpp/src/test/files/rules/detection/CxxReturnValueTestFile.c new file mode 100644 index 000000000..3e52de41f --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxReturnValueTestFile.c @@ -0,0 +1,35 @@ +#include + +static const char *digest_name(void) { + return "SHA384"; +} + +static const char *chosen_name(int strong) { + const char *name = strong ? "SHA512" : "SHA256"; + return name; +} + +static const char *default_name(void) { + return "SHA224"; +} + +static const char *given_name(const char *name) { + return name; +} + +void returned_by_a_function(EVP_MD_CTX *ctx) { + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(digest_name()), NULL); +} + +void returned_through_a_variable(EVP_MD_CTX *ctx, int strong) { + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(chosen_name(strong)), NULL); +} + +void held_by_a_variable(EVP_MD_CTX *ctx) { + const char *name = default_name(); + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(name), NULL); +} + +void returning_its_parameter(EVP_MD_CTX *ctx, const char *name) { + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(given_name(name)), NULL); +} diff --git a/cpp/src/test/files/rules/detection/CxxScopedCallTestFile.cc b/cpp/src/test/files/rules/detection/CxxScopedCallTestFile.cc new file mode 100644 index 000000000..7df6ce81a --- /dev/null +++ b/cpp/src/test/files/rules/detection/CxxScopedCallTestFile.cc @@ -0,0 +1,60 @@ +#include + +namespace util { +void digest(EVP_MD_CTX *ctx, const char *name) { + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(name), NULL); +} + +void caller_in_the_namespace(EVP_MD_CTX *ctx) { + digest(ctx, "SHA224"); +} + +namespace inner { +void caller_in_an_inner_namespace(EVP_MD_CTX *ctx) { + digest(ctx, "SHA256"); +} +} // namespace inner + +void declared_here(EVP_MD_CTX *ctx, const char *name); +} // namespace util + +void qualified_caller(EVP_MD_CTX *ctx) { + util::digest(ctx, "SHA384"); +} + +void util::declared_here(EVP_MD_CTX *ctx, const char *name) { + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(name), NULL); +} + +void caller_of_a_function_defined_outside_its_namespace(EVP_MD_CTX *ctx) { + util::declared_here(ctx, "SHA512"); +} + +void digest(EVP_MD_CTX *ctx, const char *name); + +void caller_of_another_function_of_the_same_name(EVP_MD_CTX *ctx) { + digest(ctx, "MD5"); +} + +class Hasher { + EVP_MD_CTX *ctx_; + + public: + void reset(const char *name) { + EVP_DigestInit_ex(ctx_, EVP_get_digestbyname(name), NULL); + } + + void start() { + reset("SHA3-224"); + } + + void restart() { + this->reset("SHA3-256"); + } + + void later(); +}; + +void Hasher::later() { + reset("SHA3-384"); +} diff --git a/cpp/src/test/files/rules/detection/crossfile/CipherFetchCaller.cc b/cpp/src/test/files/rules/detection/crossfile/CipherFetchCaller.cc new file mode 100644 index 000000000..bdbc50a0a --- /dev/null +++ b/cpp/src/test/files/rules/detection/crossfile/CipherFetchCaller.cc @@ -0,0 +1,17 @@ +#include + +EVP_CIPHER* make_cipher(OSSL_LIB_CTX* lib, const char* alg, const char* props); + +// Field-constant argument -> detachable, resolved from the global at record time. +static const char* ALGO = "AES-192-SIV"; + +// Literal argument -> detachable path. The call is recorded while scanning this file, then +// resolved by the hook created while scanning CipherFetchWrapper.cc. +EVP_CIPHER* call_literal(OSSL_LIB_CTX* lib) { + return make_cipher(lib, "AES-128-SIV", NULL); +} + +// Global-constant argument -> detachable, resolved from the global at record time. +EVP_CIPHER* call_field(OSSL_LIB_CTX* lib) { + return make_cipher(lib, ALGO, NULL); +} diff --git a/cpp/src/test/files/rules/detection/crossfile/CipherFetchWrapper.cc b/cpp/src/test/files/rules/detection/crossfile/CipherFetchWrapper.cc new file mode 100644 index 000000000..8e28f7591 --- /dev/null +++ b/cpp/src/test/files/rules/detection/crossfile/CipherFetchWrapper.cc @@ -0,0 +1,8 @@ +#include + +// alg is a bare function parameter with no in-scope literal assignment, so resolving it forces +// the cross-file wrapper-hook path (CxxDetectionEngine.resolveValuesInOuterScope), the same +// mechanism exercised by Java's KeyGeneratorWrapper.generate(String algo, ...). +EVP_CIPHER* make_cipher(OSSL_LIB_CTX* lib, const char* alg, const char* props) { + return EVP_CIPHER_fetch(lib, alg, props); +} diff --git a/cpp/src/test/files/rules/detection/crossfile/HasherCaller.cc b/cpp/src/test/files/rules/detection/crossfile/HasherCaller.cc new file mode 100644 index 000000000..61f361589 --- /dev/null +++ b/cpp/src/test/files/rules/detection/crossfile/HasherCaller.cc @@ -0,0 +1,15 @@ +class Hasher { + public: + Hasher(const char *name); +}; + +// The constructor calls are recorded while scanning this file, before the hook of the +// constructor, defined in HasherDefinition.cc, is created. +void name_held_by_a_variable() { + const char *name = "SHA224"; + Hasher hasher(name); +} + +void braced_argument() { + Hasher hasher{"SHA384"}; +} diff --git a/cpp/src/test/files/rules/detection/crossfile/HasherDefinition.cc b/cpp/src/test/files/rules/detection/crossfile/HasherDefinition.cc new file mode 100644 index 000000000..365f2d107 --- /dev/null +++ b/cpp/src/test/files/rules/detection/crossfile/HasherDefinition.cc @@ -0,0 +1,13 @@ +#include + +class Hasher { + EVP_MD_CTX *ctx_; + + public: + Hasher(const char *name); +}; + +Hasher::Hasher(const char *name) { + ctx_ = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx_, EVP_get_digestbyname(name), NULL); +} diff --git a/cpp/src/test/files/rules/detection/crossfile/NamespaceCaller.cc b/cpp/src/test/files/rules/detection/crossfile/NamespaceCaller.cc new file mode 100644 index 000000000..2341cb0b4 --- /dev/null +++ b/cpp/src/test/files/rules/detection/crossfile/NamespaceCaller.cc @@ -0,0 +1,11 @@ +#include + +namespace util { +void digest(EVP_MD_CTX *ctx, const char *name); + +// The call is recorded, and detached, while scanning this file, before the hook of the function, +// defined in NamespaceDefinition.cc, is created. +void caller_in_the_namespace(EVP_MD_CTX *ctx) { + digest(ctx, "SHA512-256"); +} +} // namespace util diff --git a/cpp/src/test/files/rules/detection/crossfile/NamespaceDefinition.cc b/cpp/src/test/files/rules/detection/crossfile/NamespaceDefinition.cc new file mode 100644 index 000000000..b67864a9c --- /dev/null +++ b/cpp/src/test/files/rules/detection/crossfile/NamespaceDefinition.cc @@ -0,0 +1,7 @@ +#include + +namespace util { +void digest(EVP_MD_CTX *ctx, const char *name) { + EVP_DigestInit_ex(ctx, EVP_get_digestbyname(name), NULL); +} +} // namespace util diff --git a/cpp/src/test/files/rules/detection/isolation/CxxFailingFileTestFile.cc b/cpp/src/test/files/rules/detection/isolation/CxxFailingFileTestFile.cc new file mode 100644 index 000000000..d1b0cc8a2 --- /dev/null +++ b/cpp/src/test/files/rules/detection/isolation/CxxFailingFileTestFile.cc @@ -0,0 +1,5 @@ +void explode(void); + +void fails_while_detecting(void) { + explode(); +} diff --git a/cpp/src/test/files/rules/detection/isolation/CxxFileAfterFailingFileTestFile.cc b/cpp/src/test/files/rules/detection/isolation/CxxFileAfterFailingFileTestFile.cc new file mode 100644 index 000000000..d5a6cb4b9 --- /dev/null +++ b/cpp/src/test/files/rules/detection/isolation/CxxFileAfterFailingFileTestFile.cc @@ -0,0 +1,6 @@ +#include + +void digests_after_a_failing_file(void) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); +} diff --git a/cpp/src/test/files/rules/detection/isolation/CxxUnparsableFileTestFile.cc b/cpp/src/test/files/rules/detection/isolation/CxxUnparsableFileTestFile.cc new file mode 100644 index 000000000..f37f7e864 --- /dev/null +++ b/cpp/src/test/files/rules/detection/isolation/CxxUnparsableFileTestFile.cc @@ -0,0 +1,3 @@ +int nested_deeper_than_the_parser_follows(void) { + return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((1)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))); +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTestFile.cc new file mode 100644 index 000000000..b397a890c --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTestFile.cc @@ -0,0 +1,22 @@ +#include + +void reinitialized_with_another_cipher(const unsigned char *k, const unsigned char *iv) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) AES-128-CBC}} {{(BlockCipher) DESede168-CBC}} + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, k, iv); + EVP_CIPHER_CTX_reset(ctx); + EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, k, iv); +} + +void key_length_set_after_the_cipher(const unsigned char *k) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(StreamCipher) RC4-128}} + EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(ctx, 16); + EVP_EncryptInit_ex(ctx, NULL, NULL, k, NULL); +} + +void digest_context_reused(const unsigned char *d, size_t n) { + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); // Noncompliant {{(MessageDigest) SHA-1}} + EVP_MD_CTX_reset(ctx); + EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); // Noncompliant {{(MessageDigest) SHA-512}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherContextTestFile.cc new file mode 100644 index 000000000..10b7ddaa2 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherContextTestFile.cc @@ -0,0 +1,37 @@ +#include + +int gcm_encrypt(const unsigned char *key, const unsigned char *iv, unsigned char *tag) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL); + EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag); + EVP_CIPHER_CTX_free(ctx); + return 0; +} + +int rc4_decrypt(const unsigned char *key) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(StreamCipher) RC4-256}} + EVP_DecryptInit_ex(ctx, EVP_rc4(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(ctx, 32); + EVP_DecryptInit_ex(ctx, NULL, NULL, key, NULL); + return 0; +} + +static void init_in_a_helper(EVP_CIPHER_CTX *ctx) { + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL); // Noncompliant {{(BlockCipher) AES-128-CBC}} +} + +int context_initialised_by_a_helper(void) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + init_in_a_helper(ctx); + return 0; +} + +void context_not_created_here(EVP_CIPHER_CTX *ctx) { + EVP_EncryptInit_ex(ctx, EVP_chacha20(), NULL, NULL, NULL); // Noncompliant {{(StreamCipher) ChaCha20}} +} + +void key_set_on_a_context_initialised_elsewhere(EVP_CIPHER_CTX *ctx, const unsigned char *key) { + EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL); +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTestFile.cc new file mode 100644 index 000000000..8f21b3e76 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTestFile.cc @@ -0,0 +1,15 @@ +#include + +void fetch_by_name() { + EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_CIPHER_fetch(NULL, "aes-128-cbc", NULL); // Noncompliant {{(BlockCipher) AES-128-CBC}} + EVP_CIPHER_fetch(NULL, "id-aes256-GCM", NULL); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_CIPHER_fetch(NULL, "AES256", NULL); // Noncompliant {{(BlockCipher) AES-256-CBC}} + EVP_CIPHER_fetch(NULL, "DES3", NULL); // Noncompliant {{(BlockCipher) DESede168-CBC}} + EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); // Noncompliant {{(AuthenticatedEncryption) ChaCha20-Poly1305}} +} + +void get_cipher_by_name() { + EVP_get_cipherbyname("des-ede3-cbc"); // Noncompliant {{(BlockCipher) DESede168-CBC}} + EVP_get_cipherbyname("BF"); // Noncompliant {{(BlockCipher) Blowfish-128-CBC}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTestFile.cc new file mode 100644 index 000000000..5bb1b32e5 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTestFile.cc @@ -0,0 +1,64 @@ +#include + +void test_evp_cipher_fetch() +{ + OSSL_LIB_CTX *lib = NULL; + const char *props = NULL; + + // AES-SIV + EVP_CIPHER_fetch(lib, "AES-128-SIV", props); // Noncompliant {{(BlockCipher) AES-128-SIV}} + EVP_CIPHER_fetch(lib, "AES-192-SIV", props); // Noncompliant {{(BlockCipher) AES-192-SIV}} + EVP_CIPHER_fetch(lib, "AES-256-SIV", props); // Noncompliant {{(BlockCipher) AES-256-SIV}} + + // AES-GCM-SIV + EVP_CIPHER_fetch(lib, "AES-128-GCM-SIV", props); // Noncompliant {{(BlockCipher) AES-128-GCM-SIV}} + EVP_CIPHER_fetch(lib, "AES-192-GCM-SIV", props); // Noncompliant {{(BlockCipher) AES-192-GCM-SIV}} + EVP_CIPHER_fetch(lib, "AES-256-GCM-SIV", props); // Noncompliant {{(BlockCipher) AES-256-GCM-SIV}} + + // AES CBC-CTS + EVP_CIPHER_fetch(lib, "AES-128-CBC-CTS", props); // Noncompliant {{(BlockCipher) AES-128-CBC-CTS}} + EVP_CIPHER_fetch(lib, "AES-192-CBC-CTS", props); // Noncompliant {{(BlockCipher) AES-192-CBC-CTS}} + EVP_CIPHER_fetch(lib, "AES-256-CBC-CTS", props); // Noncompliant {{(BlockCipher) AES-256-CBC-CTS}} + + // AES WRAP-INV + EVP_CIPHER_fetch(lib, "AES-128-WRAP-INV", props); // Noncompliant {{(BlockCipher) AES-128-WRAP-INV}} + EVP_CIPHER_fetch(lib, "AES-192-WRAP-INV", props); // Noncompliant {{(BlockCipher) AES-192-WRAP-INV}} + EVP_CIPHER_fetch(lib, "AES-256-WRAP-INV", props); // Noncompliant {{(BlockCipher) AES-256-WRAP-INV}} + + // AES WRAP-PAD-INV + EVP_CIPHER_fetch(lib, "AES-128-WRAP-PAD-INV", props); // Noncompliant {{(BlockCipher) AES-128-WRAP-PAD-INV}} + EVP_CIPHER_fetch(lib, "AES-192-WRAP-PAD-INV", props); // Noncompliant {{(BlockCipher) AES-192-WRAP-PAD-INV}} + EVP_CIPHER_fetch(lib, "AES-256-WRAP-PAD-INV", props); // Noncompliant {{(BlockCipher) AES-256-WRAP-PAD-INV}} + + // AES CBC-HMAC (non-ETM) + EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA1", props); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA1}} + EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA256", props); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA256}} + EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA1", props); // Noncompliant {{(BlockCipher) AES-192-CBC-HMAC-SHA1}} + EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA256", props); // Noncompliant {{(BlockCipher) AES-192-CBC-HMAC-SHA256}} + EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA1", props); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA1}} + EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA256", props); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA256}} + + // AES ETM + EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA1-ETM", props); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA1-ETM}} + EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA1-ETM", props); // Noncompliant {{(BlockCipher) AES-192-CBC-HMAC-SHA1-ETM}} + EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA1-ETM", props); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA1-ETM}} + EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA256-ETM", props); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA256-ETM}} + EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA256-ETM", props); // Noncompliant {{(BlockCipher) AES-192-CBC-HMAC-SHA256-ETM}} + EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA256-ETM", props); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA256-ETM}} + EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA512-ETM", props); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA512-ETM}} + EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA512-ETM", props); // Noncompliant {{(BlockCipher) AES-192-CBC-HMAC-SHA512-ETM}} + EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA512-ETM", props); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA512-ETM}} + + // Camellia CBC-CTS + EVP_CIPHER_fetch(lib, "CAMELLIA-128-CBC-CTS", props); // Noncompliant {{(BlockCipher) CAMELLIA-128-CBC-CTS}} + EVP_CIPHER_fetch(lib, "CAMELLIA-192-CBC-CTS", props); // Noncompliant {{(BlockCipher) CAMELLIA-192-CBC-CTS}} + EVP_CIPHER_fetch(lib, "CAMELLIA-256-CBC-CTS", props); // Noncompliant {{(BlockCipher) CAMELLIA-256-CBC-CTS}} + + // AES-SIV via a local variable + const char *alg = "AES-128-SIV"; + EVP_CIPHER_fetch(lib, alg, props); // Noncompliant {{(BlockCipher) AES-128-SIV}} + + const char *alg2 = "AES-192-SIV"; + alg2 = "AES-256-SIV"; + EVP_CIPHER_fetch(lib, alg2, props); // Noncompliant {{(BlockCipher) AES-192-SIV}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherInitTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherInitTestFile.cc new file mode 100644 index 000000000..0bd80278f --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherInitTestFile.cc @@ -0,0 +1,18 @@ +#include + +void encrypt_with_cipher_argument(EVP_CIPHER_CTX *ctx, const unsigned char *key, + const unsigned char *iv) { + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} +} + +void decrypt_with_fetched_cipher(EVP_CIPHER_CTX *ctx, const unsigned char *key, + const unsigned char *iv) { + EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); + EVP_DecryptInit_ex2(ctx, cipher, key, iv, NULL); // Noncompliant {{(AuthenticatedEncryption) ChaCha20-Poly1305}} +} + +void cipher_init_with_direction(EVP_CIPHER_CTX *ctx, const unsigned char *key, + const unsigned char *iv) { + const EVP_CIPHER *cipher = EVP_des_ede3_cbc(); + EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, 1); // Noncompliant {{(BlockCipher) DESede168-CBC}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTestFile.cc new file mode 100644 index 000000000..8e7b11a07 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTestFile.cc @@ -0,0 +1,22 @@ +#include + +void fixed_key_length(const unsigned char *k, const unsigned char *iv) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) AES-128-CBC}} + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(ctx, 32); + EVP_EncryptInit_ex(ctx, NULL, NULL, k, iv); +} + +void variable_key_length(const unsigned char *k, const unsigned char *iv) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) Blowfish-256-CBC}} + EVP_EncryptInit_ex(ctx, EVP_bf_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(ctx, 32); + EVP_EncryptInit_ex(ctx, NULL, NULL, k, iv); +} + +void variable_key_length_of_cast5(const unsigned char *k, const unsigned char *iv) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) CAST5-80-CBC}} + EVP_DecryptInit_ex(ctx, EVP_cast5_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_key_length(ctx, 10); + EVP_DecryptInit_ex(ctx, NULL, NULL, k, iv); +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTestFile.cc new file mode 100644 index 000000000..d041fb613 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTestFile.cc @@ -0,0 +1,53 @@ +#include + +void key_length_in_bytes(void) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) Blowfish-128-CBC}} + EVP_EncryptInit_ex(ctx, EVP_bf_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_KEY_LENGTH, 16, NULL); +} + +void rc2_effective_key_bits(void) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) RC2-40-CBC}} + EVP_EncryptInit_ex(ctx, EVP_rc2_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, 40, NULL); +} + +void aead_iv_and_tag_by_name(unsigned char *tag) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(AuthenticatedEncryption) AES-128-GCM}} + EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 16, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 12, tag); +} + +void ccm_iv_and_tag(unsigned char *tag) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(AuthenticatedEncryption) AES-128-CCM}} + EVP_EncryptInit_ex(ctx, EVP_aes_128_ccm(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, 7, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, 8, NULL); +} + +void numeric_commands(unsigned char *tag) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, 0x9, 8, NULL); + EVP_CIPHER_CTX_ctrl(ctx, 0x10, 14, tag); +} + +void other_commands_are_not_parameters(unsigned char *iv) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IV_FIXED, 4, iv); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_IV_GEN, 12, iv); +} + +void standard_block_padding(void) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) AES-128-CBC-PKCS7}} + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_padding(ctx, 1); +} + +void padding_disabled(void) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // Noncompliant {{(BlockCipher) AES-128-CBC}} + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL); + EVP_CIPHER_CTX_set_padding(ctx, 0); +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherTestFile.cc new file mode 100644 index 000000000..3cb028139 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLEvpCipherTestFile.cc @@ -0,0 +1,218 @@ +#include + +void test_evp_cipher() { + EVP_PKEY_CTX* ctx = NULL; + EVP_CIPHER_CTX* cctx = NULL; + unsigned char buf[64]; + size_t len = 0; + + EVP_aes_128_cbc(); // Noncompliant {{(BlockCipher) AES-128-CBC}} + EVP_aes_128_ecb(); // Noncompliant {{(BlockCipher) AES-128-ECB}} + EVP_aes_128_gcm(); // Noncompliant {{(AuthenticatedEncryption) AES-128-GCM}} + EVP_aes_128_ctr(); // Noncompliant {{(BlockCipher) AES-128-CTR}} + EVP_aes_128_ccm(); // Noncompliant {{(AuthenticatedEncryption) AES-128-CCM}} + EVP_aes_128_cfb128(); // Noncompliant {{(BlockCipher) AES-128-CFB}} + EVP_aes_128_cfb1(); // Noncompliant {{(BlockCipher) AES-128-CFB1}} + EVP_aes_128_cfb8(); // Noncompliant {{(BlockCipher) AES-128-CFB8}} + EVP_aes_128_ofb(); // Noncompliant {{(BlockCipher) AES-128-OFB}} + EVP_aes_128_xts(); // Noncompliant {{(BlockCipher) AES-128-XTS}} + EVP_aes_128_ocb(); // Noncompliant {{(BlockCipher) AES-128-OCB}} + EVP_aes_128_wrap(); // Noncompliant {{(BlockCipher) AES-128-WRAP}} + EVP_aes_128_wrap_pad(); // Noncompliant {{(BlockCipher) AES-128-WRAP-PAD}} + EVP_aes_192_cbc(); // Noncompliant {{(BlockCipher) AES-192-CBC}} + EVP_aes_192_ecb(); // Noncompliant {{(BlockCipher) AES-192-ECB}} + EVP_aes_192_gcm(); // Noncompliant {{(AuthenticatedEncryption) AES-192-GCM}} + EVP_aes_192_ctr(); // Noncompliant {{(BlockCipher) AES-192-CTR}} + EVP_aes_192_ccm(); // Noncompliant {{(AuthenticatedEncryption) AES-192-CCM}} + EVP_aes_192_cfb128(); // Noncompliant {{(BlockCipher) AES-192-CFB}} + EVP_aes_192_cfb1(); // Noncompliant {{(BlockCipher) AES-192-CFB1}} + EVP_aes_192_cfb8(); // Noncompliant {{(BlockCipher) AES-192-CFB8}} + EVP_aes_192_ofb(); // Noncompliant {{(BlockCipher) AES-192-OFB}} + EVP_aes_192_ocb(); // Noncompliant {{(BlockCipher) AES-192-OCB}} + EVP_aes_192_wrap(); // Noncompliant {{(BlockCipher) AES-192-WRAP}} + EVP_aes_192_wrap_pad(); // Noncompliant {{(BlockCipher) AES-192-WRAP-PAD}} + EVP_aes_256_cbc(); // Noncompliant {{(BlockCipher) AES-256-CBC}} + EVP_aes_256_ecb(); // Noncompliant {{(BlockCipher) AES-256-ECB}} + EVP_aes_256_gcm(); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_aes_256_ctr(); // Noncompliant {{(BlockCipher) AES-256-CTR}} + EVP_aes_256_ccm(); // Noncompliant {{(AuthenticatedEncryption) AES-256-CCM}} + EVP_aes_256_cfb128(); // Noncompliant {{(BlockCipher) AES-256-CFB}} + EVP_aes_256_cfb1(); // Noncompliant {{(BlockCipher) AES-256-CFB1}} + EVP_aes_256_cfb8(); // Noncompliant {{(BlockCipher) AES-256-CFB8}} + EVP_aes_256_ofb(); // Noncompliant {{(BlockCipher) AES-256-OFB}} + EVP_aes_256_xts(); // Noncompliant {{(BlockCipher) AES-256-XTS}} + EVP_aes_256_ocb(); // Noncompliant {{(BlockCipher) AES-256-OCB}} + EVP_aes_256_wrap(); // Noncompliant {{(BlockCipher) AES-256-WRAP}} + EVP_aes_256_wrap_pad(); // Noncompliant {{(BlockCipher) AES-256-WRAP-PAD}} + EVP_camellia_128_ecb(); // Noncompliant {{(BlockCipher) CAMELLIA-128-ECB}} + EVP_camellia_128_cbc(); // Noncompliant {{(BlockCipher) CAMELLIA-128-CBC}} + EVP_camellia_128_cfb128(); // Noncompliant {{(BlockCipher) CAMELLIA-128-CFB}} + EVP_camellia_128_cfb1(); // Noncompliant {{(BlockCipher) CAMELLIA-128-CFB1}} + EVP_camellia_128_cfb8(); // Noncompliant {{(BlockCipher) CAMELLIA-128-CFB8}} + EVP_camellia_128_ofb(); // Noncompliant {{(BlockCipher) CAMELLIA-128-OFB}} + EVP_camellia_128_ctr(); // Noncompliant {{(BlockCipher) CAMELLIA-128-CTR}} + EVP_camellia_192_ecb(); // Noncompliant {{(BlockCipher) CAMELLIA-192-ECB}} + EVP_camellia_192_cbc(); // Noncompliant {{(BlockCipher) CAMELLIA-192-CBC}} + EVP_camellia_192_cfb128(); // Noncompliant {{(BlockCipher) CAMELLIA-192-CFB}} + EVP_camellia_192_cfb1(); // Noncompliant {{(BlockCipher) CAMELLIA-192-CFB1}} + EVP_camellia_192_cfb8(); // Noncompliant {{(BlockCipher) CAMELLIA-192-CFB8}} + EVP_camellia_192_ofb(); // Noncompliant {{(BlockCipher) CAMELLIA-192-OFB}} + EVP_camellia_192_ctr(); // Noncompliant {{(BlockCipher) CAMELLIA-192-CTR}} + EVP_camellia_256_ecb(); // Noncompliant {{(BlockCipher) CAMELLIA-256-ECB}} + EVP_camellia_256_cbc(); // Noncompliant {{(BlockCipher) CAMELLIA-256-CBC}} + EVP_camellia_256_cfb128(); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB}} + EVP_camellia_256_cfb1(); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB1}} + EVP_camellia_256_cfb8(); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB8}} + EVP_camellia_256_ofb(); // Noncompliant {{(BlockCipher) CAMELLIA-256-OFB}} + EVP_camellia_256_ctr(); // Noncompliant {{(BlockCipher) CAMELLIA-256-CTR}} + EVP_aria_128_ecb(); // Noncompliant {{(BlockCipher) ARIA-128-ECB}} + EVP_aria_128_cbc(); // Noncompliant {{(BlockCipher) ARIA-128-CBC}} + EVP_aria_128_cfb128(); // Noncompliant {{(BlockCipher) ARIA-128-CFB}} + EVP_aria_128_cfb1(); // Noncompliant {{(BlockCipher) ARIA-128-CFB1}} + EVP_aria_128_cfb8(); // Noncompliant {{(BlockCipher) ARIA-128-CFB8}} + EVP_aria_128_ofb(); // Noncompliant {{(BlockCipher) ARIA-128-OFB}} + EVP_aria_128_ctr(); // Noncompliant {{(BlockCipher) ARIA-128-CTR}} + EVP_aria_128_gcm(); // Noncompliant {{(BlockCipher) ARIA-128-GCM}} + EVP_aria_128_ccm(); // Noncompliant {{(BlockCipher) ARIA-128-CCM}} + EVP_aria_192_ecb(); // Noncompliant {{(BlockCipher) ARIA-192-ECB}} + EVP_aria_192_cbc(); // Noncompliant {{(BlockCipher) ARIA-192-CBC}} + EVP_aria_192_cfb128(); // Noncompliant {{(BlockCipher) ARIA-192-CFB}} + EVP_aria_192_cfb1(); // Noncompliant {{(BlockCipher) ARIA-192-CFB1}} + EVP_aria_192_cfb8(); // Noncompliant {{(BlockCipher) ARIA-192-CFB8}} + EVP_aria_192_ofb(); // Noncompliant {{(BlockCipher) ARIA-192-OFB}} + EVP_aria_192_ctr(); // Noncompliant {{(BlockCipher) ARIA-192-CTR}} + EVP_aria_192_gcm(); // Noncompliant {{(BlockCipher) ARIA-192-GCM}} + EVP_aria_192_ccm(); // Noncompliant {{(BlockCipher) ARIA-192-CCM}} + EVP_aria_256_ecb(); // Noncompliant {{(BlockCipher) ARIA-256-ECB}} + EVP_aria_256_cbc(); // Noncompliant {{(BlockCipher) ARIA-256-CBC}} + EVP_aria_256_cfb128(); // Noncompliant {{(BlockCipher) ARIA-256-CFB}} + EVP_aria_256_cfb1(); // Noncompliant {{(BlockCipher) ARIA-256-CFB1}} + EVP_aria_256_cfb8(); // Noncompliant {{(BlockCipher) ARIA-256-CFB8}} + EVP_aria_256_ofb(); // Noncompliant {{(BlockCipher) ARIA-256-OFB}} + EVP_aria_256_ctr(); // Noncompliant {{(BlockCipher) ARIA-256-CTR}} + EVP_aria_256_gcm(); // Noncompliant {{(BlockCipher) ARIA-256-GCM}} + EVP_aria_256_ccm(); // Noncompliant {{(BlockCipher) ARIA-256-CCM}} + EVP_sm4_ecb(); // Noncompliant {{(BlockCipher) SM4-ECB}} + EVP_sm4_cbc(); // Noncompliant {{(BlockCipher) SM4-CBC}} + EVP_sm4_cfb128(); // Noncompliant {{(BlockCipher) SM4-CFB}} + EVP_sm4_ofb(); // Noncompliant {{(BlockCipher) SM4-OFB}} + EVP_sm4_ctr(); // Noncompliant {{(BlockCipher) SM4-CTR}} + EVP_des_cbc(); // Noncompliant {{(BlockCipher) DES-56-CBC}} + EVP_des_ecb(); // Noncompliant {{(BlockCipher) DES-56-ECB}} + EVP_des_ede3_cbc(); // Noncompliant {{(BlockCipher) DESede168-CBC}} + EVP_des_cfb64(); // Noncompliant {{(BlockCipher) DES-56-CFB}} + EVP_des_cfb1(); // Noncompliant {{(BlockCipher) DES-56-CFB1}} + EVP_des_cfb8(); // Noncompliant {{(BlockCipher) DES-56-CFB8}} + EVP_des_ofb(); // Noncompliant {{(BlockCipher) DES-56-OFB}} + EVP_des_ede(); // Noncompliant {{(BlockCipher) DESede112}} + EVP_des_ede_ecb(); // Noncompliant {{(BlockCipher) DESede112-ECB}} + EVP_des_ede_cbc(); // Noncompliant {{(BlockCipher) DESede112-CBC}} + EVP_des_ede_cfb64(); // Noncompliant {{(BlockCipher) DESede112-CFB64}} + EVP_des_ede_ofb(); // Noncompliant {{(BlockCipher) DESede112-OFB}} + EVP_des_ede3(); // Noncompliant {{(BlockCipher) DESede168}} + EVP_des_ede3_ecb(); // Noncompliant {{(BlockCipher) DESede168-ECB}} + EVP_des_ede3_cfb1(); // Noncompliant {{(BlockCipher) DESede168-CFB1}} + EVP_des_ede3_cfb8(); // Noncompliant {{(BlockCipher) DESede168-CFB8}} + EVP_des_ede3_cfb64(); // Noncompliant {{(BlockCipher) DESede168-CFB64}} + EVP_des_ede3_ofb(); // Noncompliant {{(BlockCipher) DESede168-OFB}} + EVP_desx_cbc(); // Noncompliant {{(BlockCipher) DESX-184-CBC}} + EVP_bf_ecb(); // Noncompliant {{(BlockCipher) Blowfish-128-ECB}} + EVP_bf_cbc(); // Noncompliant {{(BlockCipher) Blowfish-128-CBC}} + EVP_bf_cfb64(); // Noncompliant {{(BlockCipher) Blowfish-128-CFB}} + EVP_bf_ofb(); // Noncompliant {{(BlockCipher) Blowfish-128-OFB}} + EVP_cast5_ecb(); // Noncompliant {{(BlockCipher) CAST5-128-ECB}} + EVP_cast5_cbc(); // Noncompliant {{(BlockCipher) CAST5-128-CBC}} + EVP_cast5_cfb64(); // Noncompliant {{(BlockCipher) CAST5-128-CFB}} + EVP_cast5_ofb(); // Noncompliant {{(BlockCipher) CAST5-128-OFB}} + EVP_rc2_ecb(); // Noncompliant {{(BlockCipher) RC2-128-ECB}} + EVP_rc2_cbc(); // Noncompliant {{(BlockCipher) RC2-128-CBC}} + EVP_rc2_cfb64(); // Noncompliant {{(BlockCipher) RC2-128-CFB}} + EVP_rc2_ofb(); // Noncompliant {{(BlockCipher) RC2-128-OFB}} + EVP_rc2_40_cbc(); // Noncompliant {{(BlockCipher) RC2-40-CBC}} + EVP_rc2_64_cbc(); // Noncompliant {{(BlockCipher) RC2-64-CBC}} + EVP_rc4(); // Noncompliant {{(StreamCipher) RC4}} + EVP_rc4_40(); // Noncompliant {{(StreamCipher) RC4-40}} + EVP_rc4_hmac_md5(); // Noncompliant {{(StreamCipher) RC4}} + EVP_rc5_32_12_16_ecb(); // Noncompliant {{(BlockCipher) RC5-128-ECB}} + EVP_rc5_32_12_16_cbc(); // Noncompliant {{(BlockCipher) RC5-128-CBC}} + EVP_rc5_32_12_16_cfb64(); // Noncompliant {{(BlockCipher) RC5-128-CFB}} + EVP_rc5_32_12_16_ofb(); // Noncompliant {{(BlockCipher) RC5-128-OFB}} + EVP_idea_ecb(); // Noncompliant {{(BlockCipher) IDEA-ECB}} + EVP_idea_cbc(); // Noncompliant {{(BlockCipher) IDEA-CBC}} + EVP_idea_cfb64(); // Noncompliant {{(BlockCipher) IDEA-CFB}} + EVP_idea_ofb(); // Noncompliant {{(BlockCipher) IDEA-OFB}} + EVP_seed_ecb(); // Noncompliant {{(BlockCipher) SEED-128-ECB}} + EVP_seed_cbc(); // Noncompliant {{(BlockCipher) SEED-128-CBC}} + EVP_seed_cfb128(); // Noncompliant {{(BlockCipher) SEED-128-CFB}} + EVP_seed_ofb(); // Noncompliant {{(BlockCipher) SEED-128-OFB}} + EVP_chacha20(); // Noncompliant {{(StreamCipher) ChaCha20}} + EVP_chacha20_poly1305(); // Noncompliant {{(AuthenticatedEncryption) ChaCha20-Poly1305}} + EVP_aes_128_cbc_hmac_sha1(); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA1}} + EVP_aes_256_cbc_hmac_sha1(); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA1}} + EVP_aes_128_cbc_hmac_sha256(); // Noncompliant {{(BlockCipher) AES-128-CBC-HMAC-SHA256}} + EVP_aes_256_cbc_hmac_sha256(); // Noncompliant {{(BlockCipher) AES-256-CBC-HMAC-SHA256}} + EVP_PKEY_encrypt(ctx, buf, &len, buf, 64); + EVP_PKEY_encrypt_init(ctx); + EVP_PKEY_encrypt_init_ex(ctx, NULL); + EVP_PKEY_decrypt(ctx, buf, &len, buf, 64); + EVP_PKEY_decrypt_init(ctx); + EVP_PKEY_decrypt_init_ex(ctx, NULL); + EVP_PKEY_CTX_set_rsa_padding(ctx, 4); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + const EVP_MD* oaep_md = EVP_sha256(); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_PKEY_CTX_set_rsa_oaep_md(ctx, oaep_md); + // md_name given as the OpenSSL 3.x provider fetch name. + EVP_PKEY_CTX_set_rsa_oaep_md_name(ctx, "SHA2-256", NULL); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, buf, 16); + EVP_EncryptInit(cctx, NULL, buf, buf); + EVP_EncryptInit_ex(cctx, NULL, NULL, buf, buf); + EVP_EncryptInit_ex2(cctx, NULL, buf, buf, NULL); + EVP_DecryptInit(cctx, NULL, buf, buf); + EVP_DecryptInit_ex(cctx, NULL, NULL, buf, buf); + EVP_DecryptInit_ex2(cctx, NULL, buf, buf, NULL); + EVP_CipherInit(cctx, NULL, buf, buf, 1); + EVP_CipherInit_ex(cctx, NULL, NULL, buf, buf, 1); + EVP_CipherInit_ex2(cctx, NULL, buf, buf, 1, NULL); + EVP_ASYM_CIPHER_fetch(NULL, "RSA", NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_get_cipherbyname("AES-256-GCM"); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_des_ede3_wrap(); // Noncompliant {{(BlockCipher) DESede168-WRAP}} + EVP_enc_null(); + CMS_encrypt(NULL, buf, NULL, NULL); + CMS_encrypt_ex(NULL, buf, NULL, NULL, NULL, 0); + CMS_EnvelopedData_create(NULL); + CMS_EnvelopedData_create_ex(NULL, NULL, NULL); + CMS_AuthEnvelopedData_create(NULL); + CMS_AuthEnvelopedData_create_ex(NULL, NULL, NULL); + CMS_EncryptedData_encrypt(NULL, buf, NULL, 16, 0); + CMS_EncryptedData_encrypt_ex(NULL, buf, NULL, 16, 0, NULL, NULL); + CMS_EncryptedData_set1_key(NULL, NULL, buf, 32); + CMS_add0_recipient_key(NULL, 0, buf, 16, buf, 8, NULL, NULL, NULL); + PKCS7_encrypt(NULL, buf, NULL, 0); + PKCS7_encrypt_ex(NULL, buf, NULL, 0, NULL, NULL); + PKCS7_set_cipher(NULL, NULL); + + // CFB names defined as macros for their cfb64/cfb128 functions + EVP_aes_128_cfb(); // Noncompliant {{(BlockCipher) AES-128-CFB}} + EVP_des_cfb(); // Noncompliant {{(BlockCipher) DES-56-CFB}} + EVP_des_ede3_cfb(); // Noncompliant {{(BlockCipher) DESede168-CFB64}} + EVP_bf_cfb(); // Noncompliant {{(BlockCipher) Blowfish-128-CFB}} + EVP_aes_192_cfb(); // Noncompliant {{(BlockCipher) AES-192-CFB}} + EVP_aes_256_cfb(); // Noncompliant {{(BlockCipher) AES-256-CFB}} + EVP_aria_128_cfb(); // Noncompliant {{(BlockCipher) ARIA-128-CFB}} + EVP_aria_192_cfb(); // Noncompliant {{(BlockCipher) ARIA-192-CFB}} + EVP_aria_256_cfb(); // Noncompliant {{(BlockCipher) ARIA-256-CFB}} + EVP_camellia_128_cfb(); // Noncompliant {{(BlockCipher) CAMELLIA-128-CFB}} + EVP_camellia_192_cfb(); // Noncompliant {{(BlockCipher) CAMELLIA-192-CFB}} + EVP_camellia_256_cfb(); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB}} + EVP_cast5_cfb(); // Noncompliant {{(BlockCipher) CAST5-128-CFB}} + EVP_des_ede_cfb(); // Noncompliant {{(BlockCipher) DESede112-CFB64}} + EVP_idea_cfb(); // Noncompliant {{(BlockCipher) IDEA-CFB}} + EVP_rc2_cfb(); // Noncompliant {{(BlockCipher) RC2-128-CFB}} + EVP_rc5_32_12_16_cfb(); // Noncompliant {{(BlockCipher) RC5-128-CFB}} + EVP_seed_cfb(); // Noncompliant {{(BlockCipher) SEED-128-CFB}} + EVP_sm4_cfb(); // Noncompliant {{(BlockCipher) SM4-CFB}} +} + +void ciphers_by_nid(void) { + EVP_get_cipherbynid(NID_aes_256_gcm); // Noncompliant {{(AuthenticatedEncryption) AES-256-GCM}} + EVP_get_cipherbynid(1018); // Noncompliant {{(AuthenticatedEncryption) ChaCha20-Poly1305}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTestFile.cc new file mode 100644 index 000000000..980db9421 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTestFile.cc @@ -0,0 +1,33 @@ +#include +#include + +void write_encrypted_pem(BIO *out, EVP_PKEY *pkey, const char *pass) { + PEM_write_bio_PrivateKey(out, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) AES-256-CBC}} +} + +void write_encrypted_pkcs8(BIO *out, EVP_PKEY *pkey, const char *pass) { + const EVP_CIPHER *cipher = EVP_aes_128_cbc(); + PEM_write_bio_PKCS8PrivateKey(out, pkey, cipher, NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) AES-128-CBC}} +} + +void write_encrypted_der(BIO *out, EVP_PKEY *pkey, const char *pass) { + i2d_PKCS8PrivateKey_bio(out, pkey, EVP_des_ede3_cbc(), NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) DESede168-CBC}} +} + +void write_traditional(FILE *fp, EVP_PKEY *pkey, const char *pass) { + PEM_write_bio_PrivateKey_traditional(fp, pkey, EVP_aes_192_cbc(), NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) AES-192-CBC}} +} + +void write_unencrypted(BIO *out, EVP_PKEY *pkey) { + PEM_write_bio_PrivateKey(out, pkey, NULL, NULL, 0, NULL, NULL); +} + +void write_to_files(FILE *fp, EVP_PKEY *pkey, const char *pass) { + PEM_write_PrivateKey(fp, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) AES-256-CBC}} + PEM_write_PKCS8PrivateKey(fp, pkey, EVP_aes_128_cbc(), NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) AES-128-CBC}} + i2d_PKCS8PrivateKey_fp(fp, pkey, EVP_aes_192_cbc(), NULL, 0, NULL, (void *) pass); // Noncompliant {{(BlockCipher) AES-192-CBC}} +} + +void write_with_a_library_context(BIO *out, EVP_PKEY *pkey, OSSL_LIB_CTX *libctx) { + PEM_write_bio_PrivateKey_ex(out, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, NULL, libctx, NULL); // Noncompliant {{(BlockCipher) AES-256-CBC}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/cms/OpenSSLCmsTestFile.cc b/cpp/src/test/files/rules/detection/openssl/cms/OpenSSLCmsTestFile.cc new file mode 100644 index 000000000..6692d84b7 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/cms/OpenSSLCmsTestFile.cc @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +void cms_envelope(struct stack_st_X509 *certs, BIO *in) { + CMS_encrypt(certs, in, EVP_aes_256_cbc(), CMS_BINARY); // Noncompliant {{(BlockCipher) AES-256-CBC}} +} + +void cms_encrypted_data_with_fetched_cipher(BIO *in, const unsigned char *key) { + EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL); + CMS_EncryptedData_encrypt_ex(in, cipher, key, 16, 0, NULL, NULL); // Noncompliant {{(AuthenticatedEncryption) AES-128-GCM}} +} + +void cms_kek_recipient(CMS_ContentInfo *cms, unsigned char *key, unsigned char *id) { + CMS_add0_recipient_key(cms, NID_id_aes256_wrap, key, 32, id, 8, NULL, NULL, NULL); // Noncompliant {{(BlockCipher) AES-256-WRAP}} +} + +void pkcs7_envelope(struct stack_st_X509 *certs, BIO *in) { + PKCS7_encrypt(certs, in, EVP_des_ede3_cbc(), PKCS7_BINARY); // Noncompliant {{(BlockCipher) DESede168-CBC}} +} + +void decrypting_with_a_given_key(CMS_ContentInfo *cms, PKCS7 *p7, EVP_PKEY *pkey, X509 *cert, + BIO *out) { + CMS_decrypt(cms, pkey, cert, NULL, out, 0); + CMS_decrypt_set1_pkey(cms, pkey, cert); + PKCS7_decrypt(p7, pkey, cert, out, 0); +} + +void signing(X509 *cert, EVP_PKEY *pkey, BIO *data, CMS_ContentInfo *cms, PKCS7 *p7, + OCSP_BASICRESP *resp) { + CMS_sign(cert, pkey, NULL, data, CMS_PARTIAL); // Noncompliant {{(Signature) unknown}} + CMS_add1_signer(cms, cert, pkey, EVP_sha384(), 0); // Noncompliant {{(Signature) unknown}} + PKCS7_sign_add_signer(p7, cert, pkey, EVP_sha256(), 0); // Noncompliant {{(Signature) unknown}} + OCSP_basic_sign(resp, cert, pkey, EVP_sha1(), NULL, 0); // Noncompliant {{(Signature) unknown}} +} + +void rsa_pss_signing(EVP_PKEY_CTX *pctx) { + EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, RSA_PSS_SALTLEN_DIGEST); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} +} + +void timestamping(CONF *conf, TS_RESP_CTX *ctx) { + TS_CONF_set_signer_digest(conf, "tsa_config", "sha256", ctx); // Noncompliant {{(MessageDigest) SHA-256}} + TS_RESP_CTX_add_md(ctx, EVP_sha512()); // Noncompliant {{(MessageDigest) SHA-512}} +} + +void crmf_password_based_mac() { + OSSL_CRMF_PBMPARAMETER *pbm = OSSL_CRMF_pbmp_new(NULL, 16, NID_sha256, 500, NID_hmac_sha1); // Noncompliant {{(Mac) HMAC-SHA-1}} {{(MessageDigest) SHA-256}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/digest/OpenSSLEvpMessageDigestTestFile.cc b/cpp/src/test/files/rules/detection/openssl/digest/OpenSSLEvpMessageDigestTestFile.cc new file mode 100644 index 000000000..d4d6e57d8 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/digest/OpenSSLEvpMessageDigestTestFile.cc @@ -0,0 +1,47 @@ +#include + +void test_evp_message_digest() { + EVP_md2(); // Noncompliant {{(MessageDigest) MD2}} + EVP_md4(); // Noncompliant {{(MessageDigest) MD4}} + EVP_md5(); // Noncompliant {{(MessageDigest) MD5}} + EVP_mdc2(); // Noncompliant {{(MessageDigest) MDC2}} + EVP_sha1(); // Noncompliant {{(MessageDigest) SHA-1}} + EVP_sha224(); // Noncompliant {{(MessageDigest) SHA-224}} + EVP_sha256(); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_sha384(); // Noncompliant {{(MessageDigest) SHA-384}} + EVP_sha512(); // Noncompliant {{(MessageDigest) SHA-512}} + EVP_sha512_224(); // Noncompliant {{(MessageDigest) SHA-512/224}} + EVP_sha512_256(); // Noncompliant {{(MessageDigest) SHA-512/256}} + EVP_sha3_224(); // Noncompliant {{(MessageDigest) SHA3-224}} + EVP_sha3_256(); // Noncompliant {{(MessageDigest) SHA3-256}} + EVP_sha3_384(); // Noncompliant {{(MessageDigest) SHA3-384}} + EVP_sha3_512(); // Noncompliant {{(MessageDigest) SHA3-512}} + EVP_shake128(); // Noncompliant {{(ExtendableOutputFunction) SHAKE128}} + EVP_shake256(); // Noncompliant {{(ExtendableOutputFunction) SHAKE256}} + EVP_ripemd160(); // Noncompliant {{(MessageDigest) RIPEMD-160}} + EVP_whirlpool(); // Noncompliant {{(MessageDigest) Whirlpool}} + EVP_blake2b512(); // Noncompliant {{(MessageDigest) BLAKE2b-512}} + EVP_blake2s256(); // Noncompliant {{(MessageDigest) BLAKE2s-256}} + EVP_sm3(); // Noncompliant {{(MessageDigest) SM3}} + EVP_md5_sha1(); // Noncompliant {{(MessageDigest) MD5-SHA1}} + EVP_md_null(); + EVP_MD_fetch(NULL, "SHA256", NULL); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_get_digestbyname("SHA256"); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_DigestInit(NULL, NULL); + EVP_DigestInit_ex(NULL, NULL, NULL); + EVP_DigestInit_ex2(NULL, NULL, NULL); + EVP_Q_digest(NULL, "SHA256", NULL, NULL, 0, NULL, NULL); // Noncompliant {{(MessageDigest) SHA-256}} + + // Digest name via a local variable, not a literal. + const char *digest_name = "SHA256"; + EVP_MD_fetch(NULL, digest_name, NULL); // Noncompliant {{(MessageDigest) SHA-256}} + + // Digest name as the OpenSSL 3.x provider fetch name (OSSL_DIGEST_NAME_SHA2_256). + EVP_MD_fetch(NULL, "SHA2-256", NULL); // Noncompliant {{(MessageDigest) SHA-256}} +} + +void digests_by_nid(void) { + EVP_get_digestbynid(NID_sha256); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_get_digestbynid(672); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_get_digestbynid(NID_sha3_256); // Noncompliant {{(MessageDigest) SHA3-256}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfContextTestFile.cc new file mode 100644 index 000000000..08455143d --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfContextTestFile.cc @@ -0,0 +1,99 @@ +#include +#include + +void hkdf_with_digest_set_on_context() { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA2-256", 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_CTX_set_params(kctx, params); +} + +void pbkdf2_with_name_variable_and_digest_set_on_derive() { + const char *name = "PBKDF2"; + unsigned char out[32]; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, name, NULL); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-512}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA512", 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_derive(kctx, out, sizeof(out), params); +} + +void two_kdfs_in_one_function() { + EVP_KDF *hkdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + EVP_KDF_CTX *hkdf_ctx = EVP_KDF_CTX_new(hkdf); + OSSL_PARAM sha256_params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_CTX_set_params(hkdf_ctx, sha256_params); + + EVP_KDF *sshkdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); // Noncompliant {{(KeyDerivationFunction) SSHKDF-SHA-512}} + EVP_KDF_CTX *sshkdf_ctx = EVP_KDF_CTX_new(sshkdf); + OSSL_PARAM sha512_params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA512", 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_CTX_set_params(sshkdf_ctx, sha512_params); +} + +void two_kdfs_fetched_inline() { + EVP_KDF_CTX *hkdf_ctx = EVP_KDF_CTX_new(EVP_KDF_fetch(NULL, "HKDF", NULL)); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-384}} + EVP_KDF_CTX *pbkdf2_ctx = EVP_KDF_CTX_new(EVP_KDF_fetch(NULL, "PBKDF2", NULL)); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2}} + OSSL_PARAM sha384_params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA384", 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_CTX_set_params(hkdf_ctx, sha384_params); +} + +void hkdf_with_key_length_on_derive(unsigned char *out, OSSL_PARAM *params) { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF_derive(kctx, out, 32, params); +} + +void hkdf_with_digest_written_to_array_element(unsigned char *salt, size_t salt_len) { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[3]; + params[0] = OSSL_PARAM_construct_octet_string("salt", salt, salt_len); + params[1] = OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0); + params[2] = OSSL_PARAM_construct_end(); + EVP_KDF_CTX_set_params(kctx, params); +} + +void hkdf_with_digest_name_in_variable() { + const char *mdname = "SHA384"; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-384}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, (char *)mdname, 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_CTX_set_params(kctx, params); +} + +void hkdf_with_digest_set_by_param_macro() { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-512}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_utf8_string("digest", "SHA512", 0), + OSSL_PARAM_END + }; + EVP_KDF_CTX_set_params(kctx, params); +} + +void hkdf_with_key_length_and_digest_on_derive(unsigned char *out) { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_derive(kctx, out, 32, params); +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2TestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2TestFile.cc new file mode 100644 index 000000000..a3ccd4e84 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2TestFile.cc @@ -0,0 +1,9 @@ +#include + +void pbkdf2_hmac_sha256(const char *pass, const unsigned char *salt, unsigned char *out) { + PKCS5_PBKDF2_HMAC(pass, 8, salt, 16, 10000, EVP_sha256(), 32, out); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-256}} +} + +void pbkdf2_hmac_sha1(const char *pass, const unsigned char *salt, unsigned char *out) { + PKCS5_PBKDF2_HMAC_SHA1(pass, 8, salt, 16, 2048, 20, out); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-1}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTestFile.cc new file mode 100644 index 000000000..dfb15634f --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTestFile.cc @@ -0,0 +1,9 @@ +#include + +void scrypt(const char *pass, const unsigned char *salt, unsigned char *key) { + EVP_PBE_scrypt(pass, 8, salt, 16, 16384, 8, 1, 0, key, 32); // Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} +} + +void scrypt_ex(const char *pass, const unsigned char *salt, unsigned char *key) { + EVP_PBE_scrypt_ex(pass, 8, salt, 32, 1048576, 8, 1, 0, key, 64, NULL, NULL); // Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfTestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfTestFile.cc new file mode 100644 index 000000000..2c5800e0e --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpKdfTestFile.cc @@ -0,0 +1,35 @@ +#include +#include +#include + +void test_evp_kdf() { + OSSL_LIB_CTX* lib = NULL; + const char* props = NULL; + unsigned char buf[64]; + + // EVP_KDF_fetch: one finding per fetched KDF name + EVP_KDF_fetch(lib, "PBKDF2", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2}} + EVP_KDF_fetch(lib, "HKDF", props); // Noncompliant {{(KeyDerivationFunction) HKDF}} + EVP_KDF_fetch(lib, "SCRYPT", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} + EVP_KDF_fetch(lib, "TLS1-PRF", props); // Noncompliant {{(KeyDerivationFunction) TLS-PRF}} + EVP_KDF_fetch(lib, "TLS13-KDF", props); // Noncompliant {{(KeyDerivationFunction) HKDF}} + EVP_KDF_fetch(lib, "X963KDF", props); // Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.63}} + EVP_KDF_fetch(lib, "KBKDF", props); // Noncompliant {{(KeyDerivationFunction) SP800_108_CounterKDF}} + EVP_KDF_fetch(lib, "X942KDF-ASN1", props); // Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.42-ASN1}} + EVP_KDF_fetch(lib, "X942KDF-CONCAT", props); // Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.42-CONCAT}} + EVP_KDF_fetch(lib, "SSKDF", props); // Noncompliant {{(KeyDerivationFunction) ConcatenationKDF}} + EVP_KDF_fetch(lib, "SSHKDF", props); // Noncompliant {{(KeyDerivationFunction) SSHKDF}} + EVP_KDF_fetch(lib, "KRB5KDF", props); // Noncompliant {{(KeyDerivationFunction) KRB5KDF}} + EVP_KDF_fetch(lib, "ARGON2D", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) Argon2d}} + EVP_KDF_fetch(lib, "ARGON2I", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) Argon2i}} + EVP_KDF_fetch(lib, "ARGON2ID", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) Argon2id}} + EVP_KDF_fetch(lib, "PKCS12KDF", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF}} + EVP_KDF_fetch(lib, "PVKKDF", props); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PVKKDF}} + EVP_KDF_fetch(lib, "HMAC-DRBG-KDF", props); // Noncompliant {{(KeyDerivationFunction) HMAC-DRBG-KDF}} + + // PBKDF2 direct - the digest argument is traced back to its EVP_sha256() constructing call, + // separate from the "PBKDF2-HMAC" family finding. + const EVP_MD* pbkdf2_md = EVP_sha256(); + PKCS5_PBKDF2_HMAC((char*)buf, 8, buf, 16, 1000, pbkdf2_md, 32, buf); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-256}} + PKCS5_PBKDF2_HMAC_SHA1((char*)buf, 8, buf, 16, 1000, 32, buf); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-1}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTestFile.cc new file mode 100644 index 000000000..35de3e822 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTestFile.cc @@ -0,0 +1,23 @@ +#include +#include + +void hkdf_through_pkey_context(unsigned char *out, size_t *outlen) { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + EVP_PKEY_derive_init(pctx); + EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256()); + EVP_PKEY_CTX_set_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND); + EVP_PKEY_derive(pctx, out, outlen); +} + +void tls1_prf_through_pkey_context(unsigned char *out, size_t *outlen) { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); // Noncompliant {{(KeyDerivationFunction) TLS-PRF-SHA-384}} + EVP_PKEY_derive_init(pctx); + EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()); + EVP_PKEY_derive(pctx, out, outlen); +} + +void scrypt_through_pkey_context_by_name(unsigned char *out, size_t *outlen) { + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "scrypt", NULL); // Noncompliant {{(PasswordBasedKeyDerivationFunction) scrypt}} + EVP_PKEY_derive_init(pctx); + EVP_PKEY_derive(pctx, out, outlen); +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTestFile.cc new file mode 100644 index 000000000..ea46c9eab --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTestFile.cc @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +void pbes2_with_a_cipher(PKCS8_PRIV_KEY_INFO *p8inf, const char *pass, unsigned char *salt) { + PKCS8_encrypt(-1, EVP_aes_256_cbc(), pass, 8, salt, 16, 600000, p8inf); // Noncompliant {{(PasswordBasedEncryption) PBES2-AES-256-CBC}} +} + +void pkcs12_scheme(PKCS8_PRIV_KEY_INFO *p8inf, const char *pass) { + PKCS8_encrypt(NID_pbe_WithSHA1And3_Key_TripleDES_CBC, NULL, pass, 8, NULL, 8, 2048, p8inf); // Noncompliant {{(PasswordBasedEncryption) PKCS12-DESede168-CBC-SHA-1}} +} + +void pkcs5_v15_scheme(PKCS8_PRIV_KEY_INFO *p8inf, const char *pass) { + PKCS8_encrypt_ex(NID_pbeWithMD5AndDES_CBC, NULL, pass, 8, NULL, 8, 1000, p8inf, NULL, NULL); // Noncompliant {{(PasswordBasedEncryption) PBES1-DES-56-CBC-MD5}} +} + +void pbe_cipher_init(const char *pass, ASN1_TYPE *param, EVP_CIPHER_CTX *ctx) { + EVP_PBE_CipherInit(OBJ_nid2obj(NID_pbeWithSHA1AndDES_CBC), pass, 8, param, ctx, 0); // Noncompliant {{(PasswordBasedEncryption) PBES1-DES-56-CBC-SHA-1}} +} + +void pbe_cipher_init_from_a_variable(const char *pass, ASN1_TYPE *param, EVP_CIPHER_CTX *ctx) { + ASN1_OBJECT *scheme = OBJ_nid2obj(NID_pbe_WithSHA1And128BitRC4); + EVP_PBE_CipherInit_ex(scheme, pass, 8, param, ctx, 1, NULL, NULL); // Noncompliant {{(PasswordBasedEncryption) PKCS12-RC4-128-SHA-1}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLPkcs12TestFile.cc b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLPkcs12TestFile.cc new file mode 100644 index 000000000..dc8ec2073 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/kdf/OpenSSLPkcs12TestFile.cc @@ -0,0 +1,40 @@ +#include +#include + +void pkcs12_pbe_key_and_iv(EVP_CIPHER_CTX *ctx, ASN1_TYPE *param) { + PKCS12_PBE_keyivgen(ctx, "password", 8, param, EVP_des_ede3_cbc(), EVP_sha1(), 1); // Noncompliant {{(PasswordBasedEncryption) PKCS12-DESede168-CBC-SHA-1}} +} + +void pkcs5_pbe_key_and_iv(EVP_CIPHER_CTX *ctx, ASN1_TYPE *param) { + PKCS5_PBE_keyivgen_ex(ctx, "password", 8, param, EVP_rc2_cbc(), EVP_md5(), 1, NULL, NULL); // Noncompliant {{(PasswordBasedEncryption) PBES1-RC2-128-CBC-MD5}} +} + +void pkcs12_key_generation(unsigned char *salt, unsigned char *out) { + PKCS12_key_gen_utf8_ex("password", 8, salt, 8, 1, 2048, 32, out, EVP_sha256(), NULL, NULL); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF-SHA-256}} +} + +void pkcs12_mac(PKCS12 *p12, unsigned char *salt) { + PKCS12_set_mac(p12, "password", 8, salt, 8, 2048, EVP_sha256()); // Noncompliant {{(Mac) HMAC-SHA-256}} +} + +void pkcs12_container(EVP_PKEY *pkey, X509 *cert) { + PKCS12_create("password", "key", pkey, cert, NULL, NID_pbe_WithSHA1And3_Key_TripleDES_CBC, // Noncompliant {{(PasswordBasedEncryption) PKCS12-DESede168-CBC-SHA-1}} + NID_pbe_WithSHA1And40BitRC2_CBC, 2048, 2048, 0); + PKCS12_create_ex("password", "key", pkey, cert, NULL, NID_aes_256_cbc, 0, 2048, 2048, 0, // Noncompliant {{(PasswordBasedEncryption) PBES2-AES-256-CBC-HMAC-SHA-256}} + NULL, NULL); + PKCS12_create("password", "key", pkey, cert, NULL, -1, -1, 2048, 2048, 0); +} + +void other_entry_points(EVP_CIPHER_CTX *ctx, ASN1_TYPE *param, unsigned char *salt, + unsigned char *out, unsigned char *uni_pass, EVP_PKEY *pkey, X509 *cert) { + PKCS12_PBE_keyivgen_ex(ctx, "password", 8, param, EVP_aes_128_cbc(), EVP_sha256(), 0, NULL, // Noncompliant {{(PasswordBasedEncryption) PKCS12-AES-128-CBC-SHA-256}} + NULL); + PKCS5_PBE_keyivgen(ctx, "password", 8, param, EVP_des_cbc(), EVP_md5(), 0); // Noncompliant {{(PasswordBasedEncryption) PBES1-DES-56-CBC-MD5}} + PKCS12_key_gen_asc("password", 8, salt, 8, 1, 2048, 24, out, EVP_sha1()); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF-SHA-1}} + PKCS12_key_gen_asc_ex("password", 8, salt, 8, 2, 2048, 8, out, EVP_sha1(), NULL, NULL); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF-SHA-1}} + PKCS12_key_gen_uni(uni_pass, 18, salt, 8, 3, 2048, 20, out, EVP_sha1()); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF-SHA-1}} + PKCS12_key_gen_uni_ex(uni_pass, 18, salt, 8, 3, 2048, 20, out, EVP_sha1(), NULL, NULL); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF-SHA-1}} + PKCS12_key_gen_utf8("password", 8, salt, 8, 1, 2048, 32, out, EVP_sha512()); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PKCS12KDF-SHA-512}} + PKCS12_create_ex2("password", "key", pkey, cert, NULL, NID_pbe_WithSHA1And128BitRC4, // Noncompliant {{(PasswordBasedEncryption) PKCS12-RC4-128-SHA-1}} {{(PasswordBasedEncryption) PBES2-AES-128-CBC-HMAC-SHA-256}} + NID_aes_128_cbc, 2048, 2048, 0, NULL, NULL, NULL, NULL); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTestFile.cc new file mode 100644 index 000000000..c42d373bf --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTestFile.cc @@ -0,0 +1,40 @@ +#include +#include + +void test_evp_key_agreement() { + EVP_PKEY_CTX* ctx = NULL; + unsigned char buf[256]; + size_t len = 0; + + // derive init + EVP_PKEY_derive_init(ctx); + EVP_PKEY_derive_init_ex(ctx, NULL); + EVP_PKEY_derive(ctx, buf, &len); + + // DH CTX setters + EVP_PKEY_CTX_set_dh_kdf_type(ctx, 1); + const EVP_MD* dh_kdf_md = EVP_sha256(); + EVP_PKEY_CTX_set_dh_kdf_md(ctx, dh_kdf_md); + + // ECDH CTX setters + EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, 1); + const EVP_MD* ecdh_kdf_md = EVP_sha256(); + EVP_PKEY_CTX_set_ecdh_kdf_md(ctx, ecdh_kdf_md); + + // fetch + EVP_KEYEXCH_fetch(NULL, "ECDH", NULL); // Noncompliant {{(KeyAgreement) ECDH}} + EVP_KEM_fetch(NULL, "RSA", NULL); // Noncompliant {{(KeyEncapsulationMechanism) RSASVE}} + + // encapsulate / decapsulate + EVP_PKEY_encapsulate_init(ctx, NULL); + EVP_PKEY_encapsulate(ctx, buf, &len, buf, &len); + EVP_PKEY_decapsulate_init(ctx, NULL); + EVP_PKEY_decapsulate(ctx, buf, &len, buf, 256); + EVP_PKEY_auth_encapsulate_init(ctx, NULL, NULL); + EVP_PKEY_auth_decapsulate_init(ctx, NULL, NULL); + + // HPKE + OSSL_HPKE_CTX_new(0, 0, 0, NULL, NULL); + OSSL_HPKE_keygen(0, NULL, NULL, NULL, NULL, 0, NULL, NULL); + OSSL_HPKE_str2suite("X25519,HKDF-SHA256,AES-128-GCM", NULL); // Noncompliant {{(PublicKeyEncryption) HPKE}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTestFile.cc new file mode 100644 index 000000000..521dd7372 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTestFile.cc @@ -0,0 +1,45 @@ +#include +#include + +void ecdh_with_x963_kdf(EVP_PKEY *key, EVP_PKEY *peer, unsigned char *out, size_t *outlen) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); + EVP_PKEY_derive_init(ctx); + EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, EVP_PKEY_ECDH_KDF_X9_63); // Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.63}} + EVP_PKEY_CTX_set_ecdh_kdf_md(ctx, EVP_sha256()); + EVP_PKEY_derive_set_peer(ctx, peer); + EVP_PKEY_derive(ctx, out, outlen); +} + +void dh_with_x942_kdf(EVP_PKEY *key, EVP_PKEY *peer, unsigned char *out, size_t *outlen) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); + EVP_PKEY_derive_init(ctx); + EVP_PKEY_CTX_set_dh_kdf_type(ctx, EVP_PKEY_DH_KDF_X9_42); // Noncompliant {{(KeyDerivationFunction) ANSI-KDF-X9.42-SHA-384-ASN1}} + EVP_PKEY_CTX_set_dh_kdf_md(ctx, EVP_sha384()); + EVP_PKEY_derive(ctx, out, outlen); +} + +void ecdh_without_kdf(EVP_PKEY *key, unsigned char *out, size_t *outlen) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); + EVP_PKEY_derive_init(ctx); + EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, EVP_PKEY_ECDH_KDF_NONE); + EVP_PKEY_derive(ctx, out, outlen); +} + +void kems() { + EVP_KEM_fetch(NULL, "RSA", NULL); // Noncompliant {{(KeyEncapsulationMechanism) RSASVE}} + EVP_KEM_fetch(NULL, "ML-KEM-768", NULL); // Noncompliant {{(KeyEncapsulationMechanism) ML-KEM-768}} + EVP_KEM_fetch(NULL, "X25519", NULL); // Noncompliant {{(KeyEncapsulationMechanism) DHKEM}} + EVP_KEM_fetch(NULL, "EC", NULL); // Noncompliant {{(KeyEncapsulationMechanism) DHKEM}} +} + +void hpke_suites() { + OSSL_HPKE_SUITE suite; + OSSL_HPKE_str2suite("x25519,hkdf-sha256,aes-128-gcm", &suite); // Noncompliant {{(PublicKeyEncryption) HPKE}} + OSSL_HPKE_str2suite("P-384,hkdf-sha384,chacha20-poly1305", &suite); // Noncompliant {{(PublicKeyEncryption) HPKE}} + OSSL_HPKE_SUITE default_suite = OSSL_HPKE_SUITE_DEFAULT; + OSSL_HPKE_CTX *sender = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, default_suite, OSSL_HPKE_ROLE_SENDER, NULL, NULL); // Noncompliant {{(PublicKeyEncryption) HPKE}} + OSSL_HPKE_SUITE explicit_suite = {OSSL_HPKE_KEM_ID_P256, OSSL_HPKE_KDF_ID_HKDF_SHA256, OSSL_HPKE_AEAD_ID_AES_GCM_256}; + OSSL_HPKE_CTX *receiver = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, explicit_suite, OSSL_HPKE_ROLE_RECEIVER, NULL, NULL); // Noncompliant {{(PublicKeyEncryption) HPKE}} + OSSL_HPKE_SUITE numeric_suite = {0x10, 0x1, 0x2}; + OSSL_HPKE_CTX *numeric = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, numeric_suite, OSSL_HPKE_ROLE_SENDER, NULL, NULL); // Noncompliant {{(PublicKeyEncryption) HPKE}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTestFile.cc new file mode 100644 index 000000000..2345e8647 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTestFile.cc @@ -0,0 +1,40 @@ +#include +#include + +void compound_assignment(void) { + int bits = 2048; + bits += 16; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-2048}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits); +} + +void used_as_an_index(void) { + int bits = 3072; + int other[2]; + other[bits] = 512; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-3072}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits); +} + +void array_element(void) { + int bits[2]; + bits[0] = 1024; + bits[1] = 4096; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-4096}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits[1]); +} + +void written_through_a_pointer(void) { + int bits = 2048; + int *size = &bits; + *size = 512; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-2048}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits); +} + +void reassigned(void) { + int bits = 1024; + bits = 4096; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-1024}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTestFile.cc new file mode 100644 index 000000000..861726030 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTestFile.cc @@ -0,0 +1,27 @@ +#include +#include + +void product(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-4096}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 1024 * 4); +} + +void shift(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-2048}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 1 << 11); +} + +void cast(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-3072}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int) 3072); +} + +void conditional(int large) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-4096}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, large ? 4096 : 2048); +} + +void unknown_operand(int large) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, large * 1024); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTestFile.cc new file mode 100644 index 000000000..5936ab943 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTestFile.cc @@ -0,0 +1,68 @@ +#include +#include +#include "app_config.h" + +void undeclared_macro(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, APP_RSA_BITS); +} + +void literal_out_of_range(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 99999999999999999999999); +} + +void hexadecimal_literal_out_of_range(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 0xFFFFFFFFFFFFFFFFFFFF); +} + +void invalid_octal_literal(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 0999); +} + +enum key_profile { PROFILE_DEFAULT }; + +void enum_constant(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, PROFILE_UNKNOWN); +} + +void negative(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, -2048); +} + +void zero(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 0); +} + +void derived_key_length_from_macro(const char *pass, const unsigned char *salt, unsigned char *out) { + PKCS5_PBKDF2_HMAC(pass, 8, salt, 16, 10000, EVP_sha256(), APP_KEY_BYTES, out); // Noncompliant {{(PasswordBasedKeyDerivationFunction) PBKDF2-SHA-256}} +} + +void generated_with_undeclared_macro(void) { + EVP_RSA_gen(APP_RSA_BITS); // Noncompliant {{(PrivateKey) RSA}} +} + +void generated_with_negative_size(void) { + EVP_PKEY_Q_keygen(NULL, NULL, "RSA", -1); // Noncompliant {{(PrivateKey) RSA}} +} + +void shifted_out_of_range(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 1 << 70); +} + +void signed_overflow(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PublicKeyEncryption) RSA}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2147483647 * 4 + 4096); +} + +void legacy_key_set_up_with_undeclared_macro(const unsigned char *k, const unsigned char *in, unsigned char *out) { + AES_KEY key; + AES_set_encrypt_key(k, APP_AES_BITS, &key); + AES_ecb_encrypt(in, out, &key, AES_ENCRYPT); // Noncompliant {{(BlockCipher) AES-ECB}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTestFile.cc new file mode 100644 index 000000000..f08eb71f6 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTestFile.cc @@ -0,0 +1,48 @@ +#include +#include + +void rsa_key(void) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 3072); + EVP_PKEY_keygen(ctx, &pkey); +} + +void ec_key(void) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); // Noncompliant {{(PrivateKey) EC}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_group_name(ctx, "P-384"); + EVP_PKEY_generate(ctx, &pkey); +} + +void dh_parameters(void) { + EVP_PKEY *params = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + EVP_PKEY_paramgen_init(ctx); + EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, 2048); + EVP_PKEY_paramgen(ctx, ¶ms); +} + +void dh_named_groups(void) { + EVP_PKEY_CTX *ffdhe = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); // Noncompliant {{(PublicKeyEncryption) FFDH-3072}} + EVP_PKEY_CTX_set_dh_nid(ffdhe, NID_ffdhe3072); + EVP_PKEY_CTX *rfc5114 = EVP_PKEY_CTX_new_id(EVP_PKEY_DHX, NULL); // Noncompliant {{(PublicKeyEncryption) FFDH-1024}} + EVP_PKEY_CTX_set_dhx_rfc5114(rfc5114, 1); + EVP_PKEY_CTX *rfc5114_dh = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + EVP_PKEY_CTX_set_dh_rfc5114(rfc5114_dh, 3); +} + +void rsa_pss_key(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048); + EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx, EVP_sha256()); + EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx, 32); +} + +void quick_keys(void) { + EVP_PKEY *rsa = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 4096); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY *ec = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + EVP_PKEY *x25519 = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); // Noncompliant {{(PrivateKey) x25519}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenTestFile.cc new file mode 100644 index 000000000..b2daa23a9 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLEvpKeyGenTestFile.cc @@ -0,0 +1,104 @@ +#include + +void dsa_parameters(void) { + EVP_PKEY *params = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, NULL); // Noncompliant {{(Signature) DSA-2048-SHA-256}} + EVP_PKEY_paramgen_init(ctx); + EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, 2048); + const EVP_MD *md = EVP_sha256(); + EVP_PKEY_CTX_set_dsa_paramgen_md(ctx, md); + EVP_PKEY_paramgen(ctx, ¶ms); +} + +void dsa_parameters_digest_by_name(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "DSA", NULL); // Noncompliant {{(Signature) DSA-SHA-256}} + EVP_PKEY_CTX_set_dsa_paramgen_md_props(ctx, "SHA2-256", NULL); +} + +void ec_curve_by_nid(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, 415); +} + +void ec_curve_by_nid_variable(void) { + int p256_nid = 415; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, p256_nid); +} + +void ec_curve_by_group_name(void) { + EVP_PKEY_CTX *p192 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); // Noncompliant {{(PublicKeyEncryption) EC-secp192r1}} + EVP_PKEY_CTX_set_group_name(p192, "P-192"); + EVP_PKEY_CTX *p224 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); // Noncompliant {{(PublicKeyEncryption) EC-secp224r1}} + EVP_PKEY_CTX_set_group_name(p224, "SECP224R1"); +} + +void rsa_bits_variable(void) { + int rsa_bits = 2048; + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, rsa_bits); + EVP_PKEY_generate(ctx, &pkey); +} + +void quick_and_fetch(void) { + EVP_PKEY_Q_keygen(NULL, NULL, "RSA", 2048); // Noncompliant {{(PrivateKey) RSA}} + EVP_KEYMGMT_fetch(NULL, "ML-KEM-768", NULL); // Noncompliant {{(KeyEncapsulationMechanism) ML-KEM-768}} +} + +void quick_by_algorithm(void) { + EVP_RSA_gen(3072); // Noncompliant {{(PrivateKey) RSA}} + EVP_EC_gen("P-256"); // Noncompliant {{(PrivateKey) EC}} +} + +void ec_curves_without_a_nist_prime_name(void) { + EVP_PKEY_CTX *k283 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); // Noncompliant {{(PublicKeyEncryption) EC-sect283k1}} + EVP_PKEY_CTX_set_group_name(k283, "sect283k1"); + EVP_PKEY_CTX *b163 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); // Noncompliant {{(PublicKeyEncryption) EC-sect163r2}} + EVP_PKEY_CTX_set_group_name(b163, "B-163"); + EVP_PKEY_CTX *p224 = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); // Noncompliant {{(PublicKeyEncryption) EC-secp224r1}} + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(p224, NID_secp224r1); + EVP_PKEY_CTX *p224_code = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); // Noncompliant {{(PublicKeyEncryption) EC-secp224r1}} + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(p224_code, 713); +} + +void quick_by_alias(void) { + EVP_PKEY_Q_keygen(NULL, NULL, "MLKEM1024"); // Noncompliant {{(PrivateKey) ML-KEM}} + EVP_PKEY_Q_keygen(NULL, NULL, "MLDSA65"); // Noncompliant {{(PrivateKey) ML-DSA}} +} + +void rsa_bits_in_parentheses(void) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int)(3072)); + EVP_PKEY_generate(ctx, &pkey); +} + +void context_declared_with_another_variable(void) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL), *unused = NULL; // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 4096); + EVP_PKEY_generate(ctx, &pkey); +} + +enum class KeyBits : int { RSA = 2048 }; + +void rsa_bits_by_named_cast(void) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, static_cast(KeyBits::RSA)); + EVP_PKEY_generate(ctx, &pkey); +} + +void curves_without_a_model_of_their_own(void) { + EVP_PKEY_CTX *prime = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); // Noncompliant {{(PublicKeyEncryption) EC-prime239v1}} + EVP_PKEY_paramgen_init(prime); + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(prime, NID_X9_62_prime239v1); + EVP_PKEY_CTX *binary = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); // Noncompliant {{(PublicKeyEncryption) EC-c2pnb163v3}} + EVP_PKEY_paramgen_init(binary); + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(binary, 686); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLGeneratedKeyTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLGeneratedKeyTestFile.cc new file mode 100644 index 000000000..3475997e7 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLGeneratedKeyTestFile.cc @@ -0,0 +1,30 @@ +#include +#include + +EVP_PKEY *rsa_key_from_context(void) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(ctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 3072); + EVP_PKEY_keygen(ctx, &pkey); + return pkey; +} + +EVP_PKEY *ec_key_in_one_call(void) { + return EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} +} + +EVP_PKEY *dh_parameters(void) { + EVP_PKEY *params = NULL; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + EVP_PKEY_paramgen_init(ctx); + EVP_PKEY_CTX_set_dh_nid(ctx, NID_ffdhe2048); + EVP_PKEY_paramgen(ctx, ¶ms); + return params; +} + +RSA *legacy_rsa_key(BIGNUM *e) { + RSA *rsa = RSA_new(); + RSA_generate_key_ex(rsa, 2048, e, NULL); // Noncompliant {{(PrivateKey) RSA}} + return rsa; +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTestFile.cc new file mode 100644 index 000000000..d8cb73fac --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTestFile.cc @@ -0,0 +1,38 @@ +#include +#include + +void ecdsa_with_signature_digest(const unsigned char *dgst, unsigned char *sig, size_t *siglen) { + EVP_PKEY *key = EVP_EC_gen("P-256"); // Noncompliant {{(PrivateKey) EC}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); + EVP_PKEY_sign_init(ctx); + EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha384()); + EVP_PKEY_sign(ctx, sig, siglen, dgst, 48); +} + +void rsa_oaep_with_digests(const unsigned char *in, unsigned char *out, size_t *outlen) { + EVP_PKEY *key = EVP_RSA_gen(3072); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); + EVP_PKEY_encrypt_init(ctx); + EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); + EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()); + EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha1()); + EVP_PKEY_encrypt(ctx, out, outlen, in, 32); +} + +void rsa_oaep_with_digest_name(const unsigned char *in, unsigned char *out, size_t *outlen) { + EVP_PKEY *key = EVP_RSA_gen(4096); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(key, NULL); + EVP_PKEY_encrypt_init(ctx); + EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); + EVP_PKEY_CTX_set_rsa_oaep_md_name(ctx, "SHA512", NULL); + EVP_PKEY_encrypt(ctx, out, outlen, in, 32); +} + +void rsa_pss_with_mgf1_digest(void) { + EVP_PKEY *key = EVP_RSA_gen(2048); // Noncompliant {{(PrivateKey) RSA}} + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_PKEY_CTX *pctx = NULL; + EVP_DigestSignInit(mdctx, &pctx, EVP_sha256(), NULL, key); + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING); + EVP_PKEY_CTX_set_rsa_mgf1_md(pctx, EVP_sha384()); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTestFile.cc new file mode 100644 index 000000000..8761d3b8a --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTestFile.cc @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include + +void sign_certificate(X509 *cert) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + X509_sign(cert, pkey, EVP_sha256()); +} + +void sign_certificate_request(X509_REQ *req) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); // Noncompliant {{(PrivateKey) RSA}} + X509_REQ_sign(req, pkey, EVP_sha384()); +} + +void sign_crl(X509_CRL *crl) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-384"); // Noncompliant {{(PrivateKey) EC}} + X509_CRL_sign(crl, pkey, EVP_sha384()); +} + +void verify_certificate(X509 *cert) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + X509_verify(cert, pkey); +} + +void sign_cms(X509 *signcert, BIO *data) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + CMS_sign(signcert, pkey, NULL, data, 0); +} + +void add_cms_signer(CMS_ContentInfo *cms, X509 *signer) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + CMS_add1_signer(cms, signer, pkey, EVP_sha512(), 0); +} + +void sign_pkcs7(X509 *signcert, BIO *data) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + PKCS7_sign(signcert, pkey, NULL, data, 0); +} + +void sign_ocsp_response(OCSP_BASICRESP *brsp, X509 *signer) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + OCSP_basic_sign(brsp, signer, pkey, EVP_sha256(), NULL, 0); +} + +void seal_envelope(EVP_CIPHER_CTX *ctx, unsigned char *ek, int *ekl, unsigned char *iv) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + EVP_SealInit(ctx, EVP_aes_256_cbc(), &ek, ekl, iv, &pkey, 1); // Noncompliant {{(BlockCipher) AES-256-CBC}} +} + +void open_envelope(EVP_CIPHER_CTX *ctx, unsigned char *ek, int ekl, unsigned char *iv) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + EVP_OpenInit(ctx, EVP_aes_256_cbc(), ek, ekl, iv, pkey); // Noncompliant {{(BlockCipher) AES-256-CBC}} +} + +void decrypt_cms(CMS_ContentInfo *cms, X509 *cert, BIO *out) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + CMS_decrypt(cms, pkey, cert, NULL, out, 0); +} + +void decrypt_cms_with_key_set_first(CMS_ContentInfo *cms, X509 *cert, BIO *out) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); // Noncompliant {{(PrivateKey) RSA}} + CMS_decrypt_set1_pkey(cms, pkey, cert); + CMS_decrypt(cms, NULL, NULL, NULL, out, 0); +} + +void decrypt_cms_with_peer(CMS_ContentInfo *cms, X509 *cert, X509 *peer) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + CMS_decrypt_set1_pkey_and_peer(cms, pkey, cert, peer); +} + +void decrypt_pkcs7(PKCS7 *p7, X509 *cert, BIO *out) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 4096); // Noncompliant {{(PrivateKey) RSA}} + PKCS7_decrypt(p7, pkey, cert, out, 0); +} + +void key_not_generated_here(X509 *cert, EVP_PKEY *pkey) { + X509_sign(cert, pkey, EVP_sha256()); // Noncompliant {{(Signature) unknown}} +} + +void verify_request_and_crl(X509_REQ *req, X509_CRL *crl) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + X509_REQ_verify(req, pkey); + X509_CRL_verify(crl, pkey); +} + +void verify_request_ex(X509_REQ *req, OSSL_LIB_CTX *libctx) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-384"); // Noncompliant {{(PrivateKey) EC}} + X509_REQ_verify_ex(req, pkey, libctx, NULL); +} + +void sign_cms_and_pkcs7_ex(X509 *signcert, BIO *data, OSSL_LIB_CTX *libctx) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); // Noncompliant {{(PrivateKey) RSA}} + CMS_sign_ex(signcert, pkey, NULL, data, 0, libctx, NULL); + PKCS7_sign_ex(signcert, pkey, NULL, data, 0, libctx, NULL); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyUsageTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyUsageTestFile.cc new file mode 100644 index 000000000..d1b4f5e41 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLKeyUsageTestFile.cc @@ -0,0 +1,92 @@ +#include +#include + +void ecdsa_sign(const unsigned char *msg, size_t len, unsigned char *sig, size_t *siglen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); // Noncompliant {{(PrivateKey) EC}} + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey); + EVP_DigestSign(mdctx, sig, siglen, msg, len); +} + +void rsa_pss_verify(const unsigned char *msg, size_t len, const unsigned char *sig, size_t siglen) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_keygen_init(kctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, 3072); + EVP_PKEY_keygen(kctx, &pkey); + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_PKEY_CTX *pctx = NULL; + EVP_DigestVerifyInit(mdctx, &pctx, EVP_sha384(), NULL, pkey); + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING); + EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, 32); + EVP_PKEY_CTX_set_rsa_mgf1_md_name(pctx, "SHA256", NULL); + EVP_DigestVerify(mdctx, sig, siglen, msg, len); +} + +void x25519_derive(EVP_PKEY *peer, unsigned char *secret, size_t *secretlen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); // Noncompliant {{(PrivateKey) x25519}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_derive_init(ctx); + EVP_PKEY_derive_set_peer(ctx, peer); + EVP_PKEY_derive(ctx, secret, secretlen); +} + +void ecdh_derive(EVP_PKEY *peer, unsigned char *secret, size_t *secretlen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-384"); // Noncompliant {{(PrivateKey) EC}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL); + EVP_PKEY_derive_init(ctx); + EVP_PKEY_derive_set_peer(ctx, peer); + EVP_PKEY_derive(ctx, secret, secretlen); +} + +void rsa_oaep_encrypt(const unsigned char *in, size_t inlen, unsigned char *out, size_t *outlen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_encrypt_init(ctx); + EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); + EVP_PKEY_encrypt(ctx, out, outlen, in, inlen); +} + +void mlkem_encapsulate(unsigned char *wrapped, size_t *wrappedlen, unsigned char *secret, size_t *secretlen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "ML-KEM-768"); // Noncompliant {{(PrivateKey) ML-KEM}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_encapsulate_init(ctx, NULL); + EVP_PKEY_encapsulate(ctx, wrapped, wrappedlen, secret, secretlen); +} + +void ed25519_sign(const unsigned char *msg, size_t len, unsigned char *sig, size_t *siglen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "ED25519"); // Noncompliant {{(PrivateKey) Ed25519}} + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey); + EVP_DigestSign(mdctx, sig, siglen, msg, len); +} + +EVP_PKEY *returned_key_next_to_signing_with_another_key(EVP_PKEY *other, EVP_MD_CTX *mdctx) { + EVP_DigestSignInit(mdctx, NULL, EVP_sha512(), NULL, other); // Noncompliant {{(Signature) unknown}} + return EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 4096); // Noncompliant {{(PrivateKey) RSA}} +} + +void dh_derive(EVP_PKEY *peer, unsigned char *secret, size_t *secretlen) { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); // Noncompliant {{(PrivateKey) FFDH}} + EVP_PKEY_keygen_init(kctx); + EVP_PKEY_CTX_set_dh_nid(kctx, NID_ffdhe2048); + EVP_PKEY_keygen(kctx, &pkey); + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_derive_init(ctx); + EVP_PKEY_derive(ctx, secret, secretlen); +} + +void rsa_encapsulate(unsigned char *wrapped, size_t *wrappedlen, unsigned char *secret, size_t *secretlen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); // Noncompliant {{(PrivateKey) RSA}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_encapsulate_init(ctx, NULL); + EVP_PKEY_encapsulate(ctx, wrapped, wrappedlen, secret, secretlen); +} + +void x25519_encapsulate(unsigned char *wrapped, size_t *wrappedlen, unsigned char *secret, size_t *secretlen) { + EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); // Noncompliant {{(PrivateKey) x25519}} + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); + EVP_PKEY_encapsulate_init(ctx, NULL); + EVP_PKEY_encapsulate(ctx, wrapped, wrappedlen, secret, secretlen); +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLRawKeyTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLRawKeyTestFile.cc new file mode 100644 index 000000000..42b382450 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLRawKeyTestFile.cc @@ -0,0 +1,36 @@ +#include + +void hmac_with_mac_key(const unsigned char *key, const unsigned char *msg, size_t len, unsigned char *tag, size_t *taglen) { + EVP_PKEY *pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, 32); // Noncompliant {{(SecretKey) HMAC}} + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey); + EVP_DigestSign(mdctx, tag, taglen, msg, len); +} + +void hmac_with_raw_key(const unsigned char *key, EVP_MD_CTX *mdctx) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, key, 64); // Noncompliant {{(SecretKey) HMAC}} + EVP_DigestSignInit(mdctx, NULL, EVP_sha512(), NULL, pkey); +} + +void cmac_key(const unsigned char *key, EVP_MD_CTX *mdctx) { + EVP_PKEY *pkey = EVP_PKEY_new_CMAC_key(NULL, key, 16, EVP_aes_128_cbc()); // Noncompliant {{(SecretKey) CMAC}} + EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey); +} + +void siphash_key_by_name(const unsigned char *key) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "SIPHASH", NULL, key, 16); // Noncompliant {{(SecretKey) SipHash}} +} + +void imported_ed25519_key(const unsigned char *priv, EVP_MD_CTX *mdctx) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, priv, 32); // Noncompliant {{(PrivateKey) Ed25519}} + EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey); +} + +void imported_ed25519_public_key(const unsigned char *pub, EVP_MD_CTX *mdctx) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pub, 32); // Noncompliant {{(PublicKey) Ed25519}} + EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey); +} + +void imported_x25519_public_key_by_name(const unsigned char *pub) { + EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "X25519", NULL, pub, 32); // Noncompliant {{(PublicKey) x25519}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTestFile.cc b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTestFile.cc new file mode 100644 index 000000000..a333f858e --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTestFile.cc @@ -0,0 +1,17 @@ +#include +#include + +void settings_of_a_context_created_elsewhere(EVP_PKEY_CTX *ctx) { + EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx, EVP_sha256()); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx, EVP_sha384()); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_md_name(ctx, "SHA512", NULL); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name(ctx, "SHA224"); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} +} + +void settings_of_a_context_created_here(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 3072); + EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx, EVP_sha512()); + EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx, 64); +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyCipherTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyCipherTestFile.cc new file mode 100644 index 000000000..afb993741 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyCipherTestFile.cc @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void test_legacy_cipher_aes() { + unsigned char buf[64]; + unsigned char iv[16]; + int num = 0; + AES_KEY ak; + AES_KEY dk; + AES_set_encrypt_key(buf, 256, &ak); + AES_set_decrypt_key(buf, 192, &dk); + AES_ecb_encrypt(buf, buf, &ak, 1); // Noncompliant {{(BlockCipher) AES-256-ECB}} + AES_cbc_encrypt(buf, buf, 64, &ak, iv, 1); // Noncompliant {{(BlockCipher) AES-256-CBC}} + AES_cfb128_encrypt(buf, buf, 64, &ak, iv, &num, 1); // Noncompliant {{(BlockCipher) AES-256-CFB128}} + AES_ofb128_encrypt(buf, buf, 64, &ak, iv, &num); // Noncompliant {{(BlockCipher) AES-256-OFB}} + AES_ige_encrypt(buf, buf, 64, &ak, iv, 1); // Noncompliant {{(BlockCipher) AES-256-IGE}} + AES_cfb1_encrypt(buf, buf, 64, &ak, iv, &num, 1); // Noncompliant {{(BlockCipher) AES-256-CFB1}} + AES_cfb8_encrypt(buf, buf, 64, &ak, iv, &num, 1); // Noncompliant {{(BlockCipher) AES-256-CFB8}} + AES_bi_ige_encrypt(buf, buf, 64, &ak, &ak, iv, 1); // Noncompliant {{(BlockCipher) AES-256-BI-IGE}} + // RFC 3394 key wrap: the default IV when iv is NULL + AES_wrap_key(&ak, NULL, buf, buf, 32); // Noncompliant {{(BlockCipher) AES-256-WRAP}} + AES_unwrap_key(&dk, NULL, buf, buf, 40); // Noncompliant {{(BlockCipher) AES-192-WRAP}} +} + +void test_legacy_cipher_aes_key_set_up_twice() { + unsigned char buf[64]; + AES_KEY k; + AES_set_encrypt_key(buf, 128, &k); + AES_set_encrypt_key(buf, 256, &k); + // both setups reach the call: the encryption is reported as a cipher with the first key + // length and as a cipher with the second + AES_ecb_encrypt(buf, buf, &k, 1); // Noncompliant {{(BlockCipher) AES-128-ECB}} +} + +void test_legacy_cipher_aes_key_not_used() { + unsigned char buf[64]; + AES_KEY unused; + AES_set_encrypt_key(buf, 128, &unused); // Noncompliant {{(BlockCipher) AES-128}} +} + +void test_legacy_cipher_aes_key_given(const AES_KEY *key, unsigned char *buf) { + AES_ecb_encrypt(buf, buf, key, 1); // Noncompliant {{(BlockCipher) AES-ECB}} +} + +void test_legacy_cipher_des() { + unsigned char buf[64]; + unsigned char iv[8]; + int num = 0; + DES_key_schedule ds; + DES_cblock dc; + DES_set_key(&dc, &ds); + DES_ecb_encrypt(&dc, &dc, &ds, 1); // Noncompliant {{(BlockCipher) DES-56-ECB}} + DES_ede3_cbc_encrypt(buf, buf, 64, &ds, &ds, &ds, &dc, 1); // Noncompliant {{(BlockCipher) DESede168-CBC}} + DES_ecb3_encrypt(&dc, &dc, &ds, &ds, &ds, 1); // Noncompliant {{(BlockCipher) DESede168-ECB}} + DES_ede3_cfb64_encrypt(buf, buf, 64, &ds, &ds, &ds, &dc, &num, 1); // Noncompliant {{(BlockCipher) DESede168-CFB}} + DES_ofb64_encrypt(buf, buf, 64, &ds, &dc, &num); // Noncompliant {{(BlockCipher) DES-56-OFB}} + DES_set_key_checked(&dc, &ds); + DES_set_key_unchecked(&dc, &ds); + DES_ncbc_encrypt(buf, buf, 64, &ds, &dc, 1); // Noncompliant {{(BlockCipher) DES-56-CBC}} + DES_cbc_encrypt(buf, buf, 64, &ds, &dc, 1); // Noncompliant {{(BlockCipher) DES-56-CBC}} + DES_cfb64_encrypt(buf, buf, 64, &ds, &dc, &num, 1); // Noncompliant {{(BlockCipher) DES-56-CFB}} + DES_cfb_encrypt(buf, buf, 8, 64, &ds, &dc, 1); // Noncompliant {{(BlockCipher) DES-56-CFB}} + DES_ede3_cfb_encrypt(buf, buf, 8, 64, &ds, &ds, &ds, &dc, 1); // Noncompliant {{(BlockCipher) DESede168-CFB}} + DES_ede3_ofb64_encrypt(buf, buf, 64, &ds, &ds, &ds, &dc, &num); // Noncompliant {{(BlockCipher) DESede168-OFB}} + DES_xcbc_encrypt(buf, buf, 64, &ds, &dc, &dc, &dc, 1); // Noncompliant {{(BlockCipher) DESX-184-CBC}} +} + +void test_legacy_cipher_bf() { + unsigned char buf[64]; + unsigned char iv[8]; + int num = 0; + BF_KEY bk; + BF_set_key(&bk, 20, buf); + BF_ecb_encrypt(buf, buf, &bk, 1); // Noncompliant {{(BlockCipher) Blowfish-160-ECB}} + BF_cbc_encrypt(buf, buf, 64, &bk, iv, 1); // Noncompliant {{(BlockCipher) Blowfish-160-CBC}} + BF_cfb64_encrypt(buf, buf, 64, &bk, iv, &num, 1); // Noncompliant {{(BlockCipher) Blowfish-160-CFB}} + BF_ofb64_encrypt(buf, buf, 64, &bk, iv, &num); // Noncompliant {{(BlockCipher) Blowfish-160-OFB}} +} + +void test_legacy_cipher_rc() { + unsigned char buf[64]; + unsigned char iv[8]; + int num = 0; + RC4_KEY r4; + RC2_KEY r2; + RC5_32_KEY r5; + RC4_set_key(&r4, 16, buf); + RC4(&r4, 64, buf, buf); // Noncompliant {{(StreamCipher) RC4-128}} + RC2_set_key(&r2, 8, buf, 64); + RC2_ecb_encrypt(buf, buf, &r2, 1); // Noncompliant {{(BlockCipher) RC2-64-ECB}} + RC2_cbc_encrypt(buf, buf, 64, &r2, iv, 1); // Noncompliant {{(BlockCipher) RC2-64-CBC}} + RC2_cfb64_encrypt(buf, buf, 64, &r2, iv, &num, 1); // Noncompliant {{(BlockCipher) RC2-64-CFB}} + RC2_ofb64_encrypt(buf, buf, 64, &r2, iv, &num); // Noncompliant {{(BlockCipher) RC2-64-OFB}} + RC5_32_set_key(&r5, 10, buf, 12); + RC5_32_ecb_encrypt(buf, buf, &r5, 1); // Noncompliant {{(BlockCipher) RC5-80-ECB}} + RC5_32_cbc_encrypt(buf, buf, 64, &r5, iv, 1); // Noncompliant {{(BlockCipher) RC5-80-CBC}} + RC5_32_cfb64_encrypt(buf, buf, 64, &r5, iv, &num, 1); // Noncompliant {{(BlockCipher) RC5-80-CFB}} + RC5_32_ofb64_encrypt(buf, buf, 64, &r5, iv, &num); // Noncompliant {{(BlockCipher) RC5-80-OFB}} +} + +void test_legacy_cipher_cast() { + unsigned char buf[64]; + unsigned char iv[8]; + int num = 0; + CAST_KEY ck; + CAST_set_key(&ck, 10, buf); + CAST_ecb_encrypt(buf, buf, &ck, 1); // Noncompliant {{(BlockCipher) CAST5-80-ECB}} + CAST_cbc_encrypt(buf, buf, 64, &ck, iv, 1); // Noncompliant {{(BlockCipher) CAST5-80-CBC}} + CAST_cfb64_encrypt(buf, buf, 64, &ck, iv, &num, 1); // Noncompliant {{(BlockCipher) CAST5-80-CFB}} + CAST_ofb64_encrypt(buf, buf, 64, &ck, iv, &num); // Noncompliant {{(BlockCipher) CAST5-80-OFB}} +} + +void test_legacy_cipher_idea() { + unsigned char buf[64]; + unsigned char iv[8]; + int num = 0; + IDEA_KEY_SCHEDULE ik; + IDEA_set_encrypt_key(buf, &ik); + IDEA_set_decrypt_key(&ik, &ik); + IDEA_ecb_encrypt(buf, buf, &ik); // Noncompliant {{(BlockCipher) IDEA-ECB}} + IDEA_cbc_encrypt(buf, buf, 64, &ik, iv, 1); // Noncompliant {{(BlockCipher) IDEA-CBC}} + IDEA_cfb64_encrypt(buf, buf, 64, &ik, iv, &num, 1); // Noncompliant {{(BlockCipher) IDEA-CFB}} + IDEA_ofb64_encrypt(buf, buf, 64, &ik, iv, &num); // Noncompliant {{(BlockCipher) IDEA-OFB}} +} + +void test_legacy_cipher_camellia() { + unsigned char buf[64]; + unsigned char iv[16]; + int num = 0; + unsigned int unum = 0; + CAMELLIA_KEY cam; + Camellia_set_key(buf, 256, &cam); + Camellia_ecb_encrypt(buf, buf, &cam, 1); // Noncompliant {{(BlockCipher) CAMELLIA-256-ECB}} + Camellia_cbc_encrypt(buf, buf, 64, &cam, iv, 1); // Noncompliant {{(BlockCipher) CAMELLIA-256-CBC}} + Camellia_cfb128_encrypt(buf, buf, 64, &cam, iv, &num, 1); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB128}} + Camellia_cfb1_encrypt(buf, buf, 64, &cam, iv, &num, 1); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB1}} + Camellia_cfb8_encrypt(buf, buf, 64, &cam, iv, &num, 1); // Noncompliant {{(BlockCipher) CAMELLIA-256-CFB8}} + Camellia_ofb128_encrypt(buf, buf, 64, &cam, iv, &num); // Noncompliant {{(BlockCipher) CAMELLIA-256-OFB}} + Camellia_ctr128_encrypt(buf, buf, 64, &cam, iv, buf, &unum); // Noncompliant {{(BlockCipher) CAMELLIA-256-CTR}} +} + +void test_legacy_cipher_seed() { + unsigned char buf[64]; + unsigned char iv[16]; + int num = 0; + SEED_KEY_SCHEDULE sk; + SEED_set_key(buf, &sk); + SEED_ecb_encrypt(buf, buf, &sk, 1); // Noncompliant {{(BlockCipher) SEED-128-ECB}} + SEED_cbc_encrypt(buf, buf, 64, &sk, iv, 1); // Noncompliant {{(BlockCipher) SEED-128-CBC}} + SEED_cfb128_encrypt(buf, buf, 64, &sk, iv, &num, 1); // Noncompliant {{(BlockCipher) SEED-128-CFB}} + SEED_ofb128_encrypt(buf, buf, 64, &sk, iv, &num); // Noncompliant {{(BlockCipher) SEED-128-OFB}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDhTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDhTestFile.cc new file mode 100644 index 000000000..657e9c0a9 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDhTestFile.cc @@ -0,0 +1,14 @@ +#include + +void test_legacy_dh(DH *key) { + DH* dh = NULL; + unsigned char secret[256]; + BIGNUM* pub_key = NULL; + + DH_generate_parameters_ex(dh, 2048, 2, NULL); + DH_generate_key(dh); // Noncompliant {{(PrivateKey) FFDH}} + DH_get_1024_160(); // Noncompliant {{(PublicKeyEncryption) FFDH-1024}} + DH_get_2048_224(); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + DH_get_2048_256(); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + DH_compute_key(secret, pub_key, key); // Noncompliant {{(KeyAgreement) FFDH}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDigestTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDigestTestFile.cc new file mode 100644 index 000000000..6ce4993ee --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDigestTestFile.cc @@ -0,0 +1,45 @@ +#include +#include +#include + +void test_legacy_digest() { + MD5_CTX mc; + SHA_CTX s1; + SHA256_CTX s2; + SHA512_CTX s5; + RIPEMD160_CTX r; + unsigned char buf[64]; + + MD5_Init(&mc); // Noncompliant {{(MessageDigest) MD5}} + MD5(buf, 64, buf); // Noncompliant {{(MessageDigest) MD5}} + + SHA1_Init(&s1); // Noncompliant {{(MessageDigest) SHA-1}} + SHA1(buf, 64, buf); // Noncompliant {{(MessageDigest) SHA-1}} + + SHA224_Init(&s2); // Noncompliant {{(MessageDigest) SHA-224}} + SHA224(buf, 64, buf); // Noncompliant {{(MessageDigest) SHA-224}} + + SHA256_Init(&s2); // Noncompliant {{(MessageDigest) SHA-256}} + SHA256(buf, 64, buf); // Noncompliant {{(MessageDigest) SHA-256}} + + SHA384_Init(&s5); // Noncompliant {{(MessageDigest) SHA-384}} + SHA384(buf, 64, buf); // Noncompliant {{(MessageDigest) SHA-384}} + + SHA512_Init(&s5); // Noncompliant {{(MessageDigest) SHA-512}} + SHA512(buf, 64, buf); // Noncompliant {{(MessageDigest) SHA-512}} + + RIPEMD160_Init(&r); // Noncompliant {{(MessageDigest) RIPEMD-160}} + RIPEMD160(buf, 64, buf); // Noncompliant {{(MessageDigest) RIPEMD-160}} + + WHIRLPOOL(buf, 64, buf); // Noncompliant {{(MessageDigest) Whirlpool}} + WHIRLPOOL_Init(NULL); // Noncompliant {{(MessageDigest) Whirlpool}} + + MD2(buf, 64, buf); // Noncompliant {{(MessageDigest) MD2}} + MD2_Init(NULL); // Noncompliant {{(MessageDigest) MD2}} + + MD4(buf, 64, buf); // Noncompliant {{(MessageDigest) MD4}} + MD4_Init(NULL); // Noncompliant {{(MessageDigest) MD4}} + + MDC2(buf, 64, buf); // Noncompliant {{(MessageDigest) MDC2}} + MDC2_Init(NULL); // Noncompliant {{(MessageDigest) MDC2}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDsaTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDsaTestFile.cc new file mode 100644 index 000000000..550182405 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyDsaTestFile.cc @@ -0,0 +1,12 @@ +#include + +void test_legacy_dsa(DSA *key) { + DSA* dsa = NULL; + unsigned char buf[32]; + unsigned int siglen = 0; + + DSA_generate_parameters_ex(dsa, 2048, NULL, 0, NULL, NULL, NULL); + DSA_generate_key(dsa); // Noncompliant {{(PrivateKey) DSA}} + DSA_sign(0, buf, 32, buf, &siglen, key); // Noncompliant {{(Signature) DSA}} + DSA_do_sign(buf, 32, key); // Noncompliant {{(Signature) DSA}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyEcTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyEcTestFile.cc new file mode 100644 index 000000000..caa53b9bf --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyEcTestFile.cc @@ -0,0 +1,45 @@ +#include +#include +#include + +// Scoped enum (C++11 enum class): its constants are reachable only via a qualified +// reference (CurveNid::P256), not the normal enclosing-scope chain. +enum class CurveNid : int { P256 = 415 }; + +void test_legacy_ec(EC_KEY *signing_key) { + EC_KEY* key = NULL; + EC_GROUP* grp = NULL; + EC_POINT* pt = NULL; + BIGNUM* bn = NULL; + BN_CTX* ctx = NULL; + unsigned char buf[64]; + unsigned int siglen = 0; + + EC_KEY_new_by_curve_name(415); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + EC_KEY_new_by_curve_name_ex(NULL, NULL, 415); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + EC_KEY_generate_key(key); // Noncompliant {{(PrivateKey) EC}} + EC_KEY_set_group(key, grp); + + // Curve NID via a local variable, not a literal. + int p256_nid = 415; + EC_KEY_new_by_curve_name(p256_nid); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + + // Curve NID via a scoped-enum qualified reference, resolved through + // CxxSymbolResolverVisitor's TypeSymbol#memberScope lookup. + EC_KEY_new_by_curve_name(CurveNid::P256); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + + ECDSA_sign(0, buf, 32, buf, &siglen, signing_key); // Noncompliant {{(Signature) ECDSA}} + ECDSA_do_sign(buf, 32, signing_key); // Noncompliant {{(Signature) ECDSA}} + ECDSA_sign_ex(0, buf, 32, buf, &siglen, bn, bn, signing_key); // Noncompliant {{(Signature) ECDSA}} + ECDSA_do_sign_ex(buf, 32, bn, bn, signing_key); // Noncompliant {{(Signature) ECDSA}} + + EC_GROUP_new_by_curve_name(415); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + EC_GROUP_new_by_curve_name_ex(NULL, NULL, 415); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + EC_GROUP_new_curve_GFp(bn, bn, bn, ctx); // Noncompliant {{(PublicKeyEncryption) EC}} + EC_GROUP_new_curve_GF2m(bn, bn, bn, ctx); // Noncompliant {{(PublicKeyEncryption) EC}} + EC_GROUP_new_from_ecparameters(NULL); // Noncompliant {{(PublicKeyEncryption) EC}} + EC_GROUP_new_from_ecpkparameters(NULL); // Noncompliant {{(PublicKeyEncryption) EC}} + EC_GROUP_new_from_params(NULL, NULL, NULL); // Noncompliant {{(PublicKeyEncryption) EC}} + + ECDH_compute_key(buf, sizeof(buf), pt, signing_key, NULL); // Noncompliant {{(KeyAgreement) ECDH}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyKeyTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyKeyTestFile.cc new file mode 100644 index 000000000..3bba6d289 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyKeyTestFile.cc @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +void rsa_keys(BIGNUM *e) { + RSA *rsa = RSA_new(); + RSA_generate_key_ex(rsa, 3072, e, NULL); // Noncompliant {{(PrivateKey) RSA}} + RSA *old = RSA_generate_key(1024, 65537, NULL, NULL); // Noncompliant {{(PrivateKey) RSA}} + RSA *multi = RSA_new(); + RSA_generate_multi_prime_key(multi, 4096, 3, e, NULL); // Noncompliant {{(PrivateKey) RSA}} +} + +void dh_key_on_named_group(void) { + DH *dh = DH_get_2048_256(); + DH_generate_key(dh); // Noncompliant {{(PrivateKey) FFDH}} +} + +void dh_key_next_to_unrelated_groups(void) { + DH *unrelated = DH_get_1024_160(); // Noncompliant {{(PublicKeyEncryption) FFDH-1024}} + DH *dh = DH_new(); + DH_generate_parameters_ex(dh, 3072, DH_GENERATOR_2, NULL); + DH_get_2048_224(); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + DH_generate_key(dh); // Noncompliant {{(PrivateKey) FFDH}} +} + +void dh_key_on_generated_parameters(void) { + DH *dh = DH_new(); + DH_generate_parameters_ex(dh, 2048, DH_GENERATOR_2, NULL); + DH_generate_key(dh); // Noncompliant {{(PrivateKey) FFDH}} +} + +void dsa_key(void) { + DSA *dsa = DSA_new(); + DSA_generate_parameters_ex(dsa, 2048, NULL, 0, NULL, NULL, NULL); + DSA_generate_key(dsa); // Noncompliant {{(PrivateKey) DSA}} +} + +void ec_key_on_named_curve(void) { + EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + EC_KEY_generate_key(key); // Noncompliant {{(PrivateKey) EC-secp256r1}} +} + +void ec_key_on_group(void) { + EC_KEY *key = EC_KEY_new(); + EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_secp384r1); + EC_KEY_set_group(key, group); + EC_KEY_generate_key(key); // Noncompliant {{(PrivateKey) EC-secp384r1}} +} + +void ec_custom_curve(BIGNUM *p, BIGNUM *a, BIGNUM *b) { + EC_GROUP *group = EC_GROUP_new_curve_GFp(p, a, b, NULL); // Noncompliant {{(PublicKeyEncryption) EC}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTestFile.cc new file mode 100644 index 000000000..034e4d9c3 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTestFile.cc @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include +#include + +void des_schedule(DES_cblock *kb, const_DES_cblock *in, DES_cblock *out) { + DES_key_schedule ks; + DES_set_key_checked(kb, &ks); + DES_ecb_encrypt(in, out, &ks, DES_ENCRYPT); // Noncompliant {{(BlockCipher) DES-56-ECB}} +} + +void seed_schedule(const unsigned char *raw, const unsigned char *in, unsigned char *out) { + SEED_KEY_SCHEDULE ks; + SEED_set_key(raw, &ks); + SEED_ecb_encrypt(in, out, &ks, SEED_ENCRYPT); // Noncompliant {{(BlockCipher) SEED-128-ECB}} +} + +void ecdsa_with_generated_key(const unsigned char *dgst) { + EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + EC_KEY_generate_key(key); // Noncompliant {{(PrivateKey) EC-secp256r1}} + ECDSA_do_sign(dgst, 32, key); +} + +void ecdh_with_generated_key(const EC_POINT *peer, unsigned char *out) { + EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp384r1); + EC_KEY_generate_key(key); // Noncompliant {{(PrivateKey) EC-secp384r1}} + ECDH_compute_key(out, 48, peer, key, NULL); +} + +void rsa_sign_with_generated_key(BIGNUM *e, const unsigned char *m, unsigned char *sig, unsigned int *siglen) { + RSA *rsa = RSA_new(); + RSA_generate_key_ex(rsa, 3072, e, NULL); // Noncompliant {{(PrivateKey) RSA}} + RSA_sign(NID_sha256, m, 32, sig, siglen, rsa); +} + +void dsa_sign_with_generated_key(const unsigned char *dgst, unsigned char *sig, unsigned int *siglen) { + DSA *dsa = DSA_new(); + DSA_generate_parameters_ex(dsa, 2048, NULL, 0, NULL, NULL, NULL); + DSA_generate_key(dsa); // Noncompliant {{(PrivateKey) DSA}} + DSA_sign(0, dgst, 32, sig, siglen, dsa); +} + +void dh_agreement_with_generated_key(const BIGNUM *peer, unsigned char *secret) { + DH *dh = DH_get_2048_256(); + DH_generate_key(dh); // Noncompliant {{(PrivateKey) FFDH}} + DH_compute_key(secret, peer, dh); +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyMacTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyMacTestFile.cc new file mode 100644 index 000000000..10bfccfb1 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyMacTestFile.cc @@ -0,0 +1,36 @@ +#include +#include + +void test_legacy_mac() { + HMAC_CTX* hctx = NULL; + HMAC_CTX* hctx2 = NULL; + CMAC_CTX* cctx = NULL; + unsigned char key[32]; + unsigned char data[64]; + unsigned char out[32]; + unsigned int outlen = 0; + size_t outlen2 = 0; + + HMAC_CTX_new(); + HMAC_CTX_reset(hctx); + HMAC_CTX_copy(hctx, hctx2); + + // HMAC_Init_ex/HMAC_Init/HMAC's real digest argument is traced back to its EVP_sha256() + // constructing call, separate from the "HMAC" family finding. + const EVP_MD* md1 = EVP_sha256(); + HMAC_Init_ex(hctx, key, 32, md1, NULL); // Noncompliant {{(Mac) HMAC-SHA-256}} + const EVP_MD* md2 = EVP_sha256(); + HMAC_Init(hctx, key, 32, md2); // Noncompliant {{(Mac) HMAC-SHA-256}} + HMAC_Update(hctx, data, 64); + HMAC_Final(hctx, out, &outlen); + const EVP_MD* md3 = EVP_sha256(); + HMAC(md3, key, 32, data, 64, out, &outlen); // Noncompliant {{(Mac) HMAC-SHA-256}} + + CMAC_CTX_new(); + // CMAC_Init's cipher argument is traced back to its EVP_aes_128_cbc() constructing call + const EVP_CIPHER* cmac_cipher = EVP_aes_128_cbc(); + CMAC_Init(cctx, key, 32, cmac_cipher, NULL); // Noncompliant {{(Mac) CMAC-AES}} + CMAC_Update(cctx, data, 64); + CMAC_Final(cctx, out, &outlen2); + CMAC_resume(cctx); +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTestFile.cc new file mode 100644 index 000000000..993e895b7 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTestFile.cc @@ -0,0 +1,12 @@ +#include + +void rsa_encryption(RSA *rsa, const unsigned char *in, int len, unsigned char *out) { + RSA_public_encrypt(len, in, out, rsa, RSA_PKCS1_OAEP_PADDING); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + RSA_private_decrypt(len, in, out, rsa, RSA_PKCS1_PADDING); // Noncompliant {{(PublicKeyEncryption) RSA}} + RSA_public_encrypt(len, in, out, rsa, RSA_NO_PADDING); // Noncompliant {{(PublicKeyEncryption) RSA}} +} + +void rsa_signature_primitive(RSA *rsa, const unsigned char *in, int len, unsigned char *out) { + RSA_private_encrypt(len, in, out, rsa, RSA_PKCS1_PADDING); // Noncompliant {{(Signature) RSA-PKCS1-1.5}} + RSA_public_decrypt(len, in, out, rsa, RSA_X931_PADDING); // Noncompliant {{(Signature) ANSI X9.31}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyRsaTestFile.cc b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyRsaTestFile.cc new file mode 100644 index 000000000..e7b205696 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/legacy/OpenSSLLegacyRsaTestFile.cc @@ -0,0 +1,54 @@ +#include + +enum +{ + NID_sha256 = 672 +}; + +void test_legacy_rsa(RSA *key) +{ + RSA *rsa = NULL; + BIGNUM *e = NULL; + unsigned char buf[256]; + unsigned int len = 0; + unsigned char em[256]; + unsigned char mhash[64]; + + RSA_generate_key_ex(rsa, 2048, e, NULL); // Noncompliant {{(PrivateKey) RSA}} + RSA_generate_multi_prime_key(rsa, 2048, 3, e, NULL); // Noncompliant {{(PrivateKey) RSA}} + + RSA_public_encrypt(32, buf, buf, key, 1); // Noncompliant {{(PublicKeyEncryption) RSA}} + RSA_private_decrypt(32, buf, buf, key, 1); // Noncompliant {{(PublicKeyEncryption) RSA}} + + RSA_sign(NID_sha256, buf, 32, buf, &len, key); // Noncompliant {{(Signature) RSA-PKCS1-1.5-SHA-256}} + RSA_verify(NID_sha256, buf, 32, buf, 32, key); // Noncompliant {{(Signature) RSA-PKCS1-1.5-SHA-256}} + + // Digest NID via a plain local variable (not an enum constant), resolved via + // CxxSymbolResolverVisitor from the variable's initializer. + int md5_nid = 4; + RSA_sign(md5_nid, buf, 32, buf, &len, key); // Noncompliant {{(Signature) RSA-PKCS1-1.5-MD5}} + RSA_verify(md5_nid, buf, 32, buf, 32, key); // Noncompliant {{(Signature) RSA-PKCS1-1.5-MD5}} + + RSA_private_encrypt(32, buf, buf, key, 1); // Noncompliant {{(Signature) RSA-PKCS1-1.5}} + RSA_public_decrypt(32, buf, buf, key, 1); // Noncompliant {{(Signature) RSA-PKCS1-1.5}} + + RSA_padding_add_PKCS1_PSS(key, em, mhash, EVP_sha256(), 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + RSA_padding_add_PKCS1_PSS_mgf1(key, em, mhash, NULL, NULL, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + RSA_verify_PKCS1_PSS(key, mhash, EVP_sha256(), em, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + RSA_verify_PKCS1_PSS_mgf1(key, mhash, NULL, NULL, em, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + + RSA_padding_add_PKCS1_OAEP(buf, 256, buf, 32, buf, 16); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + RSA_padding_add_PKCS1_OAEP_mgf1(buf, 256, buf, 32, buf, 16, EVP_sha384(), NULL); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + + RSA_padding_add_PKCS1_type_1(buf, 256, buf, 32); // Noncompliant {{(Signature) RSA-PKCS1-1.5}} + RSA_padding_add_PKCS1_type_2(buf, 256, buf, 32); // Noncompliant {{(PublicKeyEncryption) RSA}} + RSA_padding_check_PKCS1_type_1(buf, 256, buf, 32, 256); // Noncompliant {{(Signature) RSA-PKCS1-1.5}} + RSA_padding_check_PKCS1_type_2(buf, 256, buf, 32, 256); // Noncompliant {{(PublicKeyEncryption) RSA}} + RSA_padding_check_PKCS1_OAEP(buf, 256, buf, 32, 256, buf, 16); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + RSA_padding_check_PKCS1_OAEP_mgf1(buf, 256, buf, 32, 256, buf, 16, NULL, NULL); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + RSA_padding_add_X931(buf, 256, buf, 32); // Noncompliant {{(Signature) ANSI X9.31}} + RSA_padding_check_X931(buf, 256, buf, 32, 256); // Noncompliant {{(Signature) ANSI X9.31}} + RSA_padding_add_none(buf, 256, buf, 32); // Noncompliant {{(PublicKeyEncryption) RSA}} + RSA_padding_check_none(buf, 256, buf, 32, 256); // Noncompliant {{(PublicKeyEncryption) RSA}} + RSA_generate_key(2048, 65537, NULL, NULL); // Noncompliant {{(PrivateKey) RSA}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/mac/OpenSSLEvpMacContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/mac/OpenSSLEvpMacContextTestFile.cc new file mode 100644 index 000000000..c0c1fe16b --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/mac/OpenSSLEvpMacContextTestFile.cc @@ -0,0 +1,38 @@ +#include +#include + +void hmac_with_digest_set_on_init(const unsigned char *key, size_t keylen) { + EVP_MAC *mac = EVP_MAC_fetch(NULL, "hmac", NULL); // Noncompliant {{(Mac) HMAC-SHA-256}} + EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0), + OSSL_PARAM_construct_end() + }; + EVP_MAC_init(ctx, key, keylen, params); +} + +void cmac_with_cipher_set_on_context() { + EVP_MAC *mac = EVP_MAC_fetch(NULL, "CMAC", NULL); // Noncompliant {{(Mac) CMAC-AES}} + EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("cipher", "aes-256-cbc", 0), + OSSL_PARAM_construct_end() + }; + EVP_MAC_CTX_set_params(ctx, params); +} + +void macs_without_parameters() { + EVP_MAC_fetch(NULL, "Poly1305", NULL); // Noncompliant {{(Mac) Poly1305}} + EVP_MAC_fetch(NULL, "SipHash", NULL); // Noncompliant {{(Mac) SipHash}} + EVP_MAC_fetch(NULL, "KMAC-256", NULL); // Noncompliant {{(Mac) KMAC256}} +} + +void gmac_with_cipher_set_on_context() { + EVP_MAC *mac = EVP_MAC_fetch(NULL, "GMAC", NULL); // Noncompliant {{(Mac) AES-128-GMAC}} + EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("cipher", "aes-128-gcm", 0), + OSSL_PARAM_construct_end() + }; + EVP_MAC_CTX_set_params(ctx, params); +} diff --git a/cpp/src/test/files/rules/detection/openssl/mac/OpenSSLEvpMacTestFile.cc b/cpp/src/test/files/rules/detection/openssl/mac/OpenSSLEvpMacTestFile.cc new file mode 100644 index 000000000..5a6ec9df6 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/mac/OpenSSLEvpMacTestFile.cc @@ -0,0 +1,29 @@ +#include +#include + +void test_evp_mac() { + OSSL_LIB_CTX* lib = NULL; + const char* props = NULL; + + EVP_MAC_fetch(lib, "HMAC", props); // Noncompliant {{(Mac) HMAC}} + EVP_MAC_fetch(lib, "CMAC", props); // Noncompliant {{(Mac) CMAC}} + EVP_MAC_fetch(lib, "GMAC", props); // Noncompliant {{(Mac) GMAC}} + EVP_MAC_fetch(lib, "Poly1305", props); // Noncompliant {{(Mac) Poly1305}} + EVP_MAC_fetch(lib, "SipHash", props); // Noncompliant {{(Mac) SipHash}} + EVP_MAC_fetch(lib, "KMAC128", props); // Noncompliant {{(Mac) KMAC128}} + EVP_MAC_fetch(lib, "KMAC256", props); // Noncompliant {{(Mac) KMAC256}} + EVP_MAC_fetch(lib, "BLAKE2BMAC", props); // Noncompliant {{(Mac) BLAKE2b-512}} + EVP_MAC_fetch(lib, "BLAKE2SMAC", props); // Noncompliant {{(Mac) BLAKE2s-256}} + + EVP_Q_mac(lib, "HMAC", props, "SHA256", NULL, NULL, 0, NULL, 0, NULL, 0, NULL); // Noncompliant {{(Mac) HMAC-SHA-256}} + EVP_Q_mac(lib, OSSL_MAC_NAME_CMAC, props, "AES-128-CBC", NULL, NULL, 0, NULL, 0, NULL, 0, NULL); // Noncompliant {{(Mac) CMAC-AES}} + + // Legacy HMAC()/HMAC_Init_ex()/CMAC_Init(): the digest/cipher argument is traced back to + // its EVP_sha256()/EVP_aes_128_cbc() constructing call + const EVP_MD* legacy_hmac_md = EVP_sha256(); + HMAC(legacy_hmac_md, NULL, 0, NULL, 0, NULL, NULL); // Noncompliant {{(Mac) HMAC-SHA-256}} + const EVP_MD* legacy_hmac_init_md = EVP_sha256(); + HMAC_Init_ex(NULL, NULL, 0, legacy_hmac_init_md, NULL); // Noncompliant {{(Mac) HMAC-SHA-256}} + const EVP_CIPHER* legacy_cmac_cipher = EVP_aes_128_cbc(); + CMAC_Init(NULL, NULL, 0, legacy_cmac_cipher, NULL); // Noncompliant {{(Mac) CMAC-AES}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/macros/OpenSSLMacrosTestFile.cc b/cpp/src/test/files/rules/detection/openssl/macros/OpenSSLMacrosTestFile.cc new file mode 100644 index 000000000..1091c7730 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/macros/OpenSSLMacrosTestFile.cc @@ -0,0 +1,29 @@ +#include +#include +#include + +void hkdf_selected_with_macros() { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, OSSL_KDF_NAME_HKDF, NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-256}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, OSSL_DIGEST_NAME_SHA2_256, 0), + OSSL_PARAM_construct_end() + }; + EVP_KDF_CTX_set_params(kctx, params); +} + +void mac_selected_with_macro() { + EVP_MAC_fetch(NULL, OSSL_MAC_NAME_POLY1305, NULL); // Noncompliant {{(Mac) Poly1305}} +} + +void digest_selected_with_short_name() { + EVP_get_digestbyname(SN_sha384); // Noncompliant {{(MessageDigest) SHA-384}} +} + +void minimum_tls_version(SSL_CTX *ctx) { + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); // Noncompliant {{(TLS) TLSv1.2}} +} + +void named_curve() { + EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/params/OpenSSLParamsBuilderTestFile.cc b/cpp/src/test/files/rules/detection/openssl/params/OpenSSLParamsBuilderTestFile.cc new file mode 100644 index 000000000..35b860231 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/params/OpenSSLParamsBuilderTestFile.cc @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +void kdf_digest_set_through_a_builder(unsigned char *out) { + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); // Noncompliant {{(KeyDerivationFunction) HKDF-SHA-1}} + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_PROPERTIES, "provider=default", 0); + OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, "SHA1", 0); + OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + EVP_KDF_derive(kctx, out, 32, params); +} + +void mac_cipher_set_through_a_builder(const unsigned char *key) { + EVP_MAC *mac = EVP_MAC_fetch(NULL, "CMAC", NULL); // Noncompliant {{(Mac) CMAC-AES}} + EVP_MAC_CTX *mctx = EVP_MAC_CTX_new(mac); + OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + OSSL_PARAM_BLD_push_utf8_ptr(bld, "cipher", "AES-128-CBC", 0); + OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + EVP_MAC_CTX_set_params(mctx, params); + EVP_MAC_init(mctx, key, 16, NULL); +} diff --git a/cpp/src/test/files/rules/detection/openssl/rand/OpenSSLRandContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/rand/OpenSSLRandContextTestFile.cc new file mode 100644 index 000000000..41221c96d --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/rand/OpenSSLRandContextTestFile.cc @@ -0,0 +1,26 @@ +#include +#include + +void ctr_drbg_with_cipher_set_on_context(EVP_RAND_CTX *parent) { + EVP_RAND *rand = EVP_RAND_fetch(NULL, "CTR-DRBG", NULL); // Noncompliant {{(PseudorandomNumberGenerator) CTR_DRBG-AES-256}} + EVP_RAND_CTX *ctx = EVP_RAND_CTX_new(rand, parent); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("cipher", "AES-256-CTR", 0), + OSSL_PARAM_construct_end() + }; + EVP_RAND_CTX_set_params(ctx, params); +} + +void hmac_drbg_with_digest_set_on_instantiate(EVP_RAND_CTX *parent) { + EVP_RAND *rand = EVP_RAND_fetch(NULL, "hmac-drbg", NULL); // Noncompliant {{(PseudorandomNumberGenerator) HMAC_DRBG-SHA-256}} + EVP_RAND_CTX *ctx = EVP_RAND_CTX_new(rand, parent); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0), + OSSL_PARAM_construct_end() + }; + EVP_RAND_instantiate(ctx, 256, 0, NULL, 0, params); +} + +void seed_source() { + EVP_RAND_fetch(NULL, "SEED-SRC", NULL); // Noncompliant {{(PseudorandomNumberGenerator) SEED-SRC}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/rand/OpenSSLRandTestFile.cc b/cpp/src/test/files/rules/detection/openssl/rand/OpenSSLRandTestFile.cc new file mode 100644 index 000000000..7fc9b260a --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/rand/OpenSSLRandTestFile.cc @@ -0,0 +1,21 @@ +#include +#include + +void test_rand() { + unsigned char buf[32]; + + RAND_bytes(buf, 32); // Noncompliant {{(PseudorandomNumberGenerator) RAND}} + RAND_priv_bytes(buf, 32); // Noncompliant {{(PseudorandomNumberGenerator) RAND}} + RAND_bytes_ex(NULL, buf, 32, 0); // Noncompliant {{(PseudorandomNumberGenerator) RAND}} + RAND_priv_bytes_ex(NULL, buf, 32, 0); // Noncompliant {{(PseudorandomNumberGenerator) RAND}} + + EVP_RAND_fetch(NULL, "CTR-DRBG", NULL); // Noncompliant {{(PseudorandomNumberGenerator) CTR_DRBG}} + EVP_RAND_fetch(NULL, "HASH-DRBG", NULL); // Noncompliant {{(PseudorandomNumberGenerator) Hash_DRBG}} + EVP_RAND_fetch(NULL, "HMAC-DRBG", NULL); // Noncompliant {{(PseudorandomNumberGenerator) HMAC_DRBG}} + EVP_RAND_fetch(NULL, "SEED-SRC", NULL); // Noncompliant {{(PseudorandomNumberGenerator) SEED-SRC}} + EVP_RAND_fetch(NULL, "JITTER", NULL); // Noncompliant {{(PseudorandomNumberGenerator) JITTER}} + EVP_RAND_fetch(NULL, "TEST-RAND", NULL); // Noncompliant {{(PseudorandomNumberGenerator) TEST-RAND}} + + RAND_set_DRBG_type(NULL, "CTR-DRBG", NULL, NULL, NULL); // Noncompliant {{(PseudorandomNumberGenerator) CTR_DRBG}} + RAND_set_seed_source_type(NULL, "SEED-SRC", NULL); // Noncompliant {{(PseudorandomNumberGenerator) SEED-SRC}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLEvpSignatureTestFile.cc b/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLEvpSignatureTestFile.cc new file mode 100644 index 000000000..7b8832203 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLEvpSignatureTestFile.cc @@ -0,0 +1,64 @@ +#include + +void test_evp_signature() { + EVP_MD_CTX* ctx = NULL; + EVP_PKEY_CTX* pctx = NULL; + unsigned char buf[64]; + size_t len = 0; + + // DigestSign / DigestVerify init: the digest argument is traced back to its constructing + // call, independent of the (unresolvable) key algorithm carried by the EVP_PKEY. + const EVP_MD* sign_md = EVP_sha256(); + EVP_DigestSignInit(ctx, NULL, sign_md, NULL, NULL); // Noncompliant {{(Signature) unknown}} + const EVP_MD* verify_md = EVP_sha256(); + EVP_DigestVerifyInit(ctx, NULL, verify_md, NULL, NULL); // Noncompliant {{(Signature) unknown}} + // *_ex's mdname is a digest name, reported as a digest. Given as the OpenSSL 3.x provider + // fetch name here and the legacy alias below. + EVP_DigestSignInit_ex(ctx, NULL, "SHA2-256", NULL, NULL, NULL, NULL); // Noncompliant {{(Signature) unknown}} + EVP_DigestVerifyInit_ex(ctx, NULL, "SHA256", NULL, NULL, NULL, NULL); // Noncompliant {{(Signature) unknown}} + EVP_DigestSign(ctx, NULL, NULL, 0, NULL, 0); + EVP_DigestVerify(ctx, NULL, 0, NULL, 0); + + // PKEY sign/verify (one-shot) + EVP_PKEY_sign(pctx, buf, &len, buf, len); + EVP_PKEY_verify(pctx, buf, len, buf, len); + + // PKEY sign/verify init variants + EVP_PKEY_sign_init(pctx); + EVP_PKEY_sign_init_ex(pctx, NULL); + EVP_PKEY_sign_init_ex2(pctx, NULL, NULL); + EVP_PKEY_sign_message_init(pctx, NULL, NULL); + EVP_PKEY_verify_init(pctx); + EVP_PKEY_verify_init_ex(pctx, NULL); + EVP_PKEY_verify_init_ex2(pctx, NULL, NULL); + EVP_PKEY_verify_message_init(pctx, NULL, NULL); + EVP_PKEY_verify_recover_init(pctx); + EVP_PKEY_verify_recover_init_ex(pctx, NULL); + EVP_PKEY_verify_recover_init_ex2(pctx, NULL, NULL); + EVP_VerifyInit_ex(ctx, NULL, NULL); + + // Legacy one-shot EVP sign/verify streaming API + EVP_SignInit(ctx, NULL); + EVP_SignUpdate(ctx, buf, len); + EVP_SignFinal(ctx, buf, &len, pctx); + EVP_VerifyUpdate(ctx, buf, len); + EVP_VerifyFinal(ctx, buf, len, pctx); + + // SIGNATURE fetch + EVP_SIGNATURE_fetch(NULL, "RSA", NULL); // Noncompliant {{(Signature) RSA-PKCS1-1.5}} + + // RSA PSS / MGF1 CTX setters + const EVP_MD* mgf1_md = EVP_sha256(); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_PKEY_CTX_set_rsa_mgf1_md(pctx, mgf1_md); + EVP_PKEY_CTX_set_rsa_mgf1_md_name(pctx, "SHA256", NULL); // Noncompliant {{(MaskGenerationFunction) MGF1}} + EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + const EVP_MD* signature_md = EVP_sha256(); // Noncompliant {{(MessageDigest) SHA-256}} + EVP_PKEY_CTX_set_signature_md(pctx, signature_md); + const EVP_MD* pss_keygen_md = EVP_sha256(); + EVP_PKEY_CTX_set_rsa_pss_keygen_md(pctx, pss_keygen_md); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_md_name(pctx, "SHA256", NULL); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + const EVP_MD* pss_keygen_mgf1_md = EVP_sha256(); + EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(pctx, pss_keygen_mgf1_md); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name(pctx, "SHA256"); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(pctx, 32); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLMgf1DigestTestFile.cc b/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLMgf1DigestTestFile.cc new file mode 100644 index 000000000..d3a5572c2 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLMgf1DigestTestFile.cc @@ -0,0 +1,25 @@ +#include +#include + +void rsa_pss_key_with_an_mgf1_digest(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048); + EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx, EVP_sha256()); + EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx, EVP_sha512()); +} + +void rsa_pss_key_with_an_mgf1_digest_by_name(void) { + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 3072); + EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name(ctx, "SHA384"); +} + +void legacy_pss_encoding(RSA *rsa, unsigned char *em, const unsigned char *hash) { + RSA_padding_add_PKCS1_PSS_mgf1(rsa, em, hash, EVP_sha256(), EVP_sha1(), 20); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} + RSA_verify_PKCS1_PSS_mgf1(rsa, hash, EVP_sha384(), EVP_sha224(), em, 20); // Noncompliant {{(ProbabilisticSignatureScheme) RSA-PSS}} +} + +void legacy_oaep_encoding(unsigned char *to, int tlen, const unsigned char *from, int flen) { + RSA_padding_add_PKCS1_OAEP_mgf1(to, tlen, from, flen, NULL, 0, EVP_sha256(), EVP_sha1()); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} + RSA_padding_check_PKCS1_OAEP_mgf1(to, tlen, from, flen, 256, NULL, 0, EVP_sha512(), EVP_sha384()); // Noncompliant {{(PublicKeyEncryption) RSA-OAEP}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTestFile.cc b/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTestFile.cc new file mode 100644 index 000000000..eb93650c3 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTestFile.cc @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +void sign_with_loaded_key(BIO *bio, const unsigned char *msg, size_t len, unsigned char *sig, size_t *siglen) { + EVP_PKEY *key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, key); + EVP_DigestSign(mdctx, sig, siglen, msg, len); +} + +void verify_with_given_key(EVP_PKEY *key, const unsigned char *msg, size_t len, const unsigned char *sig, size_t siglen) { + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestVerifyInit(mdctx, NULL, EVP_sha384(), NULL, key); + EVP_DigestVerify(mdctx, sig, siglen, msg, len); +} + +void rsa_pss_with_given_key(EVP_PKEY *key) { + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_PKEY_CTX *pctx = NULL; + EVP_DigestSignInit(mdctx, &pctx, EVP_sha256(), NULL, key); + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING); +} + +void sign_certificate(X509 *cert, EVP_PKEY *key) { + X509_sign(cert, key, EVP_sha256()); +} + +void sign_request(X509_REQ *req, EVP_PKEY *key) { + X509_REQ_sign(req, key, EVP_sha1()); +} + +void sign_crl(X509_CRL *crl, EVP_PKEY *key) { + X509_CRL_sign(crl, key, EVP_sha512()); +} + +void sign_ocsp_response(OCSP_BASICRESP *resp, X509 *signer, EVP_PKEY *key) { + OCSP_basic_sign(resp, signer, key, EVP_sha1(), NULL, 0); +} + +void sign_cms(X509 *signer, EVP_PKEY *key, BIO *data) { + CMS_sign(signer, key, NULL, data, CMS_BINARY); +} + +void sign_pkcs7(X509 *signer, EVP_PKEY *key, BIO *data) { + PKCS7_sign(signer, key, NULL, data, 0); +} + +void sign_with_named_digest(EVP_PKEY *key) { + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit_ex(mdctx, NULL, "SHA2-384", NULL, NULL, key, NULL); +} + +void sign_with_key_from_pkcs12(PKCS12 *p12, const char *pass) { + EVP_PKEY *key = NULL; + X509 *cert = NULL; + PKCS12_parse(p12, pass, &key, &cert, NULL); + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(mdctx, NULL, EVP_sha512(), NULL, key); +} + +void sign_certificate_with_context(X509 *cert, EVP_PKEY *key) { + EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); + EVP_DigestSignInit(mdctx, NULL, EVP_sha224(), NULL, key); + X509_sign_ctx(cert, mdctx); +} diff --git a/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslCipherListTestFile.cc b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslCipherListTestFile.cc new file mode 100644 index 000000000..fea204fc2 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslCipherListTestFile.cc @@ -0,0 +1,7 @@ +#include + +void configure_cipher_suites(SSL_CTX *ctx, SSL *ssl) { + SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES256-GCM-SHA384:HIGH:!aNULL:!MD5"); // Noncompliant {{(TLS) TLS}} + SSL_CTX_set_ciphersuites(ctx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"); // Noncompliant {{(TLS) TLS}} + SSL_set_cipher_list(ssl, "HIGH:!aNULL"); +} diff --git a/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslContextTestFile.cc new file mode 100644 index 000000000..2c83d94fe --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslContextTestFile.cc @@ -0,0 +1,36 @@ +#include + +void tls_server_context(DH *dh, EC_KEY *ecdh) { + SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES256-GCM-SHA384"); + SSL_CTX_set1_groups_list(ctx, "X25519:P-256"); +} + +void tls_client_with_method_variable() { + const SSL_METHOD *method = TLS_client_method(); + SSL_CTX *ctx = SSL_CTX_new(method); // Noncompliant {{(TLS) TLS}} +} + +void ephemeral_dh_parameters(SSL_CTX *ctx) { + DH *dh = DH_get_2048_256(); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + SSL_CTX_set_tmp_dh(ctx, dh); + EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + SSL_CTX_set_tmp_ecdh(ctx, ecdh); +} + +void method_set_later(SSL_CTX *ctx, SSL *ssl) { + SSL_CTX_set_ssl_version(ctx, TLSv1_2_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_set_ssl_method(ssl, TLS_client_method()); // Noncompliant {{(TLS) TLS}} +} + +void string_configuration(SSL_CONF_CTX *cctx, SSL_CTX *ctx, SSL *ssl) { + SSL_CONF_cmd(cctx, "MinProtocol", "TLSv1.2"); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CONF_cmd(cctx, "CipherString", "AES256-GCM-SHA384"); // Noncompliant {{(TLS) TLS}} + SSL_CONF_cmd(cctx, "Groups", "X25519"); + SSL_CONF_cmd(cctx, "Options", "ServerPreference"); + SSL_CTX_set_tlsext_use_srtp(ctx, "SRTP_AES128_CM_SHA1_80"); // Noncompliant {{(Protocol) SRTP}} + SSL_CTX_set1_sigalgs_list(ctx, "ECDSA+SHA256:RSA-PSS+SHA256"); + SSL_set_min_proto_version(ssl, TLS1_3_VERSION); // Noncompliant {{(TLS) TLSv1.3}} + SSL_set_ciphersuites(ssl, "TLS_AES_128_GCM_SHA256"); // Noncompliant {{(TLS) TLS}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTestFile.cc new file mode 100644 index 000000000..243443981 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTestFile.cc @@ -0,0 +1,51 @@ +#include + +SSL_CTX *versions_disabled_one_per_call(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); + return ctx; +} + +SSL_CTX *minimum_and_maximum_set_by_options(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); // Noncompliant {{(TLS) TLSv1.1}} {{(TLS) TLSv1.2}} + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3); + return ctx; +} + +SSL_CTX *connection_options_add_to_its_context(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); // Noncompliant {{(TLS) TLSv1.3}} + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); + SSL *ssl = SSL_new(ctx); + SSL_set_options(ssl, SSL_OP_NO_TLSv1_2); + return ctx; +} + +SSL_CTX *options_and_minimum_version(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); + return ctx; +} + +SSL_CTX *options_without_versions(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); // Noncompliant {{(TLS) TLS}} + SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); + return ctx; +} + +SSL_CTX *minimum_above_the_disabled_versions(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + return ctx; +} + +SSL_CTX *method_of_one_version(void) { + SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); + return ctx; +} diff --git a/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslOptionsTestFile.cc b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslOptionsTestFile.cc new file mode 100644 index 000000000..13ff1e426 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslOptionsTestFile.cc @@ -0,0 +1,31 @@ +#include + +void curves(SSL_CTX *ctx, SSL *ssl) { + SSL_CTX_set1_curves_list(ctx, "P-384"); + SSL_set1_curves_list(ssl, "X25519"); +} + +void minimum_raised(SSL_CTX *ctx) { + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); // Noncompliant {{(TLS) TLSv1.2}} +} + +void maximum_lowered(SSL *ssl) { + SSL_set_options(ssl, SSL_OP_NO_TLSv1_3); // Noncompliant {{(TLS) TLSv1.2}} +} + +void options_held_by_a_variable(SSL_CTX *ctx) { + long options = SSL_OP_ALL | SSL_OP_NO_TLSv1; + SSL_CTX_set_options(ctx, options); // Noncompliant {{(TLS) TLSv1.1}} +} + +void versions_above_a_disabled_version_are_not_used(SSL_CTX *ctx) { + SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); // Noncompliant {{(TLS) TLSv1.0}} +} + +void datagram_versions(SSL_CTX *ctx) { + SSL_CTX_set_options(ctx, SSL_OP_NO_DTLSv1); // Noncompliant {{(TLS) DTLSv1.2}} +} + +void options_without_versions(SSL_CTX *ctx) { + SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION | SSL_OP_CIPHER_SERVER_PREFERENCE); +} diff --git a/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslTestFile.cc b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslTestFile.cc new file mode 100644 index 000000000..fc016c259 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLLibsslTestFile.cc @@ -0,0 +1,105 @@ +#include +#include + +void test_ssl() { + SSL_CTX* ctx = NULL; + SSL* s = NULL; + + TLS_method(); // Noncompliant {{(TLS) TLS}} + TLS_client_method(); // Noncompliant {{(TLS) TLS}} + TLS_server_method(); // Noncompliant {{(TLS) TLS}} + + TLSv1_2_method(); // Noncompliant {{(TLS) TLSv1.2}} + TLSv1_2_client_method(); // Noncompliant {{(TLS) TLSv1.2}} + TLSv1_2_server_method(); // Noncompliant {{(TLS) TLSv1.2}} + + TLSv1_1_method(); // Noncompliant {{(TLS) TLSv1.1}} + TLSv1_1_client_method(); // Noncompliant {{(TLS) TLSv1.1}} + TLSv1_1_server_method(); // Noncompliant {{(TLS) TLSv1.1}} + + TLSv1_method(); // Noncompliant {{(TLS) TLSv1.0}} + TLSv1_client_method(); // Noncompliant {{(TLS) TLSv1.0}} + TLSv1_server_method(); // Noncompliant {{(TLS) TLSv1.0}} + + SSLv3_method(); // Noncompliant {{(TLS) SSLv3.0}} + SSLv3_client_method(); // Noncompliant {{(TLS) SSLv3.0}} + SSLv3_server_method(); // Noncompliant {{(TLS) SSLv3.0}} + + DTLS_method(); // Noncompliant {{(Protocol) DTLS}} + DTLS_client_method(); // Noncompliant {{(Protocol) DTLS}} + DTLS_server_method(); // Noncompliant {{(Protocol) DTLS}} + + DTLSv1_2_method(); // Noncompliant {{(TLS) DTLSv1.2}} + DTLSv1_2_client_method(); // Noncompliant {{(TLS) DTLSv1.2}} + DTLSv1_2_server_method(); // Noncompliant {{(TLS) DTLSv1.2}} + + DTLSv1_method(); // Noncompliant {{(TLS) DTLSv1.0}} + DTLSv1_client_method(); // Noncompliant {{(TLS) DTLSv1.0}} + DTLSv1_server_method(); // Noncompliant {{(TLS) DTLSv1.0}} + + OSSL_QUIC_client_method(); // Noncompliant {{(Protocol) QUIC}} + OSSL_QUIC_client_thread_method(); // Noncompliant {{(Protocol) QUIC}} + OSSL_QUIC_server_method(); // Noncompliant {{(Protocol) QUIC}} + + SSL_CTX_new(NULL); + // the method passed to SSL_CTX_new is reported once, with the context + const SSL_METHOD* tls12_method = TLSv1_2_method(); + SSL_CTX_new(tls12_method); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_cipher_list(ctx, "HIGH"); + SSL_set_cipher_list(s, "HIGH"); + SSL_CTX_set_ciphersuites(ctx, "TLS_AES_128_GCM_SHA256"); // Noncompliant {{(TLS) TLS}} + SSL_set_ciphersuites(s, "TLS_AES_128_GCM_SHA256"); // Noncompliant {{(TLS) TLS}} + + DH* dh1 = DH_get_2048_256(); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + SSL_CTX_set_tmp_dh(ctx, dh1); + DH* dh2 = DH_get_2048_256(); // Noncompliant {{(PublicKeyEncryption) FFDH-2048}} + SSL_set_tmp_dh(s, dh2); + EC_KEY* ecdh1 = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + SSL_CTX_set_tmp_ecdh(ctx, ecdh1); + EC_KEY* ecdh2 = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); // Noncompliant {{(PublicKeyEncryption) EC-secp256r1}} + SSL_set_tmp_ecdh(s, ecdh2); + SSL_CTX_set0_tmp_dh_pkey(ctx, NULL); + SSL_set0_tmp_dh_pkey(s, NULL); + + SSL_CONF_cmd(NULL, "Protocol", "TLSv1.3"); + + SSL_CTX_set_tlsext_use_srtp(ctx, "SRTP_AES128_CM_SHA1_80"); // Noncompliant {{(Protocol) SRTP}} + SSL_set_tlsext_use_srtp(s, "SRTP_AES128_CM_SHA1_80"); // Noncompliant {{(Protocol) SRTP}} + + SSL_CTX_ctrl(ctx, 0, 0, NULL); + SSL_ctrl(s, 0, 0, NULL); + SSL_CTX_set_ssl_version(ctx, NULL); + SSL_set_ssl_method(s, NULL); + + // Version via argument constant, not a versioned method name + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); // Noncompliant {{(TLS) TLSv1.3}} + SSL_set_min_proto_version(s, TLS1_2_VERSION); // Noncompliant {{(TLS) TLSv1.2}} + SSL_set_max_proto_version(s, TLS1_3_VERSION); // Noncompliant {{(TLS) TLSv1.3}} + + // Signature algorithm and group lists: the individual algorithm names are captured. + SSL_CTX_set1_sigalgs_list(ctx, "SLH-DSA-SHA2-256s:ECDSA+SHA256:RSA+SHA256"); + SSL_CTX_set1_groups_list(ctx, "MLKEM768:X25519:secp256r1"); + + SSL_CTX_set1_client_sigalgs_list(ctx, "ECDSA+SHA256"); + SSL_set1_groups_list(s, "X25519"); + SSL_set1_sigalgs_list(s, "ECDSA+SHA384"); + + // A list with one unrecognized name mixed among known ones: the unrecognized entry is + // dropped, only the recognized names appear in the resulting collection. + SSL_CTX_set1_groups_list(ctx, "X25519:FRODOKEM976AES:secp256r1"); + + // Non-list sigalg/group setters: raw int* buffer form, no algorithm names to parse - no + // detection rule matches these, so they raise no finding at all. + SSL_CTX_set1_sigalgs(ctx, NULL, 0); + SSL_set1_sigalgs(s, NULL, 0); + SSL_CTX_set1_client_sigalgs(ctx, NULL, 0); + SSL_CTX_set1_groups(ctx, NULL, 0); + SSL_set1_groups(s, NULL, 0); +} + +void former_method_names(void) { + SSLv23_method(); // Noncompliant {{(TLS) TLS}} + SSLv23_client_method(); // Noncompliant {{(TLS) TLS}} + SSLv23_server_method(); // Noncompliant {{(TLS) TLS}} +} diff --git a/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLSslContextTestFile.cc b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLSslContextTestFile.cc new file mode 100644 index 000000000..642952ec0 --- /dev/null +++ b/cpp/src/test/files/rules/detection/openssl/ssl/OpenSSLSslContextTestFile.cc @@ -0,0 +1,39 @@ +#include + +void server_context(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES256-GCM-SHA384"); + SSL_CTX_set_ciphersuites(ctx, "TLS_AES_256_GCM_SHA384"); + SSL_CTX_set1_groups_list(ctx, "X25519"); + SSL_CTX_set1_sigalgs_list(ctx, "ECDSA+SHA256"); +} + +void connection_of_a_context(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); // Noncompliant {{(TLS) TLS}} + SSL *ssl = SSL_new(ctx); + SSL_set_ciphersuites(ssl, "TLS_AES_128_GCM_SHA256"); + SSL_set1_groups_list(ssl, "X25519"); +} + +void datagram_context(OSSL_LIB_CTX *libctx) { + SSL_CTX *ctx = SSL_CTX_new_ex(libctx, NULL, DTLSv1_2_method()); // Noncompliant {{(TLS) DTLSv1.2}} + DH *dh = DH_get_2048_256(); + SSL_CTX_set_tmp_dh(ctx, dh); +} + +void version_range(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); // Noncompliant {{(TLS) TLSv1.2}} {{(TLS) TLSv1.3}} + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); + SSL_CTX_set_cipher_list(ctx, "AES128-GCM-SHA256"); +} + +void method_set_after_creation(void) { + SSL_CTX *ctx = SSL_CTX_new(TLS_method()); // Noncompliant {{(TLS) TLSv1.2}} + SSL_CTX_set_ssl_version(ctx, TLSv1_2_method()); +} + +void context_without_method(void) { + SSL_CTX *ctx = SSL_CTX_new(NULL); +} diff --git a/cpp/src/test/files/rules/detection/robustness/CxxRepeatedAssignmentsTestFile.cc b/cpp/src/test/files/rules/detection/robustness/CxxRepeatedAssignmentsTestFile.cc new file mode 100644 index 000000000..86c81082a --- /dev/null +++ b/cpp/src/test/files/rules/detection/robustness/CxxRepeatedAssignmentsTestFile.cc @@ -0,0 +1,74 @@ +#include + +void key_size_through_repeated_assignments(int c) { + int v0 = 2048; + int v1 = v0; + if (c == 0) v1 = v0; + if (c == 1) v1 = v0; + if (c == 2) v1 = v0; + if (c == 3) v1 = v0; + if (c == 4) v1 = v0; + if (c == 5) v1 = v0; + if (c == 6) v1 = v0; + int v2 = v1; + if (c == 0) v2 = v1; + if (c == 1) v2 = v1; + if (c == 2) v2 = v1; + if (c == 3) v2 = v1; + if (c == 4) v2 = v1; + if (c == 5) v2 = v1; + if (c == 6) v2 = v1; + int v3 = v2; + if (c == 0) v3 = v2; + if (c == 1) v3 = v2; + if (c == 2) v3 = v2; + if (c == 3) v3 = v2; + if (c == 4) v3 = v2; + if (c == 5) v3 = v2; + if (c == 6) v3 = v2; + int v4 = v3; + if (c == 0) v4 = v3; + if (c == 1) v4 = v3; + if (c == 2) v4 = v3; + if (c == 3) v4 = v3; + if (c == 4) v4 = v3; + if (c == 5) v4 = v3; + if (c == 6) v4 = v3; + int v5 = v4; + if (c == 0) v5 = v4; + if (c == 1) v5 = v4; + if (c == 2) v5 = v4; + if (c == 3) v5 = v4; + if (c == 4) v5 = v4; + if (c == 5) v5 = v4; + if (c == 6) v5 = v4; + int v6 = v5; + if (c == 0) v6 = v5; + if (c == 1) v6 = v5; + if (c == 2) v6 = v5; + if (c == 3) v6 = v5; + if (c == 4) v6 = v5; + if (c == 5) v6 = v5; + if (c == 6) v6 = v5; + int v7 = v6; + if (c == 0) v7 = v6; + if (c == 1) v7 = v6; + if (c == 2) v7 = v6; + if (c == 3) v7 = v6; + if (c == 4) v7 = v6; + if (c == 5) v7 = v6; + if (c == 6) v7 = v6; + int v8 = v7; + if (c == 0) v8 = v7; + if (c == 1) v8 = v7; + if (c == 2) v8 = v7; + if (c == 3) v8 = v7; + if (c == 4) v8 = v7; + if (c == 5) v8 = v7; + if (c == 6) v8 = v7; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + EVP_PKEY_keygen_init(pctx); + EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, v8); + EVP_PKEY *pkey = NULL; + EVP_PKEY_keygen(pctx, &pkey); +} diff --git a/cpp/src/test/files/verifier/IssueWithoutMarkerTestFile.cc b/cpp/src/test/files/verifier/IssueWithoutMarkerTestFile.cc new file mode 100644 index 000000000..eebe7f09c --- /dev/null +++ b/cpp/src/test/files/verifier/IssueWithoutMarkerTestFile.cc @@ -0,0 +1,5 @@ +#include + +void digest_without_marker(void) { + const EVP_MD *md = EVP_sha256(); +} diff --git a/cpp/src/test/files/verifier/MarkerWithoutIssueTestFile.cc b/cpp/src/test/files/verifier/MarkerWithoutIssueTestFile.cc new file mode 100644 index 000000000..4111b2abd --- /dev/null +++ b/cpp/src/test/files/verifier/MarkerWithoutIssueTestFile.cc @@ -0,0 +1,5 @@ +#include + +void marker_without_issue(void) { + int unused = 0; // Noncompliant {{(MessageDigest) SHA-256}} +} diff --git a/cpp/src/test/java/com/ibm/engine/language/cxx/CxxConstructorCallsTest.java b/cpp/src/test/java/com/ibm/engine/language/cxx/CxxConstructorCallsTest.java new file mode 100644 index 000000000..b5624f2ad --- /dev/null +++ b/cpp/src/test/java/com/ibm/engine/language/cxx/CxxConstructorCallsTest.java @@ -0,0 +1,106 @@ +/* + * 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.engine.language.cxx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.config.CxxSquidConfiguration; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.SquidAstVisitor; + +/** + * {@link CxxConstructorCalls} finds the calls of a constructor, with the class and the arguments: + * the declaration of an object with arguments in parentheses or braces, also when it reads as a + * function declaration whose parameters name values, an explicit type conversion and a + * new-expression, of a class in a namespace or not. The declaration of an object without arguments, + * copied from another object, of an array, of a built-in type, or of a function whose parameters + * name types calls no constructor, nor does a member function call. + */ +class CxxConstructorCallsTest { + + private static final String FILE = "src/test/files/language/CxxConstructorCallsTestFile.cc"; + + @Test + void findsTheConstructorCallsWithTheirClassAndArguments() throws IOException { + assertThat(constructorCalls()) + .containsExactly( + "Hasher(\"SHA224\")", + "Hasher(\"SHA384\")", + "Hasher(\"SHA256\",32)", + "Hasher(\"SHA512\")", + "Hasher(\"SHA1\",32)", + "Hasher(name,size)", + "crypto::Digest(\"MD5\")", + "Hasher(\"SHA3-256\")", + "Hasher(\"SHA3-384\")", + "crypto::Digest(\"SHA3-512\")", + "Hasher(\"SHAKE128\",16)"); + } + + /** The constructor calls of the file, in order, each as its class and its arguments. */ + @Nonnull + private static List constructorCalls() throws IOException { + final List calls = new ArrayList<>(); + final SquidAstVisitor visitor = + new SquidAstVisitor<>() { + @Override + public void leaveFile(AstNode root) { + // the symbols are resolved during the walk of the file + collect(root, calls); + } + }; + CxxAstScanner.create(new CxxSquidConfiguration(), visitor) + .scanInputFiles( + List.of( + TestInputFileBuilder.create("", FILE) + .setCharset(StandardCharsets.UTF_8) + .setContents(Files.readString(Path.of(FILE))) + .build())); + return calls; + } + + private static void collect(@Nonnull AstNode node, @Nonnull List calls) { + if (node.is( + CxxGrammarImpl.initDeclarator, + CxxGrammarImpl.postfixExpression, + CxxGrammarImpl.newExpression) + && CxxConstructorCalls.isConstructorCall(node)) { + calls.add( + CxxConstructorCalls.getClassName(node) + + CxxConstructorCalls.getArguments(node).stream() + .map(CxxConstructorCalls::textOf) + .collect(Collectors.joining(",", "(", ")"))); + } + node.getChildren().forEach(child -> collect(child, calls)); + } +} diff --git a/cpp/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationTest.java b/cpp/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationTest.java new file mode 100644 index 000000000..e7c9df367 --- /dev/null +++ b/cpp/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationTest.java @@ -0,0 +1,161 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.cxx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import com.sonar.cxx.sslr.api.Grammar; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.config.CxxSquidConfiguration; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.SquidAstVisitor; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * {@link CxxLanguageTranslation} gives a call the name and the object type rules and hooks match it + * by: a free function in the global scope, a member function in the class its object, pointer or + * reference is declared with, a function pointer member as a free function, a static member + * function in its class, a function named in a namespace by its qualified name, and a constructor + * as {@code } of its class. An argument that is a literal or a name has its text as its type. + */ +class CxxLanguageTranslationTest { + + private static final String FILE = "src/test/files/language/CxxLanguageTranslationTestFile.cc"; + private static final String GLOBAL = CxxLanguageTranslation.GLOBAL_SCOPE; + + private static final Map CALLS = new HashMap<>(); + private static AstNode enumSpecifier; + + private final CxxLanguageTranslation translation = new CxxLanguageTranslation(); + private final MatchContext hook = MatchContext.createForHookContext(); + + @BeforeAll + static void scan() throws IOException { + final SquidAstVisitor visitor = + new SquidAstVisitor<>() { + @Override + public void leaveFile(AstNode root) { + // the symbols are resolved during the walk of the file + collect(root); + } + }; + CxxAstScanner.create(new CxxSquidConfiguration(), visitor) + .scanInputFiles( + List.of( + TestInputFileBuilder.create("", FILE) + .setCharset(StandardCharsets.UTF_8) + .setContents(Files.readString(Path.of(FILE))) + .build())); + } + + /** The outermost call of each line, and the enum specifier. */ + private static void collect(@Nonnull AstNode node) { + if ((CxxConstructorCalls.isConstructorCall(node) || CxxAstNodeHelper.isFunctionCall(node)) + && !CALLS.containsKey(node.getTokenLine())) { + CALLS.put(node.getTokenLine(), node); + } + if (node.is(CxxGrammarImpl.enumSpecifier)) { + enumSpecifier = node; + } + node.getChildren().forEach(CxxLanguageTranslationTest::collect); + } + + @Test + void aFreeFunctionIsInTheGlobalScope() { + assertCall(19, "EVP_sha256", GLOBAL, "Hasher"); + } + + @Test + void aMemberFunctionIsInTheClassItsObjectIsDeclaredWith() { + assertCall(20, "reset", "Hasher", GLOBAL); + assertCall(21, "reset", "Hasher", GLOBAL); + } + + @Test + void aFunctionPointerMemberCallsAFreeFunction() { + assertThat(translation.getMethodName(hook, CALLS.get(22))).contains("digest"); + final IType type = + translation.getInvokedObjectTypeString(hook, CALLS.get(22)).orElseThrow(); + assertThat(type.is(GLOBAL)).isTrue(); + assertThat(type.is("Api")).isTrue(); + } + + @Test + void aStaticMemberFunctionIsInItsClass() { + assertCall(23, "make", "Hasher", GLOBAL); + } + + @Test + void aFunctionOfANamespaceIsNamedWithItsNamespace() { + assertCall(24, "crypto::helper", GLOBAL, "crypto"); + } + + @Test + void aConstructorIsInitOfItsClass() { + assertCall(25, "", "Hasher", GLOBAL); + assertThat(translation.getMethodParameterTypes(hook, CALLS.get(25))) + .singleElement() + .satisfies(type -> assertThat(type.is("\"SHA224\"")).isTrue()); + } + + @Test + void anArgumentThatIsALiteralOrANameHasItsTextAsItsType() { + final List types = translation.getMethodParameterTypes(hook, CALLS.get(26)); + assertThat(types).hasSize(6); + assertThat(types.get(0).is("32")).isTrue(); + assertThat(types.get(1).is("\"digest\"")).isTrue(); + assertThat(types.get(2).is("'c'")).isTrue(); + assertThat(types.get(3).is("count")).isTrue(); + assertThat(types.get(4).is("count + 1")).isFalse(); + assertThat(types.get(4).is("count")).isFalse(); + assertThat(types.get(5).is("FAST")).isTrue(); + } + + @Test + void namesOfIdentifiersAndEnums() { + final AstNode name = enumSpecifier.getFirstDescendant(GenericTokenType.IDENTIFIER); + assertThat(translation.resolveIdentifierAsString(hook, name)).contains("Mode"); + assertThat(translation.getEnumIdentifierName(hook, name)).contains("Mode"); + assertThat(translation.getEnumClassName(hook, enumSpecifier)).contains("Mode"); + } + + private void assertCall(int line, String name, String type, String otherType) { + final AstNode call = CALLS.get(line); + assertThat(translation.getMethodName(hook, call)).as("name").contains(name); + final IType invokedType = translation.getInvokedObjectTypeString(hook, call).orElseThrow(); + assertThat(invokedType.is(type)).as("is %s", type).isTrue(); + assertThat(invokedType.is(otherType)).as("is not %s", otherType).isFalse(); + } +} diff --git a/cpp/src/test/java/com/ibm/engine/language/cxx/CxxSemanticTest.java b/cpp/src/test/java/com/ibm/engine/language/cxx/CxxSemanticTest.java new file mode 100644 index 000000000..b6d75a9fd --- /dev/null +++ b/cpp/src/test/java/com/ibm/engine/language/cxx/CxxSemanticTest.java @@ -0,0 +1,148 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.cxx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.ResolvedValue; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.config.CxxSquidConfiguration; +import org.sonar.cxx.squidbridge.SquidAstVisitor; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * {@link CxxSemantic} resolves an expression to the values it has as C and C++ give them: string + * literals with their prefixes and adjacent literals joined, character and number literals of every + * base and suffix, the boolean and null literals, parenthesized expressions, the branches of a + * conditional, casts, constant arithmetic, the name of an undeclared macro, the value of an + * assignment, enumerators with an explicit or an implicit value, and a variable's initial and + * assigned values. A field or a parameter has no value of its own. + */ +class CxxSemanticTest { + + private static final String FILE = "src/test/files/language/CxxSemanticTestFile.cc"; + + /** The argument of the call {@code v(...)} of each line. */ + private static final Map ARGUMENTS = new HashMap<>(); + + @BeforeAll + static void scan() throws IOException { + final SquidAstVisitor visitor = + new SquidAstVisitor<>() { + @Override + public void leaveFile(AstNode root) { + // the symbols are resolved during the walk of the file + collect(root); + } + }; + CxxAstScanner.create(new CxxSquidConfiguration(), visitor) + .scanInputFiles( + List.of( + TestInputFileBuilder.create("", FILE) + .setCharset(StandardCharsets.UTF_8) + .setContents(Files.readString(Path.of(FILE))) + .build())); + } + + private static void collect(@Nonnull AstNode node) { + if (CxxAstNodeHelper.isFunctionCall(node) + && "v".equals(CxxAstNodeHelper.getFunctionCallName(node))) { + ARGUMENTS.put( + node.getTokenLine(), CxxAstNodeHelper.getFunctionCallArguments(node).get(0)); + } + node.getChildren().forEach(CxxSemanticTest::collect); + } + + static Stream values() { + return Stream.of( + Arguments.of(12, List.of("plain")), + Arguments.of(13, List.of("wide")), + Arguments.of(14, List.of("utf16")), + Arguments.of(15, List.of("utf32")), + Arguments.of(16, List.of("utf8")), + Arguments.of(17, List.of("raw")), + Arguments.of(18, List.of("concat")), + Arguments.of(19, List.of("c")), + Arguments.of(20, List.of(42)), + Arguments.of(21, List.of(42)), + Arguments.of(22, List.of(16)), + Arguments.of(23, List.of(2147483648L)), + Arguments.of(24, List.of(5)), + Arguments.of(25, List.of(8)), + Arguments.of(26, List.of(1.5)), + Arguments.of(27, List.of(1000)), + Arguments.of(28, List.of(5000000000L)), + Arguments.of(29, List.of(0.0)), + Arguments.of(30, List.of(true)), + Arguments.of(31, List.of(false)), + Arguments.of(32, List.of("nullptr")), + Arguments.of(33, List.of(24)), + Arguments.of(34, List.of("either", "or")), + Arguments.of(35, List.of("chosen")), + Arguments.of(36, List.of("cast")), + Arguments.of(37, List.of(7)), + Arguments.of(38, List.of(2048)), + Arguments.of(39, List.of("TLS1_2_VERSION")), + Arguments.of(40, List.of("assigned")), + Arguments.of(41, List.of(3)), + Arguments.of(42, List.of(4)), + Arguments.of(43, List.of(1)), + Arguments.of(44, List.of("initial", "reassigned")), + Arguments.of(45, List.of()), + Arguments.of(46, List.of())); + } + + @ParameterizedTest(name = "line {0}") + @MethodSource("values") + void resolvesTheValuesOfAnExpression(int line, List expected) { + final AstNode argument = ARGUMENTS.get(line); + assertThat(argument).as("argument of line %d", line).isNotNull(); + assertThat( + CxxSemantic.resolveValues( + Object.class, + argument, + new LinkedList<>(), + null, + false, + null) + .stream() + .map(ResolvedValue::value) + .toList()) + .as("values of line %d", line) + .isEqualTo(expected); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/CxxAggregatorTest.java b/cpp/src/test/java/com/ibm/plugin/CxxAggregatorTest.java new file mode 100644 index 000000000..c0f24ef37 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/CxxAggregatorTest.java @@ -0,0 +1,88 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class CxxAggregatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + @AfterEach + void resetSharedState() { + // CxxAggregator holds its accumulated nodes in static fields shared across every test + // in the JVM, so each test must leave it clean for the next one. + CxxAggregator.reset(); + } + + @Test + void aFreshAggregatorReportsNoDetectedNodes() { + assertThat(CxxAggregator.getDetectedNodes()).isEmpty(); + } + + @Test + void addedNodesAreReturnedByGetDetectedNodes() { + INode rsa = new RSA(TEST_LOCATION); + + CxxAggregator.addNodes(List.of(rsa)); + + assertThat(CxxAggregator.getDetectedNodes()).containsExactly(rsa); + } + + @Test + void addingNodesAcrossMultipleCallsAccumulatesThem() { + INode first = new RSA(TEST_LOCATION); + INode second = new RSA(2048, TEST_LOCATION); + + CxxAggregator.addNodes(List.of(first)); + CxxAggregator.addNodes(List.of(second)); + + assertThat(CxxAggregator.getDetectedNodes()).containsExactly(first, second); + } + + @Test + void getDetectedNodesIsUnmodifiable() { + CxxAggregator.addNodes(List.of(new RSA(TEST_LOCATION))); + + List detected = CxxAggregator.getDetectedNodes(); + + assertThat(detected).hasSize(1); + org.junit.jupiter.api.Assertions.assertThrows( + UnsupportedOperationException.class, () -> detected.add(new RSA(TEST_LOCATION))); + } + + @Test + void resetClearsAccumulatedNodesAndRebuildsLanguageSupport() { + CxxAggregator.addNodes(List.of(new RSA(TEST_LOCATION))); + + CxxAggregator.reset(); + + assertThat(CxxAggregator.getDetectedNodes()).isEmpty(); + assertThat(CxxAggregator.getLanguageSupport()).isNotNull(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/CxxPluginTest.java b/cpp/src/test/java/com/ibm/plugin/CxxPluginTest.java new file mode 100644 index 000000000..141545cd0 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/CxxPluginTest.java @@ -0,0 +1,38 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.plugin.rules.CxxInventoryRule; +import com.ibm.plugin.rules.CxxNoMD5UseRule; +import org.junit.jupiter.api.Test; + +class CxxPluginTest { + + @Test + void testCheckRegistrar() { + CxxCheckRegistrar registrar = new CxxCheckRegistrar(); + + assertThat(registrar.repositoryKey()).isEqualTo(CxxScannerRuleDefinition.REPOSITORY_KEY); + assertThat(registrar.checkClasses()) + .containsExactly(CxxInventoryRule.class, CxxNoMD5UseRule.class); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/CxxVerifier.java b/cpp/src/test/java/com/ibm/plugin/CxxVerifier.java new file mode 100644 index 000000000..84852bb7b --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/CxxVerifier.java @@ -0,0 +1,311 @@ +/* + * 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; + +import com.sonar.cxx.sslr.api.Grammar; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import javax.annotation.Nonnull; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.config.CxxSquidConfiguration; +import org.sonar.cxx.squidbridge.AstScanner; +import org.sonar.cxx.squidbridge.SquidAstVisitor; +import org.sonar.cxx.squidbridge.api.CheckMessage; +import org.sonar.cxx.squidbridge.api.SourceFile; +import org.sonarsource.analyzer.commons.checks.verifier.SingleFileVerifier; + +/** + * Test verifier for C++ detection rules. + * + *

Scans a C++ test file using {@link CxxAstScanner} and invokes the provided check (typically a + * {@link TestBase} instance) which intercepts detection findings and calls {@code asserts()}. + * + *

{@link #verify} also checks the issues reported on the file against its {@code // Noncompliant + * {{message}}} comments, as {@code GoVerifier} does for Go. The scans of several files or with + * several checks ({@link #verifyFiles}, {@link #verifyWithChecks}) only run the checks, as the + * issues then depend on the combination scanned. + * + *

No include directories are configured. Detection matches the literal OpenSSL API calls, so it + * does not depend on the preprocessor expanding headers. The preprocessor may log "cannot find + * include file" at DEBUG for the fixtures' {@code #include} lines; that is harmless. + */ +public final class CxxVerifier { + + private static final String TEST_FILES_BASE = "src/test/files/"; + + private CxxVerifier() { + // utility class + } + + /** + * Verifies a C++ test file by scanning it with the given check and checking the reported issues + * against the file's {@code // Noncompliant {{message}}} comments (see {@link #assertIssues}). + * + * @param relativePath Path to the test file relative to {@code src/test/files/} + * @param check The check (detection rule) to apply + */ + public static void verify( + @Nonnull String relativePath, @Nonnull SquidAstVisitor check) { + verify(relativePath, check, StandardCharsets.UTF_8); + } + + /** + * Verifies a C++ test file by scanning it with the given check and specified charset. + * + * @param relativePath Path to the test file relative to {@code src/test/files/} + * @param check The check (detection rule) to apply + * @param charset The character set of the test file + */ + @SuppressWarnings("unchecked") // CxxAstScanner.scanSingleInputFileConfig takes + // SquidAstVisitor... varargs — passing a single typed visitor + // triggers a harmless generic-array creation warning + public static void verify( + @Nonnull String relativePath, + @Nonnull SquidAstVisitor check, + @Nonnull Charset charset) { + String fullPath = TEST_FILES_BASE + relativePath; + File file = new File(fullPath); + if (!file.isFile()) { + throw new IllegalArgumentException("Test file not found: " + file.getAbsolutePath()); + } + + try { + String content = new String(java.nio.file.Files.readAllBytes(file.toPath()), charset); + InputFile inputFile = + TestInputFileBuilder.create("", fullPath) + .setCharset(charset) + .setProjectBaseDir(Path.of(".")) + .setContents(content) + .build(); + + // No include directories are configured: detection matches the literal OpenSSL API + // calls, so it does not depend on the preprocessor expanding headers. The preprocessor + // may log "cannot find include file" at DEBUG for the fixtures' #include lines; that is + // harmless and does not affect detection. + CxxSquidConfiguration squidConfig = new CxxSquidConfiguration(); + SourceFile sourceFile = + CxxAstScanner.scanSingleInputFileConfig(inputFile, squidConfig, check); + assertIssues(file.toPath(), charset, content, sourceFile); + } catch (IOException e) { + throw new IllegalStateException("Failed to read test file: " + fullPath, e); + } + } + + /** + * Verifies a C++ test file with several checks registered in the same scan, as SonarQube runs + * all active rules of a quality profile together. Checks are registered in the given order. + * + * @param relativePath Path to the test file relative to {@code src/test/files/} + * @param checks The checks (detection rules) to apply, in registration order + */ + public static void verifyWithChecks( + @Nonnull String relativePath, @Nonnull List> checks) { + String fullPath = TEST_FILES_BASE + relativePath; + File file = new File(fullPath); + if (!file.isFile()) { + throw new IllegalArgumentException("Test file not found: " + file.getAbsolutePath()); + } + try { + String content = + new String( + java.nio.file.Files.readAllBytes(file.toPath()), + StandardCharsets.UTF_8); + InputFile inputFile = + TestInputFileBuilder.create("", fullPath) + .setCharset(StandardCharsets.UTF_8) + .setProjectBaseDir(Path.of(".")) + .setContents(content) + .build(); + scan(List.of(inputFile), checks); + } catch (IOException e) { + throw new IllegalStateException("Failed to read test file: " + fullPath, e); + } + } + + /** + * Verifies multiple C++ test files in a single scan, sharing one {@link AstScanner} (and thus + * one {@code SquidAstVisitorContext}) across all of them — the same shape as a real multi-file + * SonarQube analysis. Files are scanned in the given order; {@code leaveFile} fires once per + * file as each finishes, before the next one starts, matching production behavior. Unlike + * {@link #verify}, this does not assert a single {@code SourceFile} was produced. + * + * @param relativePaths Paths to the test files relative to {@code src/test/files/}, in scan + * order + * @param check The check (detection rule) to apply across all files + */ + public static void verifyFiles( + @Nonnull List relativePaths, @Nonnull SquidAstVisitor check) { + verifyFiles(relativePaths, check, StandardCharsets.UTF_8); + } + + /** + * Verifies multiple C++ test files in a single scan, sharing one {@link AstScanner} (and thus + * one {@code SquidAstVisitorContext}) across all of them — the same shape as a real multi-file + * SonarQube analysis. Files are scanned in the given order; {@code leaveFile} fires once per + * file as each finishes, before the next one starts, matching production behavior. Unlike + * {@link #verify}, this does not assert a single {@code SourceFile} was produced. + * + * @param relativePaths Paths to the test files relative to {@code src/test/files/}, in scan + * order + * @param check The check (detection rule) to apply across all files + * @param charset The character set of the test files + */ + public static void verifyFiles( + @Nonnull List relativePaths, + @Nonnull SquidAstVisitor check, + @Nonnull Charset charset) { + List inputFiles = new ArrayList<>(relativePaths.size()); + for (String relativePath : relativePaths) { + String fullPath = TEST_FILES_BASE + relativePath; + File file = new File(fullPath); + if (!file.isFile()) { + throw new IllegalArgumentException( + "Test file not found: " + file.getAbsolutePath()); + } + try { + String content = + new String(java.nio.file.Files.readAllBytes(file.toPath()), charset); + inputFiles.add( + TestInputFileBuilder.create("", fullPath) + .setCharset(charset) + .setProjectBaseDir(Path.of(".")) + .setContents(content) + .build()); + } catch (IOException e) { + throw new IllegalStateException("Failed to read test file: " + fullPath, e); + } + } + scan(inputFiles, check); + } + + /** + * Verifies test files given as absolute filesystem paths, e.g. files generated into a JUnit + * {@code @TempDir} rather than checked in under {@code src/test/files/}. Otherwise identical to + * {@link #verifyFiles(List, SquidAstVisitor)}: one shared {@link AstScanner} across all files, + * scanned in order, {@code leaveFile} firing per file as production does. + * + * @param absolutePaths Absolute paths to the test files, in scan order + * @param check The check (detection rule) to apply across all files + */ + public static void verifyAbsoluteFiles( + @Nonnull List absolutePaths, @Nonnull SquidAstVisitor check) { + List inputFiles = new ArrayList<>(absolutePaths.size()); + for (Path path : absolutePaths) { + try { + String content = + new String(java.nio.file.Files.readAllBytes(path), StandardCharsets.UTF_8); + inputFiles.add( + TestInputFileBuilder.create("", path.toAbsolutePath().toString()) + .setCharset(StandardCharsets.UTF_8) + .setProjectBaseDir(path.getParent()) + .setContents(content) + .build()); + } catch (IOException e) { + throw new IllegalStateException("Failed to read test file: " + path, e); + } + } + scan(inputFiles, check); + } + + // No include directories are configured: detection matches the literal OpenSSL API calls, so + // it does not depend on the preprocessor expanding headers. + private static void scan( + @Nonnull List inputFiles, @Nonnull SquidAstVisitor check) { + scan(inputFiles, List.of(check)); + } + + @SuppressWarnings("unchecked") // generic varargs of CxxAstScanner.create + private static void scan( + @Nonnull List inputFiles, @Nonnull List> checks) { + CxxSquidConfiguration squidConfig = new CxxSquidConfiguration(); + AstScanner scanner = + CxxAstScanner.create(squidConfig, checks.toArray(new SquidAstVisitor[0])); + scanner.scanInputFiles(inputFiles); + } + + /** + * Checks the issues reported on a file against its {@code // Noncompliant} comments, as {@code + * GoVerifier} does: each reported issue needs a comment on its line, or one pointing to it with + * {@code @+n} / {@code @-n}, giving its message in {@code {{...}}}, and each such comment needs + * a reported issue. + */ + private static void assertIssues( + @Nonnull Path path, + @Nonnull Charset charset, + @Nonnull String content, + @Nonnull SourceFile sourceFile) { + SingleFileVerifier verifier = SingleFileVerifier.create(path, charset); + for (LineComment comment : lineComments(content)) { + verifier.addComment(comment.line(), comment.column(), comment.text(), 2, 0); + } + for (CheckMessage message : sourceFile.getCheckMessages()) { + verifier.reportIssue(message.getText(Locale.ENGLISH)).onLine(message.getLine()); + } + verifier.assertOneOrMoreIssues(); + } + + /** A {@code //} comment: its 1-based line and column, and its text including {@code //}. */ + record LineComment(int line, int column, @Nonnull String text) {} + + /** The {@code //} comments of C/C++ source, outside string and character literals. */ + @Nonnull + static List lineComments(@Nonnull String content) { + List comments = new ArrayList<>(); + String[] lines = content.split("\\R", -1); + boolean inBlockComment = false; + for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) { + String line = lines[lineIndex]; + char quote = 0; + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + char next = i + 1 < line.length() ? line.charAt(i + 1) : 0; + if (inBlockComment) { + if (c == '*' && next == '/') { + inBlockComment = false; + i++; + } + } else if (quote != 0) { + if (c == '\\') { + i++; + } else if (c == quote) { + quote = 0; + } + } else if (c == '"' || c == '\'') { + quote = c; + } else if (c == '/' && next == '*') { + inBlockComment = true; + i++; + } else if (c == '/' && next == '/') { + comments.add(new LineComment(lineIndex + 1, i + 1, line.substring(i))); + break; + } + } + } + return comments; + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/CxxVerifierTest.java b/cpp/src/test/java/com/ibm/plugin/CxxVerifierTest.java new file mode 100644 index 000000000..c9b9dce09 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/CxxVerifierTest.java @@ -0,0 +1,93 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +class CxxVerifierTest { + + @Test + void lineCommentsSkipStringsCharactersAndBlockComments() { + String content = + """ + f("// not a comment"); // first + g('/'); /* // inside a block comment */ h(); // second + /* a block comment + // still inside it + */ i(); // third + """; + + assertThat(CxxVerifier.lineComments(content)) + .containsExactly( + new CxxVerifier.LineComment(1, 24, "// first"), + new CxxVerifier.LineComment(2, 46, "// second"), + new CxxVerifier.LineComment(5, 9, "// third")); + } + + @Test + void anIssueWithoutMarkerFails() { + assertThatThrownBy( + () -> + CxxVerifier.verify( + "verifier/IssueWithoutMarkerTestFile.cc", + new AcceptAnyFinding())) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("(MessageDigest) SHA-256"); + } + + @Test + void aMarkerWithoutIssueFails() { + assertThatThrownBy( + () -> + CxxVerifier.verify( + "verifier/MarkerWithoutIssueTestFile.cc", + new AcceptAnyFinding())) + .isInstanceOf(AssertionError.class); + } + + /** Reports every finding without asserting on it. */ + private static final class AcceptAnyFinding extends TestBase { + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + // the verifier checks the reported issues + } + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/ExpectedFinding.java b/cpp/src/test/java/com/ibm/plugin/ExpectedFinding.java new file mode 100644 index 000000000..817ada8fa --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/ExpectedFinding.java @@ -0,0 +1,160 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.IAssetCollection; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import org.assertj.core.api.Assertions; + +/** + * The expected detection store and translation of a finding, by finding id, as the detection rule + * tests of the Java module assert them: the store with its context, its values and the stores with + * values below it, and the translated nodes with all their children. + * + *

A store is described as {@code Context{ValueType:value, ...}[child, ...]}, keeping only the + * stores that hold a value or have one below them. A node is described as {@code Kind:name[child, + * ...]}, the children sorted, and a collection as {@code Kind:[item, ...]}. + * + *

Running a test with {@code -Dcpp.printFindings=true} prints its findings in this form, as the + * entries of the table the test declares. + */ +public record ExpectedFinding(@Nonnull String store, @Nonnull List nodes) { + + private static final boolean PRINT = Boolean.getBoolean("cpp.printFindings"); + + @Nonnull + public static ExpectedFinding finding(@Nonnull String store, @Nonnull String... nodes) { + return new ExpectedFinding(store, List.of(nodes)); + } + + /** Asserts the finding {@code findingId} against the expected findings, in their order. */ + public static void assertFinding( + @Nonnull List expected, + int findingId, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (PRINT) { + return; + } + final String store = describe(detectionStore); + final List translation = nodes.stream().map(ExpectedFinding::describe).toList(); + assertThat(findingId) + .as("finding %d is not expected", findingId) + .isLessThan(expected.size()); + final ExpectedFinding finding = expected.get(findingId); + assertThat(store).as("detection store of finding %d", findingId).isEqualTo(finding.store()); + assertThat(translation) + .as("translation of finding %d", findingId) + .containsExactlyElementsOf(finding.nodes()); + } + + /** Asserts that every expected finding was reported. */ + public static void assertAllReported(@Nonnull List expected, int findings) { + if (!PRINT) { + Assertions.assertThat(findings).as("findings").isEqualTo(expected.size()); + } + } + + /** The store, its values and the stores with values below it. */ + @Nonnull + public static String describe(@Nonnull DetectionStore store) { + final String values = + store.getDetectionValues().stream() + .map(ExpectedFinding::describe) + .collect(Collectors.joining(", ")); + final List children = new ArrayList<>(); + for (DetectionStore child : store.getChildren()) { + if (hasValue(child)) { + children.add(describe(child)); + } + } + return store.getDetectionValueContext().getClass().getSimpleName() + + "{" + + values + + "}" + + (children.isEmpty() ? "" : "[" + String.join(", ", children) + "]"); + } + + /** The node, and all its children sorted. */ + @Nonnull + public static String describe(@Nonnull INode node) { + final String self = + node instanceof IAssetCollection collection + ? node.getKind().getSimpleName() + + ":[" + + collection.getCollection().stream() + .map(item -> describe((INode) item)) + .collect(Collectors.joining(", ")) + + "]" + : node.getKind().getSimpleName() + ":" + node.asString(); + final String children = + node.getChildren().values().stream() + .map(ExpectedFinding::describe) + .sorted() + .collect(Collectors.joining(", ")); + return children.isEmpty() ? self : self + "[" + children + "]"; + } + + @Nonnull + private static String describe(@Nonnull IValue value) { + return value.getClass().getSimpleName() + ":" + value.asString(); + } + + private static boolean hasValue(@Nonnull DetectionStore store) { + return !store.getDetectionValues().isEmpty() + || store.getChildren().stream().anyMatch(ExpectedFinding::hasValue); + } + + /** Prints the finding as an entry of the expected findings, when requested. */ + static void printIfRequested( + @Nonnull String test, + int findingId, + int line, + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + if (!PRINT) { + return; + } + final StringBuilder entry = + new StringBuilder("FINDING ") + .append(test) + .append(' ') + .append(findingId) + .append(' ') + .append(line) + .append(" finding(") + .append(literal(describe(detectionStore))); + nodes.forEach(node -> entry.append(", ").append(literal(describe(node)))); + System.out.println(entry.append("),")); + } + + @Nonnull + private static String literal(@Nonnull String text) { + return "\"" + text.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/TestBase.java b/cpp/src/test/java/com/ibm/plugin/TestBase.java new file mode 100644 index 000000000..05fdf37be --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/TestBase.java @@ -0,0 +1,232 @@ +/* + * 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; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.Finding; +import com.ibm.engine.model.IValue; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.engine.utils.DetectionStoreLogger; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.rules.CxxInventoryRule; +import com.ibm.plugin.rules.detection.CxxDetectionRules; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.junit.jupiter.api.BeforeEach; +import org.slf4j.event.Level; +import org.sonar.api.testfixtures.log.LogTesterJUnit5; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +public abstract class TestBase extends CxxInventoryRule { + + @Nonnull + private final DetectionStoreLogger< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + detectionStoreLogger = new DetectionStoreLogger<>(); + + private int findingId = 0; + + public TestBase(@Nonnull List> detectionRules) { + super(detectionRules); + } + + public TestBase() { + super(RuleSets.rulesOf(CxxDetectionRules.class)); + } + + @BeforeEach + public void resetState() { + CxxAggregator.reset(); + } + + @BeforeEach + public void debug() { + LogTesterJUnit5 logTesterJUnit5 = new LogTesterJUnit5(); + logTesterJUnit5.setLevel(Level.DEBUG); + } + + @Override + protected void onFinding( + @Nonnull + Finding< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + finding, + @Nonnull List nodes) { + final DetectionStore< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + detectionStore = finding.detectionStore(); + detectionStoreLogger.print(detectionStore); + + ExpectedFinding.printIfRequested( + getClass().getSimpleName(), + findingId, + finding.getMarkerTree().getTokenLine(), + detectionStore, + nodes); + asserts(findingId, detectionStore, nodes); + findingId++; + // report + this.report(finding.getMarkerTree(), nodes) + .forEach( + issue -> + finding.detectionStore() + .getScanContext() + .reportIssue(this, issue.tree(), issue.message())); + } + + public abstract void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes); + + @Nullable public DetectionStore, AstNode, Symbol, SquidAstVisitorContext> + getStoreWithValue( + @Nonnull + List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + detectionStores) { + return detectionStores.stream() + .filter(store -> !store.getDetectionValues().isEmpty()) + .findFirst() + .orElse(null); + } + + @Nullable public DetectionStore, AstNode, Symbol, SquidAstVisitorContext> + getStoreOfValueType( + @Nonnull final Class valueType, + @Nonnull + List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + detectionStores) { + // Filter the relevant detection store from a list of detection stores. + Optional< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + relevantStore = + detectionStores.stream() + .filter( + store -> + store.getDetectionValues().stream() + .anyMatch( + value -> + value.getClass() + .equals(valueType))) + .findFirst(); + // Returns the store with the closest matching value type to the given valueType. + return relevantStore.orElseGet( + () -> + detectionStores.stream() + .map( + store -> + Optional.ofNullable( + getStoreOfValueType( + valueType, store.getChildren()))) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .orElse(null)); + } + + @Nullable public List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + getStoresOfValueType( + @Nonnull final Class valueType, + @Nonnull + List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + detectionStores) { + // Filter the relevant detection store from a list of detection stores. + List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + relevantStores = + detectionStores.stream() + .filter( + store -> + store.getDetectionValues().stream() + .anyMatch( + value -> + value.getClass() + .equals(valueType))) + .toList(); + // Returns the store with the closest matching value type to the given valueType. + List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + children = + detectionStores.stream() + .map(store -> getStoresOfValueType(valueType, store.getChildren())) + .filter(Objects::nonNull) + .flatMap(List::stream) + .toList(); + List< + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext>> + res = new ArrayList<>(relevantStores); + res.addAll(children); + return res; + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/perf/CxxCallStackHeapPerfTest.java b/cpp/src/test/java/com/ibm/plugin/perf/CxxCallStackHeapPerfTest.java new file mode 100644 index 000000000..47c474c52 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/perf/CxxCallStackHeapPerfTest.java @@ -0,0 +1,138 @@ +/* + * 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.perf; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.callstack.CallContextStats; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.nio.file.Path; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Manual heap/perf harness for the cxx call-stack AST-detach mechanism - the cpp analogue of {@code + * com.ibm.plugin.perf.CallStackHeapPerfTest} in the java module. Generates a synthetic corpus of + * OpenSSL {@code EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), ...)}-shaped call sites (scale via + * {@code -Dperf.corpus.files}, default 200), where a nested call is passed as an argument and + * {@code CxxLanguageTranslation#createTypeFromCxxType} builds a type from a sonar-cxx {@code Type} + * whose {@code TypeSymbol} can reach the whole file's AST - scans it in-process via {@link + * CxxVerifier#verifyAbsoluteFiles} (the corpus lives in a JUnit {@code @TempDir}, outside {@code + * src/test/files/}, so the relative-path {@link CxxVerifier#verifyFiles} does not apply), then + * asserts the recorded calls stayed detached (ASTs released) at {@code leaveFile}. Heap delta and + * wall-time are printed for manual comparison, never asserted. + * + *

Excluded from the default build via {@code @Tag("performance")}. Run with: {@code mvn test -pl + * cpp -DexcludedGroups= -Dtest=CxxCallStackHeapPerfTest} (add {@code -Dperf.corpus.files=3000} for + * a heavy soak). + */ +@Tag("performance") +class CxxCallStackHeapPerfTest extends TestBase { + + private static final int FILES = Integer.getInteger("perf.corpus.files", 200); + + private int translatedNodes = 0; + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + // no per-finding assertions; the harness asserts on the aggregate counts below + translatedNodes += nodes.size(); + } + + @Test + void detachesRecordedCallsAtScale(@TempDir Path tmp) throws Exception { + List sources = CxxCryptoCorpusGenerator.generate(tmp, FILES); + + MemoryMXBean mem = ManagementFactory.getMemoryMXBean(); + long heapBefore = usedHeapAfterGc(mem); + long start = System.nanoTime(); + + CxxVerifier.verifyAbsoluteFiles(sources, this); + + long elapsedMs = (System.nanoTime() - start) / 1_000_000L; + long heapAfter = usedHeapAfterGc(mem); + + CallContextStats stats = CxxAggregator.getLanguageSupport().callContextStats(); + + // REPORT (never asserted) + System.out.printf( + "%n[cxx-callstack-perf] files=%d time=%dms heapDeltaMB=%d translatedNodes=%d " + + "retainedWithTree=%d detached=%d total=%d buckets=%d ratio=%.3f%n", + sources.size(), + elapsedMs, + (heapAfter - heapBefore) / (1024L * 1024L), + translatedNodes, + stats.retainedWithTree(), + stats.detached(), + stats.total(), + stats.buckets(), + stats.detachedRatio()); + + // ASSERT (deterministic gate - object-variant counts, no heap/time dependence) + assertThat(translatedNodes) + .as("each unit's cipher initialization is translated to one cipher") + .isEqualTo(sources.size()); + assertThat(stats.total()) + .as("detections must fire, or the harness proves nothing") + .isPositive(); + assertThat(stats.detachedRatio()) + .as("most recorded calls must be detached (ASTs released at leaveFile)") + .isGreaterThanOrEqualTo(0.9d); + assertThat(stats.retainedWithTree()) + .as( + "tree-pinning calls must stay bounded, not grow ~1:1 with detached: " + + "no IType factory, such as createTypeFromCxxType, may capture a " + + "live cxx Type/AstNode") + .isLessThanOrEqualTo(10); + } + + private static long usedHeapAfterGc(@Nonnull MemoryMXBean mem) { + System.gc(); + try { + Thread.sleep(50L); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return mem.getHeapMemoryUsage().getUsed(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/perf/CxxCryptoCorpusGenerator.java b/cpp/src/test/java/com/ibm/plugin/perf/CxxCryptoCorpusGenerator.java new file mode 100644 index 000000000..f4c1c6219 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/perf/CxxCryptoCorpusGenerator.java @@ -0,0 +1,69 @@ +/* + * 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.perf; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Generates a synthetic corpus of OpenSSL EVP cipher-init call sites for the cxx call-stack heap + * harness. Each unit is its own {@code .cc} file with a distinct function name so recorded calls + * grow with corpus size. Every unit's {@code EVP_EncryptInit_ex} call passes a nested function-call + * argument (e.g. {@code EVP_aes_256_gcm()}), for which {@code + * CxxLanguageTranslation#getMethodParameterTypes} reads the argument's type through {@code + * AstNodeTypeExtension.getType} and {@code createTypeFromCxxType}, a type that must not keep the + * file's AST reachable from the recorded call. + */ +final class CxxCryptoCorpusGenerator { + + private CxxCryptoCorpusGenerator() {} + + private static final List CIPHERS = + List.of("EVP_aes_128_gcm", "EVP_aes_192_gcm", "EVP_aes_256_gcm", "EVP_aes_256_cbc"); + + @Nonnull + static List generate(@Nonnull Path root, int units) throws IOException { + Path pkg = Files.createDirectories(root.resolve("perf")); + List files = new ArrayList<>(units); + for (int i = 0; i < units; i++) { + String cipher = CIPHERS.get(i % CIPHERS.size()); + Path unit = pkg.resolve("Unit" + i + ".cc"); + Files.writeString(unit, unitSource(i, cipher)); + files.add(unit); + } + return files; + } + + @Nonnull + private static String unitSource(int i, @Nonnull String cipher) { + return "#include \n\n" + + "void encrypt_unit" + + i + + "(EVP_CIPHER_CTX* ctx, unsigned char* key, unsigned char* iv) {\n" + + " EVP_EncryptInit_ex(ctx, " + + cipher + + "(), NULL, key, iv);\n" + + "}\n"; + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/CxxInventoryRuleTest.java b/cpp/src/test/java/com/ibm/plugin/rules/CxxInventoryRuleTest.java new file mode 100644 index 000000000..68204c723 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/CxxInventoryRuleTest.java @@ -0,0 +1,50 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.plugin.rules.detection.CxxBaseDetectionRule; +import com.ibm.plugin.translation.CxxTranslationProcess; +import java.lang.reflect.Field; +import java.util.List; +import org.junit.jupiter.api.Test; + +class CxxInventoryRuleTest { + + @Test + void theProductionConstructorWiresUpTheRealDetectionAndReorganizerRules() throws Exception { + // The no-arg constructor is what the SonarQube plugin actually instantiates via + // reflection at scan time; the protected overload used by other tests only accepts an + // explicit rule list, so this constructor is otherwise never exercised. Its two fields + // are declared protected on CxxBaseDetectionRule (a different package), so reflection is + // used here to observe them rather than widening their visibility just for this test. + CxxInventoryRule rule = new CxxInventoryRule(); + + Field detectionRulesField = CxxBaseDetectionRule.class.getDeclaredField("detectionRules"); + detectionRulesField.setAccessible(true); + Field translationProcessField = + CxxBaseDetectionRule.class.getDeclaredField("cxxTranslationProcess"); + translationProcessField.setAccessible(true); + + assertThat((List) detectionRulesField.get(rule)).isNotEmpty(); + assertThat(translationProcessField.get(rule)).isInstanceOf(CxxTranslationProcess.class); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxAddressArgumentTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxAddressArgumentTest.java new file mode 100644 index 000000000..72b96976d --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxAddressArgumentTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.language.cxx.CxxLanguageTranslation; +import com.ibm.engine.model.Size; +import com.ibm.engine.model.context.CipherContext; +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.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * An argument {@code &v} passes the object {@code v}: a depending rule on that argument follows + * {@code v} to the call that set it up, as it follows a variable passed as it is. Here {@code + * AES_cbc_encrypt(in, out, len, &key, iv, enc)} is followed to the {@code AES_set_encrypt_key(k, + * bits, &key)} of the same key, and reports the key size set there. + */ +class CxxAddressArgumentTest extends TestBase { + + private static final IDetectionRule KEY_SETUP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("AES_set_encrypt_key") + .shouldBeDetectedAs(new ValueActionFactory<>("AES")) + .withMethodParameter("*") + .withMethodParameter("*") + .shouldBeDetectedAs(new KeySizeFactory<>(Size.UnitType.BIT)) + .asChildOfParameterWithId(-1) + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> "OpenSSL") + .withoutDependingDetectionRules(); + + private static final IDetectionRule ENCRYPTION_WITH_KEY = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(CxxLanguageTranslation.GLOBAL_SCOPE) + .forMethods("AES_cbc_encrypt") + .shouldBeDetectedAs(new ValueActionFactory<>("AES-CBC")) + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .withMethodParameter("*") + .addDependingDetectionRules(List.of(KEY_SETUP)) + .withMethodParameter("*") + .withMethodParameter("*") + .buildForContext(new CipherContext()) + .inBundle(() -> "OpenSSL") + .withoutDependingDetectionRules(); + + private static final List FINDINGS = + List.of( + // 6: AES_cbc_encrypt(buf, buf, 64, &ak, iv, 1); + finding( + "CipherContext{ValueAction:AES-CBC}[CipherContext{ValueAction:AES}" + + "[CipherContext{KeySize:128}]]", + "BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, Mode:CBC," + + " Oid:2.16.840.1.101.3.4.1.2]"), + // 14: AES_cbc_encrypt(buf, buf, 64, &k2, iv, 1); + finding( + "CipherContext{ValueAction:AES-CBC}[CipherContext{ValueAction:AES}" + + "[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-CBC[BlockSize:128, KeyLength:256, Mode:CBC," + + " Oid:2.16.840.1.101.3.4.1.42]"), + // 18: AES_cbc_encrypt(buf, buf, 64, key, iv, 1); + finding( + "CipherContext{ValueAction:AES-CBC}", + "BlockCipher:AES-CBC[BlockSize:128, Mode:CBC," + + " Oid:2.16.840.1.101.3.4.1]")); + + private int findings = 0; + + CxxAddressArgumentTest() { + super(List.of(ENCRYPTION_WITH_KEY)); + } + + @Test + void test() { + CxxVerifier.verify("rules/detection/CxxAddressArgumentTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxArrayElementTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxArrayElementTest.java new file mode 100644 index 000000000..c8bc1989f --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxArrayElementTest.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * An element of an array resolves to the elements of the array's initializer and the values + * assigned to its elements: the element at a constant index, counting designated elements at their + * index, or every element for an index that is not constant, also of an array of arrays. An index + * out of the initializer's range gives no value. + */ +class CxxArrayElementTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verifyFiles( + List.of("rules/detection/CxxArrayElementTestFile.c"), new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(INode::asString) + .containsExactly( + "SHA-1", + "SHA-256", + "MD5", + "SHA-512", + "SHA-512/224", + "SHA-512/256", + "SM3", + "BLAKE2b-512", + "BLAKE2s-256", + "SHA3-224", + "SHA3-256"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleFailureIsolationTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleFailureIsolationTest.java new file mode 100644 index 000000000..6aa2b41ed --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleFailureIsolationTest.java @@ -0,0 +1,145 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.language.ILanguageTranslation; +import com.ibm.engine.model.context.DetectionContext; +import com.ibm.engine.rule.IBundle; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.translation.reorganizer.CxxReorganizerRules; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.event.Level; +import org.sonar.api.testfixtures.log.LogTesterJUnit5; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * A failure while analyzing one file is logged and does not stop the analysis of the files that + * follow it in the same scan. + */ +class CxxBaseDetectionRuleFailureIsolationTest { + + @RegisterExtension + final LogTesterJUnit5 logTester = new LogTesterJUnit5().setLevel(Level.ERROR); + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void aFailureInOneFileDoesNotStopTheNextFile() { + final List> rules = new ArrayList<>(); + rules.add(new FailingDetectionRule()); + rules.addAll(RuleSets.rulesOf(CxxDetectionRules.class)); + + CxxVerifier.verifyFiles( + List.of( + "rules/detection/isolation/CxxFailingFileTestFile.cc", + "rules/detection/isolation/CxxFileAfterFailingFileTestFile.cc"), + new InventoryRule(rules)); + + assertThat(logTester.logs(Level.ERROR)) + .anyMatch( + log -> + log.startsWith("Unable to detect cryptographic assets in file") + && log.contains("CxxFailingFileTestFile.cc")); + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(INode::asString) + .containsExactly("SHA-256"); + } + + @Test + void aFileThatCannotBeParsedDoesNotStopTheNextFile() { + CxxVerifier.verifyFiles( + List.of( + "rules/detection/isolation/CxxUnparsableFileTestFile.cc", + "rules/detection/isolation/CxxFileAfterFailingFileTestFile.cc"), + new InventoryRule(RuleSets.rulesOf(CxxDetectionRules.class))); + + assertThat(logTester.logs(Level.ERROR)) + .anyMatch( + log -> + log.startsWith("Unable to parse file") + && log.contains("CxxUnparsableFileTestFile.cc")); + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(INode::asString) + .containsExactly("SHA-256"); + } + + /** An inventory rule running the given detection rules. */ + private static final class InventoryRule extends CxxBaseDetectionRule { + InventoryRule(@Nonnull List> detectionRules) { + super(true, detectionRules, CxxReorganizerRules.rules()); + } + } + + /** A rule that fails on every call of a function named {@code explode}. */ + private static final class FailingDetectionRule implements IDetectionRule { + @Override + public boolean is(@Nonnull Class kind) { + return false; + } + + @Override + public boolean match( + @Nonnull AstNode expression, @Nonnull ILanguageTranslation translation) { + if (CxxAstNodeHelper.isFunctionCall(expression) + && "explode".equals(CxxAstNodeHelper.getFunctionCallName(expression))) { + throw new IllegalStateException("rule failure"); + } + return false; + } + + @Override + public boolean shouldMatchExactTypes() { + return false; + } + + @Nonnull + @Override + public DetectionContext detectionValueContext() { + throw new UnsupportedOperationException(); + } + + @Nonnull + @Override + public IBundle bundle() { + throw new UnsupportedOperationException(); + } + + @Nonnull + @Override + public List> nextDetectionRules() { + return List.of(); + } + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleSharedSymbolsTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleSharedSymbolsTest.java new file mode 100644 index 000000000..390407c3f --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleSharedSymbolsTest.java @@ -0,0 +1,98 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.ibm.plugin.rules.CxxInventoryRule; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.AstNodeTypeExtension; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * All active rules of a scan share the symbol information of a file. A digest name passed through a + * variable is only resolved through that information, so every rule must still find it no matter in + * which order sonar-cxx calls the rules. + */ +class CxxBaseDetectionRuleSharedSymbolsTest { + + private static final String TEST_FILE = + "rules/detection/CxxBaseDetectionRuleSharedSymbolsTestFile.cc"; + + private static final class DetectedValues extends TestBase { + private final List values = new ArrayList<>(); + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + detectionStore.getDetectionValues().forEach(value -> values.add(value.asString())); + } + } + + @AfterEach + void resetAggregator() { + // CxxInventoryRule adds its findings to the shared aggregator + CxxAggregator.reset(); + } + + @Test + void ruleRegisteredFirstResolvesVariable() { + DetectedValues rule = new DetectedValues(); + CxxVerifier.verifyWithChecks(TEST_FILE, List.of(rule, new CxxInventoryRule())); + assertThat(rule.values).containsExactly("MD5"); + } + + @Test + void ruleRegisteredLastResolvesVariable() { + DetectedValues rule = new DetectedValues(); + CxxVerifier.verifyWithChecks(TEST_FILE, List.of(new CxxInventoryRule(), rule)); + assertThat(rule.values).containsExactly("MD5"); + } + + @Test + void symbolsAreReleasedOnceAllRulesLeftTheFile() { + CxxVerifier.verifyWithChecks( + TEST_FILE, List.of(new DetectedValues(), new CxxInventoryRule())); + assertThat(AstNodeSymbolExtension.size()).isZero(); + assertThat(AstNodeTypeExtension.size()).isZero(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleSymbolLeakTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleSymbolLeakTest.java new file mode 100644 index 000000000..5439f9171 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleSymbolLeakTest.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.AstNodeTypeExtension; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * {@code AstNodeSymbolExtension}/{@code AstNodeTypeExtension} are process-wide {@code WeakHashMap}s + * keyed on {@code AstNode}; their values ({@code Symbol}/{@code Type}) do not hold their own key + * node strongly, but {@code WeakHashMap} eviction is lazy and GC-timed, so {@link + * CxxBaseDetectionRule#leaveFile} removes every entry for a file's own nodes deterministically, as + * soon as that file's AST is no longer needed, rather than leaving it to the next GC cycle. This + * test scans two files in a single shared scan (mirroring a real multi-file analysis, {@code + * leaveFile} firing per file as each finishes) and asserts both maps end up empty right after, + * since population-count is exact and observable without relying on GC or heap-delta timing. + */ +class CxxBaseDetectionRuleSymbolLeakTest extends TestBase { + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + // no per-finding assertions; this test only checks the symbol/type extension map sizes + } + + @Test + void symbolAndTypeExtensionsDoNotAccumulateAcrossFiles() { + CxxVerifier.verifyFiles( + List.of( + "rules/detection/openssl/keygen/OpenSSLEvpKeyGenTestFile.cc", + "rules/detection/openssl/kdf/OpenSSLEvpKdfTestFile.cc"), + this); + + assertThat(AstNodeSymbolExtension.size()) + .as("every scanned file's own nodes must be released once that file leaves") + .isEqualTo(0); + assertThat(AstNodeTypeExtension.size()) + .as("every scanned file's own nodes must be released once that file leaves") + .isEqualTo(0); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleUpdateTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleUpdateTest.java new file mode 100644 index 000000000..018c2bf0d --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxBaseDetectionRuleUpdateTest.java @@ -0,0 +1,81 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import com.ibm.plugin.translation.reorganizer.CxxReorganizerRules; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Every other test in this module drives detection through {@link com.ibm.plugin.TestBase}, which + * overrides {@link CxxBaseDetectionRule#update} to inject its own {@code asserts()} hook instead of + * calling the base implementation - so the base class's real {@code update} body (translate, + * conditionally aggregate, then report) is otherwise never exercised. This test drives a real scan + * through a bare {@link CxxInventoryRule} (no {@code TestBase} override) so that body actually + * runs. + */ +class CxxBaseDetectionRuleUpdateTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void updateTranslatesAndAggregatesEachFindingFromARealScan() { + assertThat(CxxAggregator.getDetectedNodes()).isEmpty(); + + CxxVerifier.verify( + "rules/detection/openssl/mac/OpenSSLEvpMacTestFile.cc", new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()).isNotEmpty(); + } + + /** + * {@link CxxInventoryRule} is the only production subclass of {@link CxxBaseDetectionRule} + * today, and it always overrides {@code report()}, so the base class's own default + * implementation is never reached by any real rule. This minimal subclass leaves it + * unoverridden to verify that default directly. + */ + private static final class BareDetectionRule extends CxxBaseDetectionRule { + BareDetectionRule() { + super(false, List.of(), CxxReorganizerRules.rules()); + } + } + + @Test + void theDefaultReportImplementationReportsNoIssues() { + BareDetectionRule rule = new BareDetectionRule(); + AstNode markerTree = + new AstNode(new com.sonar.cxx.sslr.api.AstNodeType() {}, "marker", null); + + List> issues = rule.report(markerTree, List.of()); + + assertThat(issues).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxChainedCallTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxChainedCallTest.java new file mode 100644 index 000000000..f852195b0 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxChainedCallTest.java @@ -0,0 +1,66 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A chain such as {@code library.digests("default").EVP_get_digestbyname("SHA512")} is detected as + * its last call, with that call's arguments. + */ +class CxxChainedCallTest extends TestBase { + + private final List values = new ArrayList<>(); + + @Test + void test() { + CxxVerifier.verify("rules/detection/CxxChainedCallTestFile.cc", this); + assertThat(values).containsExactly("SHA-512"); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + detectionStore.getDetectionValues().stream().map(IValue::asString).forEach(values::add); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxConstructorArgumentTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxConstructorArgumentTest.java new file mode 100644 index 000000000..311c7658e --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxConstructorArgumentTest.java @@ -0,0 +1,84 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A value passed to a constructor or a member function of a C++ class is followed into it, as one + * passed to a function is: the object may be declared with arguments ({@code Hasher h("SHA224")}, + * {@code Hasher h{...}}, also when the declaration reads as a function declaration, {@code Hasher + * h(name)}), created with {@code new} or as a temporary, and the constructor or member function + * defined in its class or outside it ({@code Digester::Digester(...)}), in a namespace or not, as + * is one passed to a static member function ({@code Hasher::select(...)}). A declaration of a + * variable of a built-in type, a pointer or a reference calls no constructor, nor does the + * declaration of a function whose parameters are types ({@code Hasher make(Config)}). + */ +class CxxConstructorArgumentTest extends TestBase { + + private final List findings = new ArrayList<>(); + + @Test + void test() { + CxxVerifier.verify("rules/detection/CxxConstructorArgumentTestFile.cc", this); + assertThat(findings) + .containsExactly( + "SHA-224", + "SHA-384", + "SHA-512", + "SHA-1", + "MD5", + "SHA3-256", + "SHA-256", + "SHA3-512", + "SHA-512/256", + "SHA-512/224", + "SM3"); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + assertThat(nodes).hasSize(1); + findings.add(nodes.get(0).asString()); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxDetectionRulesTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxDetectionRulesTest.java new file mode 100644 index 000000000..6b5f3fb64 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxDetectionRulesTest.java @@ -0,0 +1,90 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.RuleSets; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipher; +import com.ibm.plugin.rules.detection.openssl.cipher.OpenSSLEvpCipherFetch; +import com.ibm.plugin.rules.detection.openssl.digest.OpenSSLEvpMessageDigest; +import com.ibm.plugin.rules.detection.openssl.kdf.OpenSSLEvpKdf; +import com.ibm.plugin.rules.detection.openssl.keyagreement.OpenSSLEvpKeyAgreement; +import com.ibm.plugin.rules.detection.openssl.keygen.OpenSSLEvpKeyGen; +import com.ibm.plugin.rules.detection.openssl.keygen.OpenSSLEvpKeyGenRsa; +import com.ibm.plugin.rules.detection.openssl.keygen.OpenSSLEvpKeyUsage; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyCipher; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDh; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDigest; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyDsa; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyEc; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyMac; +import com.ibm.plugin.rules.detection.openssl.legacy.OpenSSLLegacyRsa; +import com.ibm.plugin.rules.detection.openssl.mac.OpenSSLEvpMac; +import com.ibm.plugin.rules.detection.openssl.rand.OpenSSLRand; +import com.ibm.plugin.rules.detection.openssl.signature.OpenSSLEvpSignature; +import com.ibm.plugin.rules.detection.openssl.ssl.OpenSSLLibssl; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import org.junit.jupiter.api.Test; + +class CxxDetectionRulesTest { + + @Test + void testRulesRegistered() { + List> rules = RuleSets.rulesOf(CxxDetectionRules.class); + + assertThat(rules).isNotEmpty(); + assertThat(rules).doesNotContainNull(); + } + + /** + * Asserts {@link CxxDetectionRules}'s size against the independently-computed sum of each of + * the 18 OpenSSL rule bundles' own {@code rules().size()}, rather than a hardcoded total: the + * sum tracks itself when a rule is added to any one bundle, while still catching a bundle + * removed from (or duplicated in) {@code OpenSSLDetectionRules}'s aggregation. + */ + @Test + void testAllEighteenOpenSslRuleBundlesAreAggregated() { + int expectedTotal = + RuleSets.rulesOf(OpenSSLEvpCipher.class).size() + + RuleSets.rulesOf(OpenSSLEvpCipherFetch.class).size() + + RuleSets.rulesOf(OpenSSLEvpMessageDigest.class).size() + + RuleSets.rulesOf(OpenSSLEvpMac.class).size() + + RuleSets.rulesOf(OpenSSLEvpSignature.class).size() + + RuleSets.rulesOf(OpenSSLEvpKeyGen.class).size() + + RuleSets.rulesOf(OpenSSLEvpKeyUsage.Signatures.class).size() + + RuleSets.rulesOf(OpenSSLEvpKeyGenRsa.KeyGenerationSettings.class).size() + + RuleSets.rulesOf(OpenSSLEvpKdf.class).size() + + RuleSets.rulesOf(OpenSSLEvpKeyAgreement.class).size() + + RuleSets.rulesOf(OpenSSLRand.class).size() + + RuleSets.rulesOf(OpenSSLLegacyCipher.class).size() + + RuleSets.rulesOf(OpenSSLLegacyDigest.class).size() + + RuleSets.rulesOf(OpenSSLLegacyMac.class).size() + + RuleSets.rulesOf(OpenSSLLegacyRsa.class).size() + + RuleSets.rulesOf(OpenSSLLegacyDsa.class).size() + + RuleSets.rulesOf(OpenSSLLegacyEc.class).size() + + RuleSets.rulesOf(OpenSSLLegacyDh.class).size() + + RuleSets.rulesOf(OpenSSLLibssl.class).size(); + + assertThat(RuleSets.rulesOf(CxxDetectionRules.class)).hasSize(expectedTotal); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxEnclosingCallArgumentTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxEnclosingCallArgumentTest.java new file mode 100644 index 000000000..522e09e3d --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxEnclosingCallArgumentTest.java @@ -0,0 +1,77 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A call passed as an argument to a detected call that reports it, such as {@code + * EVP_aes_256_gcm()} in {@code EVP_EncryptInit_ex}, is reported once, with that call. A call used + * on its own, or passed to a call no rule detects, is reported on its own. + */ +class CxxEnclosingCallArgumentTest extends TestBase { + + private final List findings = new ArrayList<>(); + + @Test + void test() { + CxxVerifier.verify("rules/detection/CxxEnclosingCallArgumentTestFile.cc", this); + assertThat(findings) + .containsExactly( + "AuthenticatedEncryption:AES-256-GCM [Encrypt]", + "MessageDigest:SHA-256", + "MessageDigest:SHA-384"); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + assertThat(nodes).hasSize(1); + INode node = nodes.get(0); + String description = node.getKind().getSimpleName() + ":" + node.asString(); + if (node.hasChildOfType(Encrypt.class).isPresent()) { + description += " [Encrypt]"; + } + findings.add(description); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxFunctionPointerTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxFunctionPointerTest.java new file mode 100644 index 000000000..581c6a1d1 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxFunctionPointerTest.java @@ -0,0 +1,53 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A call through a function pointer calls the functions the pointer is assigned, by name or by + * address, also through a table of pointers, and is detected as a call of each of them. A pointer + * given to the function calls no function known in the analyzed code. + */ +class CxxFunctionPointerTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verifyFiles( + List.of("rules/detection/CxxFunctionPointerTestFile.c"), new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(INode::asString) + .containsExactly("SHA-384", "SHA-512", "SHA-256", "SHA-1"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxHookValueTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxHookValueTest.java new file mode 100644 index 000000000..4331332c8 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxHookValueTest.java @@ -0,0 +1,61 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Signature; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A value found through a hook for a depending rule, the digest name given to the function that + * signs, is reported with the operation it is used by and not again on its own, although the rule + * it was found for is also a rule of its own. + */ +class CxxHookValueTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verifyFiles( + List.of("rules/detection/CxxHookValueTestFile.cc"), new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .singleElement() + .satisfies( + signature -> { + assertThat(signature.is(Signature.class)).isTrue(); + assertThat(signature.hasChildOfType(MessageDigest.class)) + .map(INode::asString) + .contains("SHA-256"); + }); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxMemberFunctionCallTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxMemberFunctionCallTest.java new file mode 100644 index 000000000..5953e7285 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxMemberFunctionCallTest.java @@ -0,0 +1,66 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A C++ member function named like an OpenSSL function, e.g. {@code hasher.MD5(...)}, is not + * detected; an OpenSSL function called through a function pointer member, e.g. {@code + * api->MD5(...)}, is detected as the standalone call is. + */ +class CxxMemberFunctionCallTest extends TestBase { + + private final List lines = new ArrayList<>(); + + @Test + void test() { + CxxVerifier.verify("rules/detection/CxxMemberFunctionCallTestFile.cc", this); + assertThat(lines).containsExactly(20, 24); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + lines.add(detectionStore.getDetectionValues().get(0).getLocation().getTokenLine()); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxRepeatedAssignmentsTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxRepeatedAssignmentsTest.java new file mode 100644 index 000000000..e56a924f1 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxRepeatedAssignmentsTest.java @@ -0,0 +1,61 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.time.Duration; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A variable assigned several times from another variable that is itself assigned several times + * resolves each variable once, not once per path through the assignments. + */ +class CxxRepeatedAssignmentsTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void aChainOfRepeatedAssignmentsResolvesInLinearTime() { + assertTimeoutPreemptively( + Duration.ofSeconds(10), + () -> + CxxVerifier.verifyFiles( + List.of( + "rules/detection/robustness/CxxRepeatedAssignmentsTestFile.cc"), + new CxxInventoryRule())); + + assertThat(CxxAggregator.getDetectedNodes()).hasSize(1); + final INode key = CxxAggregator.getDetectedNodes().get(0); + assertThat(key.asString()).isEqualTo("RSA"); + assertThat(key.hasChildOfType(KeyLength.class)).map(INode::asString).contains("2048"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxReturnValueTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxReturnValueTest.java new file mode 100644 index 000000000..7ec48c88d --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxReturnValueTest.java @@ -0,0 +1,53 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A value returned by a function of the analyzed code is resolved from the function's return + * statements, for a call given as the argument or held by the variable given as the argument. A + * function returning its own parameter returns nothing known. + */ +class CxxReturnValueTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verifyFiles( + List.of("rules/detection/CxxReturnValueTestFile.c"), new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(INode::asString) + .containsExactly("SHA-384", "SHA-512", "SHA-256", "SHA-224"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxScopedCallTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxScopedCallTest.java new file mode 100644 index 000000000..7ee976751 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/CxxScopedCallTest.java @@ -0,0 +1,62 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A call is matched with the function C++ name lookup finds for it: a function of a namespace + * called with its namespace, or without it from inside the namespace or a namespace inside it, also + * when the function is defined outside its namespace; and a member function called without an + * object, or on {@code this}, from another member function of its class. A function of a namespace + * is not the global function of the same name. + */ +class CxxScopedCallTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verifyFiles( + List.of("rules/detection/CxxScopedCallTestFile.cc"), new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(INode::asString) + .containsExactlyInAnyOrder( + "SHA-224", + "SHA-256", + "SHA-384", + "SHA-512", + "SHA3-224", + "SHA3-256", + "SHA3-384"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileConstructorCallTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileConstructorCallTest.java new file mode 100644 index 000000000..fa38e384d --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileConstructorCallTest.java @@ -0,0 +1,77 @@ +/* + * 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.crossfile; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.callstack.CallContextStats; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Constructor calls recorded while scanning {@code HasherCaller.cc} are resolved by the hook of the + * constructor created later, while scanning {@code HasherDefinition.cc}: the calls are detached + * when their file is left, with their arguments resolved while the file and its symbols are live, + * an argument held by a variable as well as a braced argument. + */ +class CxxCrossFileConstructorCallTest extends TestBase { + + private static final String CALLER = "rules/detection/crossfile/HasherCaller.cc"; + private static final String DEFINITION = "rules/detection/crossfile/HasherDefinition.cc"; + + private final List findings = new ArrayList<>(); + + @Test + void constructorCallsOfAnEarlierFileAreResolved() { + CxxVerifier.verifyFiles(List.of(CALLER, DEFINITION), this); + + assertThat(findings).containsExactlyInAnyOrder("SHA-224", "SHA-384"); + final CallContextStats stats = CxxAggregator.getLanguageSupport().callContextStats(); + assertThat(stats.retainedWithTree()) + .as("no call should still be pinning a live AST after both files' leaveFile ran") + .isZero(); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + nodes.forEach(node -> findings.add(node.asString())); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileHookDetachTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileHookDetachTest.java new file mode 100644 index 000000000..a846937e1 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileHookDetachTest.java @@ -0,0 +1,118 @@ +/* + * 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.crossfile; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.callstack.CallContextStats; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.IValue; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * First true cross-file detection test in the cpp suite: a hook created while scanning {@code + * CipherFetchWrapper.cc} resolves calls recorded while scanning {@code CipherFetchCaller.cc}, in + * one shared scan (via {@link CxxVerifier#verifyFiles}). + * + *

Unlike the Java/Python analogues, cxx's {@code CxxSymbolResolverVisitor} scopes its symbol + * table per file ({@code leaveFile} pops it), so {@code make_cipher}'s call in the caller does not + * symbolically link to its definition in the wrapper. Cross-file resolution here instead relies + * purely on the method-name-hash bucketing in {@code CallStackAgent} (see {@code + * CxxLanguageTranslation#getMethodName}, which is syntactic, not symbol-resolution-based): the hook + * registered against {@code "make_cipher"} while analyzing the wrapper's {@code + * EVP_CIPHER_fetch(lib, alg, props)} call (where {@code alg} is an unresolved parameter, forcing + * {@code CxxDetectionEngine#resolveValuesInOuterScope}) matches the recorded {@code + * make_cipher(...)} calls from the caller file purely by name. + * + *

This guards the AST-detach work end-to-end: both calls in the caller pass a + * record-time-resolvable argument (a string literal, a global constant), so both are detachable — + * asserted both via the resolved CBOM values (proving cross-file matching still works after detach) + * and via {@link CallContextStats} (proving the detach actually happened, i.e. {@code leaveFile} + * released the just-scanned file's retained calls). + */ +class CxxCrossFileHookDetachTest extends TestBase { + + private static final String WRAPPER = "rules/detection/crossfile/CipherFetchWrapper.cc"; + private static final String CALLER = "rules/detection/crossfile/CipherFetchCaller.cc"; + + private static final List resolvedValues = new ArrayList<>(); + + @Test + void crossFileDetectionAcrossDetachedCalls() { + resolvedValues.clear(); + + CxxVerifier.verifyFiles(List.of(WRAPPER, CALLER), this); + + // Both the literal and global-constant arguments resolve cross-file into the CBOM: + assertThat(resolvedValues).contains("AES-128-SIV", "AES-192-SIV"); + + // The detach mechanism actually ran: both calls recorded while scanning the wrapper and + // caller files were swapped for their tree-free detached form once each file's leaveFile + // fired, not left pinning their AST. + CallContextStats stats = CxxAggregator.getLanguageSupport().callContextStats(); + assertThat(stats.total()).as("calls must have been recorded for this scan").isPositive(); + assertThat(stats.detached()) + .as("cross-file-resolved calls must be detached, not retained") + .isPositive(); + assertThat(stats.retainedWithTree()) + .as("no call should still be pinning a live AST after both files' leaveFile ran") + .isZero(); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + collectAlgorithmValues(detectionStore); + } + + private void collectAlgorithmValues( + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + store) { + for (IValue value : store.getDetectionValues()) { + resolvedValues.add(value.asString()); + } + store.getChildren().forEach(this::collectAlgorithmValues); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileNamespaceCallTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileNamespaceCallTest.java new file mode 100644 index 000000000..cdedc5d46 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/crossfile/CxxCrossFileNamespaceCallTest.java @@ -0,0 +1,77 @@ +/* + * 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.crossfile; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.callstack.CallContextStats; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A call recorded, and detached, while scanning {@code NamespaceCaller.cc}, of a function of the + * namespace it is in named without its namespace, is resolved by the hook of {@code util::digest} + * created later, while scanning {@code NamespaceDefinition.cc}: the detached call keeps the names + * C++ name lookup tries for it. + */ +class CxxCrossFileNamespaceCallTest extends TestBase { + + private static final String CALLER = "rules/detection/crossfile/NamespaceCaller.cc"; + private static final String DEFINITION = "rules/detection/crossfile/NamespaceDefinition.cc"; + + private final List findings = new ArrayList<>(); + + @Test + void aCallOfAnEarlierFileInTheNamespaceIsResolved() { + CxxVerifier.verifyFiles(List.of(CALLER, DEFINITION), this); + + assertThat(findings).containsExactly("SHA-512/256"); + final CallContextStats stats = CxxAggregator.getLanguageSupport().callContextStats(); + assertThat(stats.retainedWithTree()) + .as("no call should still be pinning a live AST after both files' leaveFile ran") + .isZero(); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + nodes.forEach(node -> findings.add(node.asString())); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTest.java new file mode 100644 index 000000000..4bba320fe --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTest.java @@ -0,0 +1,95 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A context initialized again with another algorithm is another use of it: each cipher or digest it + * is initialized with is reported with the calls made on the context until the next one. + */ +class OpenSSLEvpCipherContextReuseTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CBC}], " + + "CipherContext{CipherAction:DECRYPT}[CipherContext{ValueAction:DESede3-CBC}]]", + "BlockCipher:AES-128-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2]", + "BlockCipher:DESede168-CBC[BlockSize:64, Decrypt:DECRYPT, KeyLength:168, " + + "Mode:CBC]"), + // 11: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:RC4}], " + + "AlgorithmParameterContext{KeySize:128}]", + "StreamCipher:RC4-128[Encrypt:ENCRYPT, KeyLength:128]"), + // 19: EVP_DigestInit_ex(ctx, EVP_sha1(), NULL); + finding( + "DigestContext{ValueAction:SHA-1}", + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]"), + // 21: EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); + finding( + "DigestContext{ValueAction:SHA-512}", + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/cipher/OpenSSLEvpCipherContextReuseTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContextTest.java new file mode 100644 index 000000000..3a86ad37f --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherContextTest.java @@ -0,0 +1,96 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A cipher context ({@code EVP_CIPHER_CTX_new}) is reported with the cipher it is initialized with + * and the parameters set on it, as the JCA {@code Cipher.getInstance} with its {@code init}. The + * initialization of a context that is created by the caller of the function, in the scanned code or + * not, is reported on its own with its cipher. An initialization without a cipher of a context + * initialized elsewhere names no algorithm and is not reported. + */ +class OpenSSLEvpCipherContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-GCM}], " + + "AlgorithmParameterContext{InitializationVectorSize:96, TagSize:128}]", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, Encrypt:ENCRYPT, " + + "InitializationVectorLength:96, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46, TagLength:128]"), + // 14: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:DECRYPT}[CipherContext{ValueAction:RC4}], " + + "AlgorithmParameterContext{KeySize:256}]", + "StreamCipher:RC4-256[Decrypt:DECRYPT, KeyLength:256]"), + // 22: EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CBC}]", + "BlockCipher:AES-128-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2]"), + // 32: EVP_EncryptInit_ex(ctx, EVP_chacha20(), NULL, NULL, NULL); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:ChaCha20}]", + "StreamCipher:ChaCha20[Encrypt:ENCRYPT]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/cipher/OpenSSLEvpCipherContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTest.java new file mode 100644 index 000000000..570a318d0 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTest.java @@ -0,0 +1,108 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Cipher names passed to {@code EVP_CIPHER_fetch} and {@code EVP_get_cipherbyname} are matched + * case-insensitively and may be OpenSSL aliases. + */ +class OpenSSLEvpCipherFetchNamesTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL); + finding( + "CipherContext{Algorithm:AES-256-GCM}", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46]"), + // 5: EVP_CIPHER_fetch(NULL, "aes-128-cbc", NULL); + finding( + "CipherContext{Algorithm:aes-128-cbc}", + "BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, Mode:CBC, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 6: EVP_CIPHER_fetch(NULL, "id-aes256-GCM", NULL); + finding( + "CipherContext{Algorithm:AES-256-GCM}", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46]"), + // 7: EVP_CIPHER_fetch(NULL, "AES256", NULL); + finding( + "CipherContext{Algorithm:AES-256-CBC}", + "BlockCipher:AES-256-CBC[BlockSize:128, KeyLength:256, Mode:CBC, " + + "Oid:2.16.840.1.101.3.4.1.42]"), + // 8: EVP_CIPHER_fetch(NULL, "DES3", NULL); + finding( + "CipherContext{Algorithm:DESede3-CBC}", + "BlockCipher:DESede168-CBC[BlockSize:64, KeyLength:168, Mode:CBC]"), + // 9: EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); + finding( + "CipherContext{Algorithm:ChaCha20-Poly1305}", + "AuthenticatedEncryption:ChaCha20-Poly1305[MessageDigest:Poly1305[Digest:DIGEST]]"), + // 13: EVP_get_cipherbyname("des-ede3-cbc"); + finding( + "CipherContext{Algorithm:DESede3-CBC}", + "BlockCipher:DESede168-CBC[BlockSize:64, KeyLength:168, Mode:CBC]"), + // 14: EVP_get_cipherbyname("BF"); + finding( + "CipherContext{Algorithm:BLOWFISH-CBC}", + "BlockCipher:Blowfish-128-CBC[KeyLength:128, Mode:CBC]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/cipher/OpenSSLEvpCipherFetchNamesTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTest.java new file mode 100644 index 000000000..a8dcd6c2e --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTest.java @@ -0,0 +1,245 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers the algorithm values detected via {@code EVP_CIPHER_fetch}, both as string literals and + * via a local variable holding one of those same values. + */ +class OpenSSLEvpCipherFetchTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 9: EVP_CIPHER_fetch(lib, "AES-128-SIV", props); + finding( + "CipherContext{Algorithm:AES-128-SIV}", + "BlockCipher:AES-128-SIV[BlockSize:128, KeyLength:128, Mode:SIV, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 10: EVP_CIPHER_fetch(lib, "AES-192-SIV", props); + finding( + "CipherContext{Algorithm:AES-192-SIV}", + "BlockCipher:AES-192-SIV[BlockSize:128, KeyLength:192, Mode:SIV, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 11: EVP_CIPHER_fetch(lib, "AES-256-SIV", props); + finding( + "CipherContext{Algorithm:AES-256-SIV}", + "BlockCipher:AES-256-SIV[BlockSize:128, KeyLength:256, Mode:SIV, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 14: EVP_CIPHER_fetch(lib, "AES-128-GCM-SIV", props); + finding( + "CipherContext{Algorithm:AES-128-GCM-SIV}", + "BlockCipher:AES-128-GCM-SIV[BlockSize:128, KeyLength:128, Mode:GCM-SIV, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 15: EVP_CIPHER_fetch(lib, "AES-192-GCM-SIV", props); + finding( + "CipherContext{Algorithm:AES-192-GCM-SIV}", + "BlockCipher:AES-192-GCM-SIV[BlockSize:128, KeyLength:192, Mode:GCM-SIV, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 16: EVP_CIPHER_fetch(lib, "AES-256-GCM-SIV", props); + finding( + "CipherContext{Algorithm:AES-256-GCM-SIV}", + "BlockCipher:AES-256-GCM-SIV[BlockSize:128, KeyLength:256, Mode:GCM-SIV, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 19: EVP_CIPHER_fetch(lib, "AES-128-CBC-CTS", props); + finding( + "CipherContext{Algorithm:AES-128-CBC-CTS}", + "BlockCipher:AES-128-CBC-CTS[BlockSize:128, KeyLength:128, Mode:CBC-CTS, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 20: EVP_CIPHER_fetch(lib, "AES-192-CBC-CTS", props); + finding( + "CipherContext{Algorithm:AES-192-CBC-CTS}", + "BlockCipher:AES-192-CBC-CTS[BlockSize:128, KeyLength:192, Mode:CBC-CTS, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 21: EVP_CIPHER_fetch(lib, "AES-256-CBC-CTS", props); + finding( + "CipherContext{Algorithm:AES-256-CBC-CTS}", + "BlockCipher:AES-256-CBC-CTS[BlockSize:128, KeyLength:256, Mode:CBC-CTS, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 24: EVP_CIPHER_fetch(lib, "AES-128-WRAP-INV", props); + finding( + "CipherContext{Algorithm:AES-128-WRAP-INV}", + "BlockCipher:AES-128-WRAP-INV[BlockSize:128, KeyLength:128, Mode:WRAP-INV, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 25: EVP_CIPHER_fetch(lib, "AES-192-WRAP-INV", props); + finding( + "CipherContext{Algorithm:AES-192-WRAP-INV}", + "BlockCipher:AES-192-WRAP-INV[BlockSize:128, KeyLength:192, Mode:WRAP-INV, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 26: EVP_CIPHER_fetch(lib, "AES-256-WRAP-INV", props); + finding( + "CipherContext{Algorithm:AES-256-WRAP-INV}", + "BlockCipher:AES-256-WRAP-INV[BlockSize:128, KeyLength:256, Mode:WRAP-INV, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 29: EVP_CIPHER_fetch(lib, "AES-128-WRAP-PAD-INV", props); + finding( + "CipherContext{Algorithm:AES-128-WRAP-PAD-INV}", + "BlockCipher:AES-128-WRAP-PAD-INV[BlockSize:128, KeyLength:128, " + + "Mode:WRAP-PAD-INV, Oid:2.16.840.1.101.3.4.1]"), + // 30: EVP_CIPHER_fetch(lib, "AES-192-WRAP-PAD-INV", props); + finding( + "CipherContext{Algorithm:AES-192-WRAP-PAD-INV}", + "BlockCipher:AES-192-WRAP-PAD-INV[BlockSize:128, KeyLength:192, " + + "Mode:WRAP-PAD-INV, Oid:2.16.840.1.101.3.4.1.2]"), + // 31: EVP_CIPHER_fetch(lib, "AES-256-WRAP-PAD-INV", props); + finding( + "CipherContext{Algorithm:AES-256-WRAP-PAD-INV}", + "BlockCipher:AES-256-WRAP-PAD-INV[BlockSize:128, KeyLength:256, " + + "Mode:WRAP-PAD-INV, Oid:2.16.840.1.101.3.4.1.4]"), + // 34: EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA1", props); + finding( + "CipherContext{Algorithm:AES-128-CBC-HMAC-SHA1}", + "BlockCipher:AES-128-CBC-HMAC-SHA1[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA1, Oid:2.16.840.1.101.3.4.1]"), + // 35: EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA256", props); + finding( + "CipherContext{Algorithm:AES-128-CBC-HMAC-SHA256}", + "BlockCipher:AES-128-CBC-HMAC-SHA256[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA256, Oid:2.16.840.1.101.3.4.1]"), + // 36: EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA1", props); + finding( + "CipherContext{Algorithm:AES-192-CBC-HMAC-SHA1}", + "BlockCipher:AES-192-CBC-HMAC-SHA1[BlockSize:128, KeyLength:192, " + + "Mode:CBC-HMAC-SHA1, Oid:2.16.840.1.101.3.4.1.2]"), + // 37: EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA256", props); + finding( + "CipherContext{Algorithm:AES-192-CBC-HMAC-SHA256}", + "BlockCipher:AES-192-CBC-HMAC-SHA256[BlockSize:128, KeyLength:192, " + + "Mode:CBC-HMAC-SHA256, Oid:2.16.840.1.101.3.4.1.2]"), + // 38: EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA1", props); + finding( + "CipherContext{Algorithm:AES-256-CBC-HMAC-SHA1}", + "BlockCipher:AES-256-CBC-HMAC-SHA1[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA1, Oid:2.16.840.1.101.3.4.1.4]"), + // 39: EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA256", props); + finding( + "CipherContext{Algorithm:AES-256-CBC-HMAC-SHA256}", + "BlockCipher:AES-256-CBC-HMAC-SHA256[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA256, Oid:2.16.840.1.101.3.4.1.4]"), + // 42: EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA1-ETM", props); + finding( + "CipherContext{Algorithm:AES-128-CBC-HMAC-SHA1-ETM}", + "BlockCipher:AES-128-CBC-HMAC-SHA1-ETM[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA1-ETM, Oid:2.16.840.1.101.3.4.1]"), + // 43: EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA1-ETM", props); + finding( + "CipherContext{Algorithm:AES-192-CBC-HMAC-SHA1-ETM}", + "BlockCipher:AES-192-CBC-HMAC-SHA1-ETM[BlockSize:128, KeyLength:192, " + + "Mode:CBC-HMAC-SHA1-ETM, Oid:2.16.840.1.101.3.4.1.2]"), + // 44: EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA1-ETM", props); + finding( + "CipherContext{Algorithm:AES-256-CBC-HMAC-SHA1-ETM}", + "BlockCipher:AES-256-CBC-HMAC-SHA1-ETM[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA1-ETM, Oid:2.16.840.1.101.3.4.1.4]"), + // 45: EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA256-ETM", props); + finding( + "CipherContext{Algorithm:AES-128-CBC-HMAC-SHA256-ETM}", + "BlockCipher:AES-128-CBC-HMAC-SHA256-ETM[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA256-ETM, Oid:2.16.840.1.101.3.4.1]"), + // 46: EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA256-ETM", props); + finding( + "CipherContext{Algorithm:AES-192-CBC-HMAC-SHA256-ETM}", + "BlockCipher:AES-192-CBC-HMAC-SHA256-ETM[BlockSize:128, KeyLength:192, " + + "Mode:CBC-HMAC-SHA256-ETM, Oid:2.16.840.1.101.3.4.1.2]"), + // 47: EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA256-ETM", props); + finding( + "CipherContext{Algorithm:AES-256-CBC-HMAC-SHA256-ETM}", + "BlockCipher:AES-256-CBC-HMAC-SHA256-ETM[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA256-ETM, Oid:2.16.840.1.101.3.4.1.4]"), + // 48: EVP_CIPHER_fetch(lib, "AES-128-CBC-HMAC-SHA512-ETM", props); + finding( + "CipherContext{Algorithm:AES-128-CBC-HMAC-SHA512-ETM}", + "BlockCipher:AES-128-CBC-HMAC-SHA512-ETM[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA512-ETM, Oid:2.16.840.1.101.3.4.1]"), + // 49: EVP_CIPHER_fetch(lib, "AES-192-CBC-HMAC-SHA512-ETM", props); + finding( + "CipherContext{Algorithm:AES-192-CBC-HMAC-SHA512-ETM}", + "BlockCipher:AES-192-CBC-HMAC-SHA512-ETM[BlockSize:128, KeyLength:192, " + + "Mode:CBC-HMAC-SHA512-ETM, Oid:2.16.840.1.101.3.4.1.2]"), + // 50: EVP_CIPHER_fetch(lib, "AES-256-CBC-HMAC-SHA512-ETM", props); + finding( + "CipherContext{Algorithm:AES-256-CBC-HMAC-SHA512-ETM}", + "BlockCipher:AES-256-CBC-HMAC-SHA512-ETM[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA512-ETM, Oid:2.16.840.1.101.3.4.1.4]"), + // 53: EVP_CIPHER_fetch(lib, "CAMELLIA-128-CBC-CTS", props); + finding( + "CipherContext{Algorithm:CAMELLIA-128-CBC-CTS}", + "BlockCipher:CAMELLIA-128-CBC-CTS[KeyLength:128, Mode:CBC-CTS]"), + // 54: EVP_CIPHER_fetch(lib, "CAMELLIA-192-CBC-CTS", props); + finding( + "CipherContext{Algorithm:CAMELLIA-192-CBC-CTS}", + "BlockCipher:CAMELLIA-192-CBC-CTS[KeyLength:192, Mode:CBC-CTS]"), + // 55: EVP_CIPHER_fetch(lib, "CAMELLIA-256-CBC-CTS", props); + finding( + "CipherContext{Algorithm:CAMELLIA-256-CBC-CTS}", + "BlockCipher:CAMELLIA-256-CBC-CTS[KeyLength:256, Mode:CBC-CTS]"), + // 59: EVP_CIPHER_fetch(lib, alg, props); + finding( + "CipherContext{Algorithm:AES-128-SIV}", + "BlockCipher:AES-128-SIV[BlockSize:128, KeyLength:128, Mode:SIV, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 63: EVP_CIPHER_fetch(lib, alg2, props); + finding( + "CipherContext{Algorithm:AES-192-SIV, Algorithm:AES-256-SIV}", + "BlockCipher:AES-192-SIV[BlockSize:128, KeyLength:192, Mode:SIV, " + + "Oid:2.16.840.1.101.3.4.1.2]", + "BlockCipher:AES-256-SIV[BlockSize:128, KeyLength:256, Mode:SIV, " + + "Oid:2.16.840.1.101.3.4.1.4]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/cipher/OpenSSLEvpCipherFetchTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherInitTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherInitTest.java new file mode 100644 index 000000000..9890ed8af --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherInitTest.java @@ -0,0 +1,86 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The cipher given to {@code EVP_EncryptInit}, {@code EVP_DecryptInit} or {@code EVP_CipherInit} is + * reported with the operation it is initialized for. + */ +class OpenSSLEvpCipherInitTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-GCM}]", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, Encrypt:ENCRYPT, " + + "KeyLength:256, Mode:GCM, Oid:2.16.840.1.101.3.4.1.46]"), + // 11: EVP_DecryptInit_ex2(ctx, cipher, key, iv, NULL); + finding( + "CipherContext{CipherAction:DECRYPT}[CipherContext{Algorithm:ChaCha20-Poly1305}]", + "AuthenticatedEncryption:ChaCha20-Poly1305[Decrypt:DECRYPT, " + + "MessageDigest:Poly1305[Digest:DIGEST]]"), + // 17: EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, 1); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:DESede3-CBC}]", + "BlockCipher:DESede168-CBC[BlockSize:64, Encrypt:ENCRYPT, KeyLength:168, " + + "Mode:CBC]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/cipher/OpenSSLEvpCipherInitTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTest.java new file mode 100644 index 000000000..8872af7f8 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTest.java @@ -0,0 +1,46 @@ +/* + * 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.openssl.cipher; + +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A key length set on a cipher context ({@code EVP_CIPHER_CTX_set_key_length}) is the key length of + * a cipher whose key length is variable; OpenSSL refuses it for a cipher whose key length is fixed, + * which keeps the key length its name gives. + */ +class OpenSSLEvpCipherKeyLengthTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/cipher/OpenSSLEvpCipherKeyLengthTestFile.cc", + new CxxInventoryRule()); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTest.java new file mode 100644 index 000000000..5e2f514ed --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTest.java @@ -0,0 +1,123 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The parameters set on a cipher context ({@link OpenSSLEvpCipherParameters}): the {@code + * EVP_CIPHER_CTX_ctrl} command, by name or by value, selects whether its argument is the key + * length, the RC2 effective key bits, the IV length or the tag length ({@link + * OpenSSLCipherCtrlFactory}); other commands set no parameter. {@code EVP_CIPHER_CTX_set_padding} + * enables the standard block padding, PKCS#7, or disables padding ({@link + * OpenSSLCipherPaddingFactory}). + */ +class OpenSSLEvpCipherParametersTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:BLOWFISH-CBC}], " + + "AlgorithmParameterContext{KeySize:128}]", + "BlockCipher:Blowfish-128-CBC[Encrypt:ENCRYPT, KeyLength:128, Mode:CBC]"), + // 10: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:RC2-CBC}], " + + "AlgorithmParameterContext{KeySize:40}]", + "BlockCipher:RC2-40-CBC[Encrypt:ENCRYPT, KeyLength:40, Mode:CBC]"), + // 16: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:DECRYPT}[CipherContext{ValueAction:AES-128-GCM}], " + + "AlgorithmParameterContext{InitializationVectorSize:128, TagSize:96}]", + "AuthenticatedEncryption:AES-128-GCM[BlockSize:128, Decrypt:DECRYPT, " + + "InitializationVectorLength:128, KeyLength:128, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.6, TagLength:96]"), + // 23: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CCM}], " + + "AlgorithmParameterContext{InitializationVectorSize:56, TagSize:64}]", + "AuthenticatedEncryption:AES-128-CCM[BlockSize:128, Encrypt:ENCRYPT, " + + "InitializationVectorLength:56, KeyLength:128, Mode:CCM, " + + "Oid:2.16.840.1.101.3.4.1.7, TagLength:64]"), + // 30: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-GCM}], " + + "AlgorithmParameterContext{InitializationVectorSize:64, TagSize:112}]", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, Encrypt:ENCRYPT, " + + "InitializationVectorLength:64, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46, TagLength:112]"), + // 37: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-GCM}]]", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, Encrypt:ENCRYPT, " + + "KeyLength:256, Mode:GCM, Oid:2.16.840.1.101.3.4.1.46]"), + // 44: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CBC}], " + + "AlgorithmParameterContext{Padding:PKCS7}]", + "BlockCipher:AES-128-CBC-PKCS7[BlockSize:128, Encrypt:ENCRYPT, " + + "KeyLength:128, Mode:CBC, Oid:2.16.840.1.101.3.4.1.2, Padding:PKCS7]"), + // 50: EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + finding( + "CipherContext{}[CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CBC}]]", + "BlockCipher:AES-128-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/cipher/OpenSSLEvpCipherParametersTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherTest.java new file mode 100644 index 000000000..01eb799a5 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLEvpCipherTest.java @@ -0,0 +1,795 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers EVP cipher detection rules in {@link OpenSSLEvpCipher}. + * + *

Every cipher family (AES, ARIA, Camellia, SM4, DES/DESede, Blowfish, CAST5, RC2, RC4, RC5, + * IDEA, SEED, ChaCha20, ChaCha20-Poly1305) and the EVP, CMS and PKCS#7 init, fetch and encryption + * functions. {@code EVP_enc_null()}, and the init and encryption functions given a {@code NULL} + * cipher, name no algorithm and are not reported. + */ +class OpenSSLEvpCipherTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 9: EVP_aes_128_cbc(); + finding( + "CipherContext{ValueAction:AES-128-CBC}", + "BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, Mode:CBC, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 10: EVP_aes_128_ecb(); + finding( + "CipherContext{ValueAction:AES-128-ECB}", + "BlockCipher:AES-128-ECB[BlockSize:128, KeyLength:128, Mode:ECB, " + + "Oid:2.16.840.1.101.3.4.1.1]"), + // 11: EVP_aes_128_gcm(); + finding( + "CipherContext{ValueAction:AES-128-GCM}", + "AuthenticatedEncryption:AES-128-GCM[BlockSize:128, KeyLength:128, " + + "Mode:GCM, Oid:2.16.840.1.101.3.4.1.6]"), + // 12: EVP_aes_128_ctr(); + finding( + "CipherContext{ValueAction:AES-128-CTR}", + "BlockCipher:AES-128-CTR[BlockSize:128, KeyLength:128, Mode:CTR, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 13: EVP_aes_128_ccm(); + finding( + "CipherContext{ValueAction:AES-128-CCM}", + "AuthenticatedEncryption:AES-128-CCM[BlockSize:128, KeyLength:128, " + + "Mode:CCM, Oid:2.16.840.1.101.3.4.1.7]"), + // 14: EVP_aes_128_cfb128(); + finding( + "CipherContext{ValueAction:AES-128-CFB}", + "BlockCipher:AES-128-CFB[BlockSize:128, KeyLength:128, Mode:CFB, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 15: EVP_aes_128_cfb1(); + finding( + "CipherContext{ValueAction:AES-128-CFB1}", + "BlockCipher:AES-128-CFB1[BlockSize:128, KeyLength:128, Mode:CFB1, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 16: EVP_aes_128_cfb8(); + finding( + "CipherContext{ValueAction:AES-128-CFB8}", + "BlockCipher:AES-128-CFB8[BlockSize:128, KeyLength:128, Mode:CFB8, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 17: EVP_aes_128_ofb(); + finding( + "CipherContext{ValueAction:AES-128-OFB}", + "BlockCipher:AES-128-OFB[BlockSize:128, KeyLength:128, Mode:OFB, " + + "Oid:2.16.840.1.101.3.4.1.3]"), + // 18: EVP_aes_128_xts(); + finding( + "CipherContext{ValueAction:AES-128-XTS}", + "BlockCipher:AES-128-XTS[BlockSize:128, KeyLength:128, Mode:XTS, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 19: EVP_aes_128_ocb(); + finding( + "CipherContext{ValueAction:AES-128-OCB}", + "BlockCipher:AES-128-OCB[BlockSize:128, KeyLength:128, Mode:OCB, " + + "Oid:2.16.840.1.101.3.4.1]"), + // 20: EVP_aes_128_wrap(); + finding( + "CipherContext{ValueAction:AES-128-WRAP}", + "BlockCipher:AES-128-WRAP[BlockSize:128, KeyLength:128, Mode:WRAP, " + + "Oid:2.16.840.1.101.3.4.1.5]"), + // 21: EVP_aes_128_wrap_pad(); + finding( + "CipherContext{ValueAction:AES-128-WRAP-PAD}", + "BlockCipher:AES-128-WRAP-PAD[BlockSize:128, KeyLength:128, " + + "Mode:WRAP-PAD, Oid:2.16.840.1.101.3.4.1.8]"), + // 22: EVP_aes_192_cbc(); + finding( + "CipherContext{ValueAction:AES-192-CBC}", + "BlockCipher:AES-192-CBC[BlockSize:128, KeyLength:192, Mode:CBC, " + + "Oid:2.16.840.1.101.3.4.1.22]"), + // 23: EVP_aes_192_ecb(); + finding( + "CipherContext{ValueAction:AES-192-ECB}", + "BlockCipher:AES-192-ECB[BlockSize:128, KeyLength:192, Mode:ECB, " + + "Oid:2.16.840.1.101.3.4.1.21]"), + // 24: EVP_aes_192_gcm(); + finding( + "CipherContext{ValueAction:AES-192-GCM}", + "AuthenticatedEncryption:AES-192-GCM[BlockSize:128, KeyLength:192, " + + "Mode:GCM, Oid:2.16.840.1.101.3.4.1.26]"), + // 25: EVP_aes_192_ctr(); + finding( + "CipherContext{ValueAction:AES-192-CTR}", + "BlockCipher:AES-192-CTR[BlockSize:128, KeyLength:192, Mode:CTR, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 26: EVP_aes_192_ccm(); + finding( + "CipherContext{ValueAction:AES-192-CCM}", + "AuthenticatedEncryption:AES-192-CCM[BlockSize:128, KeyLength:192, " + + "Mode:CCM, Oid:2.16.840.1.101.3.4.1.27]"), + // 27: EVP_aes_192_cfb128(); + finding( + "CipherContext{ValueAction:AES-192-CFB}", + "BlockCipher:AES-192-CFB[BlockSize:128, KeyLength:192, Mode:CFB, " + + "Oid:2.16.840.1.101.3.4.1.24]"), + // 28: EVP_aes_192_cfb1(); + finding( + "CipherContext{ValueAction:AES-192-CFB1}", + "BlockCipher:AES-192-CFB1[BlockSize:128, KeyLength:192, Mode:CFB1, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 29: EVP_aes_192_cfb8(); + finding( + "CipherContext{ValueAction:AES-192-CFB8}", + "BlockCipher:AES-192-CFB8[BlockSize:128, KeyLength:192, Mode:CFB8, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 30: EVP_aes_192_ofb(); + finding( + "CipherContext{ValueAction:AES-192-OFB}", + "BlockCipher:AES-192-OFB[BlockSize:128, KeyLength:192, Mode:OFB, " + + "Oid:2.16.840.1.101.3.4.1.23]"), + // 31: EVP_aes_192_ocb(); + finding( + "CipherContext{ValueAction:AES-192-OCB}", + "BlockCipher:AES-192-OCB[BlockSize:128, KeyLength:192, Mode:OCB, " + + "Oid:2.16.840.1.101.3.4.1.2]"), + // 32: EVP_aes_192_wrap(); + finding( + "CipherContext{ValueAction:AES-192-WRAP}", + "BlockCipher:AES-192-WRAP[BlockSize:128, KeyLength:192, Mode:WRAP, " + + "Oid:2.16.840.1.101.3.4.1.25]"), + // 33: EVP_aes_192_wrap_pad(); + finding( + "CipherContext{ValueAction:AES-192-WRAP-PAD}", + "BlockCipher:AES-192-WRAP-PAD[BlockSize:128, KeyLength:192, " + + "Mode:WRAP-PAD, Oid:2.16.840.1.101.3.4.1.28]"), + // 34: EVP_aes_256_cbc(); + finding( + "CipherContext{ValueAction:AES-256-CBC}", + "BlockCipher:AES-256-CBC[BlockSize:128, KeyLength:256, Mode:CBC, " + + "Oid:2.16.840.1.101.3.4.1.42]"), + // 35: EVP_aes_256_ecb(); + finding( + "CipherContext{ValueAction:AES-256-ECB}", + "BlockCipher:AES-256-ECB[BlockSize:128, KeyLength:256, Mode:ECB, " + + "Oid:2.16.840.1.101.3.4.1.41]"), + // 36: EVP_aes_256_gcm(); + finding( + "CipherContext{ValueAction:AES-256-GCM}", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, " + + "Mode:GCM, Oid:2.16.840.1.101.3.4.1.46]"), + // 37: EVP_aes_256_ctr(); + finding( + "CipherContext{ValueAction:AES-256-CTR}", + "BlockCipher:AES-256-CTR[BlockSize:128, KeyLength:256, Mode:CTR, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 38: EVP_aes_256_ccm(); + finding( + "CipherContext{ValueAction:AES-256-CCM}", + "AuthenticatedEncryption:AES-256-CCM[BlockSize:128, KeyLength:256, " + + "Mode:CCM, Oid:2.16.840.1.101.3.4.1.47]"), + // 39: EVP_aes_256_cfb128(); + finding( + "CipherContext{ValueAction:AES-256-CFB}", + "BlockCipher:AES-256-CFB[BlockSize:128, KeyLength:256, Mode:CFB, " + + "Oid:2.16.840.1.101.3.4.1.44]"), + // 40: EVP_aes_256_cfb1(); + finding( + "CipherContext{ValueAction:AES-256-CFB1}", + "BlockCipher:AES-256-CFB1[BlockSize:128, KeyLength:256, Mode:CFB1, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 41: EVP_aes_256_cfb8(); + finding( + "CipherContext{ValueAction:AES-256-CFB8}", + "BlockCipher:AES-256-CFB8[BlockSize:128, KeyLength:256, Mode:CFB8, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 42: EVP_aes_256_ofb(); + finding( + "CipherContext{ValueAction:AES-256-OFB}", + "BlockCipher:AES-256-OFB[BlockSize:128, KeyLength:256, Mode:OFB, " + + "Oid:2.16.840.1.101.3.4.1.43]"), + // 43: EVP_aes_256_xts(); + finding( + "CipherContext{ValueAction:AES-256-XTS}", + "BlockCipher:AES-256-XTS[BlockSize:128, KeyLength:256, Mode:XTS, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 44: EVP_aes_256_ocb(); + finding( + "CipherContext{ValueAction:AES-256-OCB}", + "BlockCipher:AES-256-OCB[BlockSize:128, KeyLength:256, Mode:OCB, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 45: EVP_aes_256_wrap(); + finding( + "CipherContext{ValueAction:AES-256-WRAP}", + "BlockCipher:AES-256-WRAP[BlockSize:128, KeyLength:256, Mode:WRAP, " + + "Oid:2.16.840.1.101.3.4.1.45]"), + // 46: EVP_aes_256_wrap_pad(); + finding( + "CipherContext{ValueAction:AES-256-WRAP-PAD}", + "BlockCipher:AES-256-WRAP-PAD[BlockSize:128, KeyLength:256, " + + "Mode:WRAP-PAD, Oid:2.16.840.1.101.3.4.1.48]"), + // 47: EVP_camellia_128_ecb(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-ECB}", + "BlockCipher:CAMELLIA-128-ECB[KeyLength:128, Mode:ECB]"), + // 48: EVP_camellia_128_cbc(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-CBC}", + "BlockCipher:CAMELLIA-128-CBC[KeyLength:128, Mode:CBC]"), + // 49: EVP_camellia_128_cfb128(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-CFB}", + "BlockCipher:CAMELLIA-128-CFB[KeyLength:128, Mode:CFB]"), + // 50: EVP_camellia_128_cfb1(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-CFB1}", + "BlockCipher:CAMELLIA-128-CFB1[KeyLength:128, Mode:CFB1]"), + // 51: EVP_camellia_128_cfb8(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-CFB8}", + "BlockCipher:CAMELLIA-128-CFB8[KeyLength:128, Mode:CFB8]"), + // 52: EVP_camellia_128_ofb(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-OFB}", + "BlockCipher:CAMELLIA-128-OFB[KeyLength:128, Mode:OFB]"), + // 53: EVP_camellia_128_ctr(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-CTR}", + "BlockCipher:CAMELLIA-128-CTR[KeyLength:128, Mode:CTR]"), + // 54: EVP_camellia_192_ecb(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-ECB}", + "BlockCipher:CAMELLIA-192-ECB[KeyLength:192, Mode:ECB]"), + // 55: EVP_camellia_192_cbc(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-CBC}", + "BlockCipher:CAMELLIA-192-CBC[KeyLength:192, Mode:CBC]"), + // 56: EVP_camellia_192_cfb128(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-CFB}", + "BlockCipher:CAMELLIA-192-CFB[KeyLength:192, Mode:CFB]"), + // 57: EVP_camellia_192_cfb1(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-CFB1}", + "BlockCipher:CAMELLIA-192-CFB1[KeyLength:192, Mode:CFB1]"), + // 58: EVP_camellia_192_cfb8(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-CFB8}", + "BlockCipher:CAMELLIA-192-CFB8[KeyLength:192, Mode:CFB8]"), + // 59: EVP_camellia_192_ofb(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-OFB}", + "BlockCipher:CAMELLIA-192-OFB[KeyLength:192, Mode:OFB]"), + // 60: EVP_camellia_192_ctr(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-CTR}", + "BlockCipher:CAMELLIA-192-CTR[KeyLength:192, Mode:CTR]"), + // 61: EVP_camellia_256_ecb(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-ECB}", + "BlockCipher:CAMELLIA-256-ECB[KeyLength:256, Mode:ECB]"), + // 62: EVP_camellia_256_cbc(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-CBC}", + "BlockCipher:CAMELLIA-256-CBC[KeyLength:256, Mode:CBC]"), + // 63: EVP_camellia_256_cfb128(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-CFB}", + "BlockCipher:CAMELLIA-256-CFB[KeyLength:256, Mode:CFB]"), + // 64: EVP_camellia_256_cfb1(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-CFB1}", + "BlockCipher:CAMELLIA-256-CFB1[KeyLength:256, Mode:CFB1]"), + // 65: EVP_camellia_256_cfb8(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-CFB8}", + "BlockCipher:CAMELLIA-256-CFB8[KeyLength:256, Mode:CFB8]"), + // 66: EVP_camellia_256_ofb(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-OFB}", + "BlockCipher:CAMELLIA-256-OFB[KeyLength:256, Mode:OFB]"), + // 67: EVP_camellia_256_ctr(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-CTR}", + "BlockCipher:CAMELLIA-256-CTR[KeyLength:256, Mode:CTR]"), + // 68: EVP_aria_128_ecb(); + finding( + "CipherContext{ValueAction:ARIA-128-ECB}", + "BlockCipher:ARIA-128-ECB[KeyLength:128, Mode:ECB]"), + // 69: EVP_aria_128_cbc(); + finding( + "CipherContext{ValueAction:ARIA-128-CBC}", + "BlockCipher:ARIA-128-CBC[KeyLength:128, Mode:CBC]"), + // 70: EVP_aria_128_cfb128(); + finding( + "CipherContext{ValueAction:ARIA-128-CFB}", + "BlockCipher:ARIA-128-CFB[KeyLength:128, Mode:CFB]"), + // 71: EVP_aria_128_cfb1(); + finding( + "CipherContext{ValueAction:ARIA-128-CFB1}", + "BlockCipher:ARIA-128-CFB1[KeyLength:128, Mode:CFB1]"), + // 72: EVP_aria_128_cfb8(); + finding( + "CipherContext{ValueAction:ARIA-128-CFB8}", + "BlockCipher:ARIA-128-CFB8[KeyLength:128, Mode:CFB8]"), + // 73: EVP_aria_128_ofb(); + finding( + "CipherContext{ValueAction:ARIA-128-OFB}", + "BlockCipher:ARIA-128-OFB[KeyLength:128, Mode:OFB]"), + // 74: EVP_aria_128_ctr(); + finding( + "CipherContext{ValueAction:ARIA-128-CTR}", + "BlockCipher:ARIA-128-CTR[KeyLength:128, Mode:CTR]"), + // 75: EVP_aria_128_gcm(); + finding( + "CipherContext{ValueAction:ARIA-128-GCM}", + "BlockCipher:ARIA-128-GCM[KeyLength:128, Mode:GCM]"), + // 76: EVP_aria_128_ccm(); + finding( + "CipherContext{ValueAction:ARIA-128-CCM}", + "BlockCipher:ARIA-128-CCM[KeyLength:128, Mode:CCM]"), + // 77: EVP_aria_192_ecb(); + finding( + "CipherContext{ValueAction:ARIA-192-ECB}", + "BlockCipher:ARIA-192-ECB[KeyLength:192, Mode:ECB]"), + // 78: EVP_aria_192_cbc(); + finding( + "CipherContext{ValueAction:ARIA-192-CBC}", + "BlockCipher:ARIA-192-CBC[KeyLength:192, Mode:CBC]"), + // 79: EVP_aria_192_cfb128(); + finding( + "CipherContext{ValueAction:ARIA-192-CFB}", + "BlockCipher:ARIA-192-CFB[KeyLength:192, Mode:CFB]"), + // 80: EVP_aria_192_cfb1(); + finding( + "CipherContext{ValueAction:ARIA-192-CFB1}", + "BlockCipher:ARIA-192-CFB1[KeyLength:192, Mode:CFB1]"), + // 81: EVP_aria_192_cfb8(); + finding( + "CipherContext{ValueAction:ARIA-192-CFB8}", + "BlockCipher:ARIA-192-CFB8[KeyLength:192, Mode:CFB8]"), + // 82: EVP_aria_192_ofb(); + finding( + "CipherContext{ValueAction:ARIA-192-OFB}", + "BlockCipher:ARIA-192-OFB[KeyLength:192, Mode:OFB]"), + // 83: EVP_aria_192_ctr(); + finding( + "CipherContext{ValueAction:ARIA-192-CTR}", + "BlockCipher:ARIA-192-CTR[KeyLength:192, Mode:CTR]"), + // 84: EVP_aria_192_gcm(); + finding( + "CipherContext{ValueAction:ARIA-192-GCM}", + "BlockCipher:ARIA-192-GCM[KeyLength:192, Mode:GCM]"), + // 85: EVP_aria_192_ccm(); + finding( + "CipherContext{ValueAction:ARIA-192-CCM}", + "BlockCipher:ARIA-192-CCM[KeyLength:192, Mode:CCM]"), + // 86: EVP_aria_256_ecb(); + finding( + "CipherContext{ValueAction:ARIA-256-ECB}", + "BlockCipher:ARIA-256-ECB[KeyLength:256, Mode:ECB]"), + // 87: EVP_aria_256_cbc(); + finding( + "CipherContext{ValueAction:ARIA-256-CBC}", + "BlockCipher:ARIA-256-CBC[KeyLength:256, Mode:CBC]"), + // 88: EVP_aria_256_cfb128(); + finding( + "CipherContext{ValueAction:ARIA-256-CFB}", + "BlockCipher:ARIA-256-CFB[KeyLength:256, Mode:CFB]"), + // 89: EVP_aria_256_cfb1(); + finding( + "CipherContext{ValueAction:ARIA-256-CFB1}", + "BlockCipher:ARIA-256-CFB1[KeyLength:256, Mode:CFB1]"), + // 90: EVP_aria_256_cfb8(); + finding( + "CipherContext{ValueAction:ARIA-256-CFB8}", + "BlockCipher:ARIA-256-CFB8[KeyLength:256, Mode:CFB8]"), + // 91: EVP_aria_256_ofb(); + finding( + "CipherContext{ValueAction:ARIA-256-OFB}", + "BlockCipher:ARIA-256-OFB[KeyLength:256, Mode:OFB]"), + // 92: EVP_aria_256_ctr(); + finding( + "CipherContext{ValueAction:ARIA-256-CTR}", + "BlockCipher:ARIA-256-CTR[KeyLength:256, Mode:CTR]"), + // 93: EVP_aria_256_gcm(); + finding( + "CipherContext{ValueAction:ARIA-256-GCM}", + "BlockCipher:ARIA-256-GCM[KeyLength:256, Mode:GCM]"), + // 94: EVP_aria_256_ccm(); + finding( + "CipherContext{ValueAction:ARIA-256-CCM}", + "BlockCipher:ARIA-256-CCM[KeyLength:256, Mode:CCM]"), + // 95: EVP_sm4_ecb(); + finding("CipherContext{ValueAction:SM4-ECB}", "BlockCipher:SM4-ECB[Mode:ECB]"), + // 96: EVP_sm4_cbc(); + finding("CipherContext{ValueAction:SM4-CBC}", "BlockCipher:SM4-CBC[Mode:CBC]"), + // 97: EVP_sm4_cfb128(); + finding("CipherContext{ValueAction:SM4-CFB}", "BlockCipher:SM4-CFB[Mode:CFB]"), + // 98: EVP_sm4_ofb(); + finding("CipherContext{ValueAction:SM4-OFB}", "BlockCipher:SM4-OFB[Mode:OFB]"), + // 99: EVP_sm4_ctr(); + finding("CipherContext{ValueAction:SM4-CTR}", "BlockCipher:SM4-CTR[Mode:CTR]"), + // 100: EVP_des_cbc(); + finding( + "CipherContext{ValueAction:DES-CBC}", + "BlockCipher:DES-56-CBC[BlockSize:64, KeyLength:56, Mode:CBC]"), + // 101: EVP_des_ecb(); + finding( + "CipherContext{ValueAction:DES-ECB}", + "BlockCipher:DES-56-ECB[BlockSize:64, KeyLength:56, Mode:ECB]"), + // 102: EVP_des_ede3_cbc(); + finding( + "CipherContext{ValueAction:DESede3-CBC}", + "BlockCipher:DESede168-CBC[BlockSize:64, KeyLength:168, Mode:CBC]"), + // 103: EVP_des_cfb64(); + finding( + "CipherContext{ValueAction:DES-CFB}", + "BlockCipher:DES-56-CFB[BlockSize:64, KeyLength:56, Mode:CFB]"), + // 104: EVP_des_cfb1(); + finding( + "CipherContext{ValueAction:DES-CFB1}", + "BlockCipher:DES-56-CFB1[BlockSize:64, KeyLength:56, Mode:CFB1]"), + // 105: EVP_des_cfb8(); + finding( + "CipherContext{ValueAction:DES-CFB8}", + "BlockCipher:DES-56-CFB8[BlockSize:64, KeyLength:56, Mode:CFB8]"), + // 106: EVP_des_ofb(); + finding( + "CipherContext{ValueAction:DES-OFB}", + "BlockCipher:DES-56-OFB[BlockSize:64, KeyLength:56, Mode:OFB]"), + // 107: EVP_des_ede(); + finding( + "CipherContext{ValueAction:DESede}", + "BlockCipher:DESede112[BlockSize:64, KeyLength:112]"), + // 108: EVP_des_ede_ecb(); + finding( + "CipherContext{ValueAction:DESede-ECB}", + "BlockCipher:DESede112-ECB[BlockSize:64, KeyLength:112, Mode:ECB]"), + // 109: EVP_des_ede_cbc(); + finding( + "CipherContext{ValueAction:DESede-CBC}", + "BlockCipher:DESede112-CBC[BlockSize:64, KeyLength:112, Mode:CBC]"), + // 110: EVP_des_ede_cfb64(); + finding( + "CipherContext{ValueAction:DESede-CFB64}", + "BlockCipher:DESede112-CFB64[BlockSize:64, KeyLength:112, Mode:CFB64]"), + // 111: EVP_des_ede_ofb(); + finding( + "CipherContext{ValueAction:DESede-OFB}", + "BlockCipher:DESede112-OFB[BlockSize:64, KeyLength:112, Mode:OFB]"), + // 112: EVP_des_ede3(); + finding( + "CipherContext{ValueAction:DESede3}", + "BlockCipher:DESede168[BlockSize:64, KeyLength:168]"), + // 113: EVP_des_ede3_ecb(); + finding( + "CipherContext{ValueAction:DESede3-ECB}", + "BlockCipher:DESede168-ECB[BlockSize:64, KeyLength:168, Mode:ECB]"), + // 114: EVP_des_ede3_cfb1(); + finding( + "CipherContext{ValueAction:DESede3-CFB1}", + "BlockCipher:DESede168-CFB1[BlockSize:64, KeyLength:168, Mode:CFB1]"), + // 115: EVP_des_ede3_cfb8(); + finding( + "CipherContext{ValueAction:DESede3-CFB8}", + "BlockCipher:DESede168-CFB8[BlockSize:64, KeyLength:168, Mode:CFB8]"), + // 116: EVP_des_ede3_cfb64(); + finding( + "CipherContext{ValueAction:DESede3-CFB64}", + "BlockCipher:DESede168-CFB64[BlockSize:64, KeyLength:168, Mode:CFB64]"), + // 117: EVP_des_ede3_ofb(); + finding( + "CipherContext{ValueAction:DESede3-OFB}", + "BlockCipher:DESede168-OFB[BlockSize:64, KeyLength:168, Mode:OFB]"), + // 118: EVP_desx_cbc(); + finding( + "CipherContext{ValueAction:DESX-CBC}", + "BlockCipher:DESX-184-CBC[BlockSize:64, KeyLength:184, Mode:CBC]"), + // 119: EVP_bf_ecb(); + finding( + "CipherContext{ValueAction:BLOWFISH-ECB}", + "BlockCipher:Blowfish-128-ECB[KeyLength:128, Mode:ECB]"), + // 120: EVP_bf_cbc(); + finding( + "CipherContext{ValueAction:BLOWFISH-CBC}", + "BlockCipher:Blowfish-128-CBC[KeyLength:128, Mode:CBC]"), + // 121: EVP_bf_cfb64(); + finding( + "CipherContext{ValueAction:BLOWFISH-CFB}", + "BlockCipher:Blowfish-128-CFB[KeyLength:128, Mode:CFB]"), + // 122: EVP_bf_ofb(); + finding( + "CipherContext{ValueAction:BLOWFISH-OFB}", + "BlockCipher:Blowfish-128-OFB[KeyLength:128, Mode:OFB]"), + // 123: EVP_cast5_ecb(); + finding( + "CipherContext{ValueAction:CAST5-ECB}", + "BlockCipher:CAST5-128-ECB[BlockSize:64, KeyLength:128, Mode:ECB]"), + // 124: EVP_cast5_cbc(); + finding( + "CipherContext{ValueAction:CAST5-CBC}", + "BlockCipher:CAST5-128-CBC[BlockSize:64, KeyLength:128, Mode:CBC]"), + // 125: EVP_cast5_cfb64(); + finding( + "CipherContext{ValueAction:CAST5-CFB}", + "BlockCipher:CAST5-128-CFB[BlockSize:64, KeyLength:128, Mode:CFB]"), + // 126: EVP_cast5_ofb(); + finding( + "CipherContext{ValueAction:CAST5-OFB}", + "BlockCipher:CAST5-128-OFB[BlockSize:64, KeyLength:128, Mode:OFB]"), + // 127: EVP_rc2_ecb(); + finding( + "CipherContext{ValueAction:RC2-ECB}", + "BlockCipher:RC2-128-ECB[KeyLength:128, Mode:ECB]"), + // 128: EVP_rc2_cbc(); + finding( + "CipherContext{ValueAction:RC2-CBC}", + "BlockCipher:RC2-128-CBC[KeyLength:128, Mode:CBC]"), + // 129: EVP_rc2_cfb64(); + finding( + "CipherContext{ValueAction:RC2-CFB}", + "BlockCipher:RC2-128-CFB[KeyLength:128, Mode:CFB]"), + // 130: EVP_rc2_ofb(); + finding( + "CipherContext{ValueAction:RC2-OFB}", + "BlockCipher:RC2-128-OFB[KeyLength:128, Mode:OFB]"), + // 131: EVP_rc2_40_cbc(); + finding( + "CipherContext{ValueAction:RC2-40-CBC}", + "BlockCipher:RC2-40-CBC[KeyLength:40, Mode:CBC]"), + // 132: EVP_rc2_64_cbc(); + finding( + "CipherContext{ValueAction:RC2-64-CBC}", + "BlockCipher:RC2-64-CBC[KeyLength:64, Mode:CBC]"), + // 133: EVP_rc4(); + finding("CipherContext{ValueAction:RC4}", "StreamCipher:RC4"), + // 134: EVP_rc4_40(); + finding( + "CipherContext{ValueAction:RC4-40}", + "StreamCipher:RC4-40[KeyLength:40]"), + // 135: EVP_rc4_hmac_md5(); + finding("CipherContext{ValueAction:RC4-HMAC-MD5}", "StreamCipher:RC4"), + // 136: EVP_rc5_32_12_16_ecb(); + finding( + "CipherContext{ValueAction:RC5-ECB}", + "BlockCipher:RC5-128-ECB[KeyLength:128, Mode:ECB]"), + // 137: EVP_rc5_32_12_16_cbc(); + finding( + "CipherContext{ValueAction:RC5-CBC}", + "BlockCipher:RC5-128-CBC[KeyLength:128, Mode:CBC]"), + // 138: EVP_rc5_32_12_16_cfb64(); + finding( + "CipherContext{ValueAction:RC5-CFB}", + "BlockCipher:RC5-128-CFB[KeyLength:128, Mode:CFB]"), + // 139: EVP_rc5_32_12_16_ofb(); + finding( + "CipherContext{ValueAction:RC5-OFB}", + "BlockCipher:RC5-128-OFB[KeyLength:128, Mode:OFB]"), + // 140: EVP_idea_ecb(); + finding( + "CipherContext{ValueAction:IDEA-ECB}", + "BlockCipher:IDEA-ECB[Mode:ECB]"), + // 141: EVP_idea_cbc(); + finding( + "CipherContext{ValueAction:IDEA-CBC}", + "BlockCipher:IDEA-CBC[Mode:CBC]"), + // 142: EVP_idea_cfb64(); + finding( + "CipherContext{ValueAction:IDEA-CFB}", + "BlockCipher:IDEA-CFB[Mode:CFB]"), + // 143: EVP_idea_ofb(); + finding( + "CipherContext{ValueAction:IDEA-OFB}", + "BlockCipher:IDEA-OFB[Mode:OFB]"), + // 144: EVP_seed_ecb(); + finding( + "CipherContext{ValueAction:SEED-ECB}", + "BlockCipher:SEED-128-ECB[BlockSize:128, KeyLength:128, Mode:ECB]"), + // 145: EVP_seed_cbc(); + finding( + "CipherContext{ValueAction:SEED-CBC}", + "BlockCipher:SEED-128-CBC[BlockSize:128, KeyLength:128, Mode:CBC]"), + // 146: EVP_seed_cfb128(); + finding( + "CipherContext{ValueAction:SEED-CFB}", + "BlockCipher:SEED-128-CFB[BlockSize:128, KeyLength:128, Mode:CFB]"), + // 147: EVP_seed_ofb(); + finding( + "CipherContext{ValueAction:SEED-OFB}", + "BlockCipher:SEED-128-OFB[BlockSize:128, KeyLength:128, Mode:OFB]"), + // 148: EVP_chacha20(); + finding("CipherContext{ValueAction:ChaCha20}", "StreamCipher:ChaCha20"), + // 149: EVP_chacha20_poly1305(); + finding( + "CipherContext{ValueAction:ChaCha20-Poly1305}", + "AuthenticatedEncryption:ChaCha20-Poly1305[MessageDigest:Poly1305[Digest:DIGEST]]"), + // 150: EVP_aes_128_cbc_hmac_sha1(); + finding( + "CipherContext{ValueAction:AES-128-CBC-HMAC-SHA1}", + "BlockCipher:AES-128-CBC-HMAC-SHA1[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA1, Oid:2.16.840.1.101.3.4.1]"), + // 151: EVP_aes_256_cbc_hmac_sha1(); + finding( + "CipherContext{ValueAction:AES-256-CBC-HMAC-SHA1}", + "BlockCipher:AES-256-CBC-HMAC-SHA1[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA1, Oid:2.16.840.1.101.3.4.1.4]"), + // 152: EVP_aes_128_cbc_hmac_sha256(); + finding( + "CipherContext{ValueAction:AES-128-CBC-HMAC-SHA256}", + "BlockCipher:AES-128-CBC-HMAC-SHA256[BlockSize:128, KeyLength:128, " + + "Mode:CBC-HMAC-SHA256, Oid:2.16.840.1.101.3.4.1]"), + // 153: EVP_aes_256_cbc_hmac_sha256(); + finding( + "CipherContext{ValueAction:AES-256-CBC-HMAC-SHA256}", + "BlockCipher:AES-256-CBC-HMAC-SHA256[BlockSize:128, KeyLength:256, " + + "Mode:CBC-HMAC-SHA256, Oid:2.16.840.1.101.3.4.1.4]"), + // 160: EVP_PKEY_CTX_set_rsa_padding(ctx, 4); + finding( + "CipherContext{ValueAction:RSA-OAEP}", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, Padding:OAEP]"), + // 161: const EVP_MD* oaep_md = EVP_sha256(); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 164: EVP_PKEY_CTX_set_rsa_oaep_md_name(ctx, "SHA2-256", NULL); + finding( + "DigestContext{Algorithm:SHA-256}", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, " + + "Padding:OAEP[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]]]"), + // 175: EVP_ASYM_CIPHER_fetch(NULL, "RSA", NULL); + finding( + "CipherContext{Algorithm:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 176: EVP_get_cipherbyname("AES-256-GCM"); + finding( + "CipherContext{Algorithm:AES-256-GCM}", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, " + + "Mode:GCM, Oid:2.16.840.1.101.3.4.1.46]"), + // 177: EVP_des_ede3_wrap(); + finding( + "CipherContext{ValueAction:DES-EDE3-WRAP}", + "BlockCipher:DESede168-WRAP[BlockSize:64, KeyLength:168, Mode:WRAP]"), + // 194: EVP_aes_128_cfb(); + finding( + "CipherContext{ValueAction:AES-128-CFB}", + "BlockCipher:AES-128-CFB[BlockSize:128, KeyLength:128, Mode:CFB, " + + "Oid:2.16.840.1.101.3.4.1.4]"), + // 195: EVP_des_cfb(); + finding( + "CipherContext{ValueAction:DES-CFB}", + "BlockCipher:DES-56-CFB[BlockSize:64, KeyLength:56, Mode:CFB]"), + // 196: EVP_des_ede3_cfb(); + finding( + "CipherContext{ValueAction:DESede3-CFB64}", + "BlockCipher:DESede168-CFB64[BlockSize:64, KeyLength:168, Mode:CFB64]"), + // 197: EVP_bf_cfb(); + finding( + "CipherContext{ValueAction:BLOWFISH-CFB}", + "BlockCipher:Blowfish-128-CFB[KeyLength:128, Mode:CFB]"), + // 198: EVP_aes_192_cfb(); + finding( + "CipherContext{ValueAction:AES-192-CFB}", + "BlockCipher:AES-192-CFB[BlockSize:128, KeyLength:192, Mode:CFB, " + + "Oid:2.16.840.1.101.3.4.1.24]"), + // 199: EVP_aes_256_cfb(); + finding( + "CipherContext{ValueAction:AES-256-CFB}", + "BlockCipher:AES-256-CFB[BlockSize:128, KeyLength:256, Mode:CFB, " + + "Oid:2.16.840.1.101.3.4.1.44]"), + // 200: EVP_aria_128_cfb(); + finding( + "CipherContext{ValueAction:ARIA-128-CFB}", + "BlockCipher:ARIA-128-CFB[KeyLength:128, Mode:CFB]"), + // 201: EVP_aria_192_cfb(); + finding( + "CipherContext{ValueAction:ARIA-192-CFB}", + "BlockCipher:ARIA-192-CFB[KeyLength:192, Mode:CFB]"), + // 202: EVP_aria_256_cfb(); + finding( + "CipherContext{ValueAction:ARIA-256-CFB}", + "BlockCipher:ARIA-256-CFB[KeyLength:256, Mode:CFB]"), + // 203: EVP_camellia_128_cfb(); + finding( + "CipherContext{ValueAction:CAMELLIA-128-CFB}", + "BlockCipher:CAMELLIA-128-CFB[KeyLength:128, Mode:CFB]"), + // 204: EVP_camellia_192_cfb(); + finding( + "CipherContext{ValueAction:CAMELLIA-192-CFB}", + "BlockCipher:CAMELLIA-192-CFB[KeyLength:192, Mode:CFB]"), + // 205: EVP_camellia_256_cfb(); + finding( + "CipherContext{ValueAction:CAMELLIA-256-CFB}", + "BlockCipher:CAMELLIA-256-CFB[KeyLength:256, Mode:CFB]"), + // 206: EVP_cast5_cfb(); + finding( + "CipherContext{ValueAction:CAST5-CFB}", + "BlockCipher:CAST5-128-CFB[BlockSize:64, KeyLength:128, Mode:CFB]"), + // 207: EVP_des_ede_cfb(); + finding( + "CipherContext{ValueAction:DESede-CFB64}", + "BlockCipher:DESede112-CFB64[BlockSize:64, KeyLength:112, Mode:CFB64]"), + // 208: EVP_idea_cfb(); + finding( + "CipherContext{ValueAction:IDEA-CFB}", + "BlockCipher:IDEA-CFB[Mode:CFB]"), + // 209: EVP_rc2_cfb(); + finding( + "CipherContext{ValueAction:RC2-CFB}", + "BlockCipher:RC2-128-CFB[KeyLength:128, Mode:CFB]"), + // 210: EVP_rc5_32_12_16_cfb(); + finding( + "CipherContext{ValueAction:RC5-CFB}", + "BlockCipher:RC5-128-CFB[KeyLength:128, Mode:CFB]"), + // 211: EVP_seed_cfb(); + finding( + "CipherContext{ValueAction:SEED-CFB}", + "BlockCipher:SEED-128-CFB[BlockSize:128, KeyLength:128, Mode:CFB]"), + // 212: EVP_sm4_cfb(); + finding("CipherContext{ValueAction:SM4-CFB}", "BlockCipher:SM4-CFB[Mode:CFB]"), + // 216: EVP_get_cipherbynid(NID_aes_256_gcm); + finding( + "CipherContext{ValueAction:AES-256-GCM}", + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, " + + "Mode:GCM, Oid:2.16.840.1.101.3.4.1.46]"), + // 217: EVP_get_cipherbynid(1018); + finding( + "CipherContext{ValueAction:CHACHA20-POLY1305}", + "AuthenticatedEncryption:ChaCha20-Poly1305[MessageDigest:Poly1305[Digest:DIGEST]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/cipher/OpenSSLEvpCipherTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTest.java new file mode 100644 index 000000000..298b34ad9 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTest.java @@ -0,0 +1,121 @@ +/* + * 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.openssl.cipher; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A private key written encrypted is reported as the encryption with the cipher given to the writer + * ({@link OpenSSLPrivateKeyEncryption}); a key written without a cipher is not encrypted and not + * reported. + */ +class OpenSSLPrivateKeyEncryptionTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: PEM_write_bio_PrivateKey(out, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, + // (void *) pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-CBC}]", + "BlockCipher:AES-256-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:256, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]"), + // 10: PEM_write_bio_PKCS8PrivateKey(out, pkey, cipher, NULL, 0, NULL, (void *) + // pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CBC}]", + "BlockCipher:AES-128-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2]"), + // 14: i2d_PKCS8PrivateKey_bio(out, pkey, EVP_des_ede3_cbc(), NULL, 0, NULL, + // (void *) pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:DESede3-CBC}]", + "BlockCipher:DESede168-CBC[BlockSize:64, Encrypt:ENCRYPT, KeyLength:168, " + + "Mode:CBC]"), + // 18: PEM_write_bio_PrivateKey_traditional(fp, pkey, EVP_aes_192_cbc(), NULL, + // 0, NULL, (void *) pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-192-CBC}]", + "BlockCipher:AES-192-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:192, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.22]"), + // 26: PEM_write_PrivateKey(fp, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, (void *) + // pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-CBC}]", + "BlockCipher:AES-256-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:256, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]"), + // 27: PEM_write_PKCS8PrivateKey(fp, pkey, EVP_aes_128_cbc(), NULL, 0, NULL, + // (void *) pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-128-CBC}]", + "BlockCipher:AES-128-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2]"), + // 28: i2d_PKCS8PrivateKey_fp(fp, pkey, EVP_aes_192_cbc(), NULL, 0, NULL, (void + // *) pass); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-192-CBC}]", + "BlockCipher:AES-192-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:192, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.22]"), + // 32: PEM_write_bio_PrivateKey_ex(out, pkey, EVP_aes_256_cbc(), NULL, 0, NULL, + // NULL, libctx, NULL); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-CBC}]", + "BlockCipher:AES-256-CBC[BlockSize:128, Encrypt:ENCRYPT, KeyLength:256, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/cipher/OpenSSLPrivateKeyEncryptionTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cms/OpenSSLCmsTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cms/OpenSSLCmsTest.java new file mode 100644 index 000000000..2b5020047 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/cms/OpenSSLCmsTest.java @@ -0,0 +1,147 @@ +/* + * 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.openssl.cms; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The CMS and PKCS#7 encryption functions report their content encryption cipher with the encrypt + * operation, a CMS KEK recipient reports its key wrap algorithm, the time-stamping signer digest + * given by name, the RSA-PSS salt length and the CRMF password-based MAC are reported. A CMS, + * PKCS#7 or OCSP signature made with a key given to the function is reported as a signature with + * its digest; a decryption with such a key names no algorithm and is not reported. + */ +class OpenSSLCmsTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 8: CMS_encrypt(certs, in, EVP_aes_256_cbc(), CMS_BINARY); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-CBC}]", + "BlockCipher:AES-256-CBC[BlockSize:128, Encrypt:ENCRYPT, " + + "KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]"), + // 13: CMS_EncryptedData_encrypt_ex(in, cipher, key, 16, 0, NULL, NULL); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{Algorithm:AES-128-GCM}]", + "AuthenticatedEncryption:AES-128-GCM[BlockSize:128, Encrypt:ENCRYPT, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6]"), + // 17: CMS_add0_recipient_key(cms, NID_id_aes256_wrap, key, 32, id, 8, NULL, + // NULL, NULL); + finding( + "CipherContext{ValueAction:AES-256-WRAP}", + "BlockCipher:AES-256-WRAP[BlockSize:128, KeyLength:256, Mode:WRAP, " + + "Oid:2.16.840.1.101.3.4.1.45]"), + // 21: PKCS7_encrypt(certs, in, EVP_des_ede3_cbc(), PKCS7_BINARY); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:DESede3-CBC}]", + "BlockCipher:DESede168-CBC[BlockSize:64, Encrypt:ENCRYPT, " + + "KeyLength:168, Mode:CBC]"), + // 33: CMS_sign(cert, pkey, NULL, data, CMS_PARTIAL); + finding( + "SignatureContext{SignatureAction:SIGN}", + "Signature:unknown[Sign:SIGN]"), + // 34: CMS_add1_signer(cms, cert, pkey, EVP_sha384(), 0); + finding( + "SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-384}]", + "Signature:unknown[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Sign:SIGN]"), + // 35: PKCS7_sign_add_signer(p7, cert, pkey, EVP_sha256(), 0); + finding( + "SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}]", + "Signature:unknown[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Sign:SIGN]"), + // 36: OCSP_basic_sign(resp, cert, pkey, EVP_sha1(), NULL, 0); + finding( + "SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-1}]", + "Signature:unknown[MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], Sign:SIGN]"), + // 40: EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10, " + + "SaltLength:256]"), + // 41: EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, RSA_PSS_SALTLEN_DIGEST); + finding( + "SignatureContext{ValueAction:RSA-PSS}", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10]"), + // 45: TS_CONF_set_signer_digest(conf, "tsa_config", "sha256", ctx); + finding( + "DigestContext{Algorithm:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 46: TS_RESP_CTX_add_md(ctx, EVP_sha512()); + finding( + "DigestContext{ValueAction:SHA-512}", + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]"), + // 50: OSSL_CRMF_PBMPARAMETER *pbm = OSSL_CRMF_pbmp_new(NULL, 16, NID_sha256, + // 500, NID_hmac_sha1); + finding( + "MacContext{ValueAction:HMAC-SHA1}", + "Mac:HMAC-SHA-1[MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], Oid:1.2.840.113549.2.7, " + + "Tag:TAG]"), + // 50: OSSL_CRMF_PBMPARAMETER *pbm = OSSL_CRMF_pbmp_new(NULL, 16, NID_sha256, + // 500, NID_hmac_sha1); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/cms/OpenSSLCmsTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestTest.java new file mode 100644 index 000000000..0c62f81f3 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLEvpMessageDigestTest.java @@ -0,0 +1,215 @@ +/* + * 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.openssl.digest; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers all rule entries in {@link OpenSSLEvpMessageDigest}. */ +class OpenSSLEvpMessageDigestTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_md2(); + finding( + "DigestContext{ValueAction:MD2}", + "MessageDigest:MD2[BlockSize:128, Digest:DIGEST, DigestSize:128]"), + // 5: EVP_md4(); + finding( + "DigestContext{ValueAction:MD4}", + "MessageDigest:MD4[BlockSize:512, Digest:DIGEST, DigestSize:128]"), + // 6: EVP_md5(); + finding( + "DigestContext{ValueAction:MD5}", + "MessageDigest:MD5[BlockSize:512, Digest:DIGEST, DigestSize:128]"), + // 7: EVP_mdc2(); + finding( + "DigestContext{ValueAction:MDC2}", + "MessageDigest:MDC2[BlockSize:64, Digest:DIGEST, DigestSize:128]"), + // 8: EVP_sha1(); + finding( + "DigestContext{ValueAction:SHA-1}", + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]"), + // 9: EVP_sha224(); + finding( + "DigestContext{ValueAction:SHA-224}", + "MessageDigest:SHA-224[BlockSize:512, Digest:DIGEST, DigestSize:224, " + + "Oid:2.16.840.1.101.3.4.2.4]"), + // 10: EVP_sha256(); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 11: EVP_sha384(); + finding( + "DigestContext{ValueAction:SHA-384}", + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]"), + // 12: EVP_sha512(); + finding( + "DigestContext{ValueAction:SHA-512}", + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]"), + // 13: EVP_sha512_224(); + finding( + "DigestContext{ValueAction:SHA-512/224}", + "MessageDigest:SHA-512/224[BlockSize:1024, Digest:DIGEST, DigestSize:224, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3], Oid:2.16.840.1.101.3.4.2.5]"), + // 14: EVP_sha512_256(); + finding( + "DigestContext{ValueAction:SHA-512/256}", + "MessageDigest:SHA-512/256[BlockSize:1024, Digest:DIGEST, DigestSize:256, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3], Oid:2.16.840.1.101.3.4.2.6]"), + // 15: EVP_sha3_224(); + finding( + "DigestContext{ValueAction:SHA3-224}", + "MessageDigest:SHA3-224[BlockSize:1152, Digest:DIGEST, DigestSize:224, " + + "Oid:2.16.840.1.101.3.4.2.7]"), + // 16: EVP_sha3_256(); + finding( + "DigestContext{ValueAction:SHA3-256}", + "MessageDigest:SHA3-256[BlockSize:1088, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.8]"), + // 17: EVP_sha3_384(); + finding( + "DigestContext{ValueAction:SHA3-384}", + "MessageDigest:SHA3-384[BlockSize:832, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.9]"), + // 18: EVP_sha3_512(); + finding( + "DigestContext{ValueAction:SHA3-512}", + "MessageDigest:SHA3-512[BlockSize:576, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.10]"), + // 19: EVP_shake128(); + finding( + "DigestContext{ValueAction:SHAKE128}", + "ExtendableOutputFunction:SHAKE128[Digest:DIGEST, ParameterSetIdentifier:128]"), + // 20: EVP_shake256(); + finding( + "DigestContext{ValueAction:SHAKE256}", + "ExtendableOutputFunction:SHAKE256[Digest:DIGEST, ParameterSetIdentifier:256]"), + // 21: EVP_ripemd160(); + finding( + "DigestContext{ValueAction:RIPEMD160}", + "MessageDigest:RIPEMD-160[Digest:DIGEST, DigestSize:160]"), + // 22: EVP_whirlpool(); + finding( + "DigestContext{ValueAction:WHIRLPOOL}", + "MessageDigest:Whirlpool[BlockSize:512, Digest:DIGEST, DigestSize:512, " + + "NumberOfIterations:10]"), + // 23: EVP_blake2b512(); + finding( + "DigestContext{ValueAction:BLAKE2B-512}", + "MessageDigest:BLAKE2b-512[Digest:DIGEST, DigestSize:512, SaltLength:128]"), + // 24: EVP_blake2s256(); + finding( + "DigestContext{ValueAction:BLAKE2S-256}", + "MessageDigest:BLAKE2s-256[Digest:DIGEST, DigestSize:256, SaltLength:64]"), + // 25: EVP_sm3(); + finding( + "DigestContext{ValueAction:SM3}", + "MessageDigest:SM3[Digest:DIGEST, DigestSize:256]"), + // 26: EVP_md5_sha1(); + finding( + "DigestContext{ValueAction:MD5-SHA1}", + "MessageDigest:MD5-SHA1[Digest:DIGEST, DigestSize:288]"), + // 28: EVP_MD_fetch(NULL, "SHA256", NULL); + finding( + "DigestContext{Algorithm:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 29: EVP_get_digestbyname("SHA256"); + finding( + "DigestContext{Algorithm:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 33: EVP_Q_digest(NULL, "SHA256", NULL, NULL, 0, NULL, NULL); + finding( + "DigestContext{Algorithm:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 37: EVP_MD_fetch(NULL, digest_name, NULL); + finding( + "DigestContext{Algorithm:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 40: EVP_MD_fetch(NULL, "SHA2-256", NULL); + finding( + "DigestContext{Algorithm:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 44: EVP_get_digestbynid(NID_sha256); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 45: EVP_get_digestbynid(672); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 46: EVP_get_digestbynid(NID_sha3_256); + finding( + "DigestContext{ValueAction:SHA3-256}", + "MessageDigest:SHA3-256[BlockSize:1088, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.8]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/digest/OpenSSLEvpMessageDigestTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLNameCanonicalizerFactoryTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLNameCanonicalizerFactoryTest.java new file mode 100644 index 000000000..213995649 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/digest/OpenSSLNameCanonicalizerFactoryTest.java @@ -0,0 +1,74 @@ +/* + * 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.openssl.digest; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class OpenSSLNameCanonicalizerFactoryTest { + + /** + * Every canonicalized digest name must be a string {@link + * com.ibm.plugin.translation.translator.contexts.CxxDigestContextTranslator} actually + * recognizes, otherwise the finding is silently dropped as {@code Optional.empty()}. + */ + @ParameterizedTest + @CsvSource({ + // OpenSSL EVP_MD name, expected canonical form + "SHAKE128, SHAKE128", + "SHAKE256, SHAKE256", + "RIPEMD160, RIPEMD160", + "RMD160, RIPEMD160", + "BLAKE2B512, BLAKE2B-512", + "BLAKE2S256, BLAKE2S-256", + "MDC2, MDC2", + "SSL3-SHA1, SHA-1", + "SSL3-MD5, MD5", + }) + void canonicalizesOpenSslDigestNames(String openSslName, String expectedCanonical) { + assertThat( + OpenSSLNameCanonicalizerFactory.canonicalize( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES, openSslName)) + .isEqualTo(expectedCanonical); + } + + @Test + void isCaseInsensitiveAndTrimsWhitespace() { + assertThat( + OpenSSLNameCanonicalizerFactory.canonicalize( + OpenSSLNameCanonicalizerFactory.DIGEST_NAMES, " blake2b512 ")) + .isEqualTo("BLAKE2B-512"); + } + + /** + * Every canonicalized group name must be a string {@link + * com.ibm.plugin.translation.translator.contexts.CxxKeyContextTranslator} actually recognizes, + * otherwise the finding is silently dropped as {@code Optional.empty()}. + */ + @Test + void unrecognizedNamePassesThroughUnchanged() { + assertThat(OpenSSLNameCanonicalizerFactory.canonicalize(Map.of(), "SOME-UNKNOWN-DIGEST")) + .isEqualTo("SOME-UNKNOWN-DIGEST"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfContextTest.java new file mode 100644 index 000000000..7763ad44b --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfContextTest.java @@ -0,0 +1,137 @@ +/* + * 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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A KDF fetched by name gets the digest that is set on the context created from it, whether the + * digest is passed to {@code EVP_KDF_CTX_set_params} or to {@code EVP_KDF_derive}, and whether the + * fetched KDF is held in a variable or passed directly to {@code EVP_KDF_CTX_new}. The length given + * to {@code EVP_KDF_derive} is the length of the derived key. + */ +class OpenSSLEvpKdfContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:hkdf}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-256}]]]", + "KeyDerivationFunction:HKDF-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 17: EVP_KDF *kdf = EVP_KDF_fetch(NULL, name, NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:PBKDF2}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-512}]]]", + "PasswordBasedKeyDerivationFunction:PBKDF2-SHA-512[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]]"), + // 27: EVP_KDF *hkdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-256}]]]", + "KeyDerivationFunction:HKDF-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 35: EVP_KDF *sshkdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:SSHKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-512}]]]", + "KeyDerivationFunction:SSHKDF-SHA-512[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]]"), + // 45: EVP_KDF_CTX *hkdf_ctx = EVP_KDF_CTX_new(EVP_KDF_fetch(NULL, "HKDF", + // NULL)); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-384}]]]", + "KeyDerivationFunction:HKDF-SHA-384[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]]"), + // 46: EVP_KDF_CTX *pbkdf2_ctx = EVP_KDF_CTX_new(EVP_KDF_fetch(NULL, "PBKDF2", + // NULL)); + finding( + "KeyDerivationFunctionContext{Algorithm:PBKDF2}", + "PasswordBasedKeyDerivationFunction:PBKDF2[KeyDerivation:KEYDERIVATION]"), + // 55: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{KeySize:256}]]", + "KeyDerivationFunction:HKDF[KeyDerivation:KEYDERIVATION, KeyLength:256]"), + // 61: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-256}]]]", + "KeyDerivationFunction:HKDF-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 72: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-384}]]]", + "KeyDerivationFunction:HKDF-SHA-384[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]]"), + // 82: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-512}]]]", + "KeyDerivationFunction:HKDF-SHA-512[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]]"), + // 92: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{KeySize:256}[DigestContext{Algorithm:SHA-256}]]]", + "KeyDerivationFunction:HKDF-SHA-256[KeyDerivation:KEYDERIVATION, KeyLength:256, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/kdf/OpenSSLEvpKdfContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2Test.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2Test.java new file mode 100644 index 000000000..38a6bb761 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2Test.java @@ -0,0 +1,90 @@ +/* + * 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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +class OpenSSLEvpKdfPbkdf2Test extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: PKCS5_PBKDF2_HMAC(pass, 8, salt, 16, 10000, EVP_sha256(), 32, out); + finding( + "KeyDerivationFunctionContext{ValueAction:PBKDF2-HMAC}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{IterationCount:10000}, " + + "KeyDerivationFunctionContext{KeySize:256}, " + + "DigestContext{ValueAction:SHA-256}]", + "PasswordBasedKeyDerivationFunction:PBKDF2-SHA-256[KeyDerivation:KEYDERIVATION, " + + "KeyLength:256, MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], NumberOfIterations:10000, " + + "SaltLength:128]"), + // 8: PKCS5_PBKDF2_HMAC_SHA1(pass, 8, salt, 16, 2048, 20, out); + finding( + "KeyDerivationFunctionContext{ValueAction:PBKDF2-HMAC-SHA1}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{IterationCount:2048}, " + + "KeyDerivationFunctionContext{KeySize:160}]", + "PasswordBasedKeyDerivationFunction:PBKDF2-SHA-1[KeyDerivation:KEYDERIVATION, " + + "KeyLength:160, MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], NumberOfIterations:2048, SaltLength:128]")); + + private int findings = 0; + + OpenSSLEvpKdfPbkdf2Test() { + super(RuleSets.rulesOf(OpenSSLEvpKdfPbkdf2.class)); + } + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/kdf/OpenSSLEvpKdfPbkdf2TestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTest.java new file mode 100644 index 000000000..f13b7259c --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTest.java @@ -0,0 +1,85 @@ +/* + * 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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.rule.RuleSets; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +class OpenSSLEvpKdfScryptTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_PBE_scrypt(pass, 8, salt, 16, 16384, 8, 1, 0, key, 32); + finding( + "KeyDerivationFunctionContext{ValueAction:SCRYPT}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{KeySize:256}]", + "PasswordBasedKeyDerivationFunction:scrypt[KeyDerivation:KEYDERIVATION, " + + "KeyLength:256, SaltLength:128]"), + // 8: EVP_PBE_scrypt_ex(pass, 8, salt, 32, 1048576, 8, 1, 0, key, 64, NULL, + // NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:SCRYPT}[KeyDerivationFunctionContext{SaltSize:256}, " + + "KeyDerivationFunctionContext{KeySize:512}]", + "PasswordBasedKeyDerivationFunction:scrypt[KeyDerivation:KEYDERIVATION, " + + "KeyLength:512, SaltLength:256]")); + + private int findings = 0; + + OpenSSLEvpKdfScryptTest() { + super(RuleSets.rulesOf(OpenSSLEvpKdfScrypt.class)); + } + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/kdf/OpenSSLEvpKdfScryptTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfTest.java new file mode 100644 index 000000000..0ea7a87c2 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpKdfTest.java @@ -0,0 +1,166 @@ +/* + * 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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers the KDF names accepted by {@code EVP_KDF_fetch} and the PBKDF2 functions of {@link + * OpenSSLEvpKdfPkcs12}. Each fetched name is reported as its KDF; the digest set on a fetched KDF's + * context is covered by {@link OpenSSLEvpKdfContextTest}, the EVP_PKEY interface by {@link + * OpenSSLEvpPkeyKdfTest}, and the PKCS#12 and PKCS#5 password-based functions by {@link + * OpenSSLPkcs12Test}. + */ +class OpenSSLEvpKdfTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 11: EVP_KDF_fetch(lib, "PBKDF2", props); + finding( + "KeyDerivationFunctionContext{Algorithm:PBKDF2}", + "PasswordBasedKeyDerivationFunction:PBKDF2[KeyDerivation:KEYDERIVATION]"), + // 12: EVP_KDF_fetch(lib, "HKDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}", + "KeyDerivationFunction:HKDF[KeyDerivation:KEYDERIVATION]"), + // 13: EVP_KDF_fetch(lib, "SCRYPT", props); + finding( + "KeyDerivationFunctionContext{Algorithm:SCRYPT}", + "PasswordBasedKeyDerivationFunction:scrypt[KeyDerivation:KEYDERIVATION]"), + // 14: EVP_KDF_fetch(lib, "TLS1-PRF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:TLS1-PRF}", + "KeyDerivationFunction:TLS-PRF[KeyDerivation:KEYDERIVATION]"), + // 15: EVP_KDF_fetch(lib, "TLS13-KDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:TLS13-KDF}", + "KeyDerivationFunction:HKDF[KeyDerivation:KEYDERIVATION]"), + // 16: EVP_KDF_fetch(lib, "X963KDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:X963KDF}", + "KeyDerivationFunction:ANSI-KDF-X9.63[KeyDerivation:KEYDERIVATION]"), + // 17: EVP_KDF_fetch(lib, "KBKDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:KBKDF}", + "KeyDerivationFunction:SP800_108_CounterKDF[KeyDerivation:KEYDERIVATION]"), + // 18: EVP_KDF_fetch(lib, "X942KDF-ASN1", props); + finding( + "KeyDerivationFunctionContext{Algorithm:X942KDF-ASN1}", + "KeyDerivationFunction:ANSI-KDF-X9.42-ASN1[KeyDerivation:KEYDERIVATION, " + + "ParameterSetIdentifier:ASN1]"), + // 19: EVP_KDF_fetch(lib, "X942KDF-CONCAT", props); + finding( + "KeyDerivationFunctionContext{Algorithm:X942KDF-CONCAT}", + "KeyDerivationFunction:ANSI-KDF-X9.42-CONCAT[KeyDerivation:KEYDERIVATION, " + + "ParameterSetIdentifier:CONCAT]"), + // 20: EVP_KDF_fetch(lib, "SSKDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:SSKDF}", + "KeyDerivationFunction:ConcatenationKDF[KeyDerivation:KEYDERIVATION]"), + // 21: EVP_KDF_fetch(lib, "SSHKDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:SSHKDF}", + "KeyDerivationFunction:SSHKDF[KeyDerivation:KEYDERIVATION]"), + // 22: EVP_KDF_fetch(lib, "KRB5KDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:KRB5KDF}", + "KeyDerivationFunction:KRB5KDF[KeyDerivation:KEYDERIVATION]"), + // 23: EVP_KDF_fetch(lib, "ARGON2D", props); + finding( + "KeyDerivationFunctionContext{Algorithm:ARGON2D}", + "PasswordBasedKeyDerivationFunction:Argon2d[KeyDerivation:KEYDERIVATION]"), + // 24: EVP_KDF_fetch(lib, "ARGON2I", props); + finding( + "KeyDerivationFunctionContext{Algorithm:ARGON2I}", + "PasswordBasedKeyDerivationFunction:Argon2i[KeyDerivation:KEYDERIVATION]"), + // 25: EVP_KDF_fetch(lib, "ARGON2ID", props); + finding( + "KeyDerivationFunctionContext{Algorithm:ARGON2ID}", + "PasswordBasedKeyDerivationFunction:Argon2id[KeyDerivation:KEYDERIVATION]"), + // 26: EVP_KDF_fetch(lib, "PKCS12KDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:PKCS12KDF}", + "PasswordBasedKeyDerivationFunction:PKCS12KDF[KeyDerivation:KEYDERIVATION]"), + // 27: EVP_KDF_fetch(lib, "PVKKDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:PVKKDF}", + "PasswordBasedKeyDerivationFunction:PVKKDF[KeyDerivation:KEYDERIVATION]"), + // 28: EVP_KDF_fetch(lib, "HMAC-DRBG-KDF", props); + finding( + "KeyDerivationFunctionContext{Algorithm:HMAC-DRBG-KDF}", + "KeyDerivationFunction:HMAC-DRBG-KDF[KeyDerivation:KEYDERIVATION]"), + // 33: PKCS5_PBKDF2_HMAC((char*)buf, 8, buf, 16, 1000, pbkdf2_md, 32, buf); + finding( + "KeyDerivationFunctionContext{ValueAction:PBKDF2-HMAC}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{IterationCount:1000}, " + + "KeyDerivationFunctionContext{KeySize:256}, " + + "DigestContext{ValueAction:SHA-256}]", + "PasswordBasedKeyDerivationFunction:PBKDF2-SHA-256[KeyDerivation:KEYDERIVATION, " + + "KeyLength:256, MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], NumberOfIterations:1000, " + + "SaltLength:128]"), + // 34: PKCS5_PBKDF2_HMAC_SHA1((char*)buf, 8, buf, 16, 1000, 32, buf); + finding( + "KeyDerivationFunctionContext{ValueAction:PBKDF2-HMAC-SHA1}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{IterationCount:1000}, " + + "KeyDerivationFunctionContext{KeySize:256}]", + "PasswordBasedKeyDerivationFunction:PBKDF2-SHA-1[KeyDerivation:KEYDERIVATION, " + + "KeyLength:256, MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], NumberOfIterations:1000, SaltLength:128]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/kdf/OpenSSLEvpKdfTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTest.java new file mode 100644 index 000000000..5a887cb23 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTest.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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** A KDF selected through the EVP_PKEY interface gets the digest that is set on its context. */ +class OpenSSLEvpPkeyKdfTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:HKDF}[KeyDerivationFunctionContext{}[DigestContext{ValueAction:SHA-256}]]", + "KeyDerivationFunction:HKDF-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 13: EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:TLS1-PRF}[KeyDerivationFunctionContext{}[DigestContext{ValueAction:SHA-384}]]", + "KeyDerivationFunction:TLS-PRF-SHA-384[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]]"), + // 20: EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "scrypt", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:SCRYPT}", + "PasswordBasedKeyDerivationFunction:scrypt[KeyDerivation:KEYDERIVATION]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/kdf/OpenSSLEvpPkeyKdfTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTest.java new file mode 100644 index 000000000..358273172 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTest.java @@ -0,0 +1,113 @@ +/* + * 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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Password-based encryption selected by its algorithm identifier ({@link + * OpenSSLPasswordBasedEncryption}): {@code PKCS8_encrypt} with PBES2 and a cipher (-1), a PKCS#12 + * or a PKCS#5 v1.5 scheme, with its salt length and iteration count, and {@code EVP_PBE_CipherInit} + * with the scheme of an {@code OBJ_nid2obj} object, for its operation. + */ +class OpenSSLPasswordBasedEncryptionTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 8: PKCS8_encrypt(-1, EVP_aes_256_cbc(), pass, 8, salt, 16, 600000, p8inf); + finding( + "KeyDerivationFunctionContext{ValueAction:PBES2}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{IterationCount:600000}, " + + "CipherContext{ValueAction:AES-256-CBC}]", + "PasswordBasedEncryption:PBES2-AES-256-CBC[BlockCipher:AES-256-CBC[BlockSize:128, " + + "KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42], " + + "NumberOfIterations:600000, Oid:1.2.840.113549.1.5, SaltLength:128]"), + // 12: PKCS8_encrypt(NID_pbe_WithSHA1And3_Key_TripleDES_CBC, NULL, pass, 8, + // NULL, 8, 2048, p8inf); + finding( + "KeyDerivationFunctionContext{ValueAction:PBE-SHA1-3DES}[KeyDerivationFunctionContext{SaltSize:64}, " + + "KeyDerivationFunctionContext{IterationCount:2048}]", + "PasswordBasedEncryption:PKCS12-DESede168-CBC-SHA-1[BlockCipher:DESede168-CBC[BlockSize:64, " + + "KeyLength:168, Mode:CBC], MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], NumberOfIterations:2048, SaltLength:64]"), + // 16: PKCS8_encrypt_ex(NID_pbeWithMD5AndDES_CBC, NULL, pass, 8, NULL, 8, 1000, + // p8inf, NULL, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:PBE-MD5-DES}[KeyDerivationFunctionContext{SaltSize:64}, " + + "KeyDerivationFunctionContext{IterationCount:1000}]", + "PasswordBasedEncryption:PBES1-DES-56-CBC-MD5[BlockCipher:DES-56-CBC[BlockSize:64, " + + "KeyLength:56, Mode:CBC], MessageDigest:MD5[BlockSize:512, Digest:DIGEST, " + + "DigestSize:128], NumberOfIterations:1000, Oid:1.2.840.113549.1.5.3, " + + "SaltLength:64]"), + // 20: EVP_PBE_CipherInit(OBJ_nid2obj(NID_pbeWithSHA1AndDES_CBC), pass, 8, + // param, ctx, 0); + finding( + "CipherContext{CipherAction:DECRYPT}[KeyDerivationFunctionContext{ValueAction:PBE-SHA1-DES}]", + "PasswordBasedEncryption:PBES1-DES-56-CBC-SHA-1[BlockCipher:DES-56-CBC[BlockSize:64, " + + "KeyLength:56, Mode:CBC], Decrypt:DECRYPT, " + + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26], Oid:1.2.840.113549.1.5.10]"), + // 25: EVP_PBE_CipherInit_ex(scheme, pass, 8, param, ctx, 1, NULL, NULL); + finding( + "CipherContext{CipherAction:ENCRYPT}[KeyDerivationFunctionContext{ValueAction:PBE-SHA1-RC4-128}]", + "PasswordBasedEncryption:PKCS12-RC4-128-SHA-1[Encrypt:ENCRYPT, " + + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26], StreamCipher:RC4-128[KeyLength:128]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/kdf/OpenSSLPasswordBasedEncryptionTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPkcs12Test.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPkcs12Test.java new file mode 100644 index 000000000..0a42f8c9d --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/kdf/OpenSSLPkcs12Test.java @@ -0,0 +1,190 @@ +/* + * 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.openssl.kdf; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * PKCS#12 and PKCS#5 password-based encryption, key generation and MAC functions report the scheme + * with the cipher and digest passed to them, and PKCS12_create reports the schemes selected by its + * key and certificate NIDs. + */ +class OpenSSLPkcs12Test extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: PKCS12_PBE_keyivgen(ctx, "password", 8, param, EVP_des_ede3_cbc(), + // EVP_sha1(), 1); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12-PBE}[CipherContext{ValueAction:DESede3-CBC}, " + + "DigestContext{ValueAction:SHA-1}]", + "PasswordBasedEncryption:PKCS12-DESede168-CBC-SHA-1[BlockCipher:DESede168-CBC[BlockSize:64, " + + "KeyLength:168, Mode:CBC], MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26]]"), + // 9: PKCS5_PBE_keyivgen_ex(ctx, "password", 8, param, EVP_rc2_cbc(), EVP_md5(), + // 1, NULL, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:PBES1}[CipherContext{ValueAction:RC2-CBC}, " + + "DigestContext{ValueAction:MD5}]", + "PasswordBasedEncryption:PBES1-RC2-128-CBC-MD5[BlockCipher:RC2-128-CBC[KeyLength:128, " + + "Mode:CBC], MessageDigest:MD5[BlockSize:512, Digest:DIGEST, DigestSize:128], " + + "Oid:1.2.840.113549.1.5.6]"), + // 13: PKCS12_key_gen_utf8_ex("password", 8, salt, 8, 1, 2048, 32, out, + // EVP_sha256(), NULL, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12KDF}[DigestContext{ValueAction:SHA-256}]", + "PasswordBasedKeyDerivationFunction:PKCS12KDF-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 17: PKCS12_set_mac(p12, "password", 8, salt, 8, 2048, EVP_sha256()); + finding( + "MacContext{ValueAction:HMAC}[DigestContext{ValueAction:SHA-256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG]"), + // 21: PKCS12_create("password", "key", pkey, cert, NULL, + // NID_pbe_WithSHA1And3_Key_TripleDES_CBC, + finding( + "KeyDerivationFunctionContext{ValueAction:PBE-SHA1-3DES, " + + "ValueAction:PBE-SHA1-RC2-40}", + "PasswordBasedEncryption:PKCS12-DESede168-CBC-SHA-1[BlockCipher:DESede168-CBC[BlockSize:64, " + + "KeyLength:168, Mode:CBC], MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26]]", + "PasswordBasedEncryption:PKCS12-RC2-40-CBC-SHA-1[BlockCipher:RC2-40-CBC[KeyLength:40, " + + "Mode:CBC], MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26]]"), + // 23: PKCS12_create_ex("password", "key", pkey, cert, NULL, NID_aes_256_cbc, 0, + // 2048, 2048, 0, + finding( + "KeyDerivationFunctionContext{ValueAction:PBES2-AES-256-CBC, " + + "ValueAction:PBES2-AES-256-CBC}", + "PasswordBasedEncryption:PBES2-AES-256-CBC-HMAC-SHA-256[BlockCipher:AES-256-CBC[BlockSize:128, " + + "KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42], " + + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG], Oid:1.2.840.113549.1.5]", + "PasswordBasedEncryption:PBES2-AES-256-CBC-HMAC-SHA-256[BlockCipher:AES-256-CBC[BlockSize:128, " + + "KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42], " + + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG], Oid:1.2.840.113549.1.5]"), + // 30: PKCS12_PBE_keyivgen_ex(ctx, "password", 8, param, EVP_aes_128_cbc(), + // EVP_sha256(), 0, NULL, + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12-PBE}[CipherContext{ValueAction:AES-128-CBC}, " + + "DigestContext{ValueAction:SHA-256}]", + "PasswordBasedEncryption:PKCS12-AES-128-CBC-SHA-256[BlockCipher:AES-128-CBC[BlockSize:128, " + + "KeyLength:128, Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 32: PKCS5_PBE_keyivgen(ctx, "password", 8, param, EVP_des_cbc(), EVP_md5(), + // 0); + finding( + "KeyDerivationFunctionContext{ValueAction:PBES1}[CipherContext{ValueAction:DES-CBC}, " + + "DigestContext{ValueAction:MD5}]", + "PasswordBasedEncryption:PBES1-DES-56-CBC-MD5[BlockCipher:DES-56-CBC[BlockSize:64, " + + "KeyLength:56, Mode:CBC], MessageDigest:MD5[BlockSize:512, Digest:DIGEST, " + + "DigestSize:128], Oid:1.2.840.113549.1.5.3]"), + // 33: PKCS12_key_gen_asc("password", 8, salt, 8, 1, 2048, 24, out, EVP_sha1()); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12KDF}[DigestContext{ValueAction:SHA-1}]", + "PasswordBasedKeyDerivationFunction:PKCS12KDF-SHA-1[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]]"), + // 34: PKCS12_key_gen_asc_ex("password", 8, salt, 8, 2, 2048, 8, out, + // EVP_sha1(), NULL, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12KDF}[DigestContext{ValueAction:SHA-1}]", + "PasswordBasedKeyDerivationFunction:PKCS12KDF-SHA-1[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]]"), + // 35: PKCS12_key_gen_uni(uni_pass, 18, salt, 8, 3, 2048, 20, out, EVP_sha1()); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12KDF}[DigestContext{ValueAction:SHA-1}]", + "PasswordBasedKeyDerivationFunction:PKCS12KDF-SHA-1[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]]"), + // 36: PKCS12_key_gen_uni_ex(uni_pass, 18, salt, 8, 3, 2048, 20, out, + // EVP_sha1(), NULL, NULL); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12KDF}[DigestContext{ValueAction:SHA-1}]", + "PasswordBasedKeyDerivationFunction:PKCS12KDF-SHA-1[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]]"), + // 37: PKCS12_key_gen_utf8("password", 8, salt, 8, 1, 2048, 32, out, + // EVP_sha512()); + finding( + "KeyDerivationFunctionContext{ValueAction:PKCS12KDF}[DigestContext{ValueAction:SHA-512}]", + "PasswordBasedKeyDerivationFunction:PKCS12KDF-SHA-512[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]]"), + // 38: PKCS12_create_ex2("password", "key", pkey, cert, NULL, + // NID_pbe_WithSHA1And128BitRC4, + finding( + "KeyDerivationFunctionContext{ValueAction:PBE-SHA1-RC4-128, " + + "ValueAction:PBES2-AES-128-CBC}", + "PasswordBasedEncryption:PKCS12-RC4-128-SHA-1[MessageDigest:SHA-1[BlockSize:512, " + + "Digest:DIGEST, DigestSize:160, Oid:1.3.14.3.2.26], " + + "StreamCipher:RC4-128[KeyLength:128]]", + "PasswordBasedEncryption:PBES2-AES-128-CBC-HMAC-SHA-256[BlockCipher:AES-128-CBC[BlockSize:128, " + + "KeyLength:128, Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], " + + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG], Oid:1.2.840.113549.1.5]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/kdf/OpenSSLPkcs12TestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTest.java new file mode 100644 index 000000000..f627510e0 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTest.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.keyagreement; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The key exchange, KEM and HPKE suite selections of {@link OpenSSLEvpKeyAgreement} are reported, + * while the derivation and encapsulation calls, whose key type is not known at the call, and a KDF + * type of "none" are not. The KDFs and HPKE suite forms are covered by {@link + * OpenSSLKeyAgreementKdfTest}. + */ +class OpenSSLEvpKeyAgreementTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 25: EVP_KEYEXCH_fetch(NULL, "ECDH", NULL); + finding( + "KeyAgreementContext{Algorithm:ECDH}", + "KeyAgreement:ECDH[Oid:1.3.132.1.12]"), + // 26: EVP_KEM_fetch(NULL, "RSA", NULL); + finding( + "KeyAgreementContext{Algorithm:RSA}", + "KeyEncapsulationMechanism:RSASVE"), + // 39: OSSL_HPKE_str2suite("X25519,HKDF-SHA256,AES-128-GCM", NULL); + finding( + "KeyAgreementContext{ValueAction:X25519,HKDF-SHA256,AES-128-GCM}", + "PublicKeyEncryption:HPKE[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "KeyDerivationFunction:HKDF-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]], " + + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keyagreement/OpenSSLEvpKeyAgreementTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTest.java new file mode 100644 index 000000000..89bdbca1c --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTest.java @@ -0,0 +1,147 @@ +/* + * 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.openssl.keyagreement; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** A KDF selected through the EVP_PKEY interface gets the digest that is set on its context. */ +class OpenSSLKeyAgreementKdfTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 7: EVP_PKEY_CTX_set_ecdh_kdf_type(ctx, EVP_PKEY_ECDH_KDF_X9_63); + finding( + "KeyDerivationFunctionContext{ValueAction:X963KDF}[KeyDerivationFunctionContext{}[DigestContext{ValueAction:SHA-256}]]", + "KeyDerivationFunction:ANSI-KDF-X9.63[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]]"), + // 16: EVP_PKEY_CTX_set_dh_kdf_type(ctx, EVP_PKEY_DH_KDF_X9_42); + finding( + "KeyDerivationFunctionContext{ValueAction:X942KDF-ASN1}[KeyDerivationFunctionContext{}[DigestContext{ValueAction:SHA-384}]]", + "KeyDerivationFunction:ANSI-KDF-X9.42-SHA-384-ASN1[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "ParameterSetIdentifier:ASN1]"), + // 29: EVP_KEM_fetch(NULL, "RSA", NULL); + finding( + "KeyAgreementContext{Algorithm:RSA}", + "KeyEncapsulationMechanism:RSASVE"), + // 30: EVP_KEM_fetch(NULL, "ML-KEM-768", NULL); + finding( + "KeyAgreementContext{Algorithm:ML-KEM-768}", + "KeyEncapsulationMechanism:ML-KEM-768[Oid:2.16.840.1.101.3.4.4.2, " + + "ParameterSetIdentifier:768]"), + // 31: EVP_KEM_fetch(NULL, "X25519", NULL); + finding( + "KeyAgreementContext{Algorithm:X25519}", + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]"), + // 32: EVP_KEM_fetch(NULL, "EC", NULL); + finding( + "KeyAgreementContext{Algorithm:EC}", + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:ECDH[Oid:1.3.132.1.12]]"), + // 37: OSSL_HPKE_str2suite("x25519,hkdf-sha256,aes-128-gcm", &suite); + finding( + "KeyAgreementContext{ValueAction:X25519,HKDF-SHA256,AES-128-GCM}", + "PublicKeyEncryption:HPKE[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "KeyDerivationFunction:HKDF-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]], " + + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]]"), + // 38: OSSL_HPKE_str2suite("P-384,hkdf-sha384,chacha20-poly1305", &suite); + finding( + "KeyAgreementContext{ValueAction:P-384,HKDF-SHA384,CHACHA20-POLY1305}", + "PublicKeyEncryption:HPKE[AuthenticatedEncryption:ChaCha20-Poly1305[MessageDigest:Poly1305[Digest:DIGEST]], " + + "KeyDerivationFunction:HKDF-SHA-384[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2]], " + + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:ECDH[EllipticCurve:secp384r1, " + + "Oid:1.3.132.1.12]]]"), + // 40: OSSL_HPKE_CTX *sender = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, + // default_suite, OSSL_HPKE_ROLE_SENDER, NULL, NULL); + finding( + "KeyAgreementContext{ValueAction:X25519,HKDF-SHA256,AES-128-GCM}", + "PublicKeyEncryption:HPKE[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "KeyDerivationFunction:HKDF-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]], " + + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]]"), + // 42: OSSL_HPKE_CTX *receiver = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, + // explicit_suite, OSSL_HPKE_ROLE_RECEIVER, NULL, NULL); + finding( + "KeyAgreementContext{ValueAction:P-256,HKDF-SHA256,AES-256-GCM}", + "PublicKeyEncryption:HPKE[AuthenticatedEncryption:AES-256-GCM[BlockSize:128, " + + "KeyLength:256, Mode:GCM, Oid:2.16.840.1.101.3.4.1.46], " + + "KeyDerivationFunction:HKDF-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]], " + + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:ECDH[EllipticCurve:secp256r1, " + + "Oid:1.3.132.1.12]]]"), + // 44: OSSL_HPKE_CTX *numeric = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, + // numeric_suite, OSSL_HPKE_ROLE_SENDER, NULL, NULL); + finding( + "KeyAgreementContext{ValueAction:P-256,HKDF-SHA256,AES-256-GCM}", + "PublicKeyEncryption:HPKE[AuthenticatedEncryption:AES-256-GCM[BlockSize:128, " + + "KeyLength:256, Mode:GCM, Oid:2.16.840.1.101.3.4.1.46], " + + "KeyDerivationFunction:HKDF-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]], " + + "KeyEncapsulationMechanism:DHKEM[KeyAgreement:ECDH[EllipticCurve:secp256r1, " + + "Oid:1.3.132.1.12]]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keyagreement/OpenSSLKeyAgreementKdfTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTest.java new file mode 100644 index 000000000..b58329292 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTest.java @@ -0,0 +1,95 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A variable gives a key size its initial value and the values of the plain assignments to the + * variable itself, and an element of an array the values assigned to that element. A compound + * assignment, an assignment to another element, to another array indexed by the variable, or + * through a pointer to it does not assign the variable a value. + */ +class OpenSSLEvpKeyGenBitsAssignmentTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 7: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:2048}]", + "PublicKeyEncryption:RSA-2048[KeyLength:2048, Oid:1.2.840.113549.1.1.1]"), + // 15: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:3072}]", + "PublicKeyEncryption:RSA-3072[KeyLength:3072, Oid:1.2.840.113549.1.1.1]"), + // 23: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:4096}]", + "PublicKeyEncryption:RSA-4096[KeyLength:4096, Oid:1.2.840.113549.1.1.1]"), + // 31: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:2048}]", + "PublicKeyEncryption:RSA-2048[KeyLength:2048, Oid:1.2.840.113549.1.1.1]"), + // 38: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:1024, KeySize:4096}]", + "PublicKeyEncryption:RSA-1024[KeyLength:1024, Oid:1.2.840.113549.1.1.1]", + "PublicKeyEncryption:RSA-4096[KeyLength:4096, Oid:1.2.840.113549.1.1.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsAssignmentTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTest.java new file mode 100644 index 000000000..ef31b0fd5 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTest.java @@ -0,0 +1,93 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A key size given as a constant expression is evaluated; a conditional contributes both possible + * sizes, and an expression with an unknown operand contributes none. + */ +class OpenSSLEvpKeyGenBitsExpressionTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:4096}]", + "PublicKeyEncryption:RSA-4096[KeyLength:4096, Oid:1.2.840.113549.1.1.1]"), + // 10: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:2048}]", + "PublicKeyEncryption:RSA-2048[KeyLength:2048, Oid:1.2.840.113549.1.1.1]"), + // 15: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:3072}]", + "PublicKeyEncryption:RSA-3072[KeyLength:3072, Oid:1.2.840.113549.1.1.1]"), + // 20: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:4096, KeySize:2048}]", + "PublicKeyEncryption:RSA-4096[KeyLength:4096, Oid:1.2.840.113549.1.1.1]", + "PublicKeyEncryption:RSA-2048[KeyLength:2048, Oid:1.2.840.113549.1.1.1]"), + // 25: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsExpressionTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTest.java new file mode 100644 index 000000000..ddb3db3d4 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A size argument has a value only when it is a positive integer: an undeclared macro, a number + * literal that is out of range or invalid, an enum constant whose value is not known, a constant + * expression that overflows or shifts out of range, and a size that is not positive give no size. + */ +class OpenSSLEvpKeyGenBitsUnknownTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 6: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 11: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 16: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 21: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 28: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 33: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 38: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 42: PKCS5_PBKDF2_HMAC(pass, 8, salt, 16, 10000, EVP_sha256(), APP_KEY_BYTES, + // out); + finding( + "KeyDerivationFunctionContext{ValueAction:PBKDF2-HMAC}[KeyDerivationFunctionContext{SaltSize:128}, " + + "KeyDerivationFunctionContext{IterationCount:10000}, " + + "DigestContext{ValueAction:SHA-256}]", + "PasswordBasedKeyDerivationFunction:PBKDF2-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], NumberOfIterations:10000, " + + "SaltLength:128]"), + // 46: EVP_RSA_gen(APP_RSA_BITS); + finding( + "PrivateKeyContext{ValueAction:RSA}", + "PrivateKey:RSA[PublicKeyEncryption:RSA[KeyGeneration:KEYGENERATION, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 50: EVP_PKEY_Q_keygen(NULL, NULL, "RSA", -1); + finding( + "PrivateKeyContext{Algorithm:RSA}", + "PrivateKey:RSA[PublicKeyEncryption:RSA[KeyGeneration:KEYGENERATION, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 54: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 59: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1]"), + // 66: AES_ecb_encrypt(in, out, &key, AES_ENCRYPT); + finding( + "CipherContext{ValueAction:AES-ECB}[CipherContext{ValueAction:AES}]", + "BlockCipher:AES-ECB[BlockSize:128, Mode:ECB, Oid:2.16.840.1.101.3.4.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLEvpKeyGenBitsUnknownTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTest.java new file mode 100644 index 000000000..1844a8696 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTest.java @@ -0,0 +1,124 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The key type is selected when the key generation context is created, and the size or curve set on + * that context belongs to it; {@code EVP_PKEY_Q_keygen} takes both in one call. + */ +class OpenSSLEvpKeyGenContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 6: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:3072}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:RSA[KeyLength:3072, " + + "PublicKeyEncryption:RSA-3072[KeyGeneration:KEYGENERATION, " + + "KeyLength:3072, Oid:1.2.840.113549.1.1.1]]"), + // 14: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + finding( + "KeyContext{Algorithm:EC}[KeyContext{Curve:EC-P-384}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:EC[PublicKeyEncryption:EC-secp384r1[EllipticCurve:secp384r1, " + + "KeyGeneration:KEYGENERATION, Oid:1.2.840.10045.2.1]]"), + // 22: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + finding( + "KeyContext{ValueAction:DH}[KeyContext{KeySize:2048}]", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 29: EVP_PKEY_CTX *ffdhe = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + finding( + "KeyContext{ValueAction:DH}[KeyContext{KeySize:3072}]", + "PublicKeyEncryption:FFDH-3072[KeyLength:3072, Oid:1.2.840.113549.1.3.1]"), + // 31: EVP_PKEY_CTX *rfc5114 = EVP_PKEY_CTX_new_id(EVP_PKEY_DHX, NULL); + finding( + "KeyContext{ValueAction:DH}[KeyContext{KeySize:1024}]", + "PublicKeyEncryption:FFDH-1024[KeyLength:1024, Oid:1.2.840.113549.1.3.1]"), + // 33: EVP_PKEY_CTX *rfc5114_dh = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + finding( + "KeyContext{ValueAction:DH}[KeyContext{KeySize:2048}]", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 38: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); + finding( + "KeyContext{ValueAction:RSA-PSS}[KeyContext{KeySize:2048}, " + + "KeyContext{}[DigestContext{ValueAction:SHA-256}], KeyContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[KeyLength:2048, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.1.1.10, SaltLength:256]"), + // 45: EVP_PKEY *rsa = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 4096); + finding( + "PrivateKeyContext{Algorithm:RSA}[PrivateKeyContext{KeySize:4096}]", + "PrivateKey:RSA[KeyLength:4096, " + + "PublicKeyEncryption:RSA-4096[KeyGeneration:KEYGENERATION, " + + "KeyLength:4096, Oid:1.2.840.113549.1.1.1]]"), + // 46: EVP_PKEY *ec = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "KeyGeneration:KEYGENERATION, Oid:1.2.840.10045.2.1]]"), + // 47: EVP_PKEY *x25519 = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); + finding( + "PrivateKeyContext{Algorithm:X25519}", + "PrivateKey:x25519[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "KeyGeneration:KEYGENERATION, Oid:1.3.101.110]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLEvpKeyGenContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenTest.java new file mode 100644 index 000000000..41026dd7f --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLEvpKeyGenTest.java @@ -0,0 +1,193 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers the rules of {@link OpenSSLEvpKeyGen} and its per-algorithm context rules. */ +class OpenSSLEvpKeyGenTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, NULL); + finding( + "KeyContext{ValueAction:DSA}[KeyContext{KeySize:2048}, " + + "KeyContext{}[DigestContext{ValueAction:SHA-256}]]", + "Signature:DSA-2048-SHA-256[KeyLength:2048, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:2.16.840.1.101.3.4.3.2]"), + // 14: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "DSA", NULL); + finding( + "KeyContext{Algorithm:DSA}[DigestContext{Algorithm:SHA-256}]", + "Signature:DSA-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:2.16.840.1.101.3.4.3.2]"), + // 19: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + finding( + "KeyContext{ValueAction:EC}[KeyContext{Curve:EC-prime256v1}]", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 25: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + finding( + "KeyContext{ValueAction:EC}[KeyContext{Curve:EC-prime256v1}]", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 30: EVP_PKEY_CTX *p192 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + finding( + "KeyContext{Algorithm:EC}[KeyContext{Curve:EC-P-192}]", + "PublicKeyEncryption:EC-secp192r1[EllipticCurve:secp192r1, " + + "Oid:1.2.840.10045.2.1]"), + // 32: EVP_PKEY_CTX *p224 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + finding( + "KeyContext{Algorithm:EC}[KeyContext{Curve:EC-SECP224R1}]", + "PublicKeyEncryption:EC-secp224r1[EllipticCurve:secp224r1, " + + "Oid:1.2.840.10045.2.1]"), + // 39: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:2048}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]"), + // 46: EVP_PKEY_Q_keygen(NULL, NULL, "RSA", 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]"), + // 47: EVP_KEYMGMT_fetch(NULL, "ML-KEM-768", NULL); + finding( + "KeyContext{Algorithm:ML-KEM-768}", + "KeyEncapsulationMechanism:ML-KEM-768[Oid:2.16.840.1.101.3.4.4.2, " + + "ParameterSetIdentifier:768]"), + // 51: EVP_RSA_gen(3072); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:3072}]", + "PrivateKey:RSA[KeyLength:3072, " + + "PublicKeyEncryption:RSA-3072[KeyGeneration:KEYGENERATION, " + + "KeyLength:3072, Oid:1.2.840.113549.1.1.1]]"), + // 52: EVP_EC_gen("P-256"); + finding( + "PrivateKeyContext{ValueAction:EC}[PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "KeyGeneration:KEYGENERATION, Oid:1.2.840.10045.2.1]]"), + // 56: EVP_PKEY_CTX *k283 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + finding( + "KeyContext{Algorithm:EC}[KeyContext{Curve:EC-sect283k1}]", + "PublicKeyEncryption:EC-sect283k1[EllipticCurve:sect283k1, " + + "Oid:1.2.840.10045.2.1]"), + // 58: EVP_PKEY_CTX *b163 = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + finding( + "KeyContext{Algorithm:EC}[KeyContext{Curve:EC-B-163}]", + "PublicKeyEncryption:EC-sect163r2[EllipticCurve:sect163r2, " + + "Oid:1.2.840.10045.2.1]"), + // 60: EVP_PKEY_CTX *p224 = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + finding( + "KeyContext{ValueAction:EC}[KeyContext{Curve:EC-secp224r1}]", + "PublicKeyEncryption:EC-secp224r1[EllipticCurve:secp224r1, " + + "Oid:1.2.840.10045.2.1]"), + // 62: EVP_PKEY_CTX *p224_code = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + finding( + "KeyContext{ValueAction:EC}[KeyContext{Curve:EC-secp224r1}]", + "PublicKeyEncryption:EC-secp224r1[EllipticCurve:secp224r1, " + + "Oid:1.2.840.10045.2.1]"), + // 67: EVP_PKEY_Q_keygen(NULL, NULL, "MLKEM1024"); + finding( + "PrivateKeyContext{Algorithm:ML-KEM-1024}", + "PrivateKey:ML-KEM[KeyEncapsulationMechanism:ML-KEM-1024[KeyGeneration:KEYGENERATION, " + + "Oid:2.16.840.1.101.3.4.4.3, ParameterSetIdentifier:1024]]"), + // 68: EVP_PKEY_Q_keygen(NULL, NULL, "MLDSA65"); + finding( + "PrivateKeyContext{Algorithm:ML-DSA-65}", + "PrivateKey:ML-DSA[Signature:ML-DSA-65[KeyGeneration:KEYGENERATION, " + + "Oid:2.16.840.1.101.3.4.3.18, ParameterSetIdentifier:65]]"), + // 73: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:3072}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:RSA[KeyLength:3072, " + + "PublicKeyEncryption:RSA-3072[KeyGeneration:KEYGENERATION, " + + "KeyLength:3072, Oid:1.2.840.113549.1.1.1]]"), + // 81: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL), *unused = + // NULL; + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:4096}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:RSA[KeyLength:4096, " + + "PublicKeyEncryption:RSA-4096[KeyGeneration:KEYGENERATION, " + + "KeyLength:4096, Oid:1.2.840.113549.1.1.1]]"), + // 91: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:2048}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]"), + // 98: EVP_PKEY_CTX *prime = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + finding( + "KeyContext{ValueAction:EC}[KeyContext{Curve:EC-prime239v1}]", + "PublicKeyEncryption:EC-prime239v1[EllipticCurve:prime239v1, " + + "Oid:1.2.840.10045.2.1]"), + // 101: EVP_PKEY_CTX *binary = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + finding( + "KeyContext{ValueAction:EC}[KeyContext{Curve:EC-c2pnb163v3}]", + "PublicKeyEncryption:EC-c2pnb163v3[EllipticCurve:c2pnb163v3, " + + "Oid:1.2.840.10045.2.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/keygen/OpenSSLEvpKeyGenTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLGeneratedKeyTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLGeneratedKeyTest.java new file mode 100644 index 000000000..681e476bf --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLGeneratedKeyTest.java @@ -0,0 +1,93 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A generated asymmetric key is reported as a private key holding its algorithm, as the Java and + * Python modules report generated keys. Generated domain parameters are reported as the algorithm. + */ +class OpenSSLGeneratedKeyTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 6: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:3072}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}]", + "PrivateKey:RSA[KeyLength:3072, " + + "PublicKeyEncryption:RSA-3072[KeyGeneration:KEYGENERATION, " + + "KeyLength:3072, Oid:1.2.840.113549.1.1.1]]"), + // 14: return EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "KeyGeneration:KEYGENERATION, Oid:1.2.840.10045.2.1]]"), + // 19: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + finding( + "KeyContext{ValueAction:DH}[KeyContext{KeySize:2048}]", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 28: RSA_generate_key_ex(rsa, 2048, e, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/keygen/OpenSSLGeneratedKeyTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTest.java new file mode 100644 index 000000000..f72f2a3e8 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTest.java @@ -0,0 +1,94 @@ +/* + * 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.openssl.keygen; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MaskGenerationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.Signature; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * The digests set on the context of an operation with a key belong to that operation: the digest of + * a signature ({@code EVP_PKEY_CTX_set_signature_md}), the digest of RSA-OAEP ({@code + * EVP_PKEY_CTX_set_rsa_oaep_md}, {@code EVP_PKEY_CTX_set_rsa_oaep_md_name}) and the digest of its + * mask generation function ({@code EVP_PKEY_CTX_set_rsa_mgf1_md}), also for RSA-PSS. + */ +class OpenSSLKeyContextDigestsTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLKeyContextDigestsTestFile.cc", + new CxxInventoryRule()); + + final List keys = CxxAggregator.getDetectedNodes(); + assertThat(keys).hasSize(4); + assertThat(child(keys.get(0), Signature.class).flatMap(s -> digest(s))).contains("SHA-384"); + + final INode oaep = + child(keys.get(1), PublicKeyEncryption.class) + .flatMap(pke -> child(pke, Padding.class)) + .orElseThrow(); + assertThat(oaep.asString()).isEqualTo("OAEP"); + assertThat(digest(oaep)).contains("SHA-256"); + assertThat(child(oaep, MaskGenerationFunction.class).flatMap(mgf -> digest(mgf))) + .contains("SHA-1"); + + assertThat( + child(keys.get(2), PublicKeyEncryption.class) + .flatMap(pke -> child(pke, Padding.class)) + .flatMap(padding -> digest(padding))) + .contains("SHA-512"); + + final INode pss = child(keys.get(3), ProbabilisticSignatureScheme.class).orElseThrow(); + assertThat(digest(pss)).contains("SHA-256"); + assertThat(child(pss, MaskGenerationFunction.class).flatMap(mgf -> digest(mgf))) + .contains("SHA-384"); + } + + @Nonnull + private static Optional child( + @Nonnull INode node, @Nonnull Class kind) { + return node.hasChildOfType(kind); + } + + @Nonnull + private static Optional digest(@Nonnull INode node) { + return node.hasChildOfType(MessageDigest.class).map(INode::asString); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTest.java new file mode 100644 index 000000000..a36e976e4 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTest.java @@ -0,0 +1,226 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The signatures made with a key on certificates, certificate requests and CRLs ({@code X509_sign}, + * ...), their verification, the CMS, PKCS#7 and OCSP signatures, the encryption of the session key + * of an envelope ({@code EVP_SealInit} / {@code EVP_OpenInit}) and the decryption of the content + * encryption key of a CMS or PKCS#7 message ({@code CMS_decrypt}, ...) are reported on the key, + * with the digest given to them ({@link OpenSSLEvpKeyUsage}); a CMS decryption with an EC key is + * the ECDH key agreement of a key agreement recipient. The cipher of an envelope is reported with + * its operation. A signature made with a key not generated in the scanned code is reported as a + * signature with its digest. + */ +class OpenSSLKeyUsageSigningTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 8: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}], " + + "PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp256r1-SHA-256[EllipticCurve:secp256r1, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.10045.4.3.2, Sign:SIGN]]"), + // 13: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); + finding( + "PrivateKeyContext{Algorithm:RSA}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-384}], " + + "PrivateKeyContext{KeySize:3072}]", + "PrivateKey:RSA[KeyGeneration:KEYGENERATION, KeyLength:3072, " + + "Signature:RSA-PKCS1-1.5-SHA-384[KeyLength:3072, " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.12, Sign:SIGN]]"), + // 18: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-384"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-384}], " + + "PrivateKeyContext{Curve:EC-P-384}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp384r1-SHA-384[EllipticCurve:secp384r1, " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.10045.4.3.3, Sign:SIGN]]"), + // 23: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[SignatureContext{SignatureAction:VERIFY}, " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Signature:RSA-PKCS1-1.5[KeyLength:2048, " + + "Oid:1.2.840.113549.1.1.1, Verify:VERIFY]]"), + // 28: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:SIGN}, " + + "PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp256r1[EllipticCurve:secp256r1, Sign:SIGN]]"), + // 33: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-512}], " + + "PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp256r1-SHA-512[EllipticCurve:secp256r1, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], " + + "Oid:1.2.840.10045.4.3.4, Sign:SIGN]]"), + // 38: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[SignatureContext{SignatureAction:SIGN}, " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Signature:RSA-PKCS1-1.5[KeyLength:2048, " + + "Oid:1.2.840.113549.1.1.1, Sign:SIGN]]"), + // 43: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}], " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Signature:RSA-PKCS1-1.5-SHA-256[KeyLength:2048, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.11, Sign:SIGN]]"), + // 48: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[CipherContext{CipherAction:ENCRYPT}, " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[Encrypt:ENCRYPT, " + + "KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 49: EVP_SealInit(ctx, EVP_aes_256_cbc(), &ek, ekl, iv, &pkey, 1); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:AES-256-CBC}]", + "BlockCipher:AES-256-CBC[BlockSize:128, Encrypt:ENCRYPT, " + + "KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]"), + // 53: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[CipherContext{CipherAction:DECRYPT}, " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[Decrypt:DECRYPT, " + + "KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 54: EVP_OpenInit(ctx, EVP_aes_256_cbc(), ek, ekl, iv, pkey); + finding( + "CipherContext{CipherAction:DECRYPT}[CipherContext{ValueAction:AES-256-CBC}]", + "BlockCipher:AES-256-CBC[BlockSize:128, Decrypt:DECRYPT, " + + "KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]"), + // 58: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[CipherContext{CipherAction:DECRYPT}, " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[Decrypt:DECRYPT, " + + "KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 63: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); + finding( + "PrivateKeyContext{Algorithm:RSA}[CipherContext{CipherAction:DECRYPT}, " + + "PrivateKeyContext{KeySize:3072}]", + "PrivateKey:RSA[KeyLength:3072, " + + "PublicKeyEncryption:RSA-3072[Decrypt:DECRYPT, " + + "KeyGeneration:KEYGENERATION, KeyLength:3072, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 69: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[CipherContext{CipherAction:DECRYPT}, " + + "PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[KeyAgreement:ECDH[EllipticCurve:secp256r1, " + + "KeyDerivation:KEYDERIVATION, Oid:1.3.132.1.12], " + + "KeyGeneration:KEYGENERATION]"), + // 74: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 4096); + finding( + "PrivateKeyContext{Algorithm:RSA}[CipherContext{CipherAction:DECRYPT}, " + + "PrivateKeyContext{KeySize:4096}]", + "PrivateKey:RSA[KeyLength:4096, " + + "PublicKeyEncryption:RSA-4096[Decrypt:DECRYPT, " + + "KeyGeneration:KEYGENERATION, KeyLength:4096, " + + "Oid:1.2.840.113549.1.1.1]]"), + // 79: X509_sign(cert, pkey, EVP_sha256()); + finding( + "SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}]", + "Signature:unknown[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Sign:SIGN]"), + // 83: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:VERIFY}, " + + "PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp256r1[EllipticCurve:secp256r1, " + + "Verify:VERIFY]]"), + // 89: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-384"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:VERIFY}, " + + "PrivateKeyContext{Curve:EC-P-384}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp384r1[EllipticCurve:secp384r1, " + + "Verify:VERIFY]]"), + // 94: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); + finding( + "PrivateKeyContext{Algorithm:RSA}[SignatureContext{SignatureAction:SIGN}, " + + "PrivateKeyContext{KeySize:3072}]", + "PrivateKey:RSA[KeyGeneration:KEYGENERATION, KeyLength:3072, " + + "Signature:RSA-PKCS1-1.5[KeyLength:3072, " + + "Oid:1.2.840.113549.1.1.1, Sign:SIGN]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLKeyUsageSigningTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyUsageTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyUsageTest.java new file mode 100644 index 000000000..c094cb353 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLKeyUsageTest.java @@ -0,0 +1,163 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The operations performed with a generated key are reported on that key, as the Python module + * reports the signature or key exchange made with a private key: a signature, key agreement, + * encryption or key encapsulation of the key's algorithm, with the digest or padding it uses. + */ +class OpenSSLKeyUsageTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); + finding( + "PrivateKeyContext{Algorithm:EC}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}], " + + "PrivateKeyContext{Curve:EC-P-256}]", + "PrivateKey:EC[KeyGeneration:KEYGENERATION, " + + "Signature:ECDSA-secp256r1-SHA-256[EllipticCurve:secp256r1, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.10045.4.3.2, Sign:SIGN]]"), + // 13: EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + finding( + "KeyContext{ValueAction:RSA}[KeyContext{KeySize:3072}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}[SignatureContext{SignatureAction:VERIFY}[CipherContext{ValueAction:RSA-PSS}, " + + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}], " + + "DigestContext{Algorithm:SHA-256}, " + + "DigestContext{ValueAction:SHA-384}]]]", + "PrivateKey:RSA[KeyGeneration:KEYGENERATION, KeyLength:3072, " + + "ProbabilisticSignatureScheme:RSA-PSS[KeyLength:3072, " + + "MaskGenerationFunction:MGF1[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.8], " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.10, SaltLength:256, Verify:VERIFY]]"), + // 27: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); + finding( + "PrivateKeyContext{Algorithm:X25519}[KeyContext{}[KeyContext{KeyAction:KDF}]]", + "PrivateKey:x25519[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "KeyDerivation:KEYDERIVATION, KeyGeneration:KEYGENERATION, " + + "Oid:1.3.101.110]]"), + // 35: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-384"); + finding( + "PrivateKeyContext{Algorithm:EC}[KeyContext{}[KeyContext{KeyAction:KDF}], " + + "PrivateKeyContext{Curve:EC-P-384}]", + "PrivateKey:EC[KeyAgreement:ECDH[EllipticCurve:secp384r1, " + + "KeyDerivation:KEYDERIVATION, Oid:1.3.132.1.12], " + + "KeyGeneration:KEYGENERATION]"), + // 43: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 2048); + finding( + "PrivateKeyContext{Algorithm:RSA}[KeyContext{}[CipherContext{CipherAction:ENCRYPT}, " + + "CipherContext{ValueAction:RSA-OAEP}], " + + "PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-OAEP[Encrypt:ENCRYPT, " + + "KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Oid:1.2.840.113549.1.1.7, Padding:OAEP]]"), + // 51: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "ML-KEM-768"); + finding( + "PrivateKeyContext{Algorithm:ML-KEM-768}[KeyContext{}[KeyContext{KeyAction:ENCAPSULATION}]]", + "PrivateKey:ML-KEM[KeyEncapsulationMechanism:ML-KEM-768[Encapsulate:ENCAPSULATE, " + + "KeyGeneration:KEYGENERATION, Oid:2.16.840.1.101.3.4.4.2, " + + "ParameterSetIdentifier:768]]"), + // 58: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "ED25519"); + finding( + "PrivateKeyContext{Algorithm:ED25519}[SignatureContext{SignatureAction:SIGN}]", + "PrivateKey:Ed25519[Signature:Ed25519[EllipticCurve:Edwards25519, " + + "KeyGeneration:KEYGENERATION, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], Oid:1.3.101.112, " + + "Sign:SIGN]]"), + // 65: EVP_DigestSignInit(mdctx, NULL, EVP_sha512(), NULL, other); + finding( + "SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-512}]", + "Signature:unknown[MessageDigest:SHA-512[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], " + + "Sign:SIGN]"), + // 66: return EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 4096); + finding( + "PrivateKeyContext{Algorithm:RSA}[PrivateKeyContext{KeySize:4096}]", + "PrivateKey:RSA[KeyLength:4096, " + + "PublicKeyEncryption:RSA-4096[KeyGeneration:KEYGENERATION, " + + "KeyLength:4096, Oid:1.2.840.113549.1.1.1]]"), + // 71: EVP_PKEY_CTX *kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL); + finding( + "KeyContext{ValueAction:DH}[KeyContext{KeySize:2048}, " + + "KeyContext{KeyAction:PRIVATE_KEY_GENERATION}[KeyContext{}[KeyContext{KeyAction:KDF}]]]", + "PrivateKey:FFDH[KeyAgreement:FFDH[KeyDerivation:KEYDERIVATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.3.1], " + + "KeyGeneration:KEYGENERATION, KeyLength:2048]"), + // 81: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t) 3072); + finding( + "PrivateKeyContext{Algorithm:RSA}[KeyContext{}[KeyContext{KeyAction:ENCAPSULATION}], " + + "PrivateKeyContext{KeySize:3072}]", + "PrivateKey:RSA[KeyEncapsulationMechanism:RSASVE[Encapsulate:ENCAPSULATE, " + + "KeyLength:3072], KeyGeneration:KEYGENERATION, KeyLength:3072]"), + // 88: EVP_PKEY *pkey = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); + finding( + "PrivateKeyContext{Algorithm:X25519}[KeyContext{}[KeyContext{KeyAction:ENCAPSULATION}]]", + "PrivateKey:x25519[KeyEncapsulationMechanism:DHKEM[Encapsulate:ENCAPSULATE, " + + "KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]], KeyGeneration:KEYGENERATION]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/keygen/OpenSSLKeyUsageTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLRawKeyTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLRawKeyTest.java new file mode 100644 index 000000000..90e8a62de --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLRawKeyTest.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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A key created from raw bytes is reported as a secret key holding its MAC algorithm, or as a + * private key holding its asymmetric algorithm, with its size, as the Java module reports a {@code + * SecretKeySpec}. The MAC computed with a MAC key through {@code EVP_DigestSign} is reported with + * the digest or cipher it uses. + */ +class OpenSSLRawKeyTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: EVP_PKEY *pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, 32); + finding( + "KeyContext{ValueAction:HMAC}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}], " + + "KeyContext{KeySize:256}]", + "SecretKey:HMAC[KeyLength:256, " + + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG]]"), + // 11: EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, key, + // 64); + finding( + "KeyContext{ValueAction:HMAC}[SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-512}], " + + "KeyContext{KeySize:512}]", + "SecretKey:HMAC[KeyLength:512, " + + "Mac:HMAC-SHA-512[MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], Oid:1.2.840.113549.2.11, " + + "Tag:TAG]]"), + // 16: EVP_PKEY *pkey = EVP_PKEY_new_CMAC_key(NULL, key, 16, EVP_aes_128_cbc()); + finding( + "KeyContext{ValueAction:CMAC}[SignatureContext{SignatureAction:SIGN}, " + + "KeyContext{KeySize:128}, CipherContext{ValueAction:AES-128-CBC}]", + "SecretKey:CMAC[KeyLength:128, " + + "Mac:CMAC-AES[BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], Tag:TAG]]"), + // 21: EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "SIPHASH", NULL, + // key, 16); + finding( + "KeyContext{Algorithm:SIPHASH}[KeyContext{KeySize:128}]", + "SecretKey:SipHash[KeyLength:128, Mac:SipHash[DigestSize:64, KeyLength:128, " + + "Tag:TAG]]"), + // 25: EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + // priv, 32); + finding( + "KeyContext{ValueAction:ED25519}[SignatureContext{SignatureAction:SIGN}, " + + "KeyContext{KeySize:256}]", + "PrivateKey:Ed25519[KeyLength:256, " + + "Signature:Ed25519[EllipticCurve:Edwards25519, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3], Oid:1.3.101.112, Sign:SIGN]]"), + // 30: EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + // pub, 32); + finding( + "PublicKeyContext{ValueAction:ED25519}[SignatureContext{SignatureAction:VERIFY}, " + + "PublicKeyContext{KeySize:256}]", + "PublicKey:Ed25519[KeyLength:256, " + + "Signature:Ed25519[EllipticCurve:Edwards25519, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3], Oid:1.3.101.112, Verify:VERIFY]]"), + // 35: EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "X25519", NULL, + // pub, 32); + finding( + "PublicKeyContext{Algorithm:X25519}[PublicKeyContext{KeySize:256}]", + "PublicKey:x25519[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110], KeyLength:256]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/keygen/OpenSSLRawKeyTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTest.java new file mode 100644 index 000000000..ad8a157a3 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTest.java @@ -0,0 +1,113 @@ +/* + * 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.openssl.keygen; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The RSA-PSS key generation settings, which are only accepted on an RSA-PSS key generation + * context, report RSA-PSS with the digest, MGF1 digest or salt length they restrict the key to when + * the context is created elsewhere, as {@code EVP_PKEY_CTX_set_rsa_pss_saltlen} does for a signing + * context. On a context created in the analyzed code they are reported with the key, and not again + * on their own. + */ +class OpenSSLRsaPssKeyGenSettingsTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx, EVP_sha256()); + finding( + "SignatureContext{ValueAction:RSA-PSS}[DigestContext{ValueAction:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10]"), + // 6: EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(ctx, EVP_sha384()); + finding( + "SignatureContext{ValueAction:RSA-PSS}[DigestContext{ValueAction:SHA-384}]", + "ProbabilisticSignatureScheme:RSA-PSS[MaskGenerationFunction:MGF1[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.8], Oid:1.2.840.113549.1.1.10]"), + // 7: EVP_PKEY_CTX_set_rsa_pss_keygen_md_name(ctx, "SHA512", NULL); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{Algorithm:SHA-512}]", + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-512[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], " + + "Oid:1.2.840.113549.1.1.10]"), + // 8: EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name(ctx, "SHA224"); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{Algorithm:SHA-224}]", + "ProbabilisticSignatureScheme:RSA-PSS[MaskGenerationFunction:MGF1[MessageDigest:SHA-224[BlockSize:512, " + + "Digest:DIGEST, DigestSize:224, Oid:2.16.840.1.101.3.4.2.4], " + + "Oid:1.2.840.113549.1.1.8], Oid:1.2.840.113549.1.1.10]"), + // 9: EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(ctx, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10, " + + "SaltLength:256]"), + // 13: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); + finding( + "KeyContext{ValueAction:RSA-PSS}[KeyContext{KeySize:3072}, " + + "KeyContext{}[DigestContext{ValueAction:SHA-512}], " + + "KeyContext{SaltSize:512}]", + "ProbabilisticSignatureScheme:RSA-PSS[KeyLength:3072, " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], " + + "Oid:1.2.840.113549.1.1.10, SaltLength:512]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/keygen/OpenSSLRsaPssKeyGenSettingsTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherTest.java new file mode 100644 index 000000000..3fb829bfd --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyCipherTest.java @@ -0,0 +1,307 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers all rule entries of the legacy cipher classes ({@link OpenSSLLegacyCipherAes}, {@link + * OpenSSLLegacyCipherDes}, ...). The key setup functions report the key size given as their + * argument. An encryption function reports the key size its key was set up with, and the key setup + * is then reported with it; a key set up but not used is reported on its own, and a key given to + * the function has no key size. + * + *

The fixture calls every function named by the rules, including the aliases of a rule matching + * several functions ({@code forMethods(a, b, c)}). + */ +class OpenSSLLegacyCipherTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 20: AES_ecb_encrypt(buf, buf, &ak, 1); + finding( + "CipherContext{ValueAction:AES-ECB}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-ECB[BlockSize:128, KeyLength:256, Mode:ECB, Oid:2.16.840.1.101.3.4.1.41]"), + // 21: AES_cbc_encrypt(buf, buf, 64, &ak, iv, 1); + finding( + "CipherContext{ValueAction:AES-CBC}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-CBC[BlockSize:128, KeyLength:256, Mode:CBC, Oid:2.16.840.1.101.3.4.1.42]"), + // 22: AES_cfb128_encrypt(buf, buf, 64, &ak, iv, &num, 1); + finding( + "CipherContext{ValueAction:AES-CFB128}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-CFB128[BlockSize:128, KeyLength:256, Mode:CFB128, Oid:2.16.840.1.101.3.4.1.44]"), + // 23: AES_ofb128_encrypt(buf, buf, 64, &ak, iv, &num); + finding( + "CipherContext{ValueAction:AES-OFB}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-OFB[BlockSize:128, KeyLength:256, Mode:OFB, Oid:2.16.840.1.101.3.4.1.43]"), + // 24: AES_ige_encrypt(buf, buf, 64, &ak, iv, 1); + finding( + "CipherContext{ValueAction:AES-IGE}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-IGE[BlockSize:128, KeyLength:256, Mode:IGE, Oid:2.16.840.1.101.3.4.1.4]"), + // 25: AES_cfb1_encrypt(buf, buf, 64, &ak, iv, &num, 1); + finding( + "CipherContext{ValueAction:AES-CFB1}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-CFB1[BlockSize:128, KeyLength:256, Mode:CFB1, Oid:2.16.840.1.101.3.4.1.4]"), + // 26: AES_cfb8_encrypt(buf, buf, 64, &ak, iv, &num, 1); + finding( + "CipherContext{ValueAction:AES-CFB8}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-CFB8[BlockSize:128, KeyLength:256, Mode:CFB8, Oid:2.16.840.1.101.3.4.1.4]"), + // 27: AES_bi_ige_encrypt(buf, buf, 64, &ak, &ak, iv, 1); + finding( + "CipherContext{ValueAction:AES-BI-IGE}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-BI-IGE[BlockSize:128, KeyLength:256, Mode:BI-IGE, Oid:2.16.840.1.101.3.4.1.4]"), + // 29: AES_wrap_key(&ak, NULL, buf, buf, 32); + finding( + "CipherContext{ValueAction:AES-WRAP}[CipherContext{ValueAction:AES}[CipherContext{KeySize:256}]]", + "BlockCipher:AES-256-WRAP[BlockSize:128, KeyLength:256, Mode:WRAP, Oid:2.16.840.1.101.3.4.1.45]"), + // 30: AES_unwrap_key(&dk, NULL, buf, buf, 40); + finding( + "CipherContext{ValueAction:AES-WRAP}[CipherContext{ValueAction:AES}[CipherContext{KeySize:192}]]", + "BlockCipher:AES-192-WRAP[BlockSize:128, KeyLength:192, Mode:WRAP, Oid:2.16.840.1.101.3.4.1.25]"), + // 40: AES_ecb_encrypt(buf, buf, &k, 1); + finding( + "CipherContext{ValueAction:AES-ECB}[CipherContext{ValueAction:AES}[CipherContext{KeySize:128}, CipherContext{KeySize:256}]]", + "BlockCipher:AES-128-ECB[BlockSize:128, KeyLength:128, Mode:ECB, Oid:2.16.840.1.101.3.4.1.1]", + "BlockCipher:AES-256-ECB[BlockSize:128, KeyLength:256, Mode:ECB," + + " Oid:2.16.840.1.101.3.4.1.41]"), + // 46: AES_set_encrypt_key(buf, 128, &unused); + finding( + "CipherContext{ValueAction:AES}[CipherContext{KeySize:128}]", + "BlockCipher:AES-128[BlockSize:128, KeyLength:128, Oid:2.16.840.1.101.3.4.1]"), + // 50: AES_ecb_encrypt(buf, buf, key, 1); + finding( + "CipherContext{ValueAction:AES-ECB}", + "BlockCipher:AES-ECB[BlockSize:128, Mode:ECB, Oid:2.16.840.1.101.3.4.1]"), + // 60: DES_ecb_encrypt(&dc, &dc, &ds, 1); + finding( + "CipherContext{ValueAction:DES-ECB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DES-56-ECB[BlockSize:64, KeyLength:56, Mode:ECB]"), + // 61: DES_ede3_cbc_encrypt(buf, buf, 64, &ds, &ds, &ds, &dc, 1); + finding( + "CipherContext{ValueAction:3DES-CBC}[CipherContext{ValueAction:DES}]", + "BlockCipher:DESede168-CBC[BlockSize:64, KeyLength:168, Mode:CBC]"), + // 62: DES_ecb3_encrypt(&dc, &dc, &ds, &ds, &ds, 1); + finding( + "CipherContext{ValueAction:3DES-ECB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DESede168-ECB[BlockSize:64, KeyLength:168, Mode:ECB]"), + // 63: DES_ede3_cfb64_encrypt(buf, buf, 64, &ds, &ds, &ds, &dc, &num, 1); + finding( + "CipherContext{ValueAction:3DES-CFB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DESede168-CFB[BlockSize:64, KeyLength:168, Mode:CFB]"), + // 64: DES_ofb64_encrypt(buf, buf, 64, &ds, &dc, &num); + finding( + "CipherContext{ValueAction:DES-OFB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DES-56-OFB[BlockSize:64, KeyLength:56, Mode:OFB]"), + // 67: DES_ncbc_encrypt(buf, buf, 64, &ds, &dc, 1); + finding( + "CipherContext{ValueAction:DES-CBC}[CipherContext{ValueAction:DES}]", + "BlockCipher:DES-56-CBC[BlockSize:64, KeyLength:56, Mode:CBC]"), + // 68: DES_cbc_encrypt(buf, buf, 64, &ds, &dc, 1); + finding( + "CipherContext{ValueAction:DES-CBC}[CipherContext{ValueAction:DES}]", + "BlockCipher:DES-56-CBC[BlockSize:64, KeyLength:56, Mode:CBC]"), + // 69: DES_cfb64_encrypt(buf, buf, 64, &ds, &dc, &num, 1); + finding( + "CipherContext{ValueAction:DES-CFB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DES-56-CFB[BlockSize:64, KeyLength:56, Mode:CFB]"), + // 70: DES_cfb_encrypt(buf, buf, 8, 64, &ds, &dc, 1); + finding( + "CipherContext{ValueAction:DES-CFB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DES-56-CFB[BlockSize:64, KeyLength:56, Mode:CFB]"), + // 71: DES_ede3_cfb_encrypt(buf, buf, 8, 64, &ds, &ds, &ds, &dc, 1); + finding( + "CipherContext{ValueAction:3DES-CFB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DESede168-CFB[BlockSize:64, KeyLength:168, Mode:CFB]"), + // 72: DES_ede3_ofb64_encrypt(buf, buf, 64, &ds, &ds, &ds, &dc, &num); + finding( + "CipherContext{ValueAction:3DES-OFB}[CipherContext{ValueAction:DES}]", + "BlockCipher:DESede168-OFB[BlockSize:64, KeyLength:168, Mode:OFB]"), + // 73: DES_xcbc_encrypt(buf, buf, 64, &ds, &dc, &dc, &dc, 1); + finding( + "CipherContext{ValueAction:DES-XCBC}[CipherContext{ValueAction:DES}]", + "BlockCipher:DESX-184-CBC[BlockSize:64, KeyLength:184, Mode:CBC]"), + // 82: BF_ecb_encrypt(buf, buf, &bk, 1); + finding( + "CipherContext{ValueAction:BLOWFISH-ECB}[CipherContext{ValueAction:BLOWFISH}[CipherContext{KeySize:160}]]", + "BlockCipher:Blowfish-160-ECB[KeyLength:160, Mode:ECB]"), + // 83: BF_cbc_encrypt(buf, buf, 64, &bk, iv, 1); + finding( + "CipherContext{ValueAction:BLOWFISH-CBC}[CipherContext{ValueAction:BLOWFISH}[CipherContext{KeySize:160}]]", + "BlockCipher:Blowfish-160-CBC[KeyLength:160, Mode:CBC]"), + // 84: BF_cfb64_encrypt(buf, buf, 64, &bk, iv, &num, 1); + finding( + "CipherContext{ValueAction:BLOWFISH-CFB}[CipherContext{ValueAction:BLOWFISH}[CipherContext{KeySize:160}]]", + "BlockCipher:Blowfish-160-CFB[KeyLength:160, Mode:CFB]"), + // 85: BF_ofb64_encrypt(buf, buf, 64, &bk, iv, &num); + finding( + "CipherContext{ValueAction:BLOWFISH-OFB}[CipherContext{ValueAction:BLOWFISH}[CipherContext{KeySize:160}]]", + "BlockCipher:Blowfish-160-OFB[KeyLength:160, Mode:OFB]"), + // 96: RC4(&r4, 64, buf, buf); + finding( + "CipherContext{ValueAction:RC4}[CipherContext{ValueAction:RC4}[CipherContext{KeySize:128}]]", + "StreamCipher:RC4-128[KeyLength:128]"), + // 98: RC2_ecb_encrypt(buf, buf, &r2, 1); + finding( + "CipherContext{ValueAction:RC2-ECB}[CipherContext{ValueAction:RC2}[CipherContext{KeySize:64}]]", + "BlockCipher:RC2-64-ECB[KeyLength:64, Mode:ECB]"), + // 99: RC2_cbc_encrypt(buf, buf, 64, &r2, iv, 1); + finding( + "CipherContext{ValueAction:RC2-CBC}[CipherContext{ValueAction:RC2}[CipherContext{KeySize:64}]]", + "BlockCipher:RC2-64-CBC[KeyLength:64, Mode:CBC]"), + // 100: RC2_cfb64_encrypt(buf, buf, 64, &r2, iv, &num, 1); + finding( + "CipherContext{ValueAction:RC2-CFB}[CipherContext{ValueAction:RC2}[CipherContext{KeySize:64}]]", + "BlockCipher:RC2-64-CFB[KeyLength:64, Mode:CFB]"), + // 101: RC2_ofb64_encrypt(buf, buf, 64, &r2, iv, &num); + finding( + "CipherContext{ValueAction:RC2-OFB}[CipherContext{ValueAction:RC2}[CipherContext{KeySize:64}]]", + "BlockCipher:RC2-64-OFB[KeyLength:64, Mode:OFB]"), + // 103: RC5_32_ecb_encrypt(buf, buf, &r5, 1); + finding( + "CipherContext{ValueAction:RC5-ECB}[CipherContext{ValueAction:RC5}[CipherContext{KeySize:80}]]", + "BlockCipher:RC5-80-ECB[KeyLength:80, Mode:ECB]"), + // 104: RC5_32_cbc_encrypt(buf, buf, 64, &r5, iv, 1); + finding( + "CipherContext{ValueAction:RC5-CBC}[CipherContext{ValueAction:RC5}[CipherContext{KeySize:80}]]", + "BlockCipher:RC5-80-CBC[KeyLength:80, Mode:CBC]"), + // 105: RC5_32_cfb64_encrypt(buf, buf, 64, &r5, iv, &num, 1); + finding( + "CipherContext{ValueAction:RC5-CFB}[CipherContext{ValueAction:RC5}[CipherContext{KeySize:80}]]", + "BlockCipher:RC5-80-CFB[KeyLength:80, Mode:CFB]"), + // 106: RC5_32_ofb64_encrypt(buf, buf, 64, &r5, iv, &num); + finding( + "CipherContext{ValueAction:RC5-OFB}[CipherContext{ValueAction:RC5}[CipherContext{KeySize:80}]]", + "BlockCipher:RC5-80-OFB[KeyLength:80, Mode:OFB]"), + // 115: CAST_ecb_encrypt(buf, buf, &ck, 1); + finding( + "CipherContext{ValueAction:CAST5-ECB}[CipherContext{ValueAction:CAST5}[CipherContext{KeySize:80}]]", + "BlockCipher:CAST5-80-ECB[BlockSize:64, KeyLength:80, Mode:ECB]"), + // 116: CAST_cbc_encrypt(buf, buf, 64, &ck, iv, 1); + finding( + "CipherContext{ValueAction:CAST5-CBC}[CipherContext{ValueAction:CAST5}[CipherContext{KeySize:80}]]", + "BlockCipher:CAST5-80-CBC[BlockSize:64, KeyLength:80, Mode:CBC]"), + // 117: CAST_cfb64_encrypt(buf, buf, 64, &ck, iv, &num, 1); + finding( + "CipherContext{ValueAction:CAST5-CFB}[CipherContext{ValueAction:CAST5}[CipherContext{KeySize:80}]]", + "BlockCipher:CAST5-80-CFB[BlockSize:64, KeyLength:80, Mode:CFB]"), + // 118: CAST_ofb64_encrypt(buf, buf, 64, &ck, iv, &num); + finding( + "CipherContext{ValueAction:CAST5-OFB}[CipherContext{ValueAction:CAST5}[CipherContext{KeySize:80}]]", + "BlockCipher:CAST5-80-OFB[BlockSize:64, KeyLength:80, Mode:OFB]"), + // 128: IDEA_ecb_encrypt(buf, buf, &ik); + finding( + "CipherContext{ValueAction:IDEA-ECB}[CipherContext{ValueAction:IDEA}, CipherContext{ValueAction:IDEA}]", + "BlockCipher:IDEA-ECB[Mode:ECB]"), + // 129: IDEA_cbc_encrypt(buf, buf, 64, &ik, iv, 1); + finding( + "CipherContext{ValueAction:IDEA-CBC}[CipherContext{ValueAction:IDEA}, CipherContext{ValueAction:IDEA}]", + "BlockCipher:IDEA-CBC[Mode:CBC]"), + // 130: IDEA_cfb64_encrypt(buf, buf, 64, &ik, iv, &num, 1); + finding( + "CipherContext{ValueAction:IDEA-CFB}[CipherContext{ValueAction:IDEA}, CipherContext{ValueAction:IDEA}]", + "BlockCipher:IDEA-CFB[Mode:CFB]"), + // 131: IDEA_ofb64_encrypt(buf, buf, 64, &ik, iv, &num); + finding( + "CipherContext{ValueAction:IDEA-OFB}[CipherContext{ValueAction:IDEA}, CipherContext{ValueAction:IDEA}]", + "BlockCipher:IDEA-OFB[Mode:OFB]"), + // 141: Camellia_ecb_encrypt(buf, buf, &cam, 1); + finding( + "CipherContext{ValueAction:CAMELLIA-ECB}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-ECB[KeyLength:256, Mode:ECB]"), + // 142: Camellia_cbc_encrypt(buf, buf, 64, &cam, iv, 1); + finding( + "CipherContext{ValueAction:CAMELLIA-CBC}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-CBC[KeyLength:256, Mode:CBC]"), + // 143: Camellia_cfb128_encrypt(buf, buf, 64, &cam, iv, &num, 1); + finding( + "CipherContext{ValueAction:CAMELLIA-CFB128}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-CFB128[KeyLength:256, Mode:CFB128]"), + // 144: Camellia_cfb1_encrypt(buf, buf, 64, &cam, iv, &num, 1); + finding( + "CipherContext{ValueAction:CAMELLIA-CFB1}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-CFB1[KeyLength:256, Mode:CFB1]"), + // 145: Camellia_cfb8_encrypt(buf, buf, 64, &cam, iv, &num, 1); + finding( + "CipherContext{ValueAction:CAMELLIA-CFB8}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-CFB8[KeyLength:256, Mode:CFB8]"), + // 146: Camellia_ofb128_encrypt(buf, buf, 64, &cam, iv, &num); + finding( + "CipherContext{ValueAction:CAMELLIA-OFB}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-OFB[KeyLength:256, Mode:OFB]"), + // 147: Camellia_ctr128_encrypt(buf, buf, 64, &cam, iv, buf, &unum); + finding( + "CipherContext{ValueAction:CAMELLIA-CTR}[CipherContext{ValueAction:CAMELLIA}[CipherContext{KeySize:256}]]", + "BlockCipher:CAMELLIA-256-CTR[KeyLength:256, Mode:CTR]"), + // 156: SEED_ecb_encrypt(buf, buf, &sk, 1); + finding( + "CipherContext{ValueAction:SEED-ECB}[CipherContext{ValueAction:SEED}]", + "BlockCipher:SEED-128-ECB[BlockSize:128, KeyLength:128, Mode:ECB]"), + // 157: SEED_cbc_encrypt(buf, buf, 64, &sk, iv, 1); + finding( + "CipherContext{ValueAction:SEED-CBC}[CipherContext{ValueAction:SEED}]", + "BlockCipher:SEED-128-CBC[BlockSize:128, KeyLength:128, Mode:CBC]"), + // 158: SEED_cfb128_encrypt(buf, buf, 64, &sk, iv, &num, 1); + finding( + "CipherContext{ValueAction:SEED-CFB}[CipherContext{ValueAction:SEED}]", + "BlockCipher:SEED-128-CFB[BlockSize:128, KeyLength:128, Mode:CFB]"), + // 159: SEED_ofb128_encrypt(buf, buf, 64, &sk, iv, &num); + finding( + "CipherContext{ValueAction:SEED-OFB}[CipherContext{ValueAction:SEED}]", + "BlockCipher:SEED-128-OFB[BlockSize:128, KeyLength:128, Mode:OFB]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyCipherTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDhTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDhTest.java new file mode 100644 index 000000000..a0b0d895a --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDhTest.java @@ -0,0 +1,90 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers all rule entries in {@link OpenSSLLegacyDh}. */ +class OpenSSLLegacyDhTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 9: DH_generate_key(dh); + finding( + "PrivateKeyContext{ValueAction:DH}[KeyContext{ValueAction:DH}[KeyContext{KeySize:2048}]]", + "PrivateKey:FFDH[KeyLength:2048, " + + "PublicKeyEncryption:FFDH-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.3.1]]"), + // 10: DH_get_1024_160(); + finding( + "KeyContext{ValueAction:DH-1024-160}", + "PublicKeyEncryption:FFDH-1024[KeyLength:1024, Oid:1.2.840.113549.1.3.1]"), + // 11: DH_get_2048_224(); + finding( + "KeyContext{ValueAction:DH-2048-224}", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 12: DH_get_2048_256(); + finding( + "KeyContext{ValueAction:DH-2048-256}", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 13: DH_compute_key(secret, pub_key, dh); + finding( + "KeyAgreementContext{ValueAction:DH}", + "KeyAgreement:FFDH[Oid:1.2.840.113549.1.3.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyDhTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestTest.java new file mode 100644 index 000000000..fdd9c5835 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDigestTest.java @@ -0,0 +1,168 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers all rule entries in {@link OpenSSLLegacyDigest}. */ +class OpenSSLLegacyDigestTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 13: MD5_Init(&mc); + finding( + "DigestContext{ValueAction:MD5}", + "MessageDigest:MD5[BlockSize:512, Digest:DIGEST, DigestSize:128]"), + // 14: MD5(buf, 64, buf); + finding( + "DigestContext{ValueAction:MD5}", + "MessageDigest:MD5[BlockSize:512, Digest:DIGEST, DigestSize:128]"), + // 16: SHA1_Init(&s1); + finding( + "DigestContext{ValueAction:SHA-1}", + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]"), + // 17: SHA1(buf, 64, buf); + finding( + "DigestContext{ValueAction:SHA-1}", + "MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, DigestSize:160, " + + "Oid:1.3.14.3.2.26]"), + // 19: SHA224_Init(&s2); + finding( + "DigestContext{ValueAction:SHA-224}", + "MessageDigest:SHA-224[BlockSize:512, Digest:DIGEST, DigestSize:224, " + + "Oid:2.16.840.1.101.3.4.2.4]"), + // 20: SHA224(buf, 64, buf); + finding( + "DigestContext{ValueAction:SHA-224}", + "MessageDigest:SHA-224[BlockSize:512, Digest:DIGEST, DigestSize:224, " + + "Oid:2.16.840.1.101.3.4.2.4]"), + // 22: SHA256_Init(&s2); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 23: SHA256(buf, 64, buf); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 25: SHA384_Init(&s5); + finding( + "DigestContext{ValueAction:SHA-384}", + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]"), + // 26: SHA384(buf, 64, buf); + finding( + "DigestContext{ValueAction:SHA-384}", + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]"), + // 28: SHA512_Init(&s5); + finding( + "DigestContext{ValueAction:SHA-512}", + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]"), + // 29: SHA512(buf, 64, buf); + finding( + "DigestContext{ValueAction:SHA-512}", + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, DigestSize:512, " + + "Oid:2.16.840.1.101.3.4.2.3]"), + // 31: RIPEMD160_Init(&r); + finding( + "DigestContext{ValueAction:RIPEMD160}", + "MessageDigest:RIPEMD-160[Digest:DIGEST, DigestSize:160]"), + // 32: RIPEMD160(buf, 64, buf); + finding( + "DigestContext{ValueAction:RIPEMD160}", + "MessageDigest:RIPEMD-160[Digest:DIGEST, DigestSize:160]"), + // 34: WHIRLPOOL(buf, 64, buf); + finding( + "DigestContext{ValueAction:WHIRLPOOL}", + "MessageDigest:Whirlpool[BlockSize:512, Digest:DIGEST, DigestSize:512, " + + "NumberOfIterations:10]"), + // 35: WHIRLPOOL_Init(NULL); + finding( + "DigestContext{ValueAction:WHIRLPOOL}", + "MessageDigest:Whirlpool[BlockSize:512, Digest:DIGEST, DigestSize:512, " + + "NumberOfIterations:10]"), + // 37: MD2(buf, 64, buf); + finding( + "DigestContext{ValueAction:MD2}", + "MessageDigest:MD2[BlockSize:128, Digest:DIGEST, DigestSize:128]"), + // 38: MD2_Init(NULL); + finding( + "DigestContext{ValueAction:MD2}", + "MessageDigest:MD2[BlockSize:128, Digest:DIGEST, DigestSize:128]"), + // 40: MD4(buf, 64, buf); + finding( + "DigestContext{ValueAction:MD4}", + "MessageDigest:MD4[BlockSize:512, Digest:DIGEST, DigestSize:128]"), + // 41: MD4_Init(NULL); + finding( + "DigestContext{ValueAction:MD4}", + "MessageDigest:MD4[BlockSize:512, Digest:DIGEST, DigestSize:128]"), + // 43: MDC2(buf, 64, buf); + finding( + "DigestContext{ValueAction:MDC2}", + "MessageDigest:MDC2[BlockSize:64, Digest:DIGEST, DigestSize:128]"), + // 44: MDC2_Init(NULL); + finding( + "DigestContext{ValueAction:MDC2}", + "MessageDigest:MDC2[BlockSize:64, Digest:DIGEST, DigestSize:128]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyDigestTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDsaTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDsaTest.java new file mode 100644 index 000000000..b0bc6893b --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyDsaTest.java @@ -0,0 +1,82 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers all rule entries in {@link OpenSSLLegacyDsa}. */ +class OpenSSLLegacyDsaTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 9: DSA_generate_key(dsa); + finding( + "PrivateKeyContext{ValueAction:DSA}[KeyContext{ValueAction:DSA}[KeyContext{KeySize:2048}]]", + "PrivateKey:DSA[KeyLength:2048, " + + "Signature:DSA-2048[KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Oid:1.2.840.10040.4.1]]"), + // 10: DSA_sign(0, buf, 32, buf, &siglen, dsa); + finding( + "SignatureContext{ValueAction:DSA-SIGN}", + "Signature:DSA[Oid:1.2.840.10040.4.1]"), + // 11: DSA_do_sign(buf, 32, dsa); + finding( + "SignatureContext{ValueAction:DSA-SIGN}", + "Signature:DSA[Oid:1.2.840.10040.4.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyDsaTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyEcTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyEcTest.java new file mode 100644 index 000000000..3bbe6e950 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyEcTest.java @@ -0,0 +1,135 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers all rule entries in {@link OpenSSLLegacyEc}. */ +class OpenSSLLegacyEcTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 18: EC_KEY_new_by_curve_name(415); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 19: EC_KEY_new_by_curve_name_ex(NULL, NULL, 415); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 20: EC_KEY_generate_key(key); + finding( + "PrivateKeyContext{ValueAction:EC}", + "PrivateKey:EC[PublicKeyEncryption:EC[KeyGeneration:KEYGENERATION, " + + "Oid:1.2.840.10045.2.1]]"), + // 25: EC_KEY_new_by_curve_name(p256_nid); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 29: EC_KEY_new_by_curve_name(CurveNid::P256); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 31: ECDSA_sign(0, buf, 32, buf, &siglen, key); + finding("SignatureContext{ValueAction:ECDSA-SIGN}", "Signature:ECDSA"), + // 32: ECDSA_do_sign(buf, 32, key); + finding("SignatureContext{ValueAction:ECDSA-SIGN}", "Signature:ECDSA"), + // 33: ECDSA_sign_ex(0, buf, 32, buf, &siglen, bn, bn, key); + finding("SignatureContext{ValueAction:ECDSA-SIGN}", "Signature:ECDSA"), + // 34: ECDSA_do_sign_ex(buf, 32, bn, bn, key); + finding("SignatureContext{ValueAction:ECDSA-SIGN}", "Signature:ECDSA"), + // 36: EC_GROUP_new_by_curve_name(415); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 37: EC_GROUP_new_by_curve_name_ex(NULL, NULL, 415); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 38: EC_GROUP_new_curve_GFp(bn, bn, bn, ctx); + finding( + "KeyContext{ValueAction:EC}", + "PublicKeyEncryption:EC[Oid:1.2.840.10045.2.1]"), + // 39: EC_GROUP_new_curve_GF2m(bn, bn, bn, ctx); + finding( + "KeyContext{ValueAction:EC}", + "PublicKeyEncryption:EC[Oid:1.2.840.10045.2.1]"), + // 40: EC_GROUP_new_from_ecparameters(NULL); + finding( + "KeyContext{ValueAction:EC}", + "PublicKeyEncryption:EC[Oid:1.2.840.10045.2.1]"), + // 41: EC_GROUP_new_from_ecpkparameters(NULL); + finding( + "KeyContext{ValueAction:EC}", + "PublicKeyEncryption:EC[Oid:1.2.840.10045.2.1]"), + // 42: EC_GROUP_new_from_params(NULL, NULL, NULL); + finding( + "KeyContext{ValueAction:EC}", + "PublicKeyEncryption:EC[Oid:1.2.840.10045.2.1]"), + // 44: ECDH_compute_key(buf, sizeof(buf), pt, key, NULL); + finding( + "KeyAgreementContext{ValueAction:ECDH}", + "KeyAgreement:ECDH[Oid:1.3.132.1.12]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyEcTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyKeyTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyKeyTest.java new file mode 100644 index 000000000..597272f26 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyKeyTest.java @@ -0,0 +1,136 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The legacy key generation functions report a private key holding the algorithm, with the key size + * they are given, or with the group, parameters or curve of the object the key is generated for. + * Domain parameters and EC key objects on their own are reported as the algorithm. + */ +class OpenSSLLegacyKeyTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 8: RSA_generate_key_ex(rsa, 3072, e, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:3072}]", + "PrivateKey:RSA[KeyLength:3072, " + + "PublicKeyEncryption:RSA-3072[KeyGeneration:KEYGENERATION, " + + "KeyLength:3072, Oid:1.2.840.113549.1.1.1]]"), + // 9: RSA *old = RSA_generate_key(1024, 65537, NULL, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:1024}]", + "PrivateKey:RSA[KeyLength:1024, " + + "PublicKeyEncryption:RSA-1024[KeyGeneration:KEYGENERATION, " + + "KeyLength:1024, Oid:1.2.840.113549.1.1.1]]"), + // 11: RSA_generate_multi_prime_key(multi, 4096, 3, e, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:4096}]", + "PrivateKey:RSA[KeyLength:4096, " + + "PublicKeyEncryption:RSA-4096[KeyGeneration:KEYGENERATION, " + + "KeyLength:4096, Oid:1.2.840.113549.1.1.1]]"), + // 16: DH_generate_key(dh); + finding( + "PrivateKeyContext{ValueAction:DH}[KeyContext{ValueAction:DH-2048-256}]", + "PrivateKey:FFDH[KeyLength:2048, " + + "PublicKeyEncryption:FFDH-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.3.1]]"), + // 20: DH *unrelated = DH_get_1024_160(); + finding( + "KeyContext{ValueAction:DH-1024-160}", + "PublicKeyEncryption:FFDH-1024[KeyLength:1024, Oid:1.2.840.113549.1.3.1]"), + // 23: DH_get_2048_224(); + finding( + "KeyContext{ValueAction:DH-2048-224}", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 24: DH_generate_key(dh); + finding( + "PrivateKeyContext{ValueAction:DH}[KeyContext{ValueAction:DH}[KeyContext{KeySize:3072}]]", + "PrivateKey:FFDH[KeyLength:3072, " + + "PublicKeyEncryption:FFDH-3072[KeyGeneration:KEYGENERATION, " + + "KeyLength:3072, Oid:1.2.840.113549.1.3.1]]"), + // 30: DH_generate_key(dh); + finding( + "PrivateKeyContext{ValueAction:DH}[KeyContext{ValueAction:DH}[KeyContext{KeySize:2048}]]", + "PrivateKey:FFDH[KeyLength:2048, " + + "PublicKeyEncryption:FFDH-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.3.1]]"), + // 36: DSA_generate_key(dsa); + finding( + "PrivateKeyContext{ValueAction:DSA}[KeyContext{ValueAction:DSA}[KeyContext{KeySize:2048}]]", + "PrivateKey:DSA[KeyLength:2048, " + + "Signature:DSA-2048[KeyGeneration:KEYGENERATION, KeyLength:2048, " + + "Oid:1.2.840.10040.4.1]]"), + // 41: EC_KEY_generate_key(key); + finding( + "PrivateKeyContext{ValueAction:EC}[KeyContext{ValueAction:EC-prime256v1}]", + "PrivateKey:EC-secp256r1[PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "KeyGeneration:KEYGENERATION, Oid:1.2.840.10045.2.1]]"), + // 48: EC_KEY_generate_key(key); + finding( + "PrivateKeyContext{ValueAction:EC}[KeyContext{}[KeyContext{ValueAction:EC-secp384r1}]]", + "PrivateKey:EC-secp384r1[PublicKeyEncryption:EC-secp384r1[EllipticCurve:secp384r1, " + + "KeyGeneration:KEYGENERATION, Oid:1.2.840.10045.2.1]]"), + // 52: EC_GROUP *group = EC_GROUP_new_curve_GFp(p, a, b, NULL); + finding( + "KeyContext{ValueAction:EC}", + "PublicKeyEncryption:EC[Oid:1.2.840.10045.2.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyKeyTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTest.java new file mode 100644 index 000000000..ce9fd6fc3 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTest.java @@ -0,0 +1,102 @@ +/* + * 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.openssl.legacy; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Signature; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * An operation of the legacy API is reported with the key it is made with when the key is set up in + * the analyzed code: a cipher with its key schedule, and a signature or key agreement with the key + * generated for it, as an operation of the EVP API is reported with its key. + */ +class OpenSSLLegacyKeyUsageTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/legacy/OpenSSLLegacyKeyUsageTestFile.cc", + new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(OpenSSLLegacyKeyUsageTest::operationsOf) + .containsExactly( + List.of("Mode:ECB", "KeyLength:56"), + List.of("Mode:ECB", "KeyLength:128"), + List.of("Signature:ECDSA-secp256r1"), + List.of("KeyAgreement:ECDH[EllipticCurve:secp384r1]"), + List.of("Signature:RSA-PKCS1-1.5-SHA-256"), + List.of("Signature:DSA-2048"), + List.of("KeyAgreement:FFDH[KeyLength:2048]")); + } + + /** A key agreement with the curve or the key length of its key, any other node by name. */ + @Nonnull + private static String describe(@Nonnull INode node) { + if (!node.is(KeyAgreement.class)) { + return node.asString(); + } + return node.asString() + + node.getChildren().values().stream() + .filter(child -> child.is(EllipticCurve.class) || child.is(KeyLength.class)) + .map(child -> child.getKind().getSimpleName() + ":" + child.asString()) + .toList(); + } + + /** + * The operations a key is reported with (its signatures and key agreements), or the mode and + * key length of a cipher. + */ + @Nonnull + private static List operationsOf(@Nonnull INode node) { + return node.getChildren().values().stream() + .filter( + child -> + child.is(Signature.class) + || child.is(KeyAgreement.class) + || child.is(Mode.class) + || (child.is(KeyLength.class) + && node.is(BlockCipher.class))) + .map(child -> child.getKind().getSimpleName() + ":" + describe(child)) + .sorted( + java.util.Comparator.comparing( + (String operation) -> !operation.startsWith("Mode"))) + .toList(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacTest.java new file mode 100644 index 000000000..9319ea536 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyMacTest.java @@ -0,0 +1,95 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers the rules in {@link OpenSSLLegacyMac}. The digest passed to {@code HMAC_Init_ex}, {@code + * HMAC_Init} or {@code HMAC}, and the cipher passed to {@code CMAC_Init}, are traced back to the + * call that created them and attached to the MAC, which reports them. + */ +class OpenSSLLegacyMacTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 21: HMAC_Init_ex(hctx, key, 32, md1, NULL); + finding( + "MacContext{ValueAction:HMAC}[DigestContext{ValueAction:SHA-256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG]"), + // 23: HMAC_Init(hctx, key, 32, md2); + finding( + "MacContext{ValueAction:HMAC}[DigestContext{ValueAction:SHA-256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG]"), + // 27: HMAC(md3, key, 32, data, 64, out, &outlen); + finding( + "MacContext{ValueAction:HMAC}[DigestContext{ValueAction:SHA-256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG]"), + // 32: CMAC_Init(cctx, key, 32, cmac_cipher, NULL); + finding( + "MacContext{ValueAction:CMAC}[CipherContext{ValueAction:AES-128-CBC}]", + "Mac:CMAC-AES[BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], Tag:TAG]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyMacTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTest.java new file mode 100644 index 000000000..ab89235ca --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTest.java @@ -0,0 +1,97 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The legacy RSA operations report RSA with the scheme selected by their padding argument: + * encryption and decryption with the public and private key, and the signature primitive with the + * private key (RSA_private_encrypt) and its verification (RSA_public_decrypt). + */ +class OpenSSLLegacyRsaOperationTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: RSA_public_encrypt(len, in, out, rsa, RSA_PKCS1_OAEP_PADDING); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:RSA-OAEP}]", + "PublicKeyEncryption:RSA-OAEP[Encrypt:ENCRYPT, " + + "Oid:1.2.840.113549.1.1.7, Padding:OAEP]"), + // 5: RSA_private_decrypt(len, in, out, rsa, RSA_PKCS1_PADDING); + finding( + "CipherContext{CipherAction:DECRYPT}[CipherContext{ValueAction:RSA-PKCS1-TYPE2}]", + "PublicKeyEncryption:RSA[Decrypt:DECRYPT, Oid:1.2.840.113549.1.1.1, " + + "Padding:PKCS1]"), + // 6: RSA_public_encrypt(len, in, out, rsa, RSA_NO_PADDING); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:RSA-NO-PADDING}]", + "PublicKeyEncryption:RSA[Encrypt:ENCRYPT, Oid:1.2.840.113549.1.1.1, " + + "Padding:Raw]"), + // 10: RSA_private_encrypt(len, in, out, rsa, RSA_PKCS1_PADDING); + finding( + "SignatureContext{SignatureAction:SIGN}[SignatureContext{ValueAction:RSA-PKCS1}]", + "Signature:RSA-PKCS1-1.5[Oid:1.2.840.113549.1.1.1, Padding:PKCS1, " + + "Sign:SIGN]"), + // 11: RSA_public_decrypt(len, in, out, rsa, RSA_X931_PADDING); + finding( + "SignatureContext{SignatureAction:VERIFY}[SignatureContext{ValueAction:RSA-X931}]", + "Signature:ANSI X9.31[Verify:VERIFY]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/legacy/OpenSSLLegacyRsaOperationTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsaTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsaTest.java new file mode 100644 index 000000000..d599402ef --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLLegacyRsaTest.java @@ -0,0 +1,202 @@ +/* + * 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.openssl.legacy; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** Covers all rule entries in {@link OpenSSLLegacyRsa}. */ +class OpenSSLLegacyRsaTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 17: RSA_generate_key_ex(rsa, 2048, e, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]"), + // 18: RSA_generate_multi_prime_key(rsa, 2048, 3, e, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]"), + // 20: RSA_public_encrypt(32, buf, buf, key, 1); + finding( + "CipherContext{CipherAction:ENCRYPT}[CipherContext{ValueAction:RSA-PKCS1-TYPE2}]", + "PublicKeyEncryption:RSA[Encrypt:ENCRYPT, Oid:1.2.840.113549.1.1.1, " + + "Padding:PKCS1]"), + // 21: RSA_private_decrypt(32, buf, buf, key, 1); + finding( + "CipherContext{CipherAction:DECRYPT}[CipherContext{ValueAction:RSA-PKCS1-TYPE2}]", + "PublicKeyEncryption:RSA[Decrypt:DECRYPT, Oid:1.2.840.113549.1.1.1, " + + "Padding:PKCS1]"), + // 23: RSA_sign(NID_sha256, buf, 32, buf, &len, key); + finding( + "SignatureContext{SignatureAction:SIGN}[SignatureContext{ValueAction:RSA-SHA256}]", + "Signature:RSA-PKCS1-1.5-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.11, Sign:SIGN]"), + // 24: RSA_verify(NID_sha256, buf, 32, buf, 32, key); + finding( + "SignatureContext{SignatureAction:VERIFY}[SignatureContext{ValueAction:RSA-SHA256}]", + "Signature:RSA-PKCS1-1.5-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.11, Verify:VERIFY]"), + // 29: RSA_sign(md5_nid, buf, 32, buf, &len, key); + finding( + "SignatureContext{SignatureAction:SIGN}[SignatureContext{ValueAction:RSA-MD5}]", + "Signature:RSA-PKCS1-1.5-MD5[MessageDigest:MD5[BlockSize:512, " + + "Digest:DIGEST, DigestSize:128], Oid:1.2.840.113549.1.1.4, " + + "Sign:SIGN]"), + // 30: RSA_verify(md5_nid, buf, 32, buf, 32, key); + finding( + "SignatureContext{SignatureAction:VERIFY}[SignatureContext{ValueAction:RSA-MD5}]", + "Signature:RSA-PKCS1-1.5-MD5[MessageDigest:MD5[BlockSize:512, " + + "Digest:DIGEST, DigestSize:128], Oid:1.2.840.113549.1.1.4, " + + "Verify:VERIFY]"), + // 32: RSA_private_encrypt(32, buf, buf, key, 1); + finding( + "SignatureContext{SignatureAction:SIGN}[SignatureContext{ValueAction:RSA-PKCS1}]", + "Signature:RSA-PKCS1-1.5[Oid:1.2.840.113549.1.1.1, Padding:PKCS1, " + + "Sign:SIGN]"), + // 33: RSA_public_decrypt(32, buf, buf, key, 1); + finding( + "SignatureContext{SignatureAction:VERIFY}[SignatureContext{ValueAction:RSA-PKCS1}]", + "Signature:RSA-PKCS1-1.5[Oid:1.2.840.113549.1.1.1, Padding:PKCS1, " + + "Verify:VERIFY]"), + // 35: RSA_padding_add_PKCS1_PSS(key, em, mhash, EVP_sha256(), 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}, " + + "DigestContext{ValueAction:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10, SaltLength:256]"), + // 36: RSA_padding_add_PKCS1_PSS_mgf1(key, em, mhash, NULL, NULL, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10, " + + "SaltLength:256]"), + // 37: RSA_verify_PKCS1_PSS(key, mhash, EVP_sha256(), em, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}, " + + "DigestContext{ValueAction:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10, SaltLength:256]"), + // 38: RSA_verify_PKCS1_PSS_mgf1(key, mhash, NULL, NULL, em, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10, " + + "SaltLength:256]"), + // 40: RSA_padding_add_PKCS1_OAEP(buf, 256, buf, 32, buf, 16); + finding( + "CipherContext{ValueAction:RSA-OAEP}", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, Padding:OAEP]"), + // 41: RSA_padding_add_PKCS1_OAEP_mgf1(buf, 256, buf, 32, buf, 16, EVP_sha384(), + // NULL); + finding( + "CipherContext{ValueAction:RSA-OAEP-MGF1}[DigestContext{ValueAction:SHA-384}]", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, " + + "Padding:OAEP[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2]]]"), + // 43: RSA_padding_add_PKCS1_type_1(buf, 256, buf, 32); + finding( + "CipherContext{ValueAction:RSA-PKCS1}", + "Signature:RSA-PKCS1-1.5[Oid:1.2.840.113549.1.1.1, Padding:PKCS1]"), + // 44: RSA_padding_add_PKCS1_type_2(buf, 256, buf, 32); + finding( + "CipherContext{ValueAction:RSA-PKCS1-TYPE2}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1, Padding:PKCS1]"), + // 45: RSA_padding_check_PKCS1_type_1(buf, 256, buf, 32, 256); + finding( + "CipherContext{ValueAction:RSA-PKCS1}", + "Signature:RSA-PKCS1-1.5[Oid:1.2.840.113549.1.1.1, Padding:PKCS1]"), + // 46: RSA_padding_check_PKCS1_type_2(buf, 256, buf, 32, 256); + finding( + "CipherContext{ValueAction:RSA-PKCS1-TYPE2}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1, Padding:PKCS1]"), + // 47: RSA_padding_check_PKCS1_OAEP(buf, 256, buf, 32, 256, buf, 16); + finding( + "CipherContext{ValueAction:RSA-OAEP}", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, Padding:OAEP]"), + // 48: RSA_padding_check_PKCS1_OAEP_mgf1(buf, 256, buf, 32, 256, buf, 16, NULL, + // NULL); + finding( + "CipherContext{ValueAction:RSA-OAEP-MGF1}", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, Padding:OAEP]"), + // 49: RSA_padding_add_X931(buf, 256, buf, 32); + finding("CipherContext{ValueAction:RSA-X931}", "Signature:ANSI X9.31"), + // 50: RSA_padding_check_X931(buf, 256, buf, 32, 256); + finding("CipherContext{ValueAction:RSA-X931}", "Signature:ANSI X9.31"), + // 51: RSA_padding_add_none(buf, 256, buf, 32); + finding( + "CipherContext{ValueAction:RSA-NO-PADDING}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1, Padding:Raw]"), + // 52: RSA_padding_check_none(buf, 256, buf, 32, 256); + finding( + "CipherContext{ValueAction:RSA-NO-PADDING}", + "PublicKeyEncryption:RSA[Oid:1.2.840.113549.1.1.1, Padding:Raw]"), + // 53: RSA_generate_key(2048, 65537, NULL, NULL); + finding( + "PrivateKeyContext{ValueAction:RSA}[PrivateKeyContext{KeySize:2048}]", + "PrivateKey:RSA[KeyLength:2048, " + + "PublicKeyEncryption:RSA-2048[KeyGeneration:KEYGENERATION, " + + "KeyLength:2048, Oid:1.2.840.113549.1.1.1]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/legacy/OpenSSLLegacyRsaTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLNidLookupFactoryCurvesTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLNidLookupFactoryCurvesTest.java new file mode 100644 index 000000000..f40675c19 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/legacy/OpenSSLNidLookupFactoryCurvesTest.java @@ -0,0 +1,67 @@ +/* + * 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.openssl.legacy; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.mapper.openssl.OpenSslCurveMapper; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * The curve NIDs of {@link OpenSSLNidLookupFactory} cover every named curve of OpenSSL, by their + * numbers and their names, and each curve they name is mapped. + */ +class OpenSSLNidLookupFactoryCurvesTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + @Test + void theNidNamesAndNumbersNameEveryNamedCurve() { + final List curves = + OpenSslCurveMapper.NAMED_CURVES.stream().map(curve -> "EC-" + curve).toList(); + assertThat(OpenSSLNidLookupFactory.CURVE_BY_NAME.values()) + .containsExactlyInAnyOrderElementsOf(curves); + assertThat(OpenSSLNidLookupFactory.CURVE_BY_CODE.values()) + .containsExactlyInAnyOrderElementsOf(curves); + } + + @Test + void theCurvesNamedByNidsAreMapped() { + OpenSSLNidLookupFactory.CURVE_BY_CODE + .values() + .forEach( + curve -> + assertThat( + new OpenSslCurveMapper() + .parse(curve.substring(3), location)) + .as(curve) + .isPresent()); + } + + @Test + void aCurveByNidNameAndNumber() { + assertThat(OpenSSLNidLookupFactory.CURVE_BY_NAME.get("NID_X9_62_prime239v1")) + .isEqualTo("EC-prime239v1"); + assertThat(OpenSSLNidLookupFactory.CURVE_BY_CODE.get(412)).isEqualTo("EC-prime239v1"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMacContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMacContextTest.java new file mode 100644 index 000000000..73d23e915 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMacContextTest.java @@ -0,0 +1,100 @@ +/* + * 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.openssl.mac; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A MAC fetched by name gets the digest or cipher that is set on the context created from it, + * whether it is passed to {@code EVP_MAC_init} or to {@code EVP_MAC_CTX_set_params}. A GMAC becomes + * its cipher as a MAC in GMAC mode. + */ +class OpenSSLEvpMacContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_MAC *mac = EVP_MAC_fetch(NULL, "hmac", NULL); + finding( + "MacContext{Algorithm:hmac}[MacContext{}[MacContext{}[DigestContext{Algorithm:SHA-256}]]]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.2.9, " + + "Tag:TAG]"), + // 15: EVP_MAC *mac = EVP_MAC_fetch(NULL, "CMAC", NULL); + finding( + "MacContext{Algorithm:CMAC}[MacContext{}[MacContext{}[CipherContext{Algorithm:aes-256-cbc}]]]", + "Mac:CMAC-AES[BlockCipher:AES-256-CBC[BlockSize:128, KeyLength:256, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.42], Tag:TAG]"), + // 25: EVP_MAC_fetch(NULL, "Poly1305", NULL); + finding("MacContext{Algorithm:Poly1305}", "Mac:Poly1305[Tag:TAG]"), + // 26: EVP_MAC_fetch(NULL, "SipHash", NULL); + finding( + "MacContext{Algorithm:SipHash}", + "Mac:SipHash[DigestSize:64, KeyLength:128, Tag:TAG]"), + // 27: EVP_MAC_fetch(NULL, "KMAC-256", NULL); + finding( + "MacContext{Algorithm:KMAC-256}", + "Mac:KMAC256[DigestSize:512, " + + "ExtendableOutputFunction:cSHAKE256[Digest:DIGEST, " + + "ParameterSetIdentifier:256], ParameterSetIdentifier:256, Tag:TAG]"), + // 31: EVP_MAC *mac = EVP_MAC_fetch(NULL, "GMAC", NULL); + finding( + "MacContext{Algorithm:GMAC}[MacContext{}[MacContext{}[CipherContext{Algorithm:aes-128-gcm}]]]", + "Mac:AES-128-GMAC[BlockSize:128, KeyLength:128, Mode:GMAC," + + " Oid:2.16.840.1.101.3.4.1.9, Tag:TAG]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/mac/OpenSSLEvpMacContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMacTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMacTest.java new file mode 100644 index 000000000..df278d755 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/mac/OpenSSLEvpMacTest.java @@ -0,0 +1,141 @@ +/* + * 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.openssl.mac; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers all rule entries in {@link OpenSSLEvpMac}. + * + *

{@code EVP_MAC_fetch(lib, "HMAC"/"CMAC"/"GMAC", props)} raises one finding per MAC family (the + * real fetched name) rather than guessing a digest/cipher that isn't visible at the fetch call + * site. The real digest (HMAC) or cipher (CMAC/GMAC), when the code sets one via {@code + * EVP_MAC_CTX_set_params(ctx, params)}, is detected from the {@code "digest"}/{@code "cipher"} + * entry written to {@code params} (see {@link + * com.ibm.plugin.rules.detection.openssl.params.OpenSSLParams}). + */ +class OpenSSLEvpMacTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 8: EVP_MAC_fetch(lib, "HMAC", props); + finding("MacContext{Algorithm:HMAC}", "Mac:HMAC[Tag:TAG]"), + // 9: EVP_MAC_fetch(lib, "CMAC", props); + finding("MacContext{Algorithm:CMAC}", "Mac:CMAC[Tag:TAG]"), + // 10: EVP_MAC_fetch(lib, "GMAC", props); + finding("MacContext{Algorithm:GMAC}", "Mac:GMAC[Tag:TAG]"), + // 11: EVP_MAC_fetch(lib, "Poly1305", props); + finding("MacContext{Algorithm:Poly1305}", "Mac:Poly1305[Tag:TAG]"), + // 12: EVP_MAC_fetch(lib, "SipHash", props); + finding( + "MacContext{Algorithm:SipHash}", + "Mac:SipHash[DigestSize:64, KeyLength:128, Tag:TAG]"), + // 13: EVP_MAC_fetch(lib, "KMAC128", props); + finding( + "MacContext{Algorithm:KMAC128}", + "Mac:KMAC128[DigestSize:256, " + + "ExtendableOutputFunction:cSHAKE128[Digest:DIGEST, " + + "ParameterSetIdentifier:128], ParameterSetIdentifier:128, " + + "Tag:TAG]"), + // 14: EVP_MAC_fetch(lib, "KMAC256", props); + finding( + "MacContext{Algorithm:KMAC256}", + "Mac:KMAC256[DigestSize:512, " + + "ExtendableOutputFunction:cSHAKE256[Digest:DIGEST, " + + "ParameterSetIdentifier:256], ParameterSetIdentifier:256, " + + "Tag:TAG]"), + // 15: EVP_MAC_fetch(lib, "BLAKE2BMAC", props); + finding( + "MacContext{Algorithm:BLAKE2BMAC}", + "Mac:BLAKE2b-512[DigestSize:512, SaltLength:128, Tag:TAG]"), + // 16: EVP_MAC_fetch(lib, "BLAKE2SMAC", props); + finding( + "MacContext{Algorithm:BLAKE2SMAC}", + "Mac:BLAKE2s-256[DigestSize:256, SaltLength:64, Tag:TAG]"), + // 18: EVP_Q_mac(lib, "HMAC", props, "SHA256", NULL, NULL, 0, NULL, 0, NULL, 0, + // NULL); + finding( + "MacContext{Algorithm:HMAC}[MacContext{AlgorithmParameter:SHA256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.2.9, Tag:TAG]"), + // 19: EVP_Q_mac(lib, OSSL_MAC_NAME_CMAC, props, "AES-128-CBC", NULL, NULL, 0, + // NULL, 0, NULL, 0, NULL); + finding( + "MacContext{Algorithm:CMAC}[MacContext{AlgorithmParameter:AES-128-CBC}]", + "Mac:CMAC-AES[BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], Tag:TAG]"), + // 24: HMAC(legacy_hmac_md, NULL, 0, NULL, 0, NULL, NULL); + finding( + "MacContext{ValueAction:HMAC}[DigestContext{ValueAction:SHA-256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.2.9, Tag:TAG]"), + // 26: HMAC_Init_ex(NULL, NULL, 0, legacy_hmac_init_md, NULL); + finding( + "MacContext{ValueAction:HMAC}[DigestContext{ValueAction:SHA-256}]", + "Mac:HMAC-SHA-256[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.2.9, Tag:TAG]"), + // 28: CMAC_Init(NULL, NULL, 0, legacy_cmac_cipher, NULL); + finding( + "MacContext{ValueAction:CMAC}[CipherContext{ValueAction:AES-128-CBC}]", + "Mac:CMAC-AES[BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], Tag:TAG]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/mac/OpenSSLEvpMacTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/macros/OpenSSLMacrosTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/macros/OpenSSLMacrosTest.java new file mode 100644 index 000000000..a5d4bdc20 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/macros/OpenSSLMacrosTest.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.macros; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Algorithm names, parameter keys, protocol versions and curve identifiers given as OpenSSL header + * macros are detected when the headers themselves are not available to the analysis. + */ +class OpenSSLMacrosTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 6: EVP_KDF *kdf = EVP_KDF_fetch(NULL, OSSL_KDF_NAME_HKDF, NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{}[DigestContext{Algorithm:SHA-256}]]]", + "KeyDerivationFunction:HKDF-SHA-256[KeyDerivation:KEYDERIVATION, " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]]"), + // 16: EVP_MAC_fetch(NULL, OSSL_MAC_NAME_POLY1305, NULL); + finding("MacContext{Algorithm:POLY1305}", "Mac:Poly1305[Tag:TAG]"), + // 20: EVP_get_digestbyname(SN_sha384); + finding( + "DigestContext{Algorithm:SHA-384}", + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]"), + // 24: SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + finding("ProtocolContext{Protocol:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 28: EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/macros/OpenSSLMacrosTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/params/OpenSSLParamsBuilderTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/params/OpenSSLParamsBuilderTest.java new file mode 100644 index 000000000..3a5bf0113 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/params/OpenSSLParamsBuilderTest.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.openssl.params; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The digest and cipher entries of the parameters built with an {@code OSSL_PARAM_BLD}, pushed with + * {@code OSSL_PARAM_BLD_push_utf8_string} or {@code OSSL_PARAM_BLD_push_utf8_ptr}, are reported + * with the KDF or MAC the built parameters are set on, as the entries of an {@code OSSL_PARAM} + * array are. + */ +class OpenSSLParamsBuilderTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 7: EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + finding( + "KeyDerivationFunctionContext{Algorithm:HKDF}[KeyDerivationFunctionContext{}[KeyDerivationFunctionContext{KeySize:256}[DigestContext{}[DigestContext{Algorithm:SHA-1}]]]]", + "KeyDerivationFunction:HKDF-SHA-1[KeyDerivation:KEYDERIVATION, " + + "KeyLength:256, MessageDigest:SHA-1[BlockSize:512, " + + "Digest:DIGEST, DigestSize:160, Oid:1.3.14.3.2.26]]"), + // 17: EVP_MAC *mac = EVP_MAC_fetch(NULL, "CMAC", NULL); + finding( + "MacContext{Algorithm:CMAC}[MacContext{}[MacContext{}[CipherContext{}[CipherContext{Algorithm:AES-128-CBC}]]]]", + "Mac:CMAC-AES[BlockCipher:AES-128-CBC[BlockSize:128, KeyLength:128, " + + "Mode:CBC, Oid:2.16.840.1.101.3.4.1.2], Tag:TAG]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/params/OpenSSLParamsBuilderTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRandContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRandContextTest.java new file mode 100644 index 000000000..c5c5b82af --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRandContextTest.java @@ -0,0 +1,85 @@ +/* + * 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.openssl.rand; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A DRBG fetched by name gets the cipher or digest that is set on the context created from it, + * whether it is passed to {@code EVP_RAND_CTX_set_params} or to {@code EVP_RAND_instantiate}. + */ +class OpenSSLRandContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_RAND *rand = EVP_RAND_fetch(NULL, "CTR-DRBG", NULL); + finding( + "PRNGContext{Algorithm:CTR-DRBG}[PRNGContext{}[PRNGContext{}[CipherContext{Algorithm:AES-256-CTR}]]]", + "PseudorandomNumberGenerator:CTR_DRBG-AES-256[BlockCipher:AES-256-CTR[BlockSize:128, " + + "KeyLength:256, Mode:CTR, Oid:2.16.840.1.101.3.4.1.4]]"), + // 15: EVP_RAND *rand = EVP_RAND_fetch(NULL, "hmac-drbg", NULL); + finding( + "PRNGContext{Algorithm:hmac-drbg}[PRNGContext{}[PRNGContext{}[DigestContext{Algorithm:SHA-256}]]]", + "PseudorandomNumberGenerator:HMAC_DRBG-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]]"), + // 25: EVP_RAND_fetch(NULL, "SEED-SRC", NULL); + finding( + "PRNGContext{Algorithm:SEED-SRC}", + "PseudorandomNumberGenerator:SEED-SRC")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/rand/OpenSSLRandContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRandTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRandTest.java new file mode 100644 index 000000000..2e66e23fc --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/rand/OpenSSLRandTest.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.plugin.rules.detection.openssl.rand; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers the rules in {@link com.ibm.plugin.rules.detection.openssl.rand.OpenSSLRand}: the random + * bytes functions and the DRBGs fetched by name. + * + *

As the other detection rule tests of the module, it asserts each finding, by its id, against + * the expected detection store and translation ({@link ExpectedFinding}), as the detection rule + * tests of the Java module do. + */ +class OpenSSLRandTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 7: RAND_bytes(buf, 32); + finding("PRNGContext{ValueAction:RAND}", "PseudorandomNumberGenerator:RAND"), + // 8: RAND_priv_bytes(buf, 32); + finding("PRNGContext{ValueAction:RAND}", "PseudorandomNumberGenerator:RAND"), + // 9: RAND_bytes_ex(NULL, buf, 32, 0); + finding("PRNGContext{ValueAction:RAND}", "PseudorandomNumberGenerator:RAND"), + // 10: RAND_priv_bytes_ex(NULL, buf, 32, 0); + finding("PRNGContext{ValueAction:RAND}", "PseudorandomNumberGenerator:RAND"), + // 12: EVP_RAND_fetch(NULL, "CTR-DRBG", NULL); + finding( + "PRNGContext{Algorithm:CTR-DRBG}", + "PseudorandomNumberGenerator:CTR_DRBG"), + // 13: EVP_RAND_fetch(NULL, "HASH-DRBG", NULL); + finding( + "PRNGContext{Algorithm:HASH-DRBG}", + "PseudorandomNumberGenerator:Hash_DRBG"), + // 14: EVP_RAND_fetch(NULL, "HMAC-DRBG", NULL); + finding( + "PRNGContext{Algorithm:HMAC-DRBG}", + "PseudorandomNumberGenerator:HMAC_DRBG"), + // 15: EVP_RAND_fetch(NULL, "SEED-SRC", NULL); + finding( + "PRNGContext{Algorithm:SEED-SRC}", + "PseudorandomNumberGenerator:SEED-SRC"), + // 16: EVP_RAND_fetch(NULL, "JITTER", NULL); + finding("PRNGContext{Algorithm:JITTER}", "PseudorandomNumberGenerator:JITTER"), + // 17: EVP_RAND_fetch(NULL, "TEST-RAND", NULL); + finding( + "PRNGContext{Algorithm:TEST-RAND}", + "PseudorandomNumberGenerator:TEST-RAND"), + // 19: RAND_set_DRBG_type(NULL, "CTR-DRBG", NULL, NULL, NULL); + finding( + "PRNGContext{Algorithm:CTR-DRBG}", + "PseudorandomNumberGenerator:CTR_DRBG"), + // 20: RAND_set_seed_source_type(NULL, "SEED-SRC", NULL); + finding( + "PRNGContext{Algorithm:SEED-SRC}", + "PseudorandomNumberGenerator:SEED-SRC")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/rand/OpenSSLRandTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLEvpSignatureTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLEvpSignatureTest.java new file mode 100644 index 000000000..594712623 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLEvpSignatureTest.java @@ -0,0 +1,148 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.openssl.signature; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The signature algorithm fetched by name, the RSA-PSS salt length and the digests named for or + * passed to a sign/verify operation are reported. The sign and verify calls themselves report + * nothing: their signature algorithm is the type of the {@code EVP_PKEY}, which is not known at the + * call. + */ +class OpenSSLEvpSignatureTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 12: EVP_DigestSignInit(ctx, NULL, sign_md, NULL, NULL); + finding( + "SignatureContext{SignatureAction:SIGN}[DigestContext{ValueAction:SHA-256}]", + "Signature:unknown[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Sign:SIGN]"), + // 14: EVP_DigestVerifyInit(ctx, NULL, verify_md, NULL, NULL); + finding( + "SignatureContext{SignatureAction:VERIFY}[DigestContext{ValueAction:SHA-256}]", + "Signature:unknown[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Verify:VERIFY]"), + // 17: EVP_DigestSignInit_ex(ctx, NULL, "SHA2-256", NULL, NULL, NULL, NULL); + finding( + "SignatureContext{SignatureAction:SIGN}[SignatureContext{Algorithm:SHA-256}]", + "Signature:unknown[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Sign:SIGN]"), + // 18: EVP_DigestVerifyInit_ex(ctx, NULL, "SHA256", NULL, NULL, NULL, NULL); + finding( + "SignatureContext{SignatureAction:VERIFY}[SignatureContext{Algorithm:SHA-256}]", + "Signature:unknown[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Verify:VERIFY]"), + // 48: EVP_SIGNATURE_fetch(NULL, "RSA", NULL); + finding( + "SignatureContext{Algorithm:RSA}", + "Signature:RSA-PKCS1-1.5[Oid:1.2.840.113549.1.1.1]"), + // 51: const EVP_MD* mgf1_md = EVP_sha256(); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 53: EVP_PKEY_CTX_set_rsa_mgf1_md_name(pctx, "SHA256", NULL); + finding( + "DigestContext{Algorithm:SHA-256}", + "MaskGenerationFunction:MGF1[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.8]"), + // 54: EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10, " + + "SaltLength:256]"), + // 55: const EVP_MD* signature_md = EVP_sha256(); + finding( + "DigestContext{ValueAction:SHA-256}", + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]"), + // 58: EVP_PKEY_CTX_set_rsa_pss_keygen_md(pctx, pss_keygen_md); + finding( + "SignatureContext{ValueAction:RSA-PSS}[DigestContext{ValueAction:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10]"), + // 59: EVP_PKEY_CTX_set_rsa_pss_keygen_md_name(pctx, "SHA256", NULL); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{Algorithm:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10]"), + // 61: EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md(pctx, pss_keygen_mgf1_md); + finding( + "SignatureContext{ValueAction:RSA-PSS}[DigestContext{ValueAction:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MaskGenerationFunction:MGF1[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.8], Oid:1.2.840.113549.1.1.10]"), + // 62: EVP_PKEY_CTX_set_rsa_pss_keygen_mgf1_md_name(pctx, "SHA256"); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{Algorithm:SHA-256}]", + "ProbabilisticSignatureScheme:RSA-PSS[MaskGenerationFunction:MGF1[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.8], Oid:1.2.840.113549.1.1.10]"), + // 63: EVP_PKEY_CTX_set_rsa_pss_keygen_saltlen(pctx, 32); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:256}]", + "ProbabilisticSignatureScheme:RSA-PSS[Oid:1.2.840.113549.1.1.10, " + + "SaltLength:256]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/signature/OpenSSLEvpSignatureTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLMgf1DigestTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLMgf1DigestTest.java new file mode 100644 index 000000000..3a0d2c933 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLMgf1DigestTest.java @@ -0,0 +1,138 @@ +/* + * 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.openssl.signature; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The digest of MGF1, the mask generation function of RSA-PSS and RSA-OAEP, is reported as MGF1 + * with that digest, under the scheme: the MGF1 digest an RSA-PSS key is restricted to, given as an + * {@code EVP_MD} or by name, and the MGF1 digests of the legacy PSS and OAEP encoding functions. + * Such a digest is not reported on its own as well. + */ +class OpenSSLMgf1DigestTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 5: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); + finding( + "KeyContext{ValueAction:RSA-PSS}[KeyContext{KeySize:2048}, " + + "KeyContext{}[DigestContext{ValueAction:SHA-256}], " + + "KeyContext{}[DigestContext{ValueAction:SHA-512}]]", + "ProbabilisticSignatureScheme:RSA-PSS[KeyLength:2048, " + + "MaskGenerationFunction:MGF1[MessageDigest:SHA-512[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:512, Oid:2.16.840.1.101.3.4.2.3], " + + "Oid:1.2.840.113549.1.1.8], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10]"), + // 12: EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA_PSS, NULL); + finding( + "KeyContext{ValueAction:RSA-PSS}[KeyContext{KeySize:3072}, " + + "DigestContext{Algorithm:SHA-384}]", + "ProbabilisticSignatureScheme:RSA-PSS[KeyLength:3072, " + + "MaskGenerationFunction:MGF1[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.8], Oid:1.2.840.113549.1.1.10]"), + // 18: RSA_padding_add_PKCS1_PSS_mgf1(rsa, em, hash, EVP_sha256(), EVP_sha1(), + // 20); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:160}, " + + "DigestContext{ValueAction:SHA-256}, " + + "DigestContext{ValueAction:SHA-1}]", + "ProbabilisticSignatureScheme:RSA-PSS[MaskGenerationFunction:MGF1[MessageDigest:SHA-1[BlockSize:512, " + + "Digest:DIGEST, DigestSize:160, Oid:1.3.14.3.2.26], " + + "Oid:1.2.840.113549.1.1.8], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10, SaltLength:160]"), + // 19: RSA_verify_PKCS1_PSS_mgf1(rsa, hash, EVP_sha384(), EVP_sha224(), em, 20); + finding( + "SignatureContext{ValueAction:RSA-PSS}[SignatureContext{SaltSize:160}, " + + "DigestContext{ValueAction:SHA-384}, " + + "DigestContext{ValueAction:SHA-224}]", + "ProbabilisticSignatureScheme:RSA-PSS[MaskGenerationFunction:MGF1[MessageDigest:SHA-224[BlockSize:512, " + + "Digest:DIGEST, DigestSize:224, Oid:2.16.840.1.101.3.4.2.4], " + + "Oid:1.2.840.113549.1.1.8], " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.10, SaltLength:160]"), + // 23: RSA_padding_add_PKCS1_OAEP_mgf1(to, tlen, from, flen, NULL, 0, + // EVP_sha256(), EVP_sha1()); + finding( + "CipherContext{ValueAction:RSA-OAEP-MGF1}[DigestContext{ValueAction:SHA-256}, " + + "DigestContext{ValueAction:SHA-1}]", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, " + + "Padding:OAEP[MaskGenerationFunction:MGF1[MessageDigest:SHA-1[BlockSize:512, " + + "Digest:DIGEST, DigestSize:160, Oid:1.3.14.3.2.26], " + + "Oid:1.2.840.113549.1.1.8], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1]]]"), + // 24: RSA_padding_check_PKCS1_OAEP_mgf1(to, tlen, from, flen, 256, NULL, 0, + // EVP_sha512(), EVP_sha384()); + finding( + "CipherContext{ValueAction:RSA-OAEP-MGF1}[DigestContext{ValueAction:SHA-512}, " + + "DigestContext{ValueAction:SHA-384}]", + "PublicKeyEncryption:RSA-OAEP[Oid:1.2.840.113549.1.1.7, " + + "Padding:OAEP[MaskGenerationFunction:MGF1[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.8], " + + "MessageDigest:SHA-512[BlockSize:1024, Digest:DIGEST, " + + "DigestSize:512, Oid:2.16.840.1.101.3.4.2.3]]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/signature/OpenSSLMgf1DigestTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTest.java new file mode 100644 index 000000000..e51ae4a0e --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTest.java @@ -0,0 +1,90 @@ +/* + * 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.openssl.signature; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.plugin.CxxAggregator; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.rules.CxxInventoryRule; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * A signature made with a key that is not generated in the analyzed code, e.g. a key loaded from a + * file or a PKCS#12 container, or given to the function, is reported as a signature with the digest + * it uses. The signature scheme depends on the type of the key, which is not known, unless a + * setting of the operation names it, e.g. the RSA-PSS padding. A certificate signed with {@code + * X509_sign_ctx} is reported by the initialization of its signing context. + */ +class OpenSSLSignatureWithKeyFromElsewhereTest { + + @AfterEach + void resetSharedState() { + CxxAggregator.reset(); + } + + @Test + void test() { + CxxVerifier.verifyFiles( + List.of( + "rules/detection/openssl/signature/OpenSSLSignatureWithKeyFromElsewhereTestFile.cc"), + new CxxInventoryRule()); + + assertThat(CxxAggregator.getDetectedNodes()) + .extracting(OpenSSLSignatureWithKeyFromElsewhereTest::describe) + .containsExactly( + "Signature:SIGN:SHA-256", + "Signature:VERIFY:SHA-384", + "ProbabilisticSignatureScheme:RSA-PSS:SIGN:SHA-256", + "Signature:SIGN:SHA-256", + "Signature:SIGN:SHA-1", + "Signature:SIGN:SHA-512", + "Signature:SIGN:SHA-1", + "Signature:SIGN:", + "Signature:SIGN:", + "Signature:SIGN:SHA-384", + "Signature:SIGN:SHA-512", + "Signature:SIGN:SHA-224"); + } + + /** + * The kind of a node, the RSA-PSS scheme when it is one, its operation and the digest it uses. + */ + @Nonnull + private static String describe(@Nonnull INode node) { + final String kind = node.is(Signature.class) ? "Signature" : node.getKind().getSimpleName(); + final String scheme = "RSA-PSS".equals(node.asString()) ? "RSA-PSS:" : ""; + final String operation = + node.hasChildOfType(Sign.class).isPresent() + ? "SIGN" + : node.hasChildOfType(Verify.class).isPresent() ? "VERIFY" : ""; + final Optional digest = node.hasChildOfType(MessageDigest.class); + return kind + ":" + scheme + operation + ":" + digest.map(INode::asString).orElse(""); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslCipherListTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslCipherListTest.java new file mode 100644 index 000000000..8fa65caf4 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslCipherListTest.java @@ -0,0 +1,95 @@ +/* + * 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.openssl.ssl; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The cipher suites named in the cipher string of {@code SSL_CTX_set_cipher_list} and {@code + * SSL_CTX_set_ciphersuites} are reported; keywords and exclusions select no single suite. + */ +class OpenSSLLibsslCipherListTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: SSL_CTX_set_cipher_list(ctx, + // "ECDHE-RSA-AES256-GCM-SHA384:HIGH:!aNULL:!MD5"); + finding( + "ProtocolContext{CipherSuite:ECDHE-RSA-AES256-GCM-SHA384:HIGH:!aNULL:!MD5}", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384[AssetCollection:[KeyAgreement:ECDH[Oid:1.3.132.1.12], " + + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46], " + + "PublicKeyEncryption:RSA[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.1]], IdentifierCollection:[Identifier:0xC0, " + + "Identifier:0x30]]]]"), + // 5: SSL_CTX_set_ciphersuites(ctx, + // "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"); + finding( + "ProtocolContext{CipherSuite:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256}", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_AES_256_GCM_SHA384[AssetCollection:[AuthenticatedEncryption:AES-256-GCM[BlockSize:128, " + + "KeyLength:256, Mode:GCM, Oid:2.16.840.1.101.3.4.1.46], " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x02]], " + + "CipherSuite:TLS_CHACHA20_POLY1305_SHA256[AssetCollection:[AuthenticatedEncryption:ChaCha20-Poly1305[MessageDigest:Poly1305[Digest:DIGEST]], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x03]]]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/ssl/OpenSSLLibsslCipherListTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslContextTest.java new file mode 100644 index 000000000..ad1140d38 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslContextTest.java @@ -0,0 +1,149 @@ +/* + * 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.openssl.ssl; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A TLS context created with {@code SSL_CTX_new} is reported as one protocol, with the version of + * its method and its configuration (versions, cipher suites, groups). The method, the DH and EC + * keys and the settings of a context created elsewhere are each reported once: passing a method to + * {@code SSL_CTX_set_ssl_version}, or a key to {@code SSL_CTX_set_tmp_dh}/{@code + * SSL_CTX_set_tmp_ecdh}, does not report them again. {@code SSL_CONF_cmd} values are read according + * to their command, and SRTP profiles are reported with their algorithms. + */ +class OpenSSLLibsslContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); + finding( + "ProtocolContext{}[ProtocolContext{CipherSuite:ECDHE-RSA-AES256-GCM-SHA384}, " + + "ProtocolContext{Protocol:TLSv1.2}, ProtocolContext{Algorithm:X25519:P-256}, " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[CipherSuiteCollection:[CipherSuite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384[AssetCollection:[KeyAgreement:ECDH[Oid:1.3.132.1.12], " + + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46], " + + "PublicKeyEncryption:RSA[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.1]], IdentifierCollection:[Identifier:0xC0, " + + "Identifier:0x30]]], " + + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110], KeyAgreement:ECDH[EllipticCurve:secp256r1, " + + "Oid:1.3.132.1.12]], Version:1.2]"), + // 12: SSL_CTX *ctx = SSL_CTX_new(method); + finding("ProtocolContext{}[ProtocolContext{ValueAction:TLS}]", "TLS:TLS"), + // 16: DH *dh = DH_get_2048_256(); + finding( + "KeyContext{ValueAction:DH-2048-256}", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 18: EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 23: SSL_CTX_set_ssl_version(ctx, TLSv1_2_method()); + finding("ProtocolContext{ValueAction:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 24: SSL_set_ssl_method(ssl, TLS_client_method()); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS"), + // 28: SSL_CONF_cmd(cctx, "MinProtocol", "TLSv1.2"); + finding("ProtocolContext{Protocol:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 29: SSL_CONF_cmd(cctx, "CipherString", "AES256-GCM-SHA384"); + finding( + "ProtocolContext{CipherSuite:AES256-GCM-SHA384}", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_RSA_WITH_AES_256_GCM_SHA384[AssetCollection:[KeyAgreement:RSA[Oid:1.2.840.113549.1.1.1], " + + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46], " + + "PublicKeyEncryption:RSA[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.1]], IdentifierCollection:[Identifier:0x00, " + + "Identifier:0x9D]]]]"), + // 30: SSL_CONF_cmd(cctx, "Groups", "X25519"); + finding( + "ProtocolContext{Algorithm:X25519}", + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]"), + // 32: SSL_CTX_set_tlsext_use_srtp(ctx, "SRTP_AES128_CM_SHA1_80"); + finding( + "ProtocolContext{Algorithm:SRTP_AES128_CM_SHA1_80}", + "Protocol:SRTP[CipherSuiteCollection:[CipherSuite:SRTP_AES128_CM_SHA1_80[AssetCollection:[BlockCipher:AES-128-CTR[BlockSize:128, " + + "KeyLength:128, Mode:CTR, Oid:2.16.840.1.101.3.4.1], " + + "Mac:HMAC-SHA-1[MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], Oid:1.2.840.113549.2.7, Tag:TAG, " + + "TagLength:80]], IdentifierCollection:[Identifier:0x00, Identifier:0x01]]]]"), + // 33: SSL_CTX_set1_sigalgs_list(ctx, "ECDSA+SHA256:RSA-PSS+SHA256"); + finding( + "ProtocolContext{Algorithm:ECDSA+SHA256:RSA-PSS+SHA256}", + "MergeableCollection:[Signature:ECDSA-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.10045.4.3.2], " + + "ProbabilisticSignatureScheme:RSA-PSS[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.10]]"), + // 34: SSL_set_min_proto_version(ssl, TLS1_3_VERSION); + finding("ProtocolContext{Protocol:TLSv1.3}", "TLS:TLSv1.3[Version:1.3]"), + // 35: SSL_set_ciphersuites(ssl, "TLS_AES_128_GCM_SHA256"); + finding( + "ProtocolContext{CipherSuite:TLS_AES_128_GCM_SHA256}", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_AES_128_GCM_SHA256[AssetCollection:[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x01]]]]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/ssl/OpenSSLLibsslContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTest.java new file mode 100644 index 000000000..390fa234b --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTest.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.openssl.ssl; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The options set on a TLS context add up: the protocol versions disabled by every {@code + * SSL_CTX_set_options} made on a context, and by every {@code SSL_set_options} made on a connection + * created from it, together bound the range of versions the context uses, with its minimum and + * maximum versions. A context is so reported with the minimum and the maximum version of that + * range, not with a version for each call; a context whose method is of one version keeps it. + */ +class OpenSSLLibsslOptionsContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + finding( + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.0, Protocol:TLSv1.1}, " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[Version:1.2]"), + // 12: SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); + finding( + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.0, Protocol:TLSv1.3}, " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.1[Version:1.1]", + "TLS:TLSv1.2[Version:1.2]"), + // 19: SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); + finding( + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.0:TLSv1.1}, " + + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.2}], " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.3[Version:1.3]"), + // 27: SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + finding( + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.2}, " + + "ProtocolContext{Protocol:TLSv1.0, Protocol:TLSv1.1}, " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[Version:1.2]"), + // 35: SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + finding("ProtocolContext{}[ProtocolContext{ValueAction:TLS}]", "TLS:TLS"), + // 41: SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + finding( + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.2}, " + + "ProtocolContext{Protocol:TLSv1.0}, " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[Version:1.2]"), + // 48: SSL_CTX *ctx = SSL_CTX_new(TLSv1_2_method()); + finding( + "ProtocolContext{}[ProtocolContext{Protocol:TLSv1.0}, " + + "ProtocolContext{ValueAction:TLSv1.2}]", + "TLS:TLSv1.2[Version:1.2]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify( + "rules/detection/openssl/ssl/OpenSSLLibsslOptionsContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslOptionsTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslOptionsTest.java new file mode 100644 index 000000000..5694ba0cd --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslOptionsTest.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.openssl.ssl; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * The groups set with {@code SSL_CTX_set1_curves_list} / {@code SSL_set1_curves_list}, aliases of + * the {@code *_groups_list} functions, are reported. The protocol versions disabled with {@code + * SSL_CTX_set_options} / {@code SSL_set_options} report the range of versions OpenSSL uses: the + * lowest run of versions that are not disabled, with its lowest version when the options raise the + * minimum and its highest when they lower the maximum, as {@code SSL_CTX_set_min_proto_version} and + * {@code SSL_CTX_set_max_proto_version} report them. Options that disable no version report + * nothing. + */ +class OpenSSLLibsslOptionsTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: SSL_CTX_set1_curves_list(ctx, "P-384"); + finding( + "ProtocolContext{Algorithm:P-384}", + "MergeableCollection:[KeyAgreement:ECDH[EllipticCurve:secp384r1, " + + "Oid:1.3.132.1.12]]"), + // 5: SSL_set1_curves_list(ssl, "X25519"); + finding( + "ProtocolContext{Algorithm:X25519}", + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]"), + // 9: SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | + // SSL_OP_NO_TLSv1_1); + finding( + "ProtocolContext{Protocol:TLSv1.0:TLSv1.1}", + "TLS:TLSv1.2[Version:1.2]"), + // 13: SSL_set_options(ssl, SSL_OP_NO_TLSv1_3); + finding("ProtocolContext{Protocol:TLSv1.3}", "TLS:TLSv1.2[Version:1.2]"), + // 18: SSL_CTX_set_options(ctx, options); + finding("ProtocolContext{Protocol:TLSv1.0}", "TLS:TLSv1.1[Version:1.1]"), + // 22: SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); + finding("ProtocolContext{Protocol:TLSv1.1}", "TLS:TLSv1.0[Version:1.0]"), + // 26: SSL_CTX_set_options(ctx, SSL_OP_NO_DTLSv1); + finding("ProtocolContext{Protocol:DTLSv1.0}", "TLS:DTLSv1.2[Version:1.2]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/ssl/OpenSSLLibsslOptionsTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslTest.java new file mode 100644 index 000000000..958d686dd --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLLibsslTest.java @@ -0,0 +1,248 @@ +/* + * 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.openssl.ssl; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Protocol; +import com.ibm.mapper.model.Version; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Covers the rule entries in {@link OpenSSLLibssl}. + * + *

Finding shapes: + * + *

    + *
  • TLS ({@link TLS}): with a {@link Version} child when the method or setting names a + * version, without one for {@code TLS_method()} and its client and server forms. + *
  • Generic protocol ({@link Protocol}): DTLS and QUIC methods, flat. + *
  • SRTP: a {@link Protocol} holding the configured protection profiles. + *
  • Key ({@link KeyContext}): the DH and EC keys created for {@code + * SSL_(CTX_)set_tmp_dh/ecdh}, reported once where they are created. + *
+ */ +class OpenSSLLibsslTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 8: TLS_method(); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS"), + // 9: TLS_client_method(); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS"), + // 10: TLS_server_method(); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS"), + // 12: TLSv1_2_method(); + finding("ProtocolContext{ValueAction:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 13: TLSv1_2_client_method(); + finding("ProtocolContext{ValueAction:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 14: TLSv1_2_server_method(); + finding("ProtocolContext{ValueAction:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 16: TLSv1_1_method(); + finding("ProtocolContext{ValueAction:TLSv1.1}", "TLS:TLSv1.1[Version:1.1]"), + // 17: TLSv1_1_client_method(); + finding("ProtocolContext{ValueAction:TLSv1.1}", "TLS:TLSv1.1[Version:1.1]"), + // 18: TLSv1_1_server_method(); + finding("ProtocolContext{ValueAction:TLSv1.1}", "TLS:TLSv1.1[Version:1.1]"), + // 20: TLSv1_method(); + finding("ProtocolContext{ValueAction:TLSv1.0}", "TLS:TLSv1.0[Version:1.0]"), + // 21: TLSv1_client_method(); + finding("ProtocolContext{ValueAction:TLSv1.0}", "TLS:TLSv1.0[Version:1.0]"), + // 22: TLSv1_server_method(); + finding("ProtocolContext{ValueAction:TLSv1.0}", "TLS:TLSv1.0[Version:1.0]"), + // 24: SSLv3_method(); + finding("ProtocolContext{ValueAction:SSLv3.0}", "TLS:SSLv3.0[Version:3.0]"), + // 25: SSLv3_client_method(); + finding("ProtocolContext{ValueAction:SSLv3.0}", "TLS:SSLv3.0[Version:3.0]"), + // 26: SSLv3_server_method(); + finding("ProtocolContext{ValueAction:SSLv3.0}", "TLS:SSLv3.0[Version:3.0]"), + // 28: DTLS_method(); + finding("ProtocolContext{ValueAction:DTLS}", "Protocol:DTLS"), + // 29: DTLS_client_method(); + finding("ProtocolContext{ValueAction:DTLS}", "Protocol:DTLS"), + // 30: DTLS_server_method(); + finding("ProtocolContext{ValueAction:DTLS}", "Protocol:DTLS"), + // 32: DTLSv1_2_method(); + finding("ProtocolContext{ValueAction:DTLSv1.2}", "TLS:DTLSv1.2[Version:1.2]"), + // 33: DTLSv1_2_client_method(); + finding("ProtocolContext{ValueAction:DTLSv1.2}", "TLS:DTLSv1.2[Version:1.2]"), + // 34: DTLSv1_2_server_method(); + finding("ProtocolContext{ValueAction:DTLSv1.2}", "TLS:DTLSv1.2[Version:1.2]"), + // 36: DTLSv1_method(); + finding("ProtocolContext{ValueAction:DTLSv1.0}", "TLS:DTLSv1.0[Version:1.0]"), + // 37: DTLSv1_client_method(); + finding("ProtocolContext{ValueAction:DTLSv1.0}", "TLS:DTLSv1.0[Version:1.0]"), + // 38: DTLSv1_server_method(); + finding("ProtocolContext{ValueAction:DTLSv1.0}", "TLS:DTLSv1.0[Version:1.0]"), + // 40: OSSL_QUIC_client_method(); + finding("ProtocolContext{ValueAction:QUIC}", "Protocol:QUIC"), + // 41: OSSL_QUIC_client_thread_method(); + finding("ProtocolContext{ValueAction:QUIC}", "Protocol:QUIC"), + // 42: OSSL_QUIC_server_method(); + finding("ProtocolContext{ValueAction:QUIC}", "Protocol:QUIC"), + // 47: SSL_CTX_new(tls12_method); + finding( + "ProtocolContext{}[ProtocolContext{ValueAction:TLSv1.2}]", + "TLS:TLSv1.2[Version:1.2]"), + // 50: SSL_CTX_set_ciphersuites(ctx, "TLS_AES_128_GCM_SHA256"); + finding( + "ProtocolContext{CipherSuite:TLS_AES_128_GCM_SHA256}", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_AES_128_GCM_SHA256[AssetCollection:[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x01]]]]"), + // 51: SSL_set_ciphersuites(s, "TLS_AES_128_GCM_SHA256"); + finding( + "ProtocolContext{CipherSuite:TLS_AES_128_GCM_SHA256}", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_AES_128_GCM_SHA256[AssetCollection:[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x01]]]]"), + // 53: DH* dh1 = DH_get_2048_256(); + finding( + "KeyContext{ValueAction:DH-2048-256}", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 55: DH* dh2 = DH_get_2048_256(); + finding( + "KeyContext{ValueAction:DH-2048-256}", + "PublicKeyEncryption:FFDH-2048[KeyLength:2048, Oid:1.2.840.113549.1.3.1]"), + // 57: EC_KEY* ecdh1 = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 59: EC_KEY* ecdh2 = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + finding( + "KeyContext{ValueAction:EC-prime256v1}", + "PublicKeyEncryption:EC-secp256r1[EllipticCurve:secp256r1, " + + "Oid:1.2.840.10045.2.1]"), + // 66: SSL_CTX_set_tlsext_use_srtp(ctx, "SRTP_AES128_CM_SHA1_80"); + finding( + "ProtocolContext{Algorithm:SRTP_AES128_CM_SHA1_80}", + "Protocol:SRTP[CipherSuiteCollection:[CipherSuite:SRTP_AES128_CM_SHA1_80[AssetCollection:[BlockCipher:AES-128-CTR[BlockSize:128, " + + "KeyLength:128, Mode:CTR, Oid:2.16.840.1.101.3.4.1], " + + "Mac:HMAC-SHA-1[MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], Oid:1.2.840.113549.2.7, Tag:TAG, " + + "TagLength:80]], IdentifierCollection:[Identifier:0x00, Identifier:0x01]]]]"), + // 67: SSL_set_tlsext_use_srtp(s, "SRTP_AES128_CM_SHA1_80"); + finding( + "ProtocolContext{Algorithm:SRTP_AES128_CM_SHA1_80}", + "Protocol:SRTP[CipherSuiteCollection:[CipherSuite:SRTP_AES128_CM_SHA1_80[AssetCollection:[BlockCipher:AES-128-CTR[BlockSize:128, " + + "KeyLength:128, Mode:CTR, Oid:2.16.840.1.101.3.4.1], " + + "Mac:HMAC-SHA-1[MessageDigest:SHA-1[BlockSize:512, Digest:DIGEST, " + + "DigestSize:160, Oid:1.3.14.3.2.26], Oid:1.2.840.113549.2.7, Tag:TAG, " + + "TagLength:80]], IdentifierCollection:[Identifier:0x00, Identifier:0x01]]]]"), + // 75: SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); + finding("ProtocolContext{Protocol:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 76: SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); + finding("ProtocolContext{Protocol:TLSv1.3}", "TLS:TLSv1.3[Version:1.3]"), + // 77: SSL_set_min_proto_version(s, TLS1_2_VERSION); + finding("ProtocolContext{Protocol:TLSv1.2}", "TLS:TLSv1.2[Version:1.2]"), + // 78: SSL_set_max_proto_version(s, TLS1_3_VERSION); + finding("ProtocolContext{Protocol:TLSv1.3}", "TLS:TLSv1.3[Version:1.3]"), + // 81: SSL_CTX_set1_sigalgs_list(ctx, + // "SLH-DSA-SHA2-256s:ECDSA+SHA256:RSA+SHA256"); + finding( + "ProtocolContext{Algorithm:SLH-DSA-SHA2-256s:ECDSA+SHA256:RSA+SHA256}", + "MergeableCollection:[Signature:SLH-DSA-SHA2-256S[ParameterSetIdentifier:SHA2-256S], " + + "Signature:ECDSA-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.10045.4.3.2], " + + "Signature:RSA-PKCS1-1.5-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.113549.1.1.11, Padding:PKCS1]]"), + // 82: SSL_CTX_set1_groups_list(ctx, "MLKEM768:X25519:secp256r1"); + finding( + "ProtocolContext{Algorithm:MLKEM768:X25519:secp256r1}", + "MergeableCollection:[KeyEncapsulationMechanism:ML-KEM-768[Oid:2.16.840.1.101.3.4.4.2, " + + "ParameterSetIdentifier:768], KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110], KeyAgreement:ECDH[EllipticCurve:secp256r1, " + + "Oid:1.3.132.1.12]]"), + // 84: SSL_CTX_set1_client_sigalgs_list(ctx, "ECDSA+SHA256"); + finding( + "ProtocolContext{Algorithm:ECDSA+SHA256}", + "MergeableCollection:[Signature:ECDSA-SHA-256[MessageDigest:SHA-256[BlockSize:512, " + + "Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], " + + "Oid:1.2.840.10045.4.3.2]]"), + // 85: SSL_set1_groups_list(s, "X25519"); + finding( + "ProtocolContext{Algorithm:X25519}", + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]"), + // 86: SSL_set1_sigalgs_list(s, "ECDSA+SHA384"); + finding( + "ProtocolContext{Algorithm:ECDSA+SHA384}", + "MergeableCollection:[Signature:ECDSA-SHA-384[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.10045.4.3.3]]"), + // 90: SSL_CTX_set1_groups_list(ctx, "X25519:FRODOKEM976AES:secp256r1"); + finding( + "ProtocolContext{Algorithm:X25519:FRODOKEM976AES:secp256r1}", + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110], Unknown:FRODOKEM976AES, " + + "KeyAgreement:ECDH[EllipticCurve:secp256r1, Oid:1.3.132.1.12]]"), + // 102: SSLv23_method(); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS"), + // 103: SSLv23_client_method(); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS"), + // 104: SSLv23_server_method(); + finding("ProtocolContext{ValueAction:TLS}", "TLS:TLS")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/ssl/OpenSSLLibsslTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLSslContextTest.java b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLSslContextTest.java new file mode 100644 index 000000000..fd8d46fc5 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/rules/detection/openssl/ssl/OpenSSLSslContextTest.java @@ -0,0 +1,133 @@ +/* + * 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.openssl.ssl; + +import static com.ibm.plugin.ExpectedFinding.assertAllReported; +import static com.ibm.plugin.ExpectedFinding.assertFinding; +import static com.ibm.plugin.ExpectedFinding.finding; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.mapper.model.INode; +import com.ibm.plugin.CxxVerifier; +import com.ibm.plugin.ExpectedFinding; +import com.ibm.plugin.TestBase; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * A TLS context created with {@code SSL_CTX_new} or {@code SSL_CTX_new_ex} is reported as one + * protocol ({@link OpenSSLSslContext}): the protocol of its method, with the version, cipher + * suites, groups, signature algorithms and ephemeral DH group configured on it and on the + * connections created from it with {@code SSL_new}. A version range gives one protocol per version. + */ +class OpenSSLSslContextTest extends TestBase { + + private static final List FINDINGS = + List.of( + // 4: SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); + finding( + "ProtocolContext{}[ProtocolContext{CipherSuite:ECDHE-RSA-AES256-GCM-SHA384}, " + + "ProtocolContext{CipherSuite:TLS_AES_256_GCM_SHA384}, " + + "ProtocolContext{Protocol:TLSv1.2}, ProtocolContext{Algorithm:X25519}, " + + "ProtocolContext{Algorithm:ECDSA+SHA256}, ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[CipherSuiteCollection:[CipherSuite:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384[AssetCollection:[KeyAgreement:ECDH[Oid:1.3.132.1.12], " + + "AuthenticatedEncryption:AES-256-GCM[BlockSize:128, KeyLength:256, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.46], " + + "PublicKeyEncryption:RSA[MessageDigest:SHA-384[BlockSize:1024, " + + "Digest:DIGEST, DigestSize:384, Oid:2.16.840.1.101.3.4.2.2], " + + "Oid:1.2.840.113549.1.1.1]], IdentifierCollection:[Identifier:0xC0, " + + "Identifier:0x30]], " + + "CipherSuite:TLS_AES_256_GCM_SHA384[AssetCollection:[AuthenticatedEncryption:AES-256-GCM[BlockSize:128, " + + "KeyLength:256, Mode:GCM, Oid:2.16.840.1.101.3.4.1.46], " + + "MessageDigest:SHA-384[BlockSize:1024, Digest:DIGEST, DigestSize:384, " + + "Oid:2.16.840.1.101.3.4.2.2]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x02]]], " + + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110], Signature:ECDSA-SHA-256[MessageDigest:SH" + + "A-256[BlockSize:512, Digest:DIGEST, DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.10045.4.3.2]], Version:1.2]"), + // 13: SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); + finding( + "ProtocolContext{}[ProtocolContext{}[ProtocolContext{CipherSuite:TLS_AES_128_GCM_SHA256}, " + + "ProtocolContext{Algorithm:X25519}], ProtocolContext{ValueAction:TLS}]", + "TLS:TLS[CipherSuiteCollection:[CipherSuite:TLS_AES_128_GCM_SHA256[AssetCollection:[AuthenticatedEncryption:AES-128-GCM[BlockSize:128, " + + "KeyLength:128, Mode:GCM, Oid:2.16.840.1.101.3.4.1.6], " + + "MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, DigestSize:256, " + + "Oid:2.16.840.1.101.3.4.2.1]], IdentifierCollection:[Identifier:0x13, " + + "Identifier:0x01]]], " + + "MergeableCollection:[KeyAgreement:x25519[EllipticCurve:Curve25519, " + + "Oid:1.3.101.110]]]"), + // 20: SSL_CTX *ctx = SSL_CTX_new_ex(libctx, NULL, DTLSv1_2_method()); + finding( + "ProtocolContext{}[ProtocolContext{}[KeyContext{ValueAction:DH-2048-256}], " + + "ProtocolContext{ValueAction:DTLSv1.2}]", + "TLS:DTLSv1.2[PublicKeyEncryption:FFDH-2048[KeyLength:2048, " + + "Oid:1.2.840.113549.1.3.1], Version:1.2]"), + // 26: SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + finding( + "ProtocolContext{}[ProtocolContext{CipherSuite:AES128-GCM-SHA256}, " + + "ProtocolContext{Protocol:TLSv1.2}, ProtocolContext{Protocol:TLSv1.3}, " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[CipherSuiteCollection:[CipherSuite:TLS_RSA_WITH_AES_128_GCM_SHA256[AssetCollection:[KeyAgreement:RSA[Oid:1.2.840.113549.1.1.1], " + + "AuthenticatedEncryption:AES-128-GCM[BlockSize:128, KeyLength:128, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.6], " + + "PublicKeyEncryption:RSA[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.1.1.1]], " + + "IdentifierCollection:[Identifier:0x00, Identifier:0x9C]]], Version:1.2]", + "TLS:TLSv1.3[CipherSuiteCollection:[CipherSuite:TLS_RSA_WITH_AES_128_GCM_SHA256[AssetCollection:[KeyAgreement:RSA[Oid:1.2.840.113549.1.1.1], " + + "AuthenticatedEncryption:AES-128-GCM[BlockSize:128, KeyLength:128, Mode:GCM, " + + "Oid:2.16.840.1.101.3.4.1.6], " + + "PublicKeyEncryption:RSA[MessageDigest:SHA-256[BlockSize:512, Digest:DIGEST, " + + "DigestSize:256, Oid:2.16.840.1.101.3.4.2.1], Oid:1.2.840.113549.1.1.1]], " + + "IdentifierCollection:[Identifier:0x00, Identifier:0x9C]]], Version:1.3]"), + // 33: SSL_CTX *ctx = SSL_CTX_new(TLS_method()); + finding( + "ProtocolContext{}[ProtocolContext{}[ProtocolContext{ValueAction:TLSv1.2}], " + + "ProtocolContext{ValueAction:TLS}]", + "TLS:TLSv1.2[Version:1.2]")); + + private int findings = 0; + + @Test + void test() { + CxxVerifier.verify("rules/detection/openssl/ssl/OpenSSLSslContextTestFile.cc", this); + assertAllReported(FINDINGS, findings); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull List nodes) { + findings++; + assertFinding(FINDINGS, findingId, detectionStore, nodes); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/CxxTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/CxxTranslatorTest.java new file mode 100644 index 000000000..5f39083de --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/CxxTranslatorTest.java @@ -0,0 +1,221 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.AlgorithmParameterContext; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.engine.model.context.SecretKeyContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import com.sonar.cxx.sslr.api.Token; +import com.sonar.cxx.sslr.api.TokenType; +import java.net.URI; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.parser.CxxPunctuator; + +class CxxTranslatorTest { + + private final CxxTranslator translator = new CxxTranslator(); + + private static Token fakeToken(TokenType type, String value, int line, int column) { + return Token.builder() + .setLine(line) + .setColumn(column) + .setValueAndOriginalValue(value) + .setType(type) + .setURI(URI.create("file:///test.cpp")) + .build(); + } + + private static final class PlainAstNodeType implements TokenType { + @Override + public String getName() { + return "PLAIN"; + } + + @Override + public String getValue() { + return "PLAIN"; + } + + @Override + public boolean hasToBeSkippedFromAst(AstNode node) { + return false; + } + } + + // --- getDetectionContextFrom --- + + @Test + void aNodeWithNoTokenYieldsNoDetectionLocation() { + AstNode nodeWithNoToken = new AstNode(new PlainAstNodeType(), "empty", null); + DetectionLocation location = + translator.getDetectionContextFrom(nodeWithNoToken, () -> "OpenSSL", "f.cpp"); + assertThat(location).isNull(); + } + + @Test + void aNodeWithATokenYieldsALocationCarryingItsLineAndColumn() { + AstNode leaf = new AstNode(fakeToken(GenericTokenType.IDENTIFIER, "foo", 7, 3)); + DetectionLocation location = + translator.getDetectionContextFrom(leaf, () -> "OpenSSL", "f.cpp"); + assertThat(location).isNotNull(); + assertThat(location.lineNumber()).isEqualTo(7); + assertThat(location.offSet()).isEqualTo(3); + assertThat(location.filePath()).isEqualTo("f.cpp"); + } + + // --- extractKeywords, exercised through translate()'s DetectionLocation.keywords() --- + + @Test + void aFunctionCallPostfixExpressionYieldsTheCalleeNameAsKeyword() { + AstNode idExpression = new AstNode(CxxGrammarImpl.idExpression, "idExpression", null); + AstNode identifier = + new AstNode(fakeToken(GenericTokenType.IDENTIFIER, "EVP_MD_fetch", 1, 0)); + idExpression.addChild(identifier); + + AstNode openParen = new AstNode(fakeToken(CxxPunctuator.BR_LEFT, "(", 1, 12)); + AstNode closeParen = new AstNode(fakeToken(CxxPunctuator.BR_RIGHT, ")", 1, 13)); + + // a real parser-built postfixExpression node carries its first token itself, in addition + // to that same token being reachable through its idExpression child + AstNode postfixExpression = + new AstNode( + CxxGrammarImpl.postfixExpression, + "postfixExpression", + fakeToken(GenericTokenType.IDENTIFIER, "EVP_MD_fetch", 1, 0)); + postfixExpression.addChild(idExpression); + postfixExpression.addChild(openParen); + postfixExpression.addChild(closeParen); + + DetectionLocation location = + translator.getDetectionContextFrom(postfixExpression, () -> "OpenSSL", "f.cpp"); + assertThat(location).isNotNull(); + assertThat(location.keywords()).containsExactly("EVP_MD_fetch"); + } + + @Test + void aNewExpressionYieldsTheConstructedTypeNameAsKeyword() { + AstNode typeIdentifier = + new AstNode(fakeToken(GenericTokenType.IDENTIFIER, "EVP_PKEY_CTX", 1, 4)); + AstNode typeSpecifier = new AstNode(CxxGrammarImpl.typeSpecifier, "typeSpecifier", null); + typeSpecifier.addChild(typeIdentifier); + + // a real parser-built newExpression node carries its own first token (e.g. "new") + AstNode newExpression = + new AstNode( + CxxGrammarImpl.newExpression, + "newExpression", + fakeToken(new PlainAstNodeType(), "new", 1, 0)); + newExpression.addChild(typeSpecifier); + + DetectionLocation location = + translator.getDetectionContextFrom(newExpression, () -> "OpenSSL", "f.cpp"); + assertThat(location).isNotNull(); + assertThat(location.keywords()).containsExactly("EVP_PKEY_CTX"); + } + + @Test + void aNewExpressionWithNoTypeSpecifierFallsBackToTheTokenValue() { + // newExpression itself carries a token (its first token, e.g. "new"), so with no + // typeSpecifier descendant, extractKeywords falls back to that node's own token value. + AstNode newExpression = + new AstNode( + CxxGrammarImpl.newExpression, + "newExpression", + fakeToken(new PlainAstNodeType(), "new", 1, 0)); + + DetectionLocation location = + translator.getDetectionContextFrom(newExpression, () -> "OpenSSL", "f.cpp"); + assertThat(location).isNotNull(); + assertThat(location.keywords()).containsExactly("new"); + } + + @Test + void anUnrecognizedNodeShapeFallsBackToItsOwnTokenValue() { + AstNode plainLeaf = new AstNode(fakeToken(new PlainAstNodeType(), "someToken", 2, 0)); + + DetectionLocation location = + translator.getDetectionContextFrom(plainLeaf, () -> "OpenSSL", "f.cpp"); + assertThat(location).isNotNull(); + assertThat(location.keywords()).containsExactly("someToken"); + } + + // --- translate() dispatch --- + + private static AstNode leafWithToken() { + return new AstNode(fakeToken(GenericTokenType.IDENTIFIER, "x", 1, 0)); + } + + @Test + void secretKeyContextDispatchesToTheSecretKeyTranslatorAndYieldsEmptyForAPlainValueAction() { + // No cpp rule currently produces a SecretKeyContext value, but the dispatch branch + // itself is reachable and must not throw. + Optional node = + translator.translate( + () -> "OpenSSL", + new ValueAction<>("AES-256", leafWithToken()), + new SecretKeyContext(Map.of()), + "f.cpp"); + assertThat(node).isEmpty(); + } + + @Test + void keyContextDispatchesToTheKeyTranslator() { + Optional node = + translator.translate( + () -> "OpenSSL", + new ValueAction<>("RSA", leafWithToken()), + new KeyContext(), + "f.cpp"); + assertThat(node).isPresent(); + } + + @Test + void algorithmParameterContextIsNotYetImplementedAndYieldsEmpty() { + Optional node = + translator.translate( + () -> "OpenSSL", + new ValueAction<>("whatever", leafWithToken()), + new AlgorithmParameterContext(), + "f.cpp"); + assertThat(node).isEmpty(); + } + + @Test + void aValueWithNoResolvableLocationYieldsEmpty() { + AstNode nodeWithNoToken = new AstNode(new PlainAstNodeType(), "empty", null); + Optional node = + translator.translate( + () -> "OpenSSL", + new ValueAction<>("RSA", nodeWithNoToken), + new KeyContext(), + "f.cpp"); + assertThat(node).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyAgreementContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyAgreementContextTranslatorTest.java new file mode 100644 index 000000000..314f74bb0 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyAgreementContextTranslatorTest.java @@ -0,0 +1,165 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyAgreementContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.DH; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.SecP256r1MLKEM768; +import com.ibm.mapper.model.algorithms.SecP384r1MLKEM1024; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X25519MLKEM768; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.algorithms.X448MLKEM1024; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CxxKeyAgreementContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxKeyAgreementContextTranslator translator = + new CxxKeyAgreementContextTranslator(); + + private Optional translate(String value) { + return translator.translate( + () -> "OpenSSL", + new ValueAction<>(value, (AstNode) null), + new KeyAgreementContext(), + TEST_LOCATION); + } + + private static Stream dhVariants() { + return Stream.of( + Arguments.of("DH"), + Arguments.of("DH-2048"), + Arguments.of("DH-3072"), + Arguments.of("DH-4096")); + } + + @ParameterizedTest + @MethodSource("dhVariants") + void dhVariantsResolveToDh(String value) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DH.class); + } + + private static Stream ecdhVariants() { + return Stream.of( + Arguments.of("ECDH"), + Arguments.of("ECDH-P256"), + Arguments.of("ECDH-P384"), + Arguments.of("ECDH-P521"), + Arguments.of("ECDH-SECP256K1")); + } + + @ParameterizedTest + @MethodSource("ecdhVariants") + void ecdhVariantsResolveToEcdh(String value) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(ECDH.class); + } + + @Test + void x25519IsMapped() { + Optional node = translate("X25519"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X25519.class); + } + + @Test + void x448IsMapped() { + Optional node = translate("X448"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X448.class); + } + + private static Stream mlkemVariants() { + return Stream.of( + Arguments.of("ML-KEM-512", 512), + Arguments.of("ML-KEM-768", 768), + Arguments.of("ML-KEM-1024", 1024)); + } + + @ParameterizedTest + @MethodSource("mlkemVariants") + void mlkemVariantsResolveToMlkemOfTheExpectedParameterSet(String value, int parameterSet) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(MLKEM.class); + assertThat(node.get().asString()).isEqualTo("ML-KEM-" + parameterSet); + } + + @Test + void x25519Mlkem768HybridIsMapped() { + Optional node = translate("X25519MLKEM768"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X25519MLKEM768.class); + } + + @Test + void x448Mlkem1024HybridIsMapped() { + Optional node = translate("X448MLKEM1024"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X448MLKEM1024.class); + } + + @Test + void secp256r1Mlkem768HybridIsMapped() { + Optional node = translate("SECP256R1MLKEM768"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SecP256r1MLKEM768.class); + } + + @Test + void secp384r1Mlkem1024HybridIsMapped() { + Optional node = translate("SECP384R1MLKEM1024"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SecP384r1MLKEM1024.class); + } + + @Test + void sm2KeyExchangeIsMapped() { + Optional node = translate("SM2"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SM2.class); + } + + @Test + void unknownAlgorithmNameResolvesToEmpty() { + assertThat(translate("NOT-A-REAL-KEY-AGREEMENT")).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyContextTranslatorTest.java new file mode 100644 index 000000000..0bb6b104c --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyContextTranslatorTest.java @@ -0,0 +1,287 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyContext; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +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.MLDSA; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.SPHINCSPlus; +import com.ibm.mapper.model.algorithms.SecP256r1MLKEM768; +import com.ibm.mapper.model.algorithms.SecP384r1MLKEM1024; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X25519MLKEM768; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.algorithms.X448MLKEM1024; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CxxKeyContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxKeyContextTranslator translator = new CxxKeyContextTranslator(); + + private Optional translate(String value) { + return translator.translate( + () -> "OpenSSL", + new ValueAction<>(value, (AstNode) null), + new KeyContext(), + TEST_LOCATION); + } + + @Test + void arbitraryRsaBitLengthIsMappedToRsaWithThatKeyLength() { + Optional node = translate("RSA-2048"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(RSA.class); + assertThat(node.get().asString()).isEqualTo("RSA-2048"); + } + + @Test + void bareRsaIsMapped() { + Optional node = translate("RSA"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(RSA.class); + } + + @Test + void rsaPssKeyIsNotTreatedAsAnRsaBitLength() { + Optional node = translate("RSA-PSS"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(RSAssaPSS.class); + } + + @Test + void dsaBitLengthIsTheKeyLength() { + Optional node = translate("DSA-2048"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DSA.class); + assertThat(node.get().getChildren().get(KeyLength.class).asString()).isEqualTo("2048"); + assertThat(node.get().asString()).isEqualTo("DSA-2048"); + } + + @Test + void bareDsaIsMapped() { + Optional node = translate("DSA"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DSA.class); + } + + @Test + void bareEcIsMappedToAnEcKeyWithNoCurve() { + Optional node = translate("EC"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(EllipticCurveAlgorithm.class); + assertThat(node.get().hasChildOfType(EllipticCurve.class)).isEmpty(); + } + + private static Stream ecCurveVariants() { + return Stream.of( + Arguments.of("EC-P-192", "EC-secp192r1"), + Arguments.of("EC-P-224", "EC-secp224r1"), + Arguments.of("EC-P-256", "EC-secp256r1"), + Arguments.of("EC-P-384", "EC-secp384r1"), + Arguments.of("EC-P-521", "EC-secp521r1"), + Arguments.of("EC-SECP256K1", "EC-secp256k1"), + Arguments.of("EC-BRAINPOOLP256R1", "EC-Brainpoolp256r1"), + Arguments.of("EC-BRAINPOOLP384R1", "EC-Brainpoolp384r1"), + Arguments.of("EC-BRAINPOOLP512R1", "EC-Brainpoolp512r1"), + Arguments.of("EC-sect283k1", "EC-sect283k1"), + Arguments.of("EC-B-163", "EC-sect163r2")); + } + + @ParameterizedTest + @MethodSource("ecCurveVariants") + void ecCurveVariantsResolveToAnEcKeyOnTheExpectedCurve(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(EllipticCurveAlgorithm.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + @Test + void bareDhIsMapped() { + Optional node = translate("DH"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DH.class); + } + + @Test + void dh2048IsMappedToDh() { + Optional node = translate("DH-2048"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DH.class); + } + + @Test + void dh4096IsMappedToDh() { + Optional node = translate("DH-4096"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DH.class); + } + + @Test + void ed25519IsMapped() { + Optional node = translate("ED25519"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(Ed25519.class); + } + + @Test + void ed448IsMapped() { + Optional node = translate("ED448"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(Ed448.class); + } + + @Test + void x25519IsMapped() { + Optional node = translate("X25519"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X25519.class); + } + + @Test + void x448IsMapped() { + Optional node = translate("X448"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X448.class); + } + + private static Stream mlkemVariants() { + return Stream.of( + Arguments.of("ML-KEM-512", 512), + Arguments.of("ML-KEM-768", 768), + Arguments.of("ML-KEM-1024", 1024)); + } + + @ParameterizedTest + @MethodSource("mlkemVariants") + void mlkemVariantsResolveToMlkemOfTheExpectedParameterSet(String value, int parameterSet) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(MLKEM.class); + assertThat(node.get().asString()).isEqualTo("ML-KEM-" + parameterSet); + } + + private static Stream mldsaVariants() { + return Stream.of( + Arguments.of("ML-DSA-44", "ML-DSA-44"), + Arguments.of("ML-DSA-65", "ML-DSA-65"), + Arguments.of("ML-DSA-87", "ML-DSA-87")); + } + + @ParameterizedTest + @MethodSource("mldsaVariants") + void mldsaVariantsResolveToMldsaOfTheExpectedParameterSet(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(MLDSA.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + private static Stream slhdsaVariants() { + return Stream.of( + Arguments.of("SLH-DSA-SHA2-128F", "SLH-DSA-SHA2-128F"), + Arguments.of("SLH-DSA-SHA2-128S", "SLH-DSA-SHA2-128S"), + Arguments.of("SLH-DSA-SHAKE-128F", "SLH-DSA-SHAKE-128F"), + Arguments.of("SLH-DSA-SHAKE-128S", "SLH-DSA-SHAKE-128S"), + Arguments.of("SLH-DSA-SHA2-192F", "SLH-DSA-SHA2-192F"), + Arguments.of("SLH-DSA-SHA2-192S", "SLH-DSA-SHA2-192S"), + Arguments.of("SLH-DSA-SHAKE-192F", "SLH-DSA-SHAKE-192F"), + Arguments.of("SLH-DSA-SHAKE-192S", "SLH-DSA-SHAKE-192S"), + Arguments.of("SLH-DSA-SHA2-256F", "SLH-DSA-SHA2-256F"), + Arguments.of("SLH-DSA-SHA2-256S", "SLH-DSA-SHA2-256S"), + Arguments.of("SLH-DSA-SHAKE-256F", "SLH-DSA-SHAKE-256F"), + Arguments.of("SLH-DSA-SHAKE-256S", "SLH-DSA-SHAKE-256S")); + } + + @ParameterizedTest + @MethodSource("slhdsaVariants") + void slhdsaVariantsResolveToSphincsPlusOfTheExpectedParameterSet( + String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SPHINCSPlus.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + @Test + void x25519Mlkem768HybridIsMapped() { + Optional node = translate("X25519MLKEM768"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X25519MLKEM768.class); + } + + @Test + void x448Mlkem1024HybridIsMapped() { + Optional node = translate("X448MLKEM1024"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(X448MLKEM1024.class); + } + + @Test + void secp256r1Mlkem768HybridIsMapped() { + Optional node = translate("SECP256R1MLKEM768"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SecP256r1MLKEM768.class); + } + + @Test + void secp384r1Mlkem1024HybridIsMapped() { + Optional node = translate("SECP384R1MLKEM1024"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SecP384r1MLKEM1024.class); + } + + @Test + void sm2IsMapped() { + Optional node = translate("SM2"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SM2.class); + } + + @Test + void unknownAlgorithmNameResolvesToEmpty() { + assertThat(translate("NOT-A-REAL-ALGORITHM")).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyDerivationFunctionContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyDerivationFunctionContextTranslatorTest.java new file mode 100644 index 000000000..90c8302d6 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxKeyDerivationFunctionContextTranslatorTest.java @@ -0,0 +1,133 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.translation.translator.contexts; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.KeyDerivationFunctionContext; +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.ANSIX942; +import com.ibm.mapper.model.algorithms.ANSIX963; +import com.ibm.mapper.model.algorithms.Argon2; +import com.ibm.mapper.model.algorithms.ConcatenationKDF; +import com.ibm.mapper.model.algorithms.HKDF; +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.SSHKDF; +import com.ibm.mapper.model.algorithms.Scrypt; +import com.ibm.mapper.model.algorithms.TLSPRF; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CxxKeyDerivationFunctionContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxKeyDerivationFunctionContextTranslator translator = + new CxxKeyDerivationFunctionContextTranslator(); + + /** Translates a name as passed to {@code EVP_KDF_fetch}. */ + private Optional translateFetchName(String name) { + return translate(new com.ibm.engine.model.Algorithm<>(name, (AstNode) null)); + } + + private Optional translate(IValue value) { + return translator.translate( + () -> "OpenSSL", value, new KeyDerivationFunctionContext(), TEST_LOCATION); + } + + private static Stream fetchNames() { + return Stream.of( + Arguments.of("PBKDF2", PBKDF2.class, "PBKDF2"), + Arguments.of("1.2.840.113549.1.5.12", PBKDF2.class, "PBKDF2"), + Arguments.of("PBKDF1", PBKDF1.class, "PBKDF1"), + Arguments.of("HKDF", HKDF.class, "HKDF"), + Arguments.of("HKDF-SHA256", HKDF.class, "HKDF-SHA-256"), + Arguments.of("HKDF-SHA384", HKDF.class, "HKDF-SHA-384"), + Arguments.of("HKDF-SHA512", HKDF.class, "HKDF-SHA-512"), + Arguments.of("TLS13-KDF", HKDF.class, "HKDF"), + Arguments.of("TLS1-PRF", TLSPRF.class, "TLS-PRF"), + Arguments.of("SSKDF", ConcatenationKDF.class, "ConcatenationKDF"), + Arguments.of("X963KDF", ANSIX963.class, "ANSI-KDF-X9.63"), + Arguments.of("X942KDF-ASN1", ANSIX942.class, "ANSI-KDF-X9.42-ASN1"), + Arguments.of("X942KDF", ANSIX942.class, "ANSI-KDF-X9.42-ASN1"), + Arguments.of("X942KDF-CONCAT", ANSIX942.class, "ANSI-KDF-X9.42-CONCAT"), + Arguments.of("KBKDF", KDFCounter.class, "SP800_108_CounterKDF"), + Arguments.of("SSHKDF", SSHKDF.class, "SSHKDF"), + Arguments.of("SCRYPT", Scrypt.class, "scrypt"), + Arguments.of("id-scrypt", Scrypt.class, "scrypt"), + Arguments.of("KRB5KDF", Algorithm.class, "KRB5KDF"), + Arguments.of("ARGON2D", Argon2.class, "Argon2d"), + Arguments.of("ARGON2I", Argon2.class, "Argon2i"), + Arguments.of("ARGON2ID", Argon2.class, "Argon2id"), + Arguments.of("PKCS12KDF", Algorithm.class, "PKCS12KDF"), + Arguments.of("PVKKDF", Algorithm.class, "PVKKDF"), + Arguments.of("HMAC-DRBG-KDF", Algorithm.class, "HMAC-DRBG-KDF")); + } + + @ParameterizedTest + @MethodSource("fetchNames") + void fetchNameIsMapped(String name, Class expectedClass, String expected) { + Optional node = translateFetchName(name); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(expectedClass); + assertThat(node.get().asString()).isEqualTo(expected); + } + + @Test + void fetchNameIsCaseInsensitive() { + Optional node = translateFetchName("hkdf"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(HKDF.class); + } + + @Test + void pkcs5Pbkdf2HmacIsMappedToPbkdf2() { + Optional node = translate(new ValueAction<>("PBKDF2-HMAC", (AstNode) null)); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(PBKDF2.class); + assertThat(node.get().asString()).isEqualTo("PBKDF2"); + } + + @Test + void pkcs5Pbkdf2HmacSha1IsMappedToPbkdf2WithSha1() { + Optional node = translate(new ValueAction<>("PBKDF2-HMAC-SHA1", (AstNode) null)); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(PBKDF2.class); + assertThat(node.get().asString()).isEqualTo("PBKDF2-SHA-1"); + } + + @Test + void unknownAlgorithmNameResolvesToEmpty() { + assertThat(translateFetchName("NOT-A-REAL-KDF")).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxMacContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxMacContextTranslatorTest.java new file mode 100644 index 000000000..61b071f9e --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxMacContextTranslatorTest.java @@ -0,0 +1,210 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.MacContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.CMAC; +import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.model.algorithms.KMAC; +import com.ibm.mapper.model.algorithms.Poly1305; +import com.ibm.mapper.model.algorithms.SipHash; +import com.ibm.mapper.model.algorithms.blake.BLAKE2b; +import com.ibm.mapper.model.algorithms.blake.BLAKE2s; +import com.ibm.mapper.model.mode.GMAC; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CxxMacContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxMacContextTranslator translator = new CxxMacContextTranslator(); + + private Optional translate(String value) { + return translator.translate( + () -> "OpenSSL", + new ValueAction<>(value, (AstNode) null), + new MacContext(), + TEST_LOCATION); + } + + private static Stream hmacVariants() { + return Stream.of( + Arguments.of("HMAC-MD5", "HMAC-MD5"), + Arguments.of("HMAC-SHA1", "HMAC-SHA-1"), + Arguments.of("HMAC-SHA224", "HMAC-SHA-224"), + Arguments.of("HMAC-SHA256", "HMAC-SHA-256"), + Arguments.of("HMAC-SHA384", "HMAC-SHA-384"), + Arguments.of("HMAC-SHA512", "HMAC-SHA-512"), + Arguments.of("HMAC-SHA512/224", "HMAC-SHA-224"), + Arguments.of("HMAC-SHA512/256", "HMAC-SHA-256"), + Arguments.of("HMAC-SHA3-224", "HMAC-SHA3-224"), + Arguments.of("HMAC-SHA3-256", "HMAC-SHA3-256"), + Arguments.of("HMAC-SHA3-384", "HMAC-SHA3-384"), + Arguments.of("HMAC-SHA3-512", "HMAC-SHA3-512"), + Arguments.of("HMAC-RIPEMD160", "HMAC-RIPEMD"), + Arguments.of("HMAC-SM3", "HMAC-SM3")); + } + + @ParameterizedTest + @MethodSource("hmacVariants") + void hmacVariantsResolveToHmacOfTheExpectedDigest(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(HMAC.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + @Test + void hmacBlake2bResolvesToHmac() { + Optional node = translate("HMAC-BLAKE2B"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(HMAC.class); + assertThat(node.get().asString()).isEqualTo("HMAC-BLAKE2b"); + } + + @Test + void hmacBlake2sResolvesToHmac() { + Optional node = translate("HMAC-BLAKE2S"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(HMAC.class); + assertThat(node.get().asString()).isEqualTo("HMAC-BLAKE2s"); + } + + private static Stream cmacVariants() { + return Stream.of( + Arguments.of("CMAC-AES-128", "CMAC-AES"), + Arguments.of("CMAC-AES-192", "CMAC-AES"), + Arguments.of("CMAC-AES-256", "CMAC-AES"), + Arguments.of("CMAC-3DES", "CMAC-DESede"), + Arguments.of("CMAC-CAMELLIA-128", "CMAC-CAMELLIA"), + Arguments.of("CMAC-CAMELLIA-192", "CMAC-CAMELLIA"), + Arguments.of("CMAC-CAMELLIA-256", "CMAC-CAMELLIA"), + Arguments.of("CMAC-ARIA-128", "CMAC-ARIA"), + Arguments.of("CMAC-ARIA-192", "CMAC-ARIA"), + Arguments.of("CMAC-ARIA-256", "CMAC-ARIA"), + Arguments.of("CMAC-SM4", "CMAC-SM4")); + } + + @ParameterizedTest + @MethodSource("cmacVariants") + void cmacVariantsResolveToCmacOfTheExpectedCipher(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(CMAC.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + @ParameterizedTest + @MethodSource("gmacCases") + void gmacIsItsCipherAsAMacInGmacMode(String value, int keyLength, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(AES.class); + assertThat(node.get().getKind()).isEqualTo(Mac.class); + assertThat(node.get().getChildren().get(Mode.class)).isInstanceOf(GMAC.class); + assertThat(node.get().getChildren().get(KeyLength.class).asString()) + .isEqualTo(String.valueOf(keyLength)); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + static Stream gmacCases() { + return Stream.of( + Arguments.of("GMAC-AES-128", 128, "AES-128-GMAC"), + Arguments.of("GMAC-AES-192", 192, "AES-192-GMAC"), + Arguments.of("GMAC-AES-256", 256, "AES-256-GMAC")); + } + + @Test + void gmacWithoutCipherIsAGmac() { + Optional node = translate("GMAC"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(com.ibm.mapper.model.algorithms.GMAC.class); + assertThat(node.get().getKind()).isEqualTo(Mac.class); + assertThat(node.get().asString()).isEqualTo("GMAC"); + } + + @Test + void poly1305IsMapped() { + Optional node = translate("POLY1305"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(Poly1305.class); + } + + @Test + void siphashIsMappedToSipHash() { + Optional node = translate("SIPHASH"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SipHash.class); + } + + @Test + void kmac128IsMapped() { + Optional node = translate("KMAC128"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(KMAC.class); + assertThat(node.get().asString()).isEqualTo("KMAC128"); + } + + @Test + void kmac256IsMapped() { + Optional node = translate("KMAC256"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(KMAC.class); + assertThat(node.get().asString()).isEqualTo("KMAC256"); + } + + @Test + void blake2bMacIsMapped() { + Optional node = translate("BLAKE2BMAC"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(BLAKE2b.class); + assertThat(node.get().asString()).isEqualTo("BLAKE2b-512"); + } + + @Test + void blake2sMacIsMapped() { + Optional node = translate("BLAKE2SMAC"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(BLAKE2s.class); + assertThat(node.get().asString()).isEqualTo("BLAKE2s-256"); + } + + @Test + void unknownAlgorithmNameResolvesToEmpty() { + assertThat(translate("NOT-A-REAL-MAC")).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxPRNGContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxPRNGContextTranslatorTest.java new file mode 100644 index 000000000..087630c25 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxPRNGContextTranslatorTest.java @@ -0,0 +1,108 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.PRNGContext; +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.model.algorithms.CTRDRBG; +import com.ibm.mapper.model.algorithms.HMACDRBG; +import com.ibm.mapper.model.algorithms.HashDRBG; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CxxPRNGContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxPRNGContextTranslator translator = new CxxPRNGContextTranslator(); + + private Optional translate(String value) { + return translator.translate( + () -> "OpenSSL", + new ValueAction<>(value, (AstNode) null), + new PRNGContext(), + TEST_LOCATION); + } + + private static Stream bareAlgorithmVariants() { + return Stream.of( + Arguments.of("RAND"), + Arguments.of("RAND-PSEUDO"), + Arguments.of("SEED-SRC"), + Arguments.of("JITTER"), + Arguments.of("TEST-RAND")); + } + + @ParameterizedTest + @MethodSource("bareAlgorithmVariants") + void bareAlgorithmNamesResolveToThemselves(String value) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(Algorithm.class); + assertThat(node.get().asString()).isEqualTo(value); + } + + private static Stream drbgs() { + return Stream.of( + Arguments.of("CTR-DRBG", CTRDRBG.class, "CTR_DRBG"), + Arguments.of("HASH-DRBG", HashDRBG.class, "Hash_DRBG"), + Arguments.of("HMAC-DRBG", HMACDRBG.class, "HMAC_DRBG")); + } + + @ParameterizedTest + @MethodSource("drbgs") + void drbgsResolveToTheirSp80090aModel( + String value, Class expectedClass, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(expectedClass); + assertThat(node.get().getKind()).isEqualTo(PseudorandomNumberGenerator.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + private static Stream entropySeedingOperations() { + return Stream.of( + Arguments.of("RAND-SEED"), Arguments.of("RAND-ADD"), Arguments.of("RAND-POLL")); + } + + @ParameterizedTest + @MethodSource("entropySeedingOperations") + void entropySeedingOperationsAreNotDistinctAlgorithms(String value) { + assertThat(translate(value)).isEmpty(); + } + + @Test + void unknownAlgorithmNameResolvesToEmpty() { + assertThat(translate("NOT-A-REAL-PRNG")).isEmpty(); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxProtocolContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxProtocolContextTranslatorTest.java new file mode 100644 index 000000000..3d07b2838 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxProtocolContextTranslatorTest.java @@ -0,0 +1,192 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.Algorithm; +import com.ibm.engine.model.CipherSuite; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.Protocol; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.ProtocolContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.collections.MergeableCollection; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class CxxProtocolContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxProtocolContextTranslator translator = new CxxProtocolContextTranslator(); + + private Optional translate(IValue value, ProtocolContext.Kind kind) { + return translator.translate( + () -> "OpenSSL", value, new ProtocolContext(kind), TEST_LOCATION); + } + + @Test + void aNonOpenSslBundleIsIgnored() { + Optional node = + translator.translate( + () -> "NotOpenSSL", + new ValueAction<>("TLSv1.2", (AstNode) null), + new ProtocolContext(ProtocolContext.Kind.TLS), + TEST_LOCATION); + assertThat(node).isEmpty(); + } + + @Test + void protocolValueUnderTlsKindResolvesToATlsNodeWithItsParsedVersion() { + Optional node = + translate(new Protocol<>("TLSv1.2", (AstNode) null), ProtocolContext.Kind.TLS); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(TLS.class); + assertThat(node.get().asString()).isEqualTo("TLSv1.2"); + } + + @Test + void protocolValueWithAnUnparsableVersionStillResolvesToABareTlsNode() { + Optional node = + translate( + new Protocol<>("garbage-version", (AstNode) null), + ProtocolContext.Kind.TLS); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(TLS.class); + } + + @Test + void protocolValueUnderANonTlsKindResolvesToAGenericProtocolNode() { + Optional node = + translate(new Protocol<>("QUIC", (AstNode) null), ProtocolContext.Kind.NONE); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(com.ibm.mapper.model.Protocol.class); + assertThat(node.get().asString()).isEqualTo("QUIC"); + } + + @Test + void cipherStringUnderTlsKindIsParsedByTheCipherSuiteMapper() { + Optional node = + translate( + new CipherSuite<>( + "ECDHE-RSA-AES128-GCM-SHA256:TLS_AES_128_GCM_SHA256:!aNULL:HIGH", + (AstNode) null), + ProtocolContext.Kind.TLS); + // the suites of the TLS protocol they are configured on (see CipherSuiteReorganizer) + assertThat(node).isPresent().get().isInstanceOf(CipherSuiteCollection.class); + assertThat(((CipherSuiteCollection) node.get()).getCollection()) + .extracting(INode::asString) + .containsExactly("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256"); + } + + @Test + void cipherStringWithoutSuitesUnderTlsKindResolvesToEmpty() { + assertThat( + translate( + new CipherSuite<>("HIGH:!aNULL:@SECLEVEL=2", (AstNode) null), + ProtocolContext.Kind.TLS)) + .isEmpty(); + } + + @Test + void cipherSuiteValueUnderANonTlsKindResolvesToAGenericCipherSuiteNode() { + Optional node = + translate(new CipherSuite<>("FOO", (AstNode) null), ProtocolContext.Kind.NONE); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(com.ibm.mapper.model.CipherSuite.class); + assertThat(node.get().asString()).isEqualTo("FOO"); + } + + @Test + void algorithmValueUnderTlsSignatureAlgorithmsKindResolvesToAnAssetCollection() { + Optional node = + translate( + new Algorithm<>("ECDSA+SHA256", (AstNode) null), + ProtocolContext.Kind.TLS_SIGNATURE_ALGORITHMS); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(MergeableCollection.class); + } + + @Test + void algorithmValueUnderTlsGroupsKindResolvesToAnAssetCollection() { + Optional node = + translate( + new Algorithm<>("X25519:P-256", (AstNode) null), + ProtocolContext.Kind.TLS_GROUPS); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(MergeableCollection.class); + } + + @Test + void anEmptyAlgorithmListUnderTlsGroupsKindResolvesToEmpty() { + Optional node = + translate(new Algorithm<>("", (AstNode) null), ProtocolContext.Kind.TLS_GROUPS); + assertThat(node).isEmpty(); + } + + @Test + void algorithmValueUnderANonListKindResolvesToAGenericProtocolNode() { + Optional node = + translate(new Algorithm<>("FOO", (AstNode) null), ProtocolContext.Kind.NONE); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(com.ibm.mapper.model.Protocol.class); + assertThat(node.get().asString()).isEqualTo("FOO"); + } + + @Test + void valueActionStartingWithTlsUnderTlsKindResolvesToATlsNode() { + Optional node = + translate(new ValueAction<>("TLSv1.3", (AstNode) null), ProtocolContext.Kind.TLS); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(TLS.class); + assertThat(node.get().asString()).isEqualTo("TLSv1.3"); + } + + @Test + void valueActionNotStartingWithTlsUnderTlsKindResolvesToATlsNode() { + Optional sslNode = + translate(new ValueAction<>("SSLv3", (AstNode) null), ProtocolContext.Kind.TLS); + assertThat(sslNode).isPresent(); + assertThat(sslNode.get()).isInstanceOf(TLS.class); + assertThat(sslNode.get().asString()).isEqualTo("SSLv3.0"); + + Optional dtlsNode = + translate(new ValueAction<>("DTLSv1", (AstNode) null), ProtocolContext.Kind.TLS); + assertThat(dtlsNode).isPresent(); + assertThat(dtlsNode.get()).isInstanceOf(TLS.class); + assertThat(dtlsNode.get().asString()).isEqualTo("DTLSv1.0"); + } + + @Test + void valueActionUnderANonTlsKindResolvesToAGenericProtocolNode() { + Optional node = + translate(new ValueAction<>("FOO", (AstNode) null), ProtocolContext.Kind.NONE); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(com.ibm.mapper.model.Protocol.class); + assertThat(node.get().asString()).isEqualTo("FOO"); + } +} diff --git a/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxSignatureContextTranslatorTest.java b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxSignatureContextTranslatorTest.java new file mode 100644 index 000000000..e91ca75d6 --- /dev/null +++ b/cpp/src/test/java/com/ibm/plugin/translation/translator/contexts/CxxSignatureContextTranslatorTest.java @@ -0,0 +1,190 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.SignatureContext; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.DSA; +import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.EdDSA; +import com.ibm.mapper.model.algorithms.MLDSA; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.SPHINCSPlus; +import com.ibm.mapper.utils.DetectionLocation; +import com.sonar.cxx.sslr.api.AstNode; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CxxSignatureContextTranslatorTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + private final CxxSignatureContextTranslator translator = new CxxSignatureContextTranslator(); + + private Optional translate(String value) { + return translator.translate( + () -> "OpenSSL", + new ValueAction<>(value, (AstNode) null), + new SignatureContext(), + TEST_LOCATION); + } + + private static Stream rsaPssVariants() { + return Stream.of( + Arguments.of("RSA-PSS-SHA256"), + Arguments.of("RSA-PSS-SHA384"), + Arguments.of("RSA-PSS-SHA512"), + Arguments.of("RSA-PSS-SHA1"), + // an unrecognized digest suffix still resolves to a bare RSA-PSS + Arguments.of("RSA-PSS-UNKNOWN")); + } + + @ParameterizedTest + @MethodSource("rsaPssVariants") + void rsaPssVariantsResolveToRsAssaPss(String value) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(RSAssaPSS.class); + } + + private static Stream rsaPkcs1Variants() { + return Stream.of( + Arguments.of("RSA-SHA1", "RSA-PKCS1-1.5-SHA-1"), + Arguments.of("RSA-SHA224", "RSA-PKCS1-1.5-SHA-224"), + Arguments.of("RSA-SHA256", "RSA-PKCS1-1.5-SHA-256"), + Arguments.of("RSA-SHA384", "RSA-PKCS1-1.5-SHA-384"), + Arguments.of("RSA-SHA512", "RSA-PKCS1-1.5-SHA-512"), + // an unrecognized digest suffix still resolves to a bare RSA-PKCS1-1.5 + Arguments.of("RSA-UNKNOWN", "RSA-PKCS1-1.5")); + } + + @ParameterizedTest + @MethodSource("rsaPkcs1Variants") + void rsaVariantsResolveToRsaWithTheExpectedDigest(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(RSA.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + private static Stream dsaVariants() { + return Stream.of( + Arguments.of("DSA-SHA1", "DSA-SHA-1"), + Arguments.of("DSA-SHA224", "DSA-SHA-224"), + Arguments.of("DSA-SHA256", "DSA-SHA-256"), + Arguments.of("DSA-SHA384", "DSA-SHA-384"), + Arguments.of("DSA-SHA512", "DSA-SHA-512"), + // an unrecognized digest suffix still resolves to a bare DSA + Arguments.of("DSA-UNKNOWN", "DSA")); + } + + @ParameterizedTest + @MethodSource("dsaVariants") + void dsaVariantsResolveToDsaWithTheExpectedDigest(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(DSA.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + private static Stream ecdsaVariants() { + return Stream.of( + Arguments.of("ECDSA-SHA1", "ECDSA-SHA-1"), + Arguments.of("ECDSA-SHA3-256", "ECDSA-SHA3-256"), + Arguments.of("ECDSA-SHA3-384", "ECDSA-SHA3-384"), + Arguments.of("ECDSA-SHA3-512", "ECDSA-SHA3-512"), + Arguments.of("ECDSA-SHA224", "ECDSA-SHA-224"), + Arguments.of("ECDSA-SHA256", "ECDSA-SHA-256"), + Arguments.of("ECDSA-SHA384", "ECDSA-SHA-384"), + Arguments.of("ECDSA-SHA512", "ECDSA-SHA-512"), + // an unrecognized digest suffix still resolves to a bare ECDSA + Arguments.of("ECDSA-UNKNOWN", "ECDSA")); + } + + @ParameterizedTest + @MethodSource("ecdsaVariants") + void ecdsaVariantsResolveToEcdsaWithTheExpectedDigest(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(ECDSA.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + @Test + void ed25519ResolvesToEddsa() { + Optional node = translate("ED25519"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(EdDSA.class); + } + + @Test + void ed448ResolvesToEddsa() { + Optional node = translate("ED448"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(EdDSA.class); + } + + private static Stream mldsaVariants() { + return Stream.of( + Arguments.of("ML-DSA-44", "ML-DSA-44"), + Arguments.of("ML-DSA-65", "ML-DSA-65"), + Arguments.of("ML-DSA-87", "ML-DSA-87")); + } + + @ParameterizedTest + @MethodSource("mldsaVariants") + void mldsaVariantsResolveToMldsaOfTheExpectedParameterSet(String value, String expectedName) { + Optional node = translate(value); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(MLDSA.class); + assertThat(node.get().asString()).isEqualTo(expectedName); + } + + @Test + void slhdsaParameterSetIsExtractedFromTheAlgorithmName() { + Optional node = translate("SLH-DSA-SHA2-128F"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SPHINCSPlus.class); + assertThat(node.get().asString()).isEqualTo("SLH-DSA-SHA2-128F"); + } + + @Test + void sm2IsMapped() { + Optional node = translate("SM2"); + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(SM2.class); + } + + @Test + void unknownAlgorithmNameResolvesToEmpty() { + assertThat(translate("NOT-A-REAL-SIGNATURE-ALGORITHM")).isEmpty(); + } +} diff --git a/csharp/pom.xml b/csharp/pom.xml index 229c071ea..179e69ef4 100755 --- a/csharp/pom.xml +++ b/csharp/pom.xml @@ -12,8 +12,6 @@ csharp - 17 - 17 UTF-8 diff --git a/docs/LANGUAGE_SUPPORT.md b/docs/LANGUAGE_SUPPORT.md index 8dda778b6..f1366fd0a 100644 --- a/docs/LANGUAGE_SUPPORT.md +++ b/docs/LANGUAGE_SUPPORT.md @@ -2,23 +2,22 @@ The Sonar Cryptography Plugin is designed with a modular architecture so that it can be extended to support additional programming languages and cryptography libraries. - ## Introduction to the project structure The Sonar Cryptography Plugin uses a rule-based approach to precisely identify which cryptography assets are used in the scanned code. Adding support for detecting a cryptography library means writing a set of rules covering all cryptographic assets introduced by the library. -Defining those rules as part of a SonarQube plugin allows us to easily integrate with usual SonarQube workflows, and to benefit from existing support to scan some languages (the languages supported for SonarQube plugins are listed [here](https://docs.sonarsource.com/sonarqube/latest/extension-guide/adding-coding-rules/#custom-rule-support-by-language) in the *Java* column). +Defining those rules as part of a SonarQube plugin allows us to easily integrate with usual SonarQube workflows, and to benefit from existing support to scan some languages (the languages supported for SonarQube plugins are listed [here](https://docs.sonarsource.com/sonarqube/latest/extension-guide/adding-coding-rules/#custom-rule-support-by-language) in the _Java_ column). ### Overview The project is composed of the following modules: + - The plugin: `sonar-cryptography-plugin` -- One module per supported language, like `java`, `python` and `go` +- One module per supported language: `java`, `python`, `go`, `cpp` (C/C++) and `csharp` (in development) - The detection engine: `engine` - Four other modules: `mapper`, `enricher`, `output` and `common` - ```mermaid flowchart TB SQ(["SonarQube"]) @@ -42,9 +41,21 @@ flowchart TB GTRANS["translation/"] end + subgraph CPP["cpp"] + direction TB + CRULES["rules/detection/
openssl/"] + CTRANS["translation/
translator/ · reorganizer/"] + end + + subgraph CSHARP["csharp"] + direction TB + SRULES["rules/detection/
dotnet/"] + STRANS["translation/"] + end + subgraph SHARED["shared modules"] direction LR - ENGINE["engine
language/: java/ · python/ · go/"] + ENGINE["engine
language/: java/ · python/ · go/ · cxx/ · csharp/"] MAPPER["mapper
mappers: jca/ · bc/ · pyca/ · gocrypto/ · ssl/"] OUTPUT["output
cyclonedx/ (CBOM)"] ENRICHER["enricher"] @@ -55,16 +66,20 @@ flowchart TB PLUGIN --> JAVA PLUGIN --> PYTHON PLUGIN --> GO + PLUGIN --> CPP + PLUGIN --> CSHARP JAVA --> SHARED PYTHON --> SHARED GO --> SHARED + CPP --> SHARED + CSHARP --> SHARED ``` -*High level diagram showing the architecture of the various modules composing the Sonar Cryptography Plugin, shown here with its three language extensions (java, python and go). The `sonar-cryptography-plugin` module directly depends on the language modules, which themselves depend on a set of shared modules. The extendable parts of the modules are: the engine language support (`engine/.../language/`), the library-specific mappers (in `mapper`), and the choice of output format (in `output`).* +_High level diagram showing the architecture of the various modules composing the Sonar Cryptography Plugin, shown here with its language extensions (java, python, go, cpp and csharp). The `sonar-cryptography-plugin` module directly depends on the language modules, which themselves depend on a set of shared modules. The extendable parts of the modules are: the engine language support (`engine/.../language/`), the library-specific mappers (in `mapper`), and the choice of output format (in `output`)._ ### The plugin -The ([`sonar-cryptography-plugin`](../sonar-cryptography-plugin/)) module creates the single SonarQube plugin, for all supported languages, so that we have only one cryptography plugin (and not one per language). +The ([`sonar-cryptography-plugin`](../sonar-cryptography-plugin/)) module creates the single SonarQube plugin, for all supported languages, so that we have only one cryptography plugin (and not one per language). Its main class is [`CryptographyPlugin`](../sonar-cryptography-plugin/src/main/java/com/ibm/plugin/CryptographyPlugin.java) which implements the Sonar [`Plugin`](https://javadocs.sonarsource.org/10.3.0.1951/org/sonar/api/Plugin.html) interface, and registers all rules for all languages. This is done through the `addExtensions` method, and the extension classes to add vary depending on the language (they are usually mentioned in the documentation, or at least appear in the example plugins provided by Sonar – in the class implementing `Plugin`). @@ -76,7 +91,6 @@ Currently, our plugin exports the findings in the standard [CBOM](https://cyclon Ultimately, the `sonar-cryptography-plugin` is the entry point of our SonarQube plugin, it is a lightweight class that does not contain much logic but instead relies on the following modules. - ### The language modules To write detection rules based on the content of the source code, we use an intermediary representation of the source code that is more easy to navigate than plain text: an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (abstract syntax tree). @@ -113,7 +127,7 @@ declaration in a mixed rule still requires an unmarked positional argument. See for a rule example and the declaration-order constraints. > [!TIP] -> We explain with much more details this higher level syntax for writing detection rules in [*Writing new detection rules for the Sonar Cryptography Plugin*](./DETECTION_RULE_STRUCTURE.md). +> We explain with much more details this higher level syntax for writing detection rules in [_Writing new detection rules for the Sonar Cryptography Plugin_](./DETECTION_RULE_STRUCTURE.md). Of course, we don't get this nice syntax for free: something has to bridge the gap between the language-specific AST APIs and our language-agnostic syntax. This is the role of the `engine` module, that will be detailed later. @@ -138,8 +152,7 @@ Maybe our algorithm has a default mode when no mode is specified in the code, in Additionally, we can enrich most cryptography assets with an [object identifier](https://en.wikipedia.org/wiki/Object_identifier) (OID) that uniquely identifies an algorithm and plays an important role in a CBOM. > [!TIP] -> The process of translation is also explained with more details in [*Writing new detection rules for the Sonar Cryptography Plugin*](./DETECTION_RULE_STRUCTURE.md). - +> The process of translation is also explained with more details in [_Writing new detection rules for the Sonar Cryptography Plugin_](./DETECTION_RULE_STRUCTURE.md). ### The engine @@ -162,7 +175,6 @@ Once the plugin supports the targeted language, the [next section](#adding-suppo Currently we only support languages that are provided by Sonar, aka for which there is a Sonar parser to generate an abstract syntax tree ([see](https://github.com/search?q=topic%3Alanguage-team+org%3ASonarSource+&type=repositories) the supported language parsers). Theoretically, any language parser (written in Java) that generates an AST from the source code should be integrable, and thus any language for which such a parser exists. With our current implementation, we have only tested the parsers provided by Sonar. - In the following, we will take the example of adding support for the Java language. ### Adding the language parser @@ -171,11 +183,13 @@ The first step is to add a dependency for your sonar language parser to the main You should find a parser for your language by searching the parser provided by Sonar directly ([see](https://github.com/search?q=topic%3Alanguage-team+org%3ASonarSource+&type=repositories)) or by searching for parsers created by the community, such as the one for C/C++ ([see](https://github.com/SonarOpenCommunity/sonar-cxx)) Then, first add its version under ``: + ```xml 8.22.0.41895 ``` And add the dependency (using this version reference) under ``: + ```xml org.sonarsource.java @@ -188,11 +202,11 @@ And add the dependency (using this version reference) under ` -java,jsp,py,ipynb,go +java,jsp,py,ipynb,go,cs,cxx,cpp,c++,c ``` ### Identifying the four classes to use in generics @@ -203,16 +217,18 @@ While we will talk [later](#implementing-the-language-specific-parts-of-the-engi We need to find these four types in the language's (Sonar) parser API. It is crucial to take this step early in the development, as not finding these types can compromise the integration of the language with the chosen parser. ->[!NOTE] +> [!NOTE] > If some of these classes are missing in the APIs of your language, you may create your own custom classes to try to patch this void, by investigating how these classes are used and trying to provide the same functionality. However, this has not been attempted yet and will probably result in significantly more work. To help you find these classes used to fill the four type parameters `R`, `T`, `S`, `P`, we provide the table below showing what these classes are for the languages we currently support: -| | Rule (`R`) | Tree (`T`) | Symbol (`S`) | Publisher (`P`) | -|--------|------------------------|----------------------|---------------------------|---------------------------------| -| **Java** | org.sonar.plugins.java.api.**JavaCheck** | org.sonar.plugins.java.api.tree.**Tree** | org.sonar.plugins.java.api.semantic.**Symbol** | org.sonar.plugins.java.api.**JavaFileScannerContext** | -| **Python** | org.sonar.plugins.python.api.**PythonCheck** | org.sonar.plugins.python.api.tree.**Tree** | org.sonar.plugins.python.api.symbols.**Symbol** | org.sonar.plugins.python.api.**PythonVisitorContext** | -| **Go** | org.sonar.plugins.go.api.checks.**GoCheck** | org.sonar.plugins.go.api.**Tree** | org.sonar.go.symbols.**Symbol** | com.ibm.engine.language.go.**GoScanContext**[^1] | +| | Rule (`R`) | Tree (`T`) | Symbol (`S`) | Publisher (`P`) | +| ---------- | ---------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------- | +| **Java** | org.sonar.plugins.java.api.**JavaCheck** | org.sonar.plugins.java.api.tree.**Tree** | org.sonar.plugins.java.api.semantic.**Symbol** | org.sonar.plugins.java.api.**JavaFileScannerContext** | +| **Python** | org.sonar.plugins.python.api.**PythonCheck** | org.sonar.plugins.python.api.tree.**Tree** | org.sonar.plugins.python.api.symbols.**Symbol** | org.sonar.plugins.python.api.**PythonVisitorContext** | +| **Go** | org.sonar.plugins.go.api.checks.**GoCheck** | org.sonar.plugins.go.api.**Tree** | org.sonar.go.symbols.**Symbol** | com.ibm.engine.language.go.**GoScanContext**[^1] | +| **C/C++** | org.sonar.cxx.squidbridge.checks.**SquidCheck\** | com.sonar.cxx.sslr.api.**AstNode** | org.sonar.cxx.squidbridge.api.**Symbol** | org.sonar.cxx.squidbridge.**SquidAstVisitorContext\** | +| **C#** | com.ibm.engine.language.csharp.**CSharpCheck** | com.ibm.engine.language.csharp.tree.**CSharpTree** | com.ibm.engine.language.csharp.**CSharpSymbol** | com.ibm.engine.language.csharp.**CSharpScanContext** | [^1]: The Go parser does not provide a publisher class comparable to `JavaFileScannerContext`, so the plugin defines its own class ([`GoScanContext`](../engine/src/main/java/com/ibm/engine/language/go/GoScanContext.java)) to fill the `P` type parameter — an example of the "create your own custom classes to patch this void" approach mentioned in the note above. @@ -249,6 +265,7 @@ public static ILanguageSupport return new JavaLanguageSupport(); } ``` + This function will be used [later](#the-check-registrar-extension-point). ### Creating a new language module @@ -257,7 +274,7 @@ Now that the language analyzer has been registered in the main `pom.xml` and tha > [!IMPORTANT] > This process depends on how the language analyzer APIs work: **carefully read the available documentation explaining how to create a SonarQube plugin for your language first**. This documentation should take precedence over the more general explanations that we will discuss next. -> +> > The documentation may come with a sample plugin for your language, in this case it will help you to have a look at it to understand how your plugin can integrate with SonarQube. The first step is to create another [`pom.xml`](../java/pom.xml) for your new module. @@ -265,6 +282,7 @@ Take inspiration from the `pom.xml` of the existing language modules to write it In short, it should contain a reference to the parent `sonar-cryptography`, maven properties, dependencies to the other modules that will be used (typically `common`, `output` and `enricher`), and a dependency to the language-specific sonar test kit. Then, fill your module folder with the same basic structure as the other language modules. For example for java: + ``` java └── src @@ -276,6 +294,7 @@ java ├── files/rules └── java/com/ibm/plugin/rules ``` +

edit this tree

`plugin/rules/` will contain the detection rules, organized by cryptography library, and `plugin/translation/` will contain all files related to translation for this language. @@ -283,19 +302,19 @@ The `test/` directory is not important at the beginning, it will be used only on ### Adding the extension points -Then, we have to add *extension points*: these are language-specific interfaces to implement in order to declare the custom detection rules of the module to the plugin. Read the sonar documentation to find what these interfaces are. For Java, these are the interfaces `RulesDefinition` and `CheckRegistrar`, respectively implemented in [`JavaScannerRuleDefinition`](../java/src/main/java/com/ibm/plugin/JavaScannerRuleDefinition.java) and [`JavaCheckRegistrar`](../java/src/main/java/com/ibm/plugin/JavaCheckRegistrar.java) in the `plugin` directory. +Then, we have to add _extension points_: these are language-specific interfaces to implement in order to declare the custom detection rules of the module to the plugin. Read the sonar documentation to find what these interfaces are. For Java, these are the interfaces `RulesDefinition` and `CheckRegistrar`, respectively implemented in [`JavaScannerRuleDefinition`](../java/src/main/java/com/ibm/plugin/JavaScannerRuleDefinition.java) and [`JavaCheckRegistrar`](../java/src/main/java/com/ibm/plugin/JavaCheckRegistrar.java) in the `plugin` directory. > [!IMPORTANT] -> At this point, we have to clarify an important difference: we distinguish the SonarQube *rules* that we actually add to the plugin, and the detection *rules* (defined [earlier](#the-detection-rules)) that are rules written with our high level syntax and conforming to the `IDetectionRule` interface. +> At this point, we have to clarify an important difference: we distinguish the SonarQube _rules_ that we actually add to the plugin, and the detection _rules_ (defined [earlier](#the-detection-rules)) that are rules written with our high level syntax and conforming to the `IDetectionRule` interface. > > In our plugin architecture, we have chosen to define detection rules independently of the higher-level SonarQube rules that you see in the UI. > The detection rules are there to collect all possible cryptographic information, while the SonarQube rules can be defined on top of these detections. -> The idea is that a SonarQube rule can be: *Don't use MD5* or *If using RSA, the key size should be larger than 2048*, while the detection rules try to capture all occurrences of these algorithms in the source code. +> The idea is that a SonarQube rule can be: _Don't use MD5_ or _If using RSA, the key size should be larger than 2048_, while the detection rules try to capture all occurrences of these algorithms in the source code. > At the moment, the plugin only contains an "Inventory" rule that collects all occurrences of cryptographic assets and reports them as a problem to the SonarQube UI. > This rule extends a language-specific class defined by the API of your Sonar analyzer, such as `IssuableSubscriptionVisitor` in Java or `PythonVisitorCheck` in Python. -> We will later call it the *visitor* class, as it enables to implement functions which are called upon the visitation of a some AST nodes. +> We will later call it the _visitor_ class, as it enables to implement functions which are called upon the visitation of a some AST nodes. > -> This means that on the SonarQube UI, we see only one rule per language, which reports all the cryptography findings. The actual precise information should instead be exported through the `output` module, like we currently do with the CBOM. +> This means that on the SonarQube UI, we see only one rule per language, which reports all the cryptography findings. The actual precise information should instead be exported through the `output` module, like we currently do with the CBOM. We now explain with a bit more detail what the usual extension points are. @@ -303,11 +322,12 @@ We now explain with a bit more detail what the usual extension points are. If there is a similar entry point for your language, you can set it up similarly to [`JavaCheckRegistrar`](../java/src/main/java/com/ibm/plugin/JavaCheckRegistrar.java) in Java (or to [`PythonCheckRegistrar`](../python/src/main/java/com/ibm/plugin/PythonCheckRegistrar.java) with the `PythonCustomRuleRepository` interface). -This is the place where the SonarQube rule class should be referenced. Because this rule "regroups" all the detection rules, we call it the *inventory rule*, which is defined in Java as [`JavaInventoryRule`](../java/src/main/java/com/ibm/plugin/rules/JavaInventoryRule.java) in `plugin/rules`[^2]. +This is the place where the SonarQube rule class should be referenced. Because this rule "regroups" all the detection rules, we call it the _inventory rule_, which is defined in Java as [`JavaInventoryRule`](../java/src/main/java/com/ibm/plugin/rules/JavaInventoryRule.java) in `plugin/rules`[^2]. [^2]: In the Java case, we define an intermediary class [`JavaRuleList`](../java/src/main/java/com/ibm/plugin/JavaRuleList.java) that registers all Java SonarQube rules, which in our case is only [`JavaInventoryRule`](../java/src/main/java/com/ibm/plugin/rules/JavaInventoryRule.java). This allows us to easily refer to all SonarQube rules at other places, like in [`JavaScannerRuleDefinition`](../java/src/main/java/com/ibm/plugin/JavaScannerRuleDefinition.java). SonarQube rules must extend the language-specific sonar visitor class, `IssuableSubscriptionVisitor` in Java, and must be annotated with a name (we are using "Inventory"): + ```java @Rule(key = "Inventory") ``` @@ -315,12 +335,13 @@ SonarQube rules must extend the language-specific sonar visitor class, `Issuable We define the logic behind the inventory rule (and in particular how it will relate with all the `IDetectionRule` detection rules) in the `plugin/rules/detection` directory by first creating an intermediary class implementing the language-specific sonar visitor class (`IssuableSubscriptionVisitor` in Java) and from which the inventory rule (`JavaInventoryRule`) will inherit. In Java, we call this class [`JavaBaseDetectionRule`](../java/src/main/java/com/ibm/plugin/rules/detection/JavaBaseDetectionRule.java), and it takes a constructor with a list of `IDetectionRule`[^3]. Implementing this class will require defining a translation process, but we will come to that [later](#bridging-the-gap), and you can keep these parts empty for now. -[^3]: It may also take a list of [`IReorganizerRule`](../mapper/src/main/java/com/ibm/mapper/reorganizer/IReorganizerRule.java) if necessary. More about this in the section [*Reorganizing the translation tree*](./DETECTION_RULE_STRUCTURE.md#reorganizing-the-translation-tree) of *Writing new detection rules for the Sonar Cryptography Plugin*. +[^3]: It may also take a list of [`IReorganizerRule`](../mapper/src/main/java/com/ibm/mapper/reorganizer/IReorganizerRule.java) if necessary. More about this in the section [_Reorganizing the translation tree_](./DETECTION_RULE_STRUCTURE.md#reorganizing-the-translation-tree) of _Writing new detection rules for the Sonar Cryptography Plugin_. This list of `IDetectionRule` is defined in the same directory, in a file listing all the detection rules for this language as a [`DetectionRuleSet`](../engine/src/main/java/com/ibm/engine/rule/DetectionRuleSet.java). In Java, we call this file [`JavaDetectionRules`](../java/src/main/java/com/ibm/plugin/rules/detection/JavaDetectionRules.java). -You can currently leave this list of rules empty, and we will discuss [later](#adding-support-for-another-cryptography-library) how to structure these detection rules in the module. +You can currently leave this list of rules empty, and we will discuss [later](#adding-support-for-another-cryptography-library) how to structure these detection rules in the module. Back to the intermediary class (`JavaBaseDetectionRule` in Java), this is also the place where we have to check and apply our list of `IDetectionRule`. This can be done by overriding the relevant "visit" method(s) of the visitor class. For example in Java (note that we have not yet defined `JavaAggregator`, which we will do just after): + ```java /** * Visits a tree node and applies detection rules to it. @@ -350,8 +371,9 @@ This is therefore the purpose of the (empty) interface [`IAggregator`](../output The aggregator class can maintain a list of findings, that gets extended each time a new finding is detected and reported through the `update` function. It also implements a `getLanguageSupport()` method that returns the `ILanguageSupport` (using the `LanguageSupporter` defined [earlier](#implementing-the-language-specific-parts-of-the-engine)), which you should use in your "visit" method (like in `visitNode` above). The `JavaAggregator` implementation is quite generic and can be mostly reused for your implementation, after replacing the generic types by the correct ones, and using the correct language supporter. + > [!TIP] -> By extending the `JavaBaseDetectionRule` class, you can create new high-level SonarQube rules beyond the Inventory rule. The `JavaInventoryRule` is an example of how the underlying cryptographic information (collected by the detection rules) can be utilized. +> By extending the `JavaBaseDetectionRule` class, you can create new high-level SonarQube rules beyond the Inventory rule. The `JavaInventoryRule` is an example of how the underlying cryptographic information (collected by the detection rules) can be utilized. #### The "Rule Definition" extension point @@ -361,12 +383,12 @@ The metadata for your (single) SonarQube rule may have to be described with reso In this case, similarly to the Java case, you can create a directory for resources (`java/src/main/resources/org/sonar/l10n/java/rules/java/` in Java – similarly to what was done in the example Java plugin). In the Java case, these resource files have to be named with the name of the rule they describe ("Inventory" in our case). - #### Registering the extension points Now that we have created our extension points, we need to register them at the plugin level. As explained [previously](#the-plugin), it should be done in the `addExtensions` method of [`CryptographyPlugin`](../sonar-cryptography-plugin/src/main/java/com/ibm/plugin/CryptographyPlugin.java). For Java, we add the following lines: + ```java context.addExtensions( // java @@ -376,8 +398,10 @@ context.addExtensions( // ... ) ``` + Additionally, update the function `getOutputFile` of the [`ScannerManager`](../sonar-cryptography-plugin/src/main/java/com/ibm/plugin/ScannerManager.java) of the plugin module, to add your aggregate of detections to the final list of detections that will be used for generating the CBOM. For Java, it looks like: + ```java public IOutputFile getOutputFile() { List nodes = new ArrayList<>(); @@ -388,6 +412,7 @@ public IOutputFile getOutputFile() { // ... } ``` + --- When you reach this point, congrats! You now have completed all the steps to make our Sonar Cryptography Plugin support your programming language 🥳 @@ -401,11 +426,12 @@ Now, it remains to write detection rules for the cryptography library of your ch Once your programming language is supported by our plugin, it gets fairly simple to add support for various cryptography libraries. In this section, we will explain how you should write and organize your detection rules to do so. -In the following, we will take the example of adding support for an hypothetical *mycrypto* cryptography library for the Java language. +In the following, we will take the example of adding support for an hypothetical _mycrypto_ cryptography library for the Java language. ### Organizing your files -To add support for *mycrypto*, start by creating the three directories (all named `mycrypto`) in the language module, that will be used to store all files relative to this library. The directory tree looks like this Java: +To add support for _mycrypto_, start by creating the three directories (all named `mycrypto`) in the language module, that will be used to store all files relative to this library. The directory tree looks like this Java: + ``` java └── src @@ -425,14 +451,16 @@ java ├── mycrypto └── ... [other libraries] ``` +

edit this tree

We use a [Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development) (TDD) approach, where you start by thinking about the kind of cryptographic asset you want to detect. -Then, find (or write) a *test file* (in the language of your cryptography library) containing this asset. -Next, write a detection *rule* aiming at detection precisely this asset. -Finally, create a *unit test* checking that your detection rule indeed captures the intended value in the test file. +Then, find (or write) a _test file_ (in the language of your cryptography library) containing this asset. +Next, write a detection _rule_ aiming at detection precisely this asset. +Finally, create a _unit test_ checking that your detection rule indeed captures the intended value in the test file. These three kinds of files (test file, rule and unit test) are stored in those three distinct directories: + - `main/.../plugin/rules/detection/mycrypto/`: stores the detection rules, in the structure of your choice, but usually close to the structure of the cryptography library. - `test/.../plugin/rules/detection/mycrypto/`: stores the unit tests with the exact same structure than the rules. - `test/.../files/rules/detection/mycrypto/`: stores the test files with the exact same structure than the rules (and than the unit tests). @@ -445,7 +473,7 @@ These three kinds of files (test file, rule and unit test) are stored in those t > Our file organization is inspired by the documentation of the Sonar analyzer for Java. You can [consult it](https://github.com/SonarSource/sonar-java/blob/master/docs/CUSTOM_RULES_101.md#writing-a-rule) to learn more. Additionally, we need a `TestBase` class which we will use as the base class for our unit tests (in Java, it is [`TestBase`](../java/src/test/java/com/ibm/plugin/TestBase.java)). -This class specifies that we want our tests to use all of our defined detection rules, by extending the *inventory rule* class. +This class specifies that we want our tests to use all of our defined detection rules, by extending the _inventory rule_ class. It also handles the logs of the tests, and structures how assert statements are checked. If you wrote your own language support, you should create this `TestBase` class now (very similarly to the Java implementation). Note that implementing `TestBase` requires a translation process, but we will come to that [later](#bridging-the-gap), and you can keep these parts empty for now. @@ -453,10 +481,11 @@ Note that implementing `TestBase` requires a translation process, but we will co ### Creating and testing your first detection rule > [!IMPORTANT] -> At this point, if you have not done it yet, you should read the section [*Writing a detection rule*](./DETECTION_RULE_STRUCTURE.md#writing-a-detection-rule) of *Writing new detection rules for the Sonar Cryptography Plugin* to understand how to write a detection rule. +> At this point, if you have not done it yet, you should read the section [_Writing a detection rule_](./DETECTION_RULE_STRUCTURE.md#writing-a-detection-rule) of _Writing new detection rules for the Sonar Cryptography Plugin_ to understand how to write a detection rule. -Now suppose that you want to write your first rule *MyRule* of your *mycrypto* library (that we are shortening to `Mc` in file names). You will need to create three files, in the three directories previously mentioned: -- `McMyRule.java` in `main/.../plugin/rules/detection/mycrypto/`: this is where you should define a class containing the *IDetectionRule MyRule*. Make this class extend [`DetectionRuleSet`](../engine/src/main/java/com/ibm/engine/rule/DetectionRuleSet.java) (using your language's own tree type in place of `Tree`), and implement the single method it requires, `protected List> buildRules()`, returning the list of all detection rules of your file, in your case simply `List.of(MyRule)`. You do not need a private constructor or a static accessor method: `DetectionRuleSet` already has a `protected` no-argument constructor, and rules are read back through the registry described below, not by calling your class directly. +Now suppose that you want to write your first rule _MyRule_ of your _mycrypto_ library (that we are shortening to `Mc` in file names). You will need to create three files, in the three directories previously mentioned: + +- `McMyRule.java` in `main/.../plugin/rules/detection/mycrypto/`: this is where you should define a class containing the _IDetectionRule MyRule_. Make this class extend [`DetectionRuleSet`](../engine/src/main/java/com/ibm/engine/rule/DetectionRuleSet.java) (using your language's own tree type in place of `Tree`), and implement the single method it requires, `protected List> buildRules()`, returning the list of all detection rules of your file, in your case simply `List.of(MyRule)`. You do not need a private constructor or a static accessor method: `DetectionRuleSet` already has a `protected` no-argument constructor, and rules are read back through the registry described below, not by calling your class directly. - `McMyRuleTestFile.XXX` in `test/.../files/rules/detection/mycrypto/`: this is where you should write a code example containing the function call that you aim to capture with `MyRule`. This file is written in your target programming language that you want to scan (so you should set the file extension `.XXX` accordingly). - `McMyRuleTest.java` in `test/.../plugin/rules/detection/mycrypto/`: this is where you should define your unit test class, which should `extends TestBase`. Create a `test()` method with a `@Test` annotation, in which you call the language-specific test function on the test file that you just defined. You should also override the `asserts` method, but leave it empty for now, we will [come back to it](#writing-assert-statements). @@ -469,6 +498,7 @@ You need to create another class `MyCryptoDetectionRules` listing the detection At this point, you should have already created a class listing all of your detection rules, like [`JavaDetectionRules`](../java/src/main/java/com/ibm/plugin/rules/detection/JavaDetectionRules.java) for Java, in `plugin/rules/detection/`. Therefore, register your library rules by adding them to the `buildRules()` method of this main file containing all detection rules. This step is done only once, to register your new cryptography library: + ```java @Nonnull @Override @@ -485,6 +515,7 @@ protected List> buildRules() { Finally, register your rule `McMyRule` in your class `MyCryptoDetectionRules` listing your library rules by adding it to its `buildRules()` method. This step should be done each time you are creating a new detection rule in a new file: + ```java @Nonnull @Override @@ -509,7 +540,8 @@ For example, Bouncy Castle asymmetric cipher rules use `AsymmetricCipherOverride #### Testing Once this is done, try to run your unit test and look at the logs. -If it works, you should see logs[^4] of your detected values, in a tree structure looking like this (but with the values of your test file that you specified to detect in *MyRule*): +If it works, you should see logs[^4] of your detected values, in a tree structure looking like this (but with the values of your test file that you specified to detect in _MyRule_): + ``` [id: afeca, bundle: Bc, level: 0, hash: -1948…] (CipherContext<{kind=BLOCK_CIPHER}>, ValueAction) CBCBlockCipher [id: cca99, bundle: Bc, level: 1, hash: 10584…] └─ (CipherContext<{kind=ENCRYPTION_STATUS}>, OperationMode) 0 @@ -519,13 +551,14 @@ If it works, you should see logs[^4] of your detected values, in a tree structur [^4]: If you wrote your own `TestBase`, make sure that you call the `DetectionStoreLogger.print(DetectionStore ds)` method at the right place to display your findings in the logs. Note that the unit test may fail (because we have not yet handled the translation), but what is important at this step is to observe these logs. If you do not observe these logs, - - and you have written your own support layer for a new programming language, then it is very likely to be a problem coming from this language support layer (that you could not have tested until now). You will have to spend some time tuning your code written in the `engine` module, which will be explained in the [next part](#tuning-the-engine-if-necessary). - - and you rely on existing language support, then the problem is probably coming from your detection rule or your test file. You should double-check that your rule is correctly registered, and that your rule correctly matches what you expect to detect in your test file. If you don't solve your problem by simply double-checking, try to use the debugger to see why your rule is not triggered. You can add a breakpoint to the `match` method of [`MethodMatcher`](../engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java) to watch if each function call of your test file matches with your rule. + +- and you have written your own support layer for a new programming language, then it is very likely to be a problem coming from this language support layer (that you could not have tested until now). You will have to spend some time tuning your code written in the `engine` module, which will be explained in the [next part](#tuning-the-engine-if-necessary). +- and you rely on existing language support, then the problem is probably coming from your detection rule or your test file. You should double-check that your rule is correctly registered, and that your rule correctly matches what you expect to detect in your test file. If you don't solve your problem by simply double-checking, try to use the debugger to see why your rule is not triggered. You can add a breakpoint to the `match` method of [`MethodMatcher`](../engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java) to watch if each function call of your test file matches with your rule. ### Tuning the engine (if necessary) > [!NOTE] -> This part is only relevant if you wrote your own language support and you are now experiencing detection problems. +> This part is only relevant if you wrote your own language support and you are now experiencing detection problems. Recall that when [implementing the language specific parts of the engine](#implementing-the-language-specific-parts-of-the-engine), you have implemented multiple interfaces without making any tests, and possibly without handling some edge cases yet. @@ -535,7 +568,6 @@ Also take the time to understand how your language-specific AST (defined by your If you have additional questions about how a function should work and be implemented, and you have not found an answer in this documentation, we advise you to debug existing unit tests in other supported languages to observe the other (functioning) implementations of these functions. - ### Translating your first detection rule > [!NOTE] @@ -544,8 +576,8 @@ If you have additional questions about how a function should work and be impleme > However, you should still read this part to better understand which files you should modify to translate your detection rules. > [!IMPORTANT] -> At this point, if you have not done it yet, you should read the sections [*Translating findings of a detection rule*](./DETECTION_RULE_STRUCTURE.md#translating-findings-of-a-detection-rule) and [*Reorganizing the translation tree*](./DETECTION_RULE_STRUCTURE.md#reorganizing-the-translation-tree) of *Writing new detection rules for the Sonar Cryptography Plugin* to understand how to translate the findings of a detection rule. -> +> At this point, if you have not done it yet, you should read the sections [_Translating findings of a detection rule_](./DETECTION_RULE_STRUCTURE.md#translating-findings-of-a-detection-rule) and [_Reorganizing the translation tree_](./DETECTION_RULE_STRUCTURE.md#reorganizing-the-translation-tree) of _Writing new detection rules for the Sonar Cryptography Plugin_ to understand how to translate the findings of a detection rule. +> > In the following, we will assume that your translation includes a reorganization phase, but feel free to remove it if you know that it is not necessary in the case of your library. In addition to the file structure we introduced earlier, we introduce new subdirectories in `translation/`, that you should have in your language module. @@ -564,6 +596,7 @@ java │ └── contexts └── reorganizer ``` +

edit this tree

#### The translator @@ -589,7 +622,7 @@ When translating your detected values implies parsing complicated strings (typic Ultimately, those mapper classes can be called from your context-specific translation functions. -You can now add content to these files to translate the findings from your first detection rule, following the section [*Translating findings of a detection rule*](./DETECTION_RULE_STRUCTURE.md#translating-findings-of-a-detection-rule). +You can now add content to these files to translate the findings from your first detection rule, following the section [_Translating findings of a detection rule_](./DETECTION_RULE_STRUCTURE.md#translating-findings-of-a-detection-rule). #### The reorganization @@ -598,7 +631,7 @@ You can use the helpers defined in [`UsualPerformActions.java`](../mapper/src/ma Then, in the directory `translation/reorganizer/`, create a file listing all reorganization rules for your language. In Java, this is the [`JavaReorganizerRules`](../java/src/main/java/com/ibm/plugin/translation/reorganizer/JavaReorganizerRules.java) class. -If necessary, you can now reorganize the translation of the findings of your first detection rule, following the section [*Reorganizing the translation tree*](./DETECTION_RULE_STRUCTURE.md#reorganizing-the-translation-tree). +If necessary, you can now reorganize the translation of the findings of your first detection rule, following the section [_Reorganizing the translation tree_](./DETECTION_RULE_STRUCTURE.md#reorganizing-the-translation-tree). #### Bridging the gap @@ -609,6 +642,7 @@ In Java, this is [`JavaTranslationProcess`](../java/src/main/java/com/ibm/plugin This is where you will apply the translation and reorganization steps, as well as the enrichment process, which is a step adding external information to the translation tree, as mentioned [earlier](#the-translation). Make sure to log the translated, reorganized and enriched trees of findings by calling `Utils.printNodeTree(List nodes)` at the right places (with `Utils` from `com.ibm.mapper.utils`). These steps must be called in the `initiate` function, which looks like this in Java: + ```java @Override @Nonnull @@ -634,13 +668,13 @@ public List initiate( ``` Finally, this translation process file should be registered in two places: + - In your class implementing the language-specific sonar visitor class ([`JavaBaseDetectionRule`](../java/src/main/java/com/ibm/plugin/rules/detection/JavaBaseDetectionRule.java) in Java), mentioned in [here](#the-check-registrar-extension-point). - In your `TestBase` class ([`TestBase`](../java/src/test/java/com/ibm/plugin/TestBase.java) in Java), mentioned in [here](#organizing-your-files). You can now run again your unit test to check whether your detected values are correctly translated. If your implementation works, you should observe logs displaying the translated (and potentially reorganized/enriched) trees, after the initial logs of the detected values. - ### Writing assert statements Your detection rule now works, and its findings get correctly translated? Congrats, your rule is almost finished! @@ -652,13 +686,12 @@ You can look into existing unit tests to know how these assert statements are wr If you have to write a lot of assert statements for a lot of detection rules, you may want to automate this process. While we do not provide a generic methodology to do so, we have done it for Java (and Python), with [`GenerateAssertsHelper`](../java/src/test/java/com/ibm/plugin/utils/GenerateAssertsHelper.java) (documented in its file), which can easily be reused for another language, requiring only minor modifications. - ### Going further: using graph visualization to better understand dependent detection rules If you are adding support to a big cryptography library, you will probably need a lot a detection rules, possibly with a lot of dependent detection rules. Depending on how your cryptography library is structured, this may result in a large web of widely connected rules, which may make it hard to visualize and reason about. -To make it easily visualizable, we provide a language-agnostic way to export all your detection rules to a simple JSON representation. This is done with the [`ExportRules`](../engine/src/main/java/com/ibm/engine/serializer/ExportRules.java) class, that you can extend with a unit test to automatically generate this JSON export each time you run the test ([`ExportJavaRulesToJsonTest`](../java/src/test/java/com/ibm/plugin/ExportJavaRulesToJsonTest.java) in Java). +To make it easily visualizable, we provide a language-agnostic way to export all your detection rules to a simple JSON representation. This is done with the [`ExportRules`](../engine/src/main/java/com/ibm/engine/serializer/ExportRules.java) class, that you can extend with a unit test to automatically generate this JSON export each time you run the test ([`ExportJavaRulesToJsonTest`](../java/src/test/java/com/ibm/plugin/ExportJavaRulesToJsonTest.java) in Java). It will be exported to `target/rules.json` in your language module. You can then build whatever representation you like from this JSON export. @@ -666,6 +699,6 @@ In Java, we provide a graph representation using the `pyvis` Python library. The file [`parse.py`](../java/rule-graph/parse.py) (which you can execute with the right requirements using [`build-graph.sh`](../java/rule-graph/build-graph.sh) if you have `pip` and `python3` installed) is a Python script parsing the JSON and building a graph, that gets exported into an [HTML file](../docs/index.html) that you can visualize in your browser. This script can be reused in to create the same graph for the language of your choice, provided that you have exported the rules to the same JSON format. -| ![example graph](./images/graph.png) | -|:--:| -| *What the Java graph looks like at the time of writing this section. Red nodes are entry-point rules (rules that are directly checked when scanning code), blue nodes are dependent-only nodes (rules that are only checked when another rule has matched), and green nodes are interfaces. An arrow from node A to node B means that B is a dependent detection rule of rule A. Notice that there are two main connected parts: most JCA rules are in the top connected component, and most BouncyCastle rules are in the main big connected component. Also notice that some rules are completely disconnected: it just means that they do not have dependent detection rules.* | +| ![example graph](./images/graph.png) | +| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| _What the Java graph looks like at the time of writing this section. Red nodes are entry-point rules (rules that are directly checked when scanning code), blue nodes are dependent-only nodes (rules that are only checked when another rule has matched), and green nodes are interfaces. An arrow from node A to node B means that B is a dependent detection rule of rule A. Notice that there are two main connected parts: most JCA rules are in the top connected component, and most BouncyCastle rules are in the main big connected component. Also notice that some rules are completely disconnected: it just means that they do not have dependent detection rules._ | diff --git a/docs/PERFORMANCE_TESTING.md b/docs/PERFORMANCE_TESTING.md index dfa54dc8b..337d7212f 100644 --- a/docs/PERFORMANCE_TESTING.md +++ b/docs/PERFORMANCE_TESTING.md @@ -7,10 +7,10 @@ AST-detach fix (see `docs/superpowers/plans/2026-07-05-callstack-hooks-heap-redu There are two levels of testing: -| Level | What it is | When to use | -|---|---|---| -| **A. Self-contained JUnit harness** | `CallStackHeapPerfTest` — generates a synthetic cross-file corpus, scans it in-process, asserts detach invariants. No Docker, no network. | Quick, deterministic regression check. Runs in seconds–minutes. | -| **B. Keycloak end-to-end scan** | Full `mvn sonar:sonar` of Keycloak against a local SonarQube with the plugin installed. | Realistic heap/time numbers on a large project. Manual, ~10–15 min + setup. | +| Level | What it is | When to use | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| **A. Self-contained JUnit harness** | `CallStackHeapPerfTest` (Java) and `CxxCallStackHeapPerfTest` (C/C++) — generate a synthetic corpus, scan it in-process, assert detach invariants. No Docker, no network. | Quick, deterministic regression check. Runs in seconds–minutes. | +| **B. Keycloak end-to-end scan** | Full `mvn sonar:sonar` of Keycloak against a local SonarQube with the plugin installed. | Realistic heap/time numbers on a large project. Manual, ~10–15 min + setup. | Start with **A** for a fast signal; use **B** to get true, large-project numbers. @@ -41,7 +41,7 @@ It prints a line like: - **`retainedWithTree`** — recorded calls still pinning a live AST. Must stay ~0. - **`detached` / `ratio`** — calls converted to tree-free records. Ratio must stay high. - **`heapDeltaMB`** — reported only, **never asserted** (a coarse whole-JVM number; at this - synthetic scale it is *not* a reliable proxy for AST-pinning savings — the generated files + synthetic scale it is _not_ a reliable proxy for AST-pinning savings — the generated files are tiny, so pinned ASTs cost little in bytes). Use Keycloak (Part B) for real byte numbers. The assertions (`ratio >= 0.9`, `retainedWithTree <= 10`) fail hard if AST-detaching regresses. @@ -57,7 +57,7 @@ The assertions (`ratio >= 0.9`, `retainedWithTree <= 10`) fail hard if AST-detac ### Prerequisites - **Docker** + **Docker Compose** (for the local SonarQube + PostgreSQL). -- **JDK 17** to build this plugin; **the JDK Keycloak requires** to build Keycloak +- **JDK 21** to build this plugin; **the JDK Keycloak requires** to build Keycloak (currently **JDK 21** for `main`). - **Maven 3.9+**. - A full **JDK** (not just a JRE) on `PATH` for `jcmd` (used to sample heap — see Step 6). @@ -128,7 +128,7 @@ cp sonar-cryptography-plugin/target/sonar-cryptography-plugin-*.jar .SonarQube/p (Do **not** copy the `*-sources.jar` or `original-*.jar`.) **3c. Start SonarQube + PostgreSQL.** `docker-compose.yaml` uses `user: "${UID}"`, and in -zsh `UID` is a read-only variable that is *not* exported — if it is blank the container runs +zsh `UID` is a read-only variable that is _not_ exported — if it is blank the container runs as root and Elasticsearch/temp dirs get root-owned, causing permission crashes. Provide `UID` explicitly via a `.env` file (Compose reads it automatically): @@ -147,7 +147,7 @@ curl -s http://localhost:9000/api/system/status # {"status":"UP", ...} > `docker compose down -v && echo "UID=$(id -u)" > .env && docker compose up -d`. **3d. Verify the running instance loaded YOUR plugin** (the deployed JAR must contain your -changes, and SonarQube must have started *after* you copied it): +changes, and SonarQube must have started _after_ you copied it): ```bash # the plugin's registered timestamp should be AFTER your JAR's build time @@ -165,7 +165,7 @@ If you rebuild the plugin later, repeat 3a–3b then `docker compose restart son ### Step 4 — Create an analysis token In the SonarQube UI (`http://localhost:9000`, default login `admin`/`admin`, change on first -login): **My Account → Security → Generate Token** (type: *Global Analysis Token*). Copy it. +login): **My Account → Security → Generate Token** (type: _Global Analysis Token_). Copy it. ```bash export TOKEN=sqp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -181,7 +181,7 @@ this repo). **Important — the scanner forks a separate JVM.** `sonar-maven-plugin` downloads and runs the **SonarScanner Engine** in a child JVM under `~/.sonar/cache/...` — this is where the plugin, `CallStackAgent`, and the heap actually live. It does **not** inherit `MAVEN_OPTS`. To cap and -instrument the *right* JVM, pass `-Dsonar.scanner.javaOpts`. +instrument the _right_ JVM, pass `-Dsonar.scanner.javaOpts`. ```bash cd ~/Downloads/keycloak-main @@ -206,7 +206,7 @@ mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \ ### Step 6 — Sample the engine heap during the scan While the scan runs, sample the **engine** JVM's heap with `jcmd` (from a full JDK — the -scanner's bundled JRE has no `jcmd`). Run this in a second terminal *after* the scan reaches +scanner's bundled JRE has no `jcmd`). Run this in a second terminal _after_ the scan reaches its analysis phase: ```bash @@ -247,11 +247,11 @@ exercised. **What good looks like (with the AST-detach fix, measured on Keycloak `main`, SonarQube 26.1, 93 modules / ~8200 files):** -| Metric | Pre-fix (old) | With AST-detach fix | -|---|---|---| -| Outcome | ~7 GB **and climbing, did not finish** | **ANALYSIS SUCCESSFUL** in ~11 min | -| Under `-Xmx6g` | would OOM | completed, no OOM | -| Peak heap used | 7 GB+ (linear, no plateau) | ~4.6 GB (oscillating, GC reclaims) | +| Metric | Pre-fix (old) | With AST-detach fix | +| -------------- | -------------------------------------- | ---------------------------------- | +| Outcome | ~7 GB **and climbing, did not finish** | **ANALYSIS SUCCESSFUL** in ~11 min | +| Under `-Xmx6g` | would OOM | completed, no OOM | +| Peak heap used | 7 GB+ (linear, no plateau) | ~4.6 GB (oscillating, GC reclaims) | - **Healthy:** `heap_used` **oscillates** — rises then drops as G1 reclaims — and the scan completes. The post-GC floor may grow modestly but the run finishes under the cap. @@ -259,10 +259,10 @@ exercised. `/tmp`), or the scan never finishes. That signals AST pinning (or another unbounded term) has returned. -Note the residual ~4.6 GB is *not* the call-stack AST term (that is eliminated — see the +Note the residual ~4.6 GB is _not_ the call-stack AST term (that is eliminated — see the synthetic harness's `retainedWithTree=0`); it is SonarQube's baseline analysis cost plus the CBOM nodes accumulated for the whole scan (`JavaAggregator.detectedNodes`). `jcmd`'s -`heap_used` includes uncollected garbage, so the true retained set sits at the GC *floors*, +`heap_used` includes uncollected garbage, so the true retained set sits at the GC _floors_, below the sampled peak. --- @@ -301,7 +301,7 @@ Enable DEBUG for the plugin (e.g. `-Dsonar.log.level=DEBUG` on the scanner, or t harness — it runs with `isInventory=false`, so CBOM nodes are not aggregated there; the count is meaningful only on a real inventory scan). -Counts size the *populations*, not their bytes — a small count of heavy objects can still +Counts size the _populations_, not their bytes — a small count of heavy objects can still dominate. Use them to spot which population grows, then confirm bytes with the histogram below. **2. Byte attribution via `jmap` (decisive).** During a constrained-heap Keycloak scan (see @@ -316,13 +316,13 @@ jmap -histo:live > histo.txt Bucket the top entries of `histo.txt` into the three sources: -| Bucket | Classes to sum in `histo.txt` | -|---|---| -| Retained CBOM nodes | `com.ibm.mapper.model.**` (e.g. `Algorithm`, `Key`, `Property`, `MessageDigest`, …) and their child `HashMap`/`HashMap$Node` share | -| Detached call-stack | `com.ibm.engine.callstack.DetachedCall`, `...callstack.ArgSnapshot`, `...callstack.ResolvedSnapshotValue` | -| Residual Tree / hooks | `org.sonar.**Tree*` still live + `com.ibm.engine.hooks.**` | +| Bucket | Classes to sum in `histo.txt` | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| Retained CBOM nodes | `com.ibm.mapper.model.**` (e.g. `Algorithm`, `Key`, `Property`, `MessageDigest`, …) and their child `HashMap`/`HashMap$Node` share | +| Detached call-stack | `com.ibm.engine.callstack.DetachedCall`, `...callstack.ArgSnapshot`, `...callstack.ResolvedSnapshotValue` | +| Residual Tree / hooks | `org.sonar.**Tree*` still live + `com.ibm.engine.hooks.**` | -Sample two or three histograms as the scan progresses to see which bucket *grows* (the floor is +Sample two or three histograms as the scan progresses to see which bucket _grows_ (the floor is about accumulation, not a one-time cost). ### Decision — measured 2026-07-06 (Keycloak `main`, 94 compiled modules, SonarQube 26.1, `-Xmx6g`) @@ -332,20 +332,20 @@ components), no OOM; post-GC heap floor oscillated and ended ~2.9–3.4 GB (heal no monotonic climb). Attribution from live `GC.class_histogram` (post-full-GC) sampled early / mid / late in the run: -| Population | early | mid | late (near end) | -|---|---|---|---| -| Retained CBOM nodes (`com.ibm.mapper.**`) | ~0 | 0.01 MB (484) | **0.02 MB (824 inst)** | -| Detached call-stack (`com.ibm.engine.callstack.**`) | ~0 | 7.6 MB (291k) | **15.6 MB (594k inst)** | -| Hooks (`com.ibm.engine.hooks.**`) | ~0 | 0.001 MB | **~1 KB (1376 inst)** | -| **Plugin total (`com.ibm.**`)** | ~0 | 14.2 MB | **28.4 MB** | -| **Whole heap floor** | 0.05 GB | 1.55 GB | **2.90 GB** | +| Population | early | mid | late (near end) | +| --------------------------------------------------- | ------- | ------------- | ----------------------- | +| Retained CBOM nodes (`com.ibm.mapper.**`) | ~0 | 0.01 MB (484) | **0.02 MB (824 inst)** | +| Detached call-stack (`com.ibm.engine.callstack.**`) | ~0 | 7.6 MB (291k) | **15.6 MB (594k inst)** | +| Hooks (`com.ibm.engine.hooks.**`) | ~0 | 0.001 MB | **~1 KB (1376 inst)** | +| **Plugin total (`com.ibm.**`)\*\* | ~0 | 14.2 MB | **28.4 MB** | +| **Whole heap floor** | 0.05 GB | 1.55 GB | **2.90 GB** | The ~2.9 GB floor is overwhelmingly **SonarQube / ECJ baseline**, not plugin state — the top retained classes are `byte[]` (332 MB), `HashMap$Node` (251 MB), `Object[]` (219 MB), `char[]` (210 MB), `ArrayList` (176 MB), and sonar-java/ECJ semantic objects (`InternalPosition` 152 MB, `InternalSyntaxToken` 122 MB, `MethodBinding` 95 MB, `InternalRange` 76 MB). -**Outcome — the original hypothesis is disproven.** The floor growth (~1.6 → ~3.4 GB) is *not* +**Outcome — the original hypothesis is disproven.** The floor growth (~1.6 → ~3.4 GB) is _not_ `JavaAggregator.detectedNodes`: retained CBOM nodes are **negligible (~25 KB, 824 instances)**. The entire plugin footprint is **~28 MB (~1 % of the floor)**, and the floor growth tracks SonarQube's own accumulating semantic model / AST of the module under analysis — which the plugin @@ -354,6 +354,7 @@ linearly/unbounded (291k → 594k records mid→late), but AST-detach keeps each absolute heap cost is small. **H2 routing (revised by this measurement):** + - **No `detectedNodes` spec.** CBOM-node retention is not a heap problem — drop that candidate. - **No heap-motivated retention cap.** 594k detached records ≈ 15.6 MB is not a memory risk; the cap (old Task 5) is unjustified on heap grounds — defer/drop it. @@ -367,7 +368,7 @@ absolute heap cost is small. > is the reliable attribution path; use it directly. > Note on H2's eligibility filter: the predicate cannot be derived from `methodSymbol().declaration()` -> — cross-file *user* calls resolve via `sonar.java.binaries` and have a null declaration, exactly +> — cross-file _user_ calls resolve via `sonar.java.binaries` and have a null declaration, exactly > like library calls (see the comment in `JavaLanguageSupport.isDetachableCall`). The discriminator > must be pinned empirically against the `crossfile/` fixtures before the filter is written. diff --git a/docs/RUNNING_WITH_SONARQUBE.md b/docs/RUNNING_WITH_SONARQUBE.md new file mode 100644 index 000000000..87d7cbc0a --- /dev/null +++ b/docs/RUNNING_WITH_SONARQUBE.md @@ -0,0 +1,191 @@ +# Running the Plugin with SonarQube + +This guide describes how to build the plugin, run it in a local SonarQube instance and scan a +project to generate a CBOM. + +## Prerequisites + +- Java 21 +- Maven 3.9 or newer +- Docker with the Docker Compose plugin +- [SonarScanner CLI](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/scanners/sonarscanner/) + 6.0 or newer + +> [!IMPORTANT] +> The plugin requires Java 21 on both the SonarQube server and the scanner. SonarScanner CLI 6.0 +> and newer obtain the required Java runtime from the server automatically. SonarScanner CLI 5.x +> uses its bundled Java 17 runtime and therefore must be configured to use Java 21: set +> `use_embedded_jre=false` in `bin/sonar-scanner` and set `JAVA_HOME` to a Java 21 installation. + +## Build the plugin + +From the repository root: + +```bash +mvn clean package -DskipTests +``` + +The plugin JAR is copied to `.SonarQube/plugins/`, which the SonarQube container uses as its +plugins directory. + +> [!NOTE] +> C/C++ support is bundled in the plugin JAR. Do not install the sonar-cxx plugin separately. + +## Start SonarQube + +From the repository root: + +```bash +echo "UID=$(id -u)" > .env +docker compose up -d +``` + +Wait until SonarQube reports the status `UP`: + +```bash +until curl -sf http://localhost:9000/api/system/status | grep -q '"status":"UP"'; do + sleep 3 +done +``` + +SonarQube is available at . The default credentials are `admin` / +`admin`. + +## Configure SonarQube + +The following steps use the SonarQube Web API. They can also be performed in the SonarQube UI +(see [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md)). + +### Generate a token + +```bash +curl -s -u admin:admin -X POST "http://localhost:9000/api/user_tokens/generate" \ + -d "name=my-scan-token" +``` + +Save the value of the `token` field. It is used to authenticate the scanner. + +### Create a quality profile with the Inventory rule + +The _Cryptographic Inventory (CBOM)_ rule generates the CBOM. It must be active in the quality +profile used by the project. Use the keys for the language to scan: + +| Language | Language key | Rule key | +| -------- | ------------ | ------------------------------- | +| Java | `java` | `sonar-java-crypto:Inventory` | +| Python | `py` | `sonar-python-crypto:Inventory` | +| Go | `go` | `sonar-go-crypto:Inventory` | +| C/C++ | `cxx` | `sonar-cpp-crypto:Inventory` | + +Example for C/C++: + +```bash +curl -s -u admin:admin -X POST "http://localhost:9000/api/qualityprofiles/create" \ + -d "language=cxx&name=Cryptography" +``` + +Use the `key` of the created profile to activate the rule: + +```bash +curl -s -u admin:admin -X POST "http://localhost:9000/api/qualityprofiles/activate_rule" \ + -d "key=&rule=sonar-cpp-crypto:Inventory" +``` + +### Create a project + +```bash +curl -s -u admin:admin -X POST "http://localhost:9000/api/projects/create" \ + -d "project=my-project&name=my-project" +curl -s -u admin:admin -X POST "http://localhost:9000/api/qualityprofiles/add_project" \ + -d "qualityProfile=Cryptography&language=cxx&project=my-project" +``` + +## Scan a project + +Run the scanner from the root directory of the project to scan. + +### C/C++ + +```bash +sonar-scanner \ + -Dsonar.projectKey=my-project \ + -Dsonar.sources=src \ + -Dsonar.cxx.file.suffixes=.c,.cc,.cpp,.cxx,.c++,.h,.hh,.hpp,.hxx,.h++ \ + -Dsonar.host.url=http://localhost:9000 \ + -Dsonar.token= +``` + +> [!IMPORTANT] +> `sonar.cxx.file.suffixes` is required. Without it, no C/C++ file is analyzed. + +If the code depends on preprocessor macros or include directories defined by its build system, +set them with `sonar.cxx.defines` and `sonar.cxx.includeDirectories`. These settings are easier +to maintain in a `sonar-project.properties` file in the root directory of the scanned project: + +```bash +sonar.projectKey=my-project +sonar.sources=src +sonar.cxx.file.suffixes=.c,.cc,.cpp,.cxx,.c++,.h,.hh,.hpp,.hxx,.h++ +sonar.cxx.defines=USE_OPENSSL 1\n\ +TLS1_3_VERSION 0x0304 +sonar.cxx.includeDirectories=include +sonar.host.url=http://localhost:9000 +sonar.token= +``` + +With this file in place, run `sonar-scanner` without arguments. + +### Java + +Compile the project first, then run: + +```bash +sonar-scanner \ + -Dsonar.projectKey=my-project \ + -Dsonar.sources=src/main/java \ + -Dsonar.java.binaries=target/classes \ + -Dsonar.java.libraries= \ + -Dsonar.host.url=http://localhost:9000 \ + -Dsonar.token= +``` + +`sonar.java.binaries` must point to the compiled classes. `sonar.java.libraries` must list the +JARs of the cryptography libraries used by the code, such as BouncyCastle. + +### Python and Go + +```bash +sonar-scanner \ + -Dsonar.projectKey=my-project \ + -Dsonar.sources=. \ + -Dsonar.host.url=http://localhost:9000 \ + -Dsonar.token= +``` + +## Results + +After a successful scan, the scanner prints the CBOM statistics and writes `cbom.json` to the +directory it was run from. The file name can be changed with the `sonar.cryptoScanner.cbom` +property (see [Configuration](../README.md#configuration)). The detected assets are also listed +in the _Issues_ tab of the project in SonarQube. + +If no CBOM is generated, check that: + +1. The Inventory rule is active in the quality profile assigned to the project. +2. The scanner runs on Java 21. +3. For Java, `sonar.java.binaries` and `sonar.java.libraries` point to existing files. +4. For C/C++, `sonar.cxx.file.suffixes` is set. + +## Stop SonarQube + +Stop the containers and keep the data: + +```bash +docker compose down +``` + +To also delete all SonarQube data: + +```bash +docker compose down -v +``` diff --git a/engine/pom.xml b/engine/pom.xml index ea041a413..bdb4b5325 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -12,8 +12,6 @@ engine - 17 - 17 UTF-8 @@ -35,6 +33,12 @@ cbomkit-csharp-parser 0.1.3
+ + ${sonar.cxx.groupId} + cxx-squid + ${sonar.cxx.version} + provided + \ No newline at end of file diff --git a/engine/src/main/java/com/ibm/engine/callstack/ArgSnapshot.java b/engine/src/main/java/com/ibm/engine/callstack/ArgSnapshot.java index 733576f4b..b1efbd6ea 100644 --- a/engine/src/main/java/com/ibm/engine/callstack/ArgSnapshot.java +++ b/engine/src/main/java/com/ibm/engine/callstack/ArgSnapshot.java @@ -25,10 +25,16 @@ /** * Tree-free snapshot of one argument of a detached recorded call: the value(s) it resolved to at * record time (while the file was still live) plus, for each, an AST-free location to report on. + * + * @param the language's tree type, shared with the live (non-detached) path throughout the call + * stack ({@code DetachedCall}, {@code IScanContext}); the location is an AST-free + * stand-in for it (e.g. {@link DetachedSyntaxToken} for Java, which fakes a {@code Tree}) so it + * can flow through existing {@code T}-typed code unchanged. {@link + * DetachedScanContext#reportIssue} checks the actual runtime value against {@link + * DetachedLocation}. */ -public record ArgSnapshot(int index, @Nonnull List values) { +public record ArgSnapshot(int index, @Nonnull List> values) { /** A single resolved value plus its detached location. */ - public record ResolvedSnapshotValue( - @Nonnull Object value, @Nonnull DetachedSyntaxToken location) {} + public record ResolvedSnapshotValue(@Nonnull Object value, @Nonnull T location) {} } diff --git a/engine/src/main/java/com/ibm/engine/callstack/CallStackAgent.java b/engine/src/main/java/com/ibm/engine/callstack/CallStackAgent.java index 42ded8e7a..4f626a416 100644 --- a/engine/src/main/java/com/ibm/engine/callstack/CallStackAgent.java +++ b/engine/src/main/java/com/ibm/engine/callstack/CallStackAgent.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nonnull; import org.sonar.api.batch.fs.InputFile; @@ -43,6 +44,16 @@ public class CallStackAgent private final ConcurrentMap>> invokedCallStack = new ConcurrentHashMap<>(); + /** + * Bucket-key/index pairs of each file's still-retained, detachable calls, keyed by {@link + * InputFile}. Populated in {@link #addedToCallContext} as calls are recorded, so {@link + * #detachCallsForFile} can locate a file's own entries directly instead of rescanning every + * bucket recorded so far. + */ + @Nonnull + private final ConcurrentMap> detachableCallsByFile = + new ConcurrentHashMap<>(); + @Nonnull private final List>> listeners = new ArrayList<>(); @Nonnull private final ILanguageSupport languageSupport; @@ -62,13 +73,19 @@ public void addCall(@Nonnull T tree, @Nonnull IScanContext scanContext) { * before this runs, so their SonarQube issues are unaffected. */ public void detachCallsForFile(@Nonnull InputFile inputFile) { - for (List> bucket : invokedCallStack.values()) { - for (int i = 0; i < bucket.size(); i++) { - if (bucket.get(i) instanceof RetainedCall retained - && retained.detachedForm() != null - && inputFile.equals(retained.publisher().getInputFile())) { - bucket.set(i, retained.detachedForm()); - } + final List locations = detachableCallsByFile.remove(inputFile); + if (locations == null) { + return; + } + for (int[] location : locations) { + final List> bucket = invokedCallStack.get(location[0]); + if (bucket == null || location[1] >= bucket.size()) { + continue; + } + if (bucket.get(location[1]) instanceof RetainedCall retained + && retained.detachedForm() != null + && inputFile.equals(retained.publisher().getInputFile())) { + bucket.set(location[1], retained.detachedForm()); } } } @@ -90,10 +107,38 @@ public void add(@Nonnull CallContext callContext) { } } + /** + * Whether {@code tree} is already present in the recorded-call population, under the same key + * and identity check {@link #add} itself uses to dedupe. A single call node is recorded once + * per matching detection rule that visits it, so callers building an expensive {@link + * DetachedCall} snapshot before calling {@link #add} can check this first and skip that work + * once the node's first recording has already happened. + */ + public boolean isRecorded(@Nonnull T tree) { + final Optional keyOptional = getKeyFormT(tree); + if (keyOptional.isEmpty()) { + return false; + } + final List> bucket = invokedCallStack.get(keyOptional.get()); + if (bucket == null) { + return false; + } + for (CallContext existing : bucket) { + if (tree.equals(existing.tree())) { + return true; + } + } + return false; + } + @Nonnull private Optional keyOf(@Nonnull CallContext callContext) { if (callContext instanceof DetachedCall detached) { - return Optional.of(detached.methodName().hashCode()); + return Optional.of( + languageSupport + .translation() + .getMethodNameKey(detached.methodName()) + .hashCode()); } final T tree = callContext.tree(); return tree == null ? Optional.empty() : getKeyFormT(tree); @@ -156,9 +201,10 @@ public void onNewHookSubscription( } /** - * The call-stack buckets a hook's matcher could match. Recorded calls are keyed by their - * invoked method name's hash, so a single-name matcher only needs that one bucket; {@code - * ANY}/multi-name matchers fall back to scanning every bucket. + * The call-stack buckets a hook's matcher could match. Recorded calls are keyed by the hash of + * the key of their invoked method name (see {@code ILanguageTranslation#getMethodNameKey}), so + * a single-name matcher only needs that one bucket; {@code ANY}/multi-name matchers fall back + * to scanning every bucket. */ @Nonnull private Collection>> bucketsToScan( @@ -166,7 +212,11 @@ private Collection>> bucketsToScan( final List methodNames = methodMatcher.getMethodNamesSerializable(); if (methodNames.size() == 1 && !MethodMatcher.ANY.equals(methodNames.get(0))) { final List> bucket = - invokedCallStack.get(methodNames.get(0).hashCode()); + invokedCallStack.get( + languageSupport + .translation() + .getMethodNameKey(methodNames.get(0)) + .hashCode()); return bucket == null ? List.of() : List.of(bucket); } return invokedCallStack.values(); @@ -177,8 +227,7 @@ private boolean matches( @Nonnull CallContext callContext, @Nonnull IHook hook) { if (callContext instanceof DetachedCall detached) { - return methodMatcher.matchKeys( - detached.invokedObjectType(), detached.methodName(), detached.parameterTypes()); + return detached.isMatchedBy(methodMatcher); } final T tree = callContext.tree(); return tree != null @@ -188,6 +237,7 @@ private boolean matches( private boolean addedToCallContext(int key, @Nonnull CallContext callContext) { final T tree = callContext.tree(); final boolean[] added = {true}; + final int[] insertedIndex = {-1}; invokedCallStack.compute( key, (k, v) -> { @@ -201,9 +251,19 @@ private boolean addedToCallContext(int key, @Nonnull CallContext callConte } } } + insertedIndex[0] = callContexts.size(); callContexts.add(callContext); return callContexts; }); + if (added[0] + && callContext instanceof RetainedCall retained + && retained.detachedForm() != null) { + final List locations = + detachableCallsByFile.computeIfAbsent( + retained.publisher().getInputFile(), + ignored -> new CopyOnWriteArrayList<>()); + locations.add(new int[] {key, insertedIndex[0]}); + } return added[0]; } @@ -213,6 +273,7 @@ private Optional getKeyFormT(@Nonnull T tree) { languageSupport .translation() .getMethodName(MatchContext.createForHookContext(), tree) + .map(name -> languageSupport.translation().getMethodNameKey(name)) .orElse( languageSupport .translation() diff --git a/engine/src/main/java/com/ibm/engine/callstack/DetachedCall.java b/engine/src/main/java/com/ibm/engine/callstack/DetachedCall.java index cc8894c2d..7fd750759 100644 --- a/engine/src/main/java/com/ibm/engine/callstack/DetachedCall.java +++ b/engine/src/main/java/com/ibm/engine/callstack/DetachedCall.java @@ -20,6 +20,7 @@ package com.ibm.engine.callstack; import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MethodMatcher; import com.ibm.engine.language.IScanContext; import java.util.List; import javax.annotation.Nonnull; @@ -35,11 +36,37 @@ public record DetachedCall( @Nonnull IType invokedObjectType, @Nonnull String methodName, + @Nonnull List methodNames, @Nonnull List parameterTypes, - @Nonnull List arguments, + @Nonnull List> arguments, @Nonnull DetachedScanContext detachedPublisher) implements CallContext { + /** A detached call of the one method named {@code methodName}. */ + public DetachedCall( + @Nonnull IType invokedObjectType, + @Nonnull String methodName, + @Nonnull List parameterTypes, + @Nonnull List> arguments, + @Nonnull DetachedScanContext detachedPublisher) { + this( + invokedObjectType, + methodName, + List.of(methodName), + parameterTypes, + arguments, + detachedPublisher); + } + + /** + * Whether a method matcher matches the call, by one of the names of the methods it may call + * (see {@code ILanguageTranslation#getMethodNames}). + */ + public boolean isMatchedBy(@Nonnull MethodMatcher methodMatcher) { + return methodNames.stream() + .anyMatch(name -> methodMatcher.matchKeys(invokedObjectType, name, parameterTypes)); + } + @Nullable @Override public T tree() { return null; diff --git a/engine/src/main/java/com/ibm/engine/callstack/DetachedLocation.java b/engine/src/main/java/com/ibm/engine/callstack/DetachedLocation.java new file mode 100644 index 000000000..dc44c7f29 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/callstack/DetachedLocation.java @@ -0,0 +1,29 @@ +/* + * 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.engine.callstack; + +/** + * Marker for an AST-free stand-in location used by a detached (tree-free) cross-file detection + * (e.g. Java's {@link DetachedSyntaxToken}, or C++'s {@code CxxDetachedAstNode}). Each such type + * also implements the language's real tree interface, so it can flow through existing {@code + * T}-typed code unchanged; {@link DetachedScanContext} checks against this marker to tell, at + * runtime, whether a given {@code T} is one of those AST-free stand-ins. + */ +public interface DetachedLocation {} diff --git a/engine/src/main/java/com/ibm/engine/callstack/DetachedScanContext.java b/engine/src/main/java/com/ibm/engine/callstack/DetachedScanContext.java index b533e233f..bedce185a 100644 --- a/engine/src/main/java/com/ibm/engine/callstack/DetachedScanContext.java +++ b/engine/src/main/java/com/ibm/engine/callstack/DetachedScanContext.java @@ -33,22 +33,23 @@ * so that replaying it does not pin the file's AST while still supporting CBOM translation and * cross-file SonarQube issue reporting. * - *

{@link #reportIssue} raises the issue through {@link #issueReporter} when the location is a - * {@link DetachedSyntaxToken}; if no reporter is available it logs and skips (the CBOM node is - * still produced via translation). + *

{@link #reportIssue} raises the issue through {@link #issueReporter} when the location + * implements {@link DetachedLocation} (Java's {@link DetachedSyntaxToken} or C++'s {@code + * CxxDetachedAstNode}); if no reporter is available, or the location is not one of those AST-free + * stand-ins, it logs and skips (the CBOM node is still produced via translation). */ public record DetachedScanContext( @Nonnull InputFile inputFile, @Nonnull String filePath, - @Nullable IDetachedIssueReporter issueReporter) + @Nullable IDetachedIssueReporter issueReporter) implements IScanContext { private static final Logger LOGGER = LoggerFactory.getLogger(DetachedScanContext.class); @Override public void reportIssue(@Nonnull R currentRule, @Nonnull T tree, @Nonnull String message) { - if (issueReporter != null && tree instanceof DetachedSyntaxToken location) { - issueReporter.report(currentRule, location, message); + if (issueReporter != null && tree instanceof DetachedLocation) { + issueReporter.report(currentRule, tree, message); return; } LOGGER.debug( diff --git a/engine/src/main/java/com/ibm/engine/callstack/DetachedSyntaxToken.java b/engine/src/main/java/com/ibm/engine/callstack/DetachedSyntaxToken.java index 913dcab62..2621df840 100644 --- a/engine/src/main/java/com/ibm/engine/callstack/DetachedSyntaxToken.java +++ b/engine/src/main/java/com/ibm/engine/callstack/DetachedSyntaxToken.java @@ -38,7 +38,7 @@ * tree. Column offsets are stored 0-based (matching {@link Position#columnOffset()}); {@link * Position#at(int, int)} takes a 1-based column, so {@link #range()} adds one. */ -public final class DetachedSyntaxToken implements SyntaxToken { +public final class DetachedSyntaxToken implements SyntaxToken, DetachedLocation { private final int line; private final int columnOffset; diff --git a/engine/src/main/java/com/ibm/engine/callstack/IDetachedIssueReporter.java b/engine/src/main/java/com/ibm/engine/callstack/IDetachedIssueReporter.java index f3e30a70b..16a348ee1 100644 --- a/engine/src/main/java/com/ibm/engine/callstack/IDetachedIssueReporter.java +++ b/engine/src/main/java/com/ibm/engine/callstack/IDetachedIssueReporter.java @@ -22,15 +22,19 @@ import javax.annotation.Nonnull; /** - * Raises a SonarQube issue for a detached (tree-free) cross-file detection, at the location - * captured in the {@link DetachedSyntaxToken}. Implemented per language over a shared, + * Raises a SonarQube issue for a detached (tree-free) cross-file detection, at the given AST-free + * location (e.g. Java's {@link DetachedSyntaxToken}, which fakes a {@code Tree}, or C++'s {@code + * CxxDetachedAstNode}, which fakes an {@code AstNode}). Implemented per language over a shared, * non-AST-pinning reporting channel (for Java, {@code SonarComponents} + the file's {@code - * InputFile}), so a detached record can report an issue without retaining the file's AST. + * InputFile}; for C++, the originating file's {@code SourceFile} data bag), so a detached record + * can report an issue without retaining the file's AST. * * @param the language's rule/check type + * @param the language's tree type; the location passed to {@link #report} is an AST-free + * stand-in for it */ @FunctionalInterface -public interface IDetachedIssueReporter { +public interface IDetachedIssueReporter { - void report(@Nonnull R rule, @Nonnull DetachedSyntaxToken location, @Nonnull String message); + void report(@Nonnull R rule, @Nonnull T location, @Nonnull String message); } diff --git a/engine/src/main/java/com/ibm/engine/detection/DetectionStore.java b/engine/src/main/java/com/ibm/engine/detection/DetectionStore.java index 02cb6699c..fdcfeb2d3 100644 --- a/engine/src/main/java/com/ibm/engine/detection/DetectionStore.java +++ b/engine/src/main/java/com/ibm/engine/detection/DetectionStore.java @@ -68,6 +68,10 @@ public class DetectionStore implements IHookDetectionObserver actionValue; + /* + * the calls or object creations the detection rule matched, in the order they were detected + */ + @Nonnull private final List detectedExpressions = new ArrayList<>(); public DetectionStore( final int level, @@ -140,6 +144,35 @@ public Optional> getActionValue() { return Optional.ofNullable(actionValue); } + /** + * Returns the first call or object creation the detection rule matched, if the rule matched + * one. + * + * @return the first matched expression + */ + @Nonnull + public Optional getDetectedExpression() { + return detectedExpressions.stream().findFirst(); + } + + /** + * Returns the calls or object creations the detection rule matched, in the order they were + * detected. A depending rule matching several calls made on the same object, e.g. two {@code + * init} calls, records each of them in the same store. + * + * @return the matched expressions + */ + @Nonnull + public List getDetectedExpressions() { + return Collections.unmodifiableList(detectedExpressions); + } + + private void addDetectedExpression(@Nonnull T expression) { + if (detectedExpressions.stream().noneMatch(detected -> detected == expression)) { + detectedExpressions.add(expression); + } + } + /** * Returns all detection values, including actionValue, in the order they were added to the * store. If actionValue is null, then only detectionValues are returned. @@ -290,6 +323,7 @@ public void analyse(@Nonnull final T tree) { @SuppressWarnings("java:S3776") public void onReceivingNewDetection(@Nonnull IDetection detection) { if (detection instanceof MethodDetection methodDetection) { + addDetectedExpression(methodDetection.expression()); List> nextDetectionRules = new LinkedList<>(); // A detectionRule can also reflect the actual action (method) as a value if provided if (detectionRule.is(DetectionRule.class)) { @@ -321,6 +355,7 @@ public void onReceivingNewDetection(@Nonnull IDetection detection) { } else if (detection instanceof ValueDetection valueDetection) { final DetectableParameter detectableParameter = valueDetection.detectableParameter(); + addDetectedExpression(valueDetection.expression()); final Optional positionMove = detectableParameter.getShouldBeMovedUnder(); // Check if the parameter should be moved under @@ -339,6 +374,8 @@ public void onReceivingNewDetection(@Nonnull IDetection detection) { scanContext, handler, statusReporting); + detectionStore.addDetectedExpression( + valueDetection.expression()); // Compute the detection values for the given id addValue(detectionStore, id, iValue); // Attach the detection store to the given id @@ -450,7 +487,7 @@ protected TraceSymbol getAssignedTraceSymbol(@Nonnull T expression) { IDetectionEngine detectionEngine = handler.getLanguageSupport().createDetectionEngineInstance(this); return detectionEngine - .getAssignedSymbol(expression) + .getObjectSymbol(expression) .orElse(TraceSymbol.createWithStateNoSymbol()); } diff --git a/engine/src/main/java/com/ibm/engine/detection/DetectionStoreWithHook.java b/engine/src/main/java/com/ibm/engine/detection/DetectionStoreWithHook.java index 6dcb53ec4..d4076776c 100644 --- a/engine/src/main/java/com/ibm/engine/detection/DetectionStoreWithHook.java +++ b/engine/src/main/java/com/ibm/engine/detection/DetectionStoreWithHook.java @@ -212,9 +212,9 @@ private void handleMethodInvocationHookWithParameterResolvement( /** * Fire-time replay for a {@link DetachedCall}: the argument was already resolved at record * time, so we skip {@code extractArgumentFromMethodCaller}/{@code resolveValuesInInnerScope} - * and feed the pre-resolved snapshot (with its {@code DetachedSyntaxToken} location) straight - * to the hook's value factory. (Java parameter hooks never set {@code expressionToResolve}, so - * the cross-boundary path is not needed here.) + * and feed the pre-resolved snapshot (with its AST-free location) straight to the hook's value + * factory. (Java parameter hooks never set {@code expressionToResolve}, so the cross-boundary + * path is not needed here.) */ private void replayDetachedParameterHook( @Nonnull final DetachedCall detachedCall, @@ -226,10 +226,9 @@ private void replayDetachedParameterHook( if (index >= 0 && index < detachedCall.arguments().size() && hook.getParameter() instanceof DetectableParameter detectableParameter) { - for (ArgSnapshot.ResolvedSnapshotValue snapshot : + for (ArgSnapshot.ResolvedSnapshotValue snapshot : detachedCall.arguments().get(index).values()) { - @SuppressWarnings("unchecked") - final T location = (T) snapshot.location(); + final T location = snapshot.location(); final ResolvedValue resolvedValue = new ResolvedValue<>(snapshot.value(), location); new ValueDetection<>(resolvedValue, detectableParameter, location, null) diff --git a/engine/src/main/java/com/ibm/engine/detection/Finding.java b/engine/src/main/java/com/ibm/engine/detection/Finding.java index 57458a31e..1330bf2f4 100644 --- a/engine/src/main/java/com/ibm/engine/detection/Finding.java +++ b/engine/src/main/java/com/ibm/engine/detection/Finding.java @@ -22,6 +22,7 @@ import com.ibm.engine.model.IValue; import com.ibm.engine.rule.IBundle; import java.util.List; +import java.util.Optional; import javax.annotation.Nonnull; public record Finding(@Nonnull DetectionStore detectionStore) { @@ -35,14 +36,35 @@ public IBundle bundle() { return detectionStore.getDetectionRule().bundle(); } + /** + * The tree to report the finding on: the location of the first value of the store, or, for a + * store without a value that reports the calls made on the object it detected, the call that + * created the object (or, when the store has no matched call, the first value below it). + */ @Nonnull public T getMarkerTree() { return detectionStore.getDetectionValues().stream() .map(IValue::getLocation) .findFirst() + .or(detectionStore::getDetectedExpression) + .or(() -> firstValueLocation(detectionStore)) .orElseThrow(); } + @Nonnull + private static Optional firstValueLocation( + @Nonnull DetectionStore store) { + final Optional own = + store.getDetectionValues().stream().map(IValue::getLocation).findFirst(); + if (own.isPresent()) { + return own; + } + return store.getChildren().stream() + .map(Finding::firstValueLocation) + .flatMap(Optional::stream) + .findFirst(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/engine/src/main/java/com/ibm/engine/detection/Handler.java b/engine/src/main/java/com/ibm/engine/detection/Handler.java index 015464960..74d957d89 100644 --- a/engine/src/main/java/com/ibm/engine/detection/Handler.java +++ b/engine/src/main/java/com/ibm/engine/detection/Handler.java @@ -87,6 +87,14 @@ public void addRecordedCall(@Nonnull CallContext recordedCall) { this.callStackAgent.add(recordedCall); } + /** + * Whether {@code tree} has already been recorded by an earlier detection rule's pass over the + * same call node. + */ + public boolean isCallAlreadyRecorded(@Nonnull T tree) { + return this.callStackAgent.isRecorded(tree); + } + public void detachCallsForFile(@Nonnull InputFile inputFile) { this.callStackAgent.detachCallsForFile(inputFile); } diff --git a/engine/src/main/java/com/ibm/engine/detection/IDetectionEngine.java b/engine/src/main/java/com/ibm/engine/detection/IDetectionEngine.java index 3cc5ac043..42616e895 100644 --- a/engine/src/main/java/com/ibm/engine/detection/IDetectionEngine.java +++ b/engine/src/main/java/com/ibm/engine/detection/IDetectionEngine.java @@ -145,6 +145,18 @@ void resolveMethodReturnValues( @Nonnull Optional> getAssignedSymbol(@Nonnull T expression); + /** + * Returns the symbol of the object a call creates or operates on, which the rules depending on + * the call follow. By default, this is the symbol the call is assigned to. + * + * @param expression the call. + * @return The symbol of the object, if any, or an empty optional otherwise. + */ + @Nonnull + default Optional> getObjectSymbol(@Nonnull T expression) { + return getAssignedSymbol(expression); + } + /** * Returns the related symbol for the detectionParameter of a given expression. * 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 3e216da0d..04d802d5e 100644 --- a/engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java +++ b/engine/src/main/java/com/ibm/engine/detection/MethodMatcher.java @@ -82,15 +82,17 @@ public boolean match( @Nonnull ILanguageTranslation translation, @Nonnull MatchContext context) { var owner = translation.getInvokedObjectTypeString(context, expression); - var name = translation.getMethodName(context, expression); + var names = translation.getMethodNames(context, expression); var arguments = translation.getMethodParameterTypes(context, expression); return owner.isPresent() - && name.isPresent() - && acceptsCall( - owner.get(), - name.get(), - arguments, - translation.supportsSubsetParameterMatching()); + && names.stream() + .anyMatch( + name -> + acceptsCall( + owner.get(), + name, + arguments, + translation.supportsSubsetParameterMatching())); } /** diff --git a/engine/src/main/java/com/ibm/engine/detection/TraceSymbol.java b/engine/src/main/java/com/ibm/engine/detection/TraceSymbol.java index fd08533a8..35d9f53cc 100644 --- a/engine/src/main/java/com/ibm/engine/detection/TraceSymbol.java +++ b/engine/src/main/java/com/ibm/engine/detection/TraceSymbol.java @@ -19,6 +19,7 @@ */ package com.ibm.engine.detection; +import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -48,10 +49,16 @@ public enum State { private final S symbol; private final State state; + @Nullable private final Object origin; private TraceSymbol(S symbol, State state) { + this(symbol, state, null); + } + + private TraceSymbol(S symbol, State state, @Nullable Object origin) { this.symbol = symbol; this.state = state; + this.origin = origin; } public S getSymbol() { @@ -73,6 +80,22 @@ public static TraceSymbol createWithStateNoSymbol() { return new TraceSymbol<>(null, State.NO_SYMBOL); } + /** + * NO_SYMBOL for the result of a known call that is passed as an argument of another call, + * example: test(create()). Only the call that takes the origin as an argument is connected to + * it. + */ + @Nonnull + public static TraceSymbol createWithStateNoSymbol(@Nonnull Object origin) { + return new TraceSymbol<>(null, State.NO_SYMBOL, origin); + } + + /** The call whose result is traced, for a NO_SYMBOL created for a known call. */ + @Nonnull + public Optional getOrigin() { + return Optional.ofNullable(origin); + } + @Nonnull public static TraceSymbol createStart() { return new TraceSymbol<>(null, State.SYMBOL_IGNORED); diff --git a/engine/src/main/java/com/ibm/engine/executive/DetectionExecutive.java b/engine/src/main/java/com/ibm/engine/executive/DetectionExecutive.java index b7cf5f760..f6198f151 100644 --- a/engine/src/main/java/com/ibm/engine/executive/DetectionExecutive.java +++ b/engine/src/main/java/com/ibm/engine/executive/DetectionExecutive.java @@ -83,6 +83,14 @@ public void addAdditionalExpectedRuleVisits(int number) { this.expectedRuleVisits += number; } + /** + * The stores to report as findings. A store with a value is one finding. A store without a + * value whose rule follows the object it detects (it has depending rules, e.g. the creation of + * a context by a C function, whose arguments and the calls made on it describe the context) is + * one finding as well, reporting the values below it together - unless its own value was found + * elsewhere (a child store of the same rule, e.g. resolved through a called function): then, as + * otherwise, the stores with a value below it are reported separately. + */ @Nonnull private List> getRootStoresWithValue( @Nonnull DetectionStore detectionStore) { @@ -90,10 +98,21 @@ private List> getRootStoresWithValue( || detectionStore.getActionValue().isPresent()) { return List.of(detectionStore); } - return detectionStore.getChildren().stream() - .map(this::getRootStoresWithValue) - .flatMap(Collection::stream) - .toList(); + final List> storesWithValue = + detectionStore.getChildren().stream() + .map(this::getRootStoresWithValue) + .flatMap(Collection::stream) + .toList(); + if (!storesWithValue.isEmpty() + && !detectionStore.getDetectionRule().nextDetectionRules().isEmpty() + && detectionStore.getChildren().stream() + .noneMatch( + child -> + child.getDetectionRule() + == detectionStore.getDetectionRule())) { + return List.of(detectionStore); + } + return storesWithValue; } @Override diff --git a/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithParameterResolvement.java b/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithParameterResolvement.java index b9dde7084..df201fb2f 100644 --- a/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithParameterResolvement.java +++ b/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithParameterResolvement.java @@ -63,11 +63,7 @@ public boolean isInvocationOn( if (callContext instanceof DetachedCall detached) { final MethodMatcher methodMatcher = languageSupport.createMethodMatcherBasedOn(methodDefinition); - return methodMatcher != null - && methodMatcher.matchKeys( - detached.invokedObjectType(), - detached.methodName(), - detached.parameterTypes()); + return methodMatcher != null && detached.isMatchedBy(methodMatcher); } final T tree = callContext.tree(); return tree != null && isInvocationOn(tree, languageSupport); diff --git a/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithReturnResolvement.java b/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithReturnResolvement.java index 7859ffe71..f33473e05 100644 --- a/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithReturnResolvement.java +++ b/engine/src/main/java/com/ibm/engine/hooks/MethodInvocationHookWithReturnResolvement.java @@ -51,11 +51,7 @@ public boolean isInvocationOn( if (callContext instanceof DetachedCall detached) { final MethodMatcher methodMatcher = languageSupport.createMethodMatcherBasedOn(methodDefinition); - return methodMatcher != null - && methodMatcher.matchKeys( - detached.invokedObjectType(), - detached.methodName(), - detached.parameterTypes()); + return methodMatcher != null && detached.isMatchedBy(methodMatcher); } final T tree = callContext.tree(); return tree != null && isInvocationOn(tree, languageSupport); diff --git a/engine/src/main/java/com/ibm/engine/language/ILanguageTranslation.java b/engine/src/main/java/com/ibm/engine/language/ILanguageTranslation.java index 890bc5f95..d29dc8680 100644 --- a/engine/src/main/java/com/ibm/engine/language/ILanguageTranslation.java +++ b/engine/src/main/java/com/ibm/engine/language/ILanguageTranslation.java @@ -43,6 +43,34 @@ public interface ILanguageTranslation { @Nonnull Optional getMethodName(@Nonnull MatchContext matchContext, @Nonnull T methodInvocation); + /** + * Returns the names of the methods a method invocation may call. An invocation through a + * function pointer may call any of the functions the pointer is assigned; any other invocation + * calls the one method {@link #getMethodName} names. + * + * @param matchContext provides context the matching procedure + * @param methodInvocation to resolve the method names of. + * @return names of the methods the invocation may call. + */ + @Nonnull + default List getMethodNames( + @Nonnull MatchContext matchContext, @Nonnull T methodInvocation) { + return getMethodName(matchContext, methodInvocation).stream().toList(); + } + + /** + * Returns the key the calls of a method are recorded under: the calls a method matcher can + * match, of the method names {@link #getMethodNames} gives and of the name of the method a + * definition defines, have the same key. By default it is the method name. + * + * @param methodName a method name. + * @return the key of the method name. + */ + @Nonnull + default String getMethodNameKey(@Nonnull String methodName) { + return methodName; + } + /** * Returns the invoked object type from a method invocation. * diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxBaseMethodVisitor.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxBaseMethodVisitor.java new file mode 100644 index 000000000..d7774c20d --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxBaseMethodVisitor.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.cxx; + +import com.ibm.engine.detection.IBaseMethodVisitor; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.TraceSymbol; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.AstNodeType; +import javax.annotation.Nonnull; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.api.AstNodeTraversal; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +public class CxxBaseMethodVisitor implements IBaseMethodVisitor { + @Nonnull private final TraceSymbol traceSymbol; + @Nonnull private final IDetectionEngine detectionEngine; + + private static final AstNodeType[] DETECTION_NODE_TYPES = { + CxxGrammarImpl.postfixExpression, CxxGrammarImpl.newExpression, CxxGrammarImpl.enumSpecifier + }; + + public CxxBaseMethodVisitor( + @Nonnull TraceSymbol traceSymbol, + @Nonnull IDetectionEngine detectionEngine) { + this.traceSymbol = traceSymbol; + this.detectionEngine = detectionEngine; + } + + @Override + public void visitMethodDefinition(@Nonnull AstNode method) { + if (method.is(CxxGrammarImpl.functionDefinition)) { + AstNode functionBody = CxxAstNodeHelper.getFunctionDefinitionBody(method); + if (functionBody != null) { + traverseAndDetect(functionBody); + } + } else if (method.is(CxxGrammarImpl.translationUnit)) { + traverseAndDetect(method); + } + } + + private void traverseAndDetect(@Nonnull AstNode root) { + AstNodeTraversal.traverse(root, DETECTION_NODE_TYPES, this::visitNode); + } + + private void visitNode(@Nonnull AstNode node) { + if (node.is(CxxGrammarImpl.postfixExpression)) { + visitPostfixExpression(node); + } else if (node.is(CxxGrammarImpl.newExpression)) { + visitNewExpression(node); + } else if (node.is(CxxGrammarImpl.enumSpecifier)) { + visitEnumSpecifier(node); + } + } + + private void visitPostfixExpression(@Nonnull AstNode node) { + if (CxxAstNodeHelper.isFunctionCall(node)) { + detectionEngine.run(traceSymbol, node); + } + } + + private void visitNewExpression(@Nonnull AstNode node) { + detectionEngine.run(traceSymbol, node); + } + + private void visitEnumSpecifier(@Nonnull AstNode node) { + detectionEngine.run(traceSymbol, node); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxConstructorCalls.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxConstructorCalls.java new file mode 100644 index 000000000..dd73c0ac3 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxConstructorCalls.java @@ -0,0 +1,357 @@ +/* + * 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.engine.language.cxx; + +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.parser.CxxPunctuator; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * The calls of a C++ constructor. An object of a class is constructed by a new-expression ({@code + * new Hasher("SHA224")}), by the declaration of an object with arguments ({@code Hasher + * h("SHA224")}, {@code Hasher h{"SHA224"}}, {@code Hasher h = {"SHA224"}}) and by an explicit type + * conversion ({@code Hasher("SHA224")}, {@code Hasher{"SHA224"}}), which constructs a temporary + * object. + * + *

The declaration of an object whose arguments are all names, {@code Hasher h(name)}, is read by + * the parser as the declaration of a function {@code h} with untyped parameters. It constructs an + * object when the names name values, as C++ reads it, and declares a function when they name types. + * + *

A name is the name of a class when it resolves to a class, struct or union, or, as a class + * declared in another namespace is not always resolved, when a class of that name is declared in + * the translation unit. + */ +public final class CxxConstructorCalls { + + /** The names of the classes declared in a translation unit, by its root node. */ + private static final Map> CLASS_NAMES = + Collections.synchronizedMap(new WeakHashMap<>()); + + private CxxConstructorCalls() { + // private + } + + /** Whether the node calls a constructor. */ + public static boolean isConstructorCall(@Nullable AstNode node) { + if (node == null) { + return false; + } + if (node.is(CxxGrammarImpl.newExpression)) { + return true; + } + if (node.is(CxxGrammarImpl.initDeclarator)) { + return isObjectDeclaration(node); + } + return node.is(CxxGrammarImpl.postfixExpression) && isTypeConversion(node); + } + + /** The arguments passed to the constructor a {@link #isConstructorCall call} calls. */ + @Nonnull + public static List getArguments(@Nonnull AstNode call) { + if (call.is(CxxGrammarImpl.newExpression)) { + final AstNode initializer = call.getFirstChild(CxxGrammarImpl.newInitializer); + return initializer == null ? List.of() : argumentsOf(initializer); + } + if (call.is(CxxGrammarImpl.initDeclarator)) { + final AstNode initializer = call.getFirstChild(CxxGrammarImpl.initializer); + if (initializer != null) { + return argumentsOf(initializer); + } + return argumentsReadAsParameters(call); + } + if (call.is(CxxGrammarImpl.postfixExpression)) { + return argumentsOf(call); + } + return List.of(); + } + + /** + * The name of the class a {@link #isConstructorCall call} constructs an object of, as it is + * written, e.g. {@code crypto::Digest}. + */ + @CheckForNull + public static String getClassName(@Nonnull AstNode call) { + if (call.is(CxxGrammarImpl.newExpression)) { + final AstNode typeId = call.getFirstChild(CxxGrammarImpl.newTypeId); + final AstNode type = + typeId == null ? null : typeId.getFirstDescendant(CxxGrammarImpl.typeName); + return type == null ? null : textOf(typeNameOf(type)); + } + if (call.is(CxxGrammarImpl.initDeclarator)) { + final AstNode type = declaredType(call); + return type == null ? null : textOf(type); + } + if (call.is(CxxGrammarImpl.postfixExpression)) { + return textOf(call.getFirstChild()); + } + return null; + } + + /** + * The name of the class a variable, a pointer or a reference is declared with, e.g. {@code + * Hasher} for {@code Hasher &h} or {@code crypto::Digest} for {@code crypto::Digest *d}, or + * null when its type is not a class. + */ + @CheckForNull + public static String getDeclaredClassName(@Nonnull Symbol.VariableSymbol variable) { + final Symbol.TypeSymbol declaredType = variable.declaredType(); + if (declaredType != null && declaredType.fullyQualifiedName() != null) { + return declaredType.fullyQualifiedName(); + } + AstNode declaration = variable.declaration(); + while (declaration != null + && !declaration.is( + CxxGrammarImpl.simpleDeclaration, + CxxGrammarImpl.parameterDeclaration, + CxxGrammarImpl.memberDeclaration)) { + declaration = declaration.getParent(); + } + if (declaration == null) { + return null; + } + final AstNode specifiers = + declaration.getFirstChild( + CxxGrammarImpl.declSpecifierSeq, + CxxGrammarImpl.declSpecifier, + CxxGrammarImpl.memberDeclSpecifierSeq, + CxxGrammarImpl.parameterDeclSpecifierSeq); + if (specifiers == null) { + return null; + } + final AstNode qualified = specifiers.getFirstDescendant(CxxGrammarImpl.simpleTypeSpecifier); + if (qualified != null && qualified.hasDirectChildren(CxxGrammarImpl.typeName)) { + return textOf(qualified); + } + final AstNode type = specifiers.getFirstDescendant(CxxGrammarImpl.typeName); + return type == null ? null : textOf(type); + } + + /** + * Whether a name written at the given node names a class; of a qualified name, e.g. {@code + * crypto::Digest}, its last component is the name of the class. + */ + public static boolean namesAClass(@Nonnull AstNode node, @Nonnull String name) { + final Symbol symbol = AstNodeSymbolExtension.getSymbol(node); + if (symbol instanceof Symbol.TypeSymbol type && !symbol.isUnknown()) { + return type.isClass() || type.isStruct() || type.isUnion(); + } + final int qualifier = name.lastIndexOf("::"); + final String simpleName = qualifier < 0 ? name : name.substring(qualifier + 2); + return classNamesOf(rootOf(node)).contains(simpleName); + } + + /** + * Whether two names of a class name the same class: they are equal, or one is the other + * qualified by namespaces or classes, e.g. {@code Digest} written inside {@code namespace + * crypto} and {@code crypto::Digest}. + */ + public static boolean isSameClass(@Nonnull String name, @Nonnull String other) { + return name.equals(other) || name.endsWith("::" + other) || other.endsWith("::" + name); + } + + /** The name of a class or namespace written in tokens, e.g. {@code crypto::Digest}. */ + @Nonnull + public static String textOf(@Nonnull AstNode node) { + final StringBuilder text = new StringBuilder(); + node.getTokens().forEach(token -> text.append(token.getValue())); + return text.toString(); + } + + /** + * Whether an init-declarator declares an object of a class with constructor arguments: neither + * a pointer, a reference nor an array, initialized with arguments in parentheses or braces, or + * read as a function declaration whose parameters are arguments. + */ + private static boolean isObjectDeclaration(@Nonnull AstNode initDeclarator) { + final AstNode declarator = initDeclarator.getFirstChild(CxxGrammarImpl.declarator); + if (declarator == null || !declaresAnObject(declarator)) { + return false; + } + final AstNode initializer = initDeclarator.getFirstChild(CxxGrammarImpl.initializer); + final boolean hasArguments = + initializer != null + ? initializer.getFirstChild().is(CxxPunctuator.BR_LEFT) + || initializer.getLastChild().is(CxxGrammarImpl.bracedInitList) + : !argumentsReadAsParameters(initDeclarator).isEmpty(); + if (!hasArguments) { + return false; + } + final AstNode type = declaredType(initDeclarator); + return type != null && namesAClass(lastIdentifier(type), textOf(type)); + } + + /** Whether a declarator declares an object, not a pointer, a reference or an array. */ + private static boolean declaresAnObject(@Nonnull AstNode declarator) { + final AstNode first = declarator.getFirstChild(); + if (first.is(CxxGrammarImpl.ptrDeclarator) + && first.hasDirectChildren(CxxGrammarImpl.ptrOperator)) { + return false; + } + return !(first.is(CxxGrammarImpl.noptrDeclarator) + && first.hasDirectChildren(CxxPunctuator.SQBR_LEFT)); + } + + /** + * The arguments of a constructor call the parser read as the untyped parameters of a function + * declaration, {@code name} in {@code Hasher h(name)}, see {@link + * CxxAstNodeHelper#getUntypedParameters}: the names of the parameters, when each names a value. + * Empty when the declarator declares a function, or is not read as one. + */ + @Nonnull + private static List argumentsReadAsParameters(@Nonnull AstNode initDeclarator) { + final List arguments = new ArrayList<>(); + for (AstNode parameter : + CxxAstNodeHelper.getUntypedParameters( + initDeclarator.getFirstChild(CxxGrammarImpl.declarator))) { + final AstNode name = CxxAstNodeHelper.getUntypedParameterName(parameter); + final Symbol symbol = name == null ? null : AstNodeSymbolExtension.getSymbol(name); + if (symbol == null || symbol.isUnknown() || symbol.isTypeSymbol()) { + return List.of(); + } + arguments.add(name); + } + return arguments; + } + + /** + * The type of the object an init-declarator declares when it is a class named by its + * declaration, e.g. {@code Hasher} or {@code crypto::Digest}, or null. + */ + @CheckForNull + private static AstNode declaredType(@Nonnull AstNode initDeclarator) { + final AstNode list = initDeclarator.getParent(); + final AstNode declaration = list == null ? null : list.getParent(); + if (declaration == null || !declaration.is(CxxGrammarImpl.simpleDeclaration)) { + return null; + } + final AstNode specifiers = + declaration.hasDirectChildren(CxxGrammarImpl.declSpecifierSeq) + ? declaration.getFirstChild(CxxGrammarImpl.declSpecifierSeq) + : declaration; + for (AstNode specifier : specifiers.getChildren(CxxGrammarImpl.declSpecifier)) { + final AstNode type = specifier.getFirstChild(); + if (type.is(CxxGrammarImpl.typeName)) { + return type; + } + if (type.is(CxxGrammarImpl.simpleTypeSpecifier) + && type.hasDirectChildren(CxxGrammarImpl.typeName)) { + return type; + } + } + return null; + } + + /** + * Whether a postfix expression is an explicit type conversion to a class, {@code Hasher("x")} + * or {@code Hasher{"x"}}: a class name followed by arguments in parentheses or braces. + */ + private static boolean isTypeConversion(@Nonnull AstNode postfixExpression) { + final List children = postfixExpression.getChildren(); + final AstNode type = children.get(0); + if (!type.is(CxxGrammarImpl.typeName) + && !(type.is(CxxGrammarImpl.simpleTypeSpecifier) + && type.hasDirectChildren(CxxGrammarImpl.typeName))) { + return false; + } + final boolean parenthesized = + children.size() >= 3 + && children.size() <= 4 + && children.get(1).is(CxxPunctuator.BR_LEFT) + && children.get(children.size() - 1).is(CxxPunctuator.BR_RIGHT); + final boolean braced = + children.size() == 2 && children.get(1).is(CxxGrammarImpl.bracedInitList); + return (parenthesized || braced) && namesAClass(lastIdentifier(type), textOf(type)); + } + + /** + * The arguments in parentheses or braces of a constructor call: of a new-initializer, an + * initializer, or an explicit type conversion. + */ + @Nonnull + private static List argumentsOf(@Nonnull AstNode node) { + final AstNode braced = node.getFirstChild(CxxGrammarImpl.bracedInitList); + if (braced != null) { + return CxxSemantic.elementsOf(braced); + } + final AstNode list = node.getFirstChild(CxxGrammarImpl.expressionList); + if (list == null) { + return List.of(); + } + final AstNode clauses = list.getFirstChild(CxxGrammarImpl.initializerList); + if (clauses == null) { + return list.getChildren(); + } + return clauses.getChildren().stream() + .filter(clause -> !clause.is(CxxPunctuator.COMMA)) + .toList(); + } + + /** The type name of a new-type-id, qualified when it is, e.g. {@code crypto::Digest}. */ + @Nonnull + private static AstNode typeNameOf(@Nonnull AstNode typeName) { + final AstNode parent = typeName.getParent(); + return parent != null && parent.is(CxxGrammarImpl.simpleTypeSpecifier) ? parent : typeName; + } + + @Nonnull + private static AstNode lastIdentifier(@Nonnull AstNode type) { + final List identifiers = type.getDescendants(GenericTokenType.IDENTIFIER); + return identifiers.isEmpty() ? type : identifiers.get(identifiers.size() - 1); + } + + @Nonnull + private static AstNode rootOf(@Nonnull AstNode node) { + AstNode root = node; + while (root.getParent() != null) { + root = root.getParent(); + } + return root; + } + + @Nonnull + private static Set classNamesOf(@Nonnull AstNode root) { + return CLASS_NAMES.computeIfAbsent( + root, + unit -> { + final Set names = new HashSet<>(); + for (AstNode specifier : unit.getDescendants(CxxGrammarImpl.classSpecifier)) { + final String name = CxxAstNodeHelper.getClassName(specifier); + if (name != null) { + names.add(name); + } + } + return names; + }); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetachedAstNode.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetachedAstNode.java new file mode 100644 index 000000000..402d3d88e --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetachedAstNode.java @@ -0,0 +1,72 @@ +/* + * 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.engine.language.cxx; + +import com.ibm.engine.callstack.DetachedLocation; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import com.sonar.cxx.sslr.api.Token; +import java.net.URI; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * AST-free {@link AstNode} used as the location of a detached cross-file detection value. + * + *

Mirrors the engine's Java {@code DetachedSyntaxToken}: it fakes being a real parsed node so it + * can flow through {@code CxxTranslator.getDetectionContextFrom(AstNode, ...)} and {@code + * CxxLanguageTranslation} unchanged, but it holds only primitives captured at record time (never a + * parent, children, or a reference back into the file's real AST), so it never pins the file's + * parse tree. It has no parent and no children, so any traversal starting from it (ancestor/sibling + * lookups) simply terminates immediately, which is correct for a leaf-only snapshot. + * + *

{@code CxxTranslator.getDetectionContextFrom} reads {@link AstNode#getToken()} (line/column) + * and, via {@code extractKeywords}, falls through to {@link AstNode#getToken()}'s value since this + * node's type never matches {@code postfixExpression}/{@code newExpression} — {@link #keywords()} + * carries the richer, pre-extracted keyword list for callers that want it directly. + */ +public final class CxxDetachedAstNode extends AstNode implements DetachedLocation { + + @Nonnull private static final URI DETACHED_URI = URI.create("detached:///cross-file"); + + @Nonnull private final List keywords; + + public CxxDetachedAstNode( + int line, int column, @Nonnull String text, @Nonnull List keywords) { + super(buildToken(line, column, text)); + this.keywords = List.copyOf(keywords); + } + + @Nonnull + public List keywords() { + return keywords; + } + + @Nonnull + private static Token buildToken(int line, int column, @Nonnull String text) { + return Token.builder() + .setType(GenericTokenType.LITERAL) + .setValueAndOriginalValue(text) + .setLine(Math.max(line, 1)) + .setColumn(Math.max(column, 0)) + .setURI(DETACHED_URI) + .build(); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetachedIssueReporter.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetachedIssueReporter.java new file mode 100644 index 000000000..70cb0d8fd --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetachedIssueReporter.java @@ -0,0 +1,82 @@ +/* + * 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.engine.language.cxx; + +import com.ibm.engine.callstack.IDetachedIssueReporter; +import com.sonar.cxx.sslr.api.AstNode; +import javax.annotation.Nonnull; +import org.sonar.cxx.squidbridge.api.SourceFile; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import org.sonar.cxx.utils.CxxReportIssue; +import org.sonar.cxx.visitors.MultiLocatitionSquidCheck; + +/** + * Raises a SonarQube issue for a detached (tree-free) cross-file detection by queuing a {@link + * CxxReportIssue} onto the originating file's own {@link SourceFile} data bag, via {@link + * MultiLocatitionSquidCheck#addMultiLocationViolation}. {@code CxxSquidSensor} drains every file's + * data bag into real SonarQube issues, via {@code scanner.getIndex().search(...)}, only after the + * whole batch has finished — so a {@link SourceFile} captured at record time (while that file is + * still being visited) remains a valid target for {@link #report} regardless of which file is being + * visited when the cross-file hook actually fires. + * + *

The queued {@link CxxReportIssue} carries the raising check's {@code Class}, so {@code + * CxxSquidSensor} resolves the issue to that check's own registered rule repository (not + * sonar-cxx's built-in one) — see {@code CxxChecks#ruleKeyForClass}. + */ +public final class CxxDetachedIssueReporter + implements IDetachedIssueReporter, AstNode> { + + @Nonnull private final SourceFile sourceFile; + @Nonnull private final String filePath; + + private CxxDetachedIssueReporter(@Nonnull SourceFile sourceFile, @Nonnull String filePath) { + this.sourceFile = sourceFile; + this.filePath = filePath; + } + + @Nonnull + static CxxDetachedIssueReporter create( + @Nonnull SourceFile sourceFile, @Nonnull String filePath) { + return new CxxDetachedIssueReporter(sourceFile, filePath); + } + + @Override + public void report( + @Nonnull SquidCheck rule, @Nonnull AstNode location, @Nonnull String message) { + final String ruleId = ruleKeyOf(rule); + final String line = String.valueOf(location.getTokenLine()); + final CxxReportIssue issue = + new CxxReportIssue(ruleId, rule.getClass(), filePath, line, null, message); + MultiLocatitionSquidCheck.addMultiLocationViolation(sourceFile, issue); + } + + /** + * The rule id stamped onto the {@link CxxReportIssue}. {@code CxxSquidSensor} resolves the real + * {@code RuleKey} via the check's {@code Class} ({@code CxxChecks#ruleKeyForClass}), so this + * value is a diagnostic label only (used in its fallback path / {@code toString}), not the + * source of truth for which repository the issue belongs to. + */ + @Nonnull + private static String ruleKeyOf(@Nonnull SquidCheck rule) { + final org.sonar.check.Rule ruleAnnotation = + rule.getClass().getAnnotation(org.sonar.check.Rule.class); + return ruleAnnotation != null ? ruleAnnotation.key() : rule.getClass().getSimpleName(); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetectionEngine.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetectionEngine.java new file mode 100644 index 000000000..8a30f20d2 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxDetectionEngine.java @@ -0,0 +1,850 @@ +/* + * 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.engine.language.cxx; + +import com.ibm.engine.callstack.ArgSnapshot; +import com.ibm.engine.callstack.DetachedCall; +import com.ibm.engine.callstack.DetachedScanContext; +import com.ibm.engine.callstack.RetainedCall; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.DetectionStoreWithHook; +import com.ibm.engine.detection.Handler; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.ibm.engine.detection.MethodDetection; +import com.ibm.engine.detection.MethodMatcher; +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.detection.TraceSymbol; +import com.ibm.engine.detection.ValueDetection; +import com.ibm.engine.hooks.MethodInvocationHookWithParameterResolvement; +import com.ibm.engine.hooks.MethodInvocationHookWithReturnResolvement; +import com.ibm.engine.language.ILanguageTranslation; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.model.factory.IValueFactory; +import com.ibm.engine.rule.DetectableParameter; +import com.ibm.engine.rule.DetectionRule; +import com.ibm.engine.rule.MethodDetectionRule; +import com.ibm.engine.rule.Parameter; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.SourceCode; +import org.sonar.cxx.squidbridge.api.SourceFile; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +public class CxxDetectionEngine implements IDetectionEngine { + @Nonnull + private final DetectionStore< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + detectionStore; + + @Nonnull + private final Handler, AstNode, Symbol, SquidAstVisitorContext> + handler; + + public CxxDetectionEngine( + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore, + @Nonnull + Handler< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + handler) { + this.detectionStore = detectionStore; + this.handler = handler; + } + + @Override + public void run(@Nonnull AstNode tree) { + run(TraceSymbol.createStart(), tree); + } + + @Override + public void run(@Nonnull TraceSymbol traceSymbol, @Nonnull AstNode tree) { + if (CxxAstNodeHelper.isFunctionCall(tree) || CxxConstructorCalls.isConstructorCall(tree)) { + recordCall(tree); + if (detectionStore + .getDetectionRule() + .match(tree, handler.getLanguageSupport().translation())) { + this.analyseExpression(traceSymbol, tree); + } + } else if (tree.is(CxxGrammarImpl.enumSpecifier)) { + handler.addCallToCallStack(tree, detectionStore.getScanContext()); + } + } + + /** + * Records a call of a function or a constructor for later cross-file hook matching, detaching + * it from the AST when possible (its arguments are pre-resolved here while the file is live). + * Falls back to retaining the tree when the call is not detachable or an argument cannot be + * faithfully snapshotted. + * + *

{@code run} is invoked once per detection rule for the same call node, so this same {@code + * invocation} reaches here once per rule too; only the first such call actually needs + * recording, so every later one exits before doing the argument-resolution and detached-call + * construction work below. + */ + private void recordCall(@Nonnull AstNode invocation) { + if (handler.isCallAlreadyRecorded(invocation)) { + return; + } + final IScanContext, AstNode> scanContext = detectionStore.getScanContext(); + DetachedCall, AstNode> detachedForm = null; + if (handler.getLanguageSupport().isDetachableCall(invocation)) { + detachedForm = buildDetachedCall(invocation, scanContext); + } + handler.addRecordedCall(new RetainedCall<>(invocation, scanContext, detachedForm)); + } + + @Nullable private DetachedCall, AstNode> buildDetachedCall( + @Nonnull AstNode invocation, + @Nonnull IScanContext, AstNode> scanContext) { + // A detached call is only ever matched in hook context (MethodMatcher.matchKeys), so its + // type keys must be snapshotted with hook-context semantics (exact type matching) to + // reproduce the live retained-call path, which matches via the hook's isHookContext=true + // MatchContext. Using record-context (isHookContext=false) here would make cross-file + // matching subtype-permissive and diverge from the same-file result. + final MatchContext matchContext = MatchContext.createForHookContext(); + final ILanguageTranslation translation = + handler.getLanguageSupport().translation(); + final Optional invokedType = + translation.getInvokedObjectTypeString(matchContext, invocation); + final Optional name = translation.getMethodName(matchContext, invocation); + if (invokedType.isEmpty() || name.isEmpty()) { + return null; + } + final List parameterTypes = + translation.getMethodParameterTypes(matchContext, invocation); + + final List> arguments = new ArrayList<>(); + final List actualArguments = argumentsOf(invocation); + for (int i = 0; i < actualArguments.size(); i++) { + final List> resolved = + resolveValuesInInnerScope(Object.class, actualArguments.get(i), null); + final List> snapshots = new ArrayList<>(); + for (ResolvedValue resolvedValue : resolved) { + final CxxDetachedAstNode location = + captureLocation(resolvedValue.tree(), resolvedValue.value().toString()); + if (location == null) { + return null; // cannot faithfully snapshot -> fall back to retaining the tree + } + snapshots.add( + new ArgSnapshot.ResolvedSnapshotValue<>(resolvedValue.value(), location)); + } + arguments.add(new ArgSnapshot<>(i, snapshots)); + } + + final SourceFile sourceFile = + scanContext instanceof CxxScanContext cxxScanContext + ? sourceFileOf(cxxScanContext) + : null; + final CxxDetachedIssueReporter issueReporter = + sourceFile != null + ? CxxDetachedIssueReporter.create(sourceFile, scanContext.getFilePath()) + : null; + final DetachedScanContext, AstNode> detachedScanContext = + new DetachedScanContext<>( + scanContext.getInputFile(), scanContext.getFilePath(), issueReporter); + return new DetachedCall<>( + invokedType.get(), + name.get(), + translation.getMethodNames(matchContext, invocation), + parameterTypes, + arguments, + detachedScanContext); + } + + /** + * The current file's {@code SourceFile}, captured while the file is live so a detached call's + * issue reporter can append to it later regardless of which file is being visited when a + * cross-file hook fires. {@code SourceFile} objects are kept alive for the whole batch by + * sonar-cxx's own index — see {@link CxxDetachedIssueReporter}. + */ + @Nullable private SourceFile sourceFileOf(@Nonnull CxxScanContext cxxScanContext) { + final SourceCode current = cxxScanContext.cxxVisitorContext().peekSourceCode(); + if (current instanceof SourceFile sourceFile) { + return sourceFile; + } + return current == null ? null : current.getParent(SourceFile.class); + } + + /** + * Captures a value's location as an AST-free {@link CxxDetachedAstNode}, mirroring {@code + * CxxTranslator.getDetectionContextFrom} so a detached detection's CBOM occurrence is identical + * to a non-detached one. + */ + @Nullable private CxxDetachedAstNode captureLocation(@Nonnull AstNode location, @Nonnull String text) { + final com.sonar.cxx.sslr.api.Token token = location.getToken(); + if (token == null) { + return null; + } + final List keywords; + if (CxxAstNodeHelper.isFunctionCall(location)) { + final String functionName = CxxAstNodeHelper.getFunctionCallName(location); + keywords = functionName != null ? List.of(functionName) : List.of(); + } else { + keywords = List.of(); + } + return new CxxDetachedAstNode(token.getLine(), token.getColumn(), text, keywords); + } + + @Nullable @Override + public AstNode extractArgumentFromMethodCaller( + @Nonnull AstNode methodDefinition, + @Nonnull AstNode methodInvocation, + @Nonnull AstNode methodParameterIdentifier) { + if (!methodDefinition.is(CxxGrammarImpl.functionDefinition)) { + return null; + } + + List defParams = + CxxAstNodeHelper.getFunctionDefinitionParameters(methodDefinition); + if (!CxxAstNodeHelper.isFunctionCall(methodInvocation) + && !CxxConstructorCalls.isConstructorCall(methodInvocation)) { + return null; + } + final List callArgs = argumentsOf(methodInvocation); + + if (defParams.size() != callArgs.size()) { + return null; + } + + final MatchContext matchContext = + MatchContext.build(false, detectionStore.getDetectionRule()); + Optional targetVarIdOptional = + handler.getLanguageSupport() + .translation() + .resolveIdentifierAsString(matchContext, methodParameterIdentifier); + + if (targetVarIdOptional.isEmpty()) { + return null; + } + final String targetVarId = targetVarIdOptional.get(); + + for (int i = 0; i < defParams.size(); i++) { + AstNode paramDecl = defParams.get(i); + String paramName = CxxAstNodeHelper.getIdentifierName(paramDecl); + if (paramName != null && paramName.equals(targetVarId)) { + return callArgs.get(i); + } + } + return null; + } + + @Nonnull + @Override + public List> resolveValuesInInnerScope( + @Nonnull Class clazz, + @Nonnull AstNode expression, + @Nullable IValueFactory valueFactory) { + return CxxSemantic.resolveValues( + clazz, expression, new LinkedList<>(), valueFactory, false, this); + } + + @Override + public void resolveValuesInOuterScope( + @Nonnull AstNode expression, @Nonnull Parameter parameter) { + // a value returned by a function of the analyzed code, e.g. digest_name() in + // EVP_get_digestbyname(digest_name()), is resolved from the function's return statements + for (AstNode call : callsGivingTheValueOf(expression)) { + final AstNode definition = definitionOf(call); + if (definition != null) { + createAMethodHook(definition, null, parameter); + } + } + + Optional optionalMethodNode = + handler.getLanguageSupport().getEnclosingMethod(expression); + if (optionalMethodNode.isEmpty()) { + return; + } + AstNode methodNode = optionalMethodNode.get(); + + List> resolvedValues = + CxxSemantic.resolveValues( + Object.class, expression, new LinkedList<>(), null, true, this); + + if (resolvedValues.size() != 1) { + return; + } + final AstNode resolvedParameter = resolvedValues.get(0).tree(); + + createAMethodHook(methodNode, resolvedParameter, parameter); + } + + /** + * The calls whose returned value an expression has: the expression itself when it is a call of + * a function, or the calls a variable is initialized with or assigned. + */ + @Nonnull + private static List callsGivingTheValueOf(@Nonnull AstNode expression) { + AstNode value = expression; + while (value.getNumberOfChildren() == 1 && !CxxAstNodeHelper.isFunctionCall(value)) { + value = value.getFirstChild(); + } + if (CxxAstNodeHelper.isFunctionCall(value)) { + return List.of(value); + } + if (!(AstNodeSymbolExtension.getSymbol(value) instanceof Symbol.VariableSymbol variable) + || variable.isParameter() + || variable.isField()) { + return List.of(); + } + final List calls = new ArrayList<>(); + final AstNode initializer = variable.initializer(); + if (initializer != null && CxxAstNodeHelper.isFunctionCall(initializer.getLastChild())) { + calls.add(initializer.getLastChild()); + } + for (Symbol.Usage usage : variable.usages()) { + if (usage.kind() != Symbol.Usage.UsageKind.WRITE) { + continue; + } + final AstNode assignment = + usage.node().getFirstAncestor(CxxGrammarImpl.assignmentExpression); + if (assignment != null && CxxAstNodeHelper.isFunctionCall(assignment.getLastChild())) { + calls.add(assignment.getLastChild()); + } + } + return calls; + } + + /** + * The definition, in the translation unit of the call, of the function a call calls, or null + * when the function is not defined there. + */ + @Nullable private AstNode definitionOf(@Nonnull AstNode call) { + AstNode root = call; + while (root.getParent() != null) { + root = root.getParent(); + } + final MatchContext matchContext = MatchContext.createForHookContext(); + for (AstNode definition : root.getDescendants(CxxGrammarImpl.functionDefinition)) { + final MethodMatcher matcher = + handler.getLanguageSupport().createMethodMatcherBasedOn(definition); + if (matcher != null + && matcher.match( + call, handler.getLanguageSupport().translation(), matchContext)) { + return definition; + } + } + return null; + } + + private void createAMethodHook( + @Nonnull AstNode methodNode, + @Nullable AstNode methodParameter, + @Nonnull Parameter detectableParameter) { + final MatchContext matchContext = + MatchContext.build(true, detectionStore.getDetectionRule()); + + if (methodParameter == null) { + MethodInvocationHookWithReturnResolvement< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + methodInvocationHookWithReturnResolvement = + new MethodInvocationHookWithReturnResolvement<>( + methodNode, detectableParameter, matchContext); + if (this.detectionStore + instanceof + final DetectionStoreWithHook< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStoreWithHook) { + detectionStoreWithHook.onSuccessiveHook(methodInvocationHookWithReturnResolvement); + } else { + handler.addHookToHookRepository(methodInvocationHookWithReturnResolvement); + detectionStore.onNewHookRegistration(methodInvocationHookWithReturnResolvement); + } + return; + } + + MethodInvocationHookWithParameterResolvement< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + methodInvocationHookWithParameterResolvement = + new MethodInvocationHookWithParameterResolvement<>( + methodNode, methodParameter, detectableParameter, matchContext); + if (this.detectionStore + instanceof + final DetectionStoreWithHook< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStoreWithHook) { + detectionStoreWithHook.onSuccessiveHook(methodInvocationHookWithParameterResolvement); + } else { + handler.addHookToHookRepository(methodInvocationHookWithParameterResolvement); + detectionStore.onNewHookRegistration(methodInvocationHookWithParameterResolvement); + } + } + + @Override + public void resolveMethodReturnValues( + @Nonnull Class clazz, + @Nonnull AstNode methodDefinition, + @Nonnull Parameter parameter) { + if (!methodDefinition.is(CxxGrammarImpl.functionDefinition)) { + return; + } + AstNode body = CxxAstNodeHelper.getFunctionDefinitionBody(methodDefinition); + if (body == null) { + return; + } + for (AstNode node : body.getDescendants(CxxGrammarImpl.jumpStatement)) { + if (!CxxAstNodeHelper.isReturnStatement(node)) { + continue; + } + AstNode returnExpr = CxxAstNodeHelper.getReturnExpression(node); + if (returnExpr == null) { + continue; + } + if (parameter.is(DetectableParameter.class)) { + DetectableParameter detectable = (DetectableParameter) parameter; + List> resolved = + resolveValuesInInnerScope(clazz, returnExpr, detectable.getiValueFactory()); + if (!resolved.isEmpty()) { + resolved.stream() + .map(rv -> new ValueDetection<>(rv, detectable, returnExpr, returnExpr)) + .forEach(detectionStore::onReceivingNewDetection); + continue; + } + } + resolveValuesInOuterScope(returnExpr, parameter); + } + } + + @Nullable @Override + public ResolvedValue resolveEnumValue( + @Nonnull Class clazz, + @Nonnull AstNode enumClassDefinition, + @Nonnull LinkedList selections) { + // CxxSemantic.resolveIdentifier resolves enum constants eagerly during traversal, unlike + // Java's lazy EnumHook, so no enum reference ever reaches this fallback unresolved. + return null; + } + + @Nonnull + @Override + public Optional> getAssignedSymbol(@Nonnull AstNode expression) { + Symbol symbol = CxxAstNodeHelper.getAssignedSymbol(expression); + if (symbol == null && CxxAstNodeHelper.isFunctionCall(expression)) { + symbol = outputArgumentSymbol(expression); + } + if (symbol != null) { + return Optional.of(TraceSymbol.createFrom(symbol)); + } + if (CxxAstNodeHelper.isFunctionCall(expression) + && !CxxAstNodeHelper.isCallOnCallResult(expression) + && !CxxAstNodeHelper.isMemberAccess(expression)) { + // the result of a call that is not assigned is used by the call it is an argument + // of, if any; a returned or discarded result is used by no call of this function + return Optional.of( + isCallArgument(expression) + ? TraceSymbol.createWithStateNoSymbol(expression) + : TraceSymbol.createWithStateDifferent()); + } + return Optional.empty(); + } + + /** + * A C function that returns no object operates on the object it is given first, e.g. {@code + * RSA_generate_key_ex(rsa, bits, e, cb)} generates the key {@code rsa} and {@code + * EC_KEY_generate_key(key)} the key {@code key}: the rules depending on such a call follow that + * object, as they follow the variable a call returning an object is assigned to. + */ + @Nonnull + @Override + public Optional> getObjectSymbol(@Nonnull AstNode expression) { + final Optional> assigned = getAssignedSymbol(expression); + if (assigned.isPresent() + && assigned.get().is(TraceSymbol.State.DIFFERENT) + && CxxAstNodeHelper.isFunctionCall(expression) + && !CxxAstNodeHelper.isMemberAccess(expression)) { + final List arguments = CxxAstNodeHelper.getFunctionCallArguments(expression); + final Symbol object = arguments.isEmpty() ? null : handleSymbol(arguments.get(0)); + if (object != null && !object.isUnknown()) { + return Optional.of(TraceSymbol.createFrom(object)); + } + } + return assigned; + } + + /** Whether a call is written as an argument of another call. */ + private static boolean isCallArgument(@Nonnull AstNode call) { + for (AstNode node = call.getParent(); node != null; node = node.getParent()) { + if (node.is(CxxGrammarImpl.expressionList)) { + return CxxAstNodeHelper.isFunctionCall(node.getParent()); + } + if (node.is(CxxGrammarImpl.statement) + || node.is(CxxGrammarImpl.expressionStatement) + || node.is(CxxGrammarImpl.jumpStatement) + || node.is(CxxGrammarImpl.initDeclarator)) { + return false; + } + } + return false; + } + + /** Whether one of the arguments of a call is the traced origin call. */ + private boolean takesOrigin( + @Nonnull AstNode methodInvocation, @Nonnull TraceSymbol traceSymbol) { + final Object origin = traceSymbol.getOrigin().orElse(null); + if (!(origin instanceof AstNode originCall) + || !CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + return false; + } + for (AstNode argument : CxxAstNodeHelper.getFunctionCallArguments(methodInvocation)) { + if (argument.getToken() == originCall.getToken() + && argument.getLastToken() == originCall.getLastToken()) { + return true; + } + } + return false; + } + + /** + * A C function returns an object through an output argument, the address of a variable, e.g. + * {@code EVP_PKEY_keygen(ctx, &pkey)}. The variable is the result of a call that is not + * assigned and has exactly one such argument. + */ + @Nullable private Symbol outputArgumentSymbol(@Nonnull AstNode functionCall) { + Symbol output = null; + for (AstNode argument : CxxAstNodeHelper.getFunctionCallArguments(functionCall)) { + final Symbol addressed = addressedVariable(argument); + if (addressed != null) { + if (output != null) { + return null; + } + output = addressed; + } + } + return output; + } + + /** The variable {@code v} of an argument {@code &v}, or null for any other argument. */ + @Nullable private static Symbol addressedVariable(@Nonnull AstNode argument) { + if (!"&".equals(argument.getTokenValue()) || argument.getNumberOfChildren() != 2) { + return null; + } + return handleSymbol(argument.getLastChild()); + } + + @Nonnull + @Override + public Optional> getMethodInvocationParameterSymbol( + @Nonnull AstNode methodInvocation, @Nonnull Parameter parameter) { + if (CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + List arguments = CxxAstNodeHelper.getFunctionCallArguments(methodInvocation); + return getTraceSymbol(parameter, arguments); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional> getNewClassParameterSymbol( + @Nonnull AstNode newClass, @Nonnull Parameter parameter) { + if (CxxConstructorCalls.isConstructorCall(newClass)) { + return getTraceSymbol(parameter, CxxConstructorCalls.getArguments(newClass)); + } + return Optional.empty(); + } + + @Nonnull + private Optional> getTraceSymbol( + @Nonnull Parameter parameter, @Nonnull List arguments) { + if (parameter.getIndex() >= arguments.size()) { + return Optional.of(TraceSymbol.createWithStateDifferent()); + } + AstNode arg = arguments.get(parameter.getIndex()); + Symbol symbol = AstNodeSymbolExtension.getSymbol(arg); + if (symbol != null && !symbol.isUnknown()) { + return Optional.of(TraceSymbol.createFrom(symbol)); + } + // `&v` passes the object v, e.g. the key of AES_cbc_encrypt(in, out, len, &key, iv, enc), + // so the argument is followed to v, as the call that sets up v assigns it + final Symbol addressed = addressedVariable(arg); + if (addressed != null && !addressed.isUnknown()) { + return Optional.of(TraceSymbol.createFrom(addressed)); + } + // NO_SYMBOL means "the argument is itself an inline constructing call with no variable to + // trace" (e.g. foo(new Test())), which triggers a re-scan of the enclosing method for a + // matching unassigned call. A bare literal/macro argument (e.g. foo(NULL)) is neither a + // symbol nor a call, so it must map to DIFFERENT instead - otherwise the re-scan would + // match any unassigned call in the method, not just ones related to this argument. + if (CxxAstNodeHelper.isFunctionCall(arg) || CxxConstructorCalls.isConstructorCall(arg)) { + return Optional.of(TraceSymbol.createWithStateNoSymbol()); + } + return Optional.of(TraceSymbol.createWithStateDifferent()); + } + + @Override + public boolean isInvocationOnVariable( + @Nonnull AstNode methodInvocation, @Nonnull TraceSymbol variableSymbol) { + if (!CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + return false; + } + if (!variableSymbol.is(TraceSymbol.State.SYMBOL)) { + return false; + } + Symbol variable = variableSymbol.getSymbol(); + if (variable == null) { + return false; + } + return CxxAstNodeHelper.isInvocationOnVariable(methodInvocation, variable, true) + || isInvocationOnHandle(methodInvocation, variable); + } + + /** + * C APIs operate on an object through a handle passed as an argument of a free function, e.g. + * {@code EVP_KDF_CTX_set_params(kctx, params)} operates on {@code kctx} and {@code + * EVP_DigestSignInit(mdctx, NULL, md, NULL, pkey)} signs with {@code pkey}. Such a call counts + * as an invocation on the variable when one of its arguments is exactly that variable, or its + * address: {@code EVP_SealInit(ctx, type, &ek, &ekl, iv, &pkey, 1)} encrypts with {@code pkey}, + * passed as an array of one key. The calls are those a depending rule names, which operate on + * the object they are given. + */ + private boolean isInvocationOnHandle( + @Nonnull AstNode methodInvocation, @Nonnull Symbol variable) { + if (CxxAstNodeHelper.isMemberAccess(methodInvocation)) { + return false; + } + for (AstNode argument : CxxAstNodeHelper.getFunctionCallArguments(methodInvocation)) { + if (handleSymbol(argument) == variable || addressedVariable(argument) == variable) { + return true; + } + } + return false; + } + + /** The variable an argument is exactly, or null for any other expression. */ + @Nullable private static Symbol handleSymbol(@Nonnull AstNode argument) { + if (argument.getToken() != argument.getLastToken()) { + // an expression such as `&ctx` or `ctx->field` is not the handle itself + return null; + } + Symbol symbol = AstNodeSymbolExtension.getSymbol(argument); + if (symbol == null) { + AstNode identifier = argument.getFirstDescendant(GenericTokenType.IDENTIFIER); + symbol = identifier != null ? AstNodeSymbolExtension.getSymbol(identifier) : null; + } + return symbol; + } + + @Override + public boolean isInitForVariable( + @Nonnull AstNode newClass, @Nonnull TraceSymbol variableSymbol) { + if (!variableSymbol.is(TraceSymbol.State.SYMBOL)) { + return false; + } + Symbol variable = variableSymbol.getSymbol(); + Optional> symbolOptional = getAssignedSymbol(newClass); + if (symbolOptional.isEmpty()) { + return false; + } + TraceSymbol traceSymbol = symbolOptional.get(); + Symbol symbol = traceSymbol.getSymbol(); + if (symbol == null || variable == null) { + return false; + } + return symbol.name().equals(variable.name()); + } + + private void analyseExpression( + @Nonnull TraceSymbol traceSymbol, @Nonnull AstNode expressionNode) { + if (detectionStore.getDetectionRule().is(MethodDetectionRule.class)) { + // like a rule's own action below, the call is a finding of a constrained re-scan only + // when it is connected to the traced argument + if (traceSymbol.is(TraceSymbol.State.SYMBOL_IGNORED) + || (traceSymbol.is(TraceSymbol.State.NO_SYMBOL) + && traceSymbol.getOrigin().isEmpty()) + || takesOrigin(expressionNode, traceSymbol) + || isInvocationOnVariable(expressionNode, traceSymbol) + || isInitForVariable(expressionNode, traceSymbol)) { + MethodDetection methodDetection = + new MethodDetection<>(expressionNode, null); + detectionStore.onReceivingNewDetection(methodDetection); + } + return; + } + + DetectionRule detectionRule = + (DetectionRule) detectionStore.getDetectionRule(); + + if (!CxxAstNodeHelper.isFunctionCall(expressionNode) + && !CxxConstructorCalls.isConstructorCall(expressionNode)) { + return; + } + final List arguments = argumentsOf(expressionNode); + + // A chained/builder-pattern call (foo().goo()) has no variable to trace back to - its + // qualifier is itself a call - so it must not be rejected by the NO_SYMBOL branch in + // checkCurrentIndexState the way an unrelated assigned call would be. + boolean isBuilderPattern = CxxAstNodeHelper.isCallOnCallResult(expressionNode); + + boolean isInvocation = + isInvocationOnVariable(expressionNode, traceSymbol) + || isInitForVariable(expressionNode, traceSymbol) + || takesOrigin(expressionNode, traceSymbol); + // an untraceable argument re-scans the method for any call; a known origin call is only + // connected to the call it is an argument of + boolean isUnconnectedNoSymbol = + traceSymbol.is(TraceSymbol.State.NO_SYMBOL) && traceSymbol.getOrigin().isEmpty(); + + // Emitting this candidate's own finding is valid on an unconstrained top-level scan + // (traceSymbol=SYMBOL_IGNORED) or, on a constrained re-scan from a depending-parameter + // trace (see onDetectedDependingParameter's ENCLOSED_METHOD case), only when this + // candidate is actually connected to the traced argument. Without this guard, an + // untraceable argument (e.g. a bare NULL) re-scans the whole enclosing method and + // spuriously matches every unrelated call the depending rules cover. + boolean isConstrainedRescan = !traceSymbol.is(TraceSymbol.State.SYMBOL_IGNORED); + if ((detectionRule.actionFactory() != null || isPassThrough(detectionRule)) + && (!isConstrainedRescan || isInvocation || isUnconnectedNoSymbol)) { + MethodDetection methodDetection = new MethodDetection<>(expressionNode, null); + detectionStore.onReceivingNewDetection(methodDetection); + } + + int index = 0; + for (Parameter parameter : detectionRule.parameters()) { + if (!checkCurrentIndexState( + index, + arguments, + isInvocation, + isBuilderPattern, + traceSymbol, + expressionNode)) { + index++; + continue; + } + + AstNode expression = arguments.get(index); + + if (parameter.is(DetectableParameter.class)) { + DetectableParameter detectableParameter = + (DetectableParameter) parameter; + List> resolvedValues = + resolveValuesInInnerScope( + Object.class, expression, detectableParameter.getiValueFactory()); + if (resolvedValues.isEmpty()) { + resolveValuesInOuterScope(expression, detectableParameter); + } else { + resolvedValues.stream() + .map( + resolvedValue -> + new ValueDetection<>( + resolvedValue, + detectableParameter, + expressionNode, + expressionNode)) + .forEach(detectionStore::onReceivingNewDetection); + } + } else if (!parameter.getDetectionRules().isEmpty()) { + if (CxxAstNodeHelper.isFunctionCall(expression) + || CxxConstructorCalls.isConstructorCall(expression)) { + // the argument is itself a call/constructor expression - analyse it directly, + // no variable to trace back to an assignment. + detectionStore.onDetectedDependingParameter( + parameter, expression, DetectionStore.Scope.EXPRESSION); + } else { + // the argument is a variable reference - walk the enclosing method body to + // find the call that constructed it. getParameterTraceSymbol needs the + // enclosing call (expressionNode), not the argument itself, to resolve the + // parameter's symbol via its own argument list. + detectionStore.onDetectedDependingParameter( + parameter, expressionNode, DetectionStore.Scope.ENCLOSED_METHOD); + } + } + + index++; + } + } + + /** + * A rule without an action and without a detectable parameter only links its depending rules to + * the object it returns, e.g. {@code kctx = EVP_KDF_CTX_new(kdf)} links the calls made on + * {@code kctx} to the fetched {@code kdf}. Its method detection carries no value of its own. + */ + private static boolean isPassThrough(@Nonnull DetectionRule detectionRule) { + return !detectionRule.nextDetectionRules().isEmpty() + && detectionRule.parameters().stream() + .noneMatch(parameter -> parameter.is(DetectableParameter.class)); + } + + private boolean checkCurrentIndexState( + int index, + List arguments, + boolean isInvocation, + boolean isBuilderPattern, + @Nonnull TraceSymbol traceSymbol, + @Nonnull AstNode expressionNode) { + if (arguments.size() <= index) { + return false; + } + + if (traceSymbol.is(TraceSymbol.State.DIFFERENT)) { + return false; + } + if (traceSymbol.is(TraceSymbol.State.SYMBOL)) { + return isInvocation; + } + if (traceSymbol.is(TraceSymbol.State.NO_SYMBOL)) { + if (traceSymbol.getOrigin().isPresent()) { + // the result of a known call, connected to the call it is an argument of + return isInvocation; + } + // an untraceable argument: any call that is not assigned to another variable + Optional assignedSymbol = + getAssignedSymbol(expressionNode).map(TraceSymbol::getSymbol); + return assignedSymbol.isEmpty() || isBuilderPattern; + } + return true; + } + + /** The arguments of a call of a function or a constructor. */ + @Nonnull + private static List argumentsOf(@Nonnull AstNode call) { + return CxxConstructorCalls.isConstructorCall(call) + ? CxxConstructorCalls.getArguments(call) + : CxxAstNodeHelper.getFunctionCallArguments(call); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageSupport.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageSupport.java new file mode 100644 index 000000000..169c73313 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageSupport.java @@ -0,0 +1,219 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.cxx; + +import com.ibm.engine.callstack.CallContextStats; +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.EnumMatcher; +import com.ibm.engine.detection.Handler; +import com.ibm.engine.detection.IBaseMethodVisitorFactory; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.MatchContext; +import com.ibm.engine.detection.MethodMatcher; +import com.ibm.engine.executive.DetectionExecutive; +import com.ibm.engine.language.ILanguageSupport; +import com.ibm.engine.language.ILanguageTranslation; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.rule.IDetectionRule; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +public final class CxxLanguageSupport + implements ILanguageSupport< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> { + private static final Logger LOGGER = LoggerFactory.getLogger(CxxLanguageSupport.class); + + @Nonnull + private final Handler, AstNode, Symbol, SquidAstVisitorContext> + handler; + + @Nonnull private final CxxLanguageTranslation translation; + + public CxxLanguageSupport() { + this.handler = new Handler<>(this); + this.translation = new CxxLanguageTranslation(); + } + + @Nonnull + @Override + public ILanguageTranslation translation() { + return translation; + } + + @Nonnull + @Override + public DetectionExecutive< + SquidCheck, AstNode, Symbol, SquidAstVisitorContext> + createDetectionExecutive( + @Nonnull AstNode tree, + @Nonnull IDetectionRule detectionRule, + @Nonnull IScanContext, AstNode> scanContext) { + return new DetectionExecutive<>(tree, detectionRule, scanContext, this.handler); + } + + @Nonnull + @Override + public IDetectionEngine createDetectionEngineInstance( + @Nonnull + DetectionStore< + SquidCheck, + AstNode, + Symbol, + SquidAstVisitorContext> + detectionStore) { + return new CxxDetectionEngine(detectionStore, this.handler); + } + + @Nonnull + @Override + public IBaseMethodVisitorFactory getBaseMethodVisitorFactory() { + return CxxBaseMethodVisitor::new; + } + + @Nonnull + @Override + public Optional getEnclosingMethod(@Nonnull AstNode expression) { + AstNode enclosingFunction = CxxAstNodeHelper.getEnclosingFunction(expression); + return Optional.ofNullable(enclosingFunction); + } + + @Nullable @Override + public MethodMatcher createMethodMatcherBasedOn(@Nonnull AstNode methodDefinition) { + if (!methodDefinition.is(CxxGrammarImpl.functionDefinition)) { + return null; + } + + try { + String functionName = CxxAstNodeHelper.getFunctionDefinitionName(methodDefinition); + if (functionName == null) { + return null; + } + + // A member function is defined in its class, or outside it with a name qualified by + // the class, e.g. Hasher::reset; the class is named with the namespaces and classes + // it is declared in. A constructor is called as of its class, see + // CxxLanguageTranslation#getMethodName. + final String className = CxxScopes.classOfFunction(methodDefinition); + String invocationObjectName; + if (className != null) { + invocationObjectName = className; + functionName = unqualified(functionName); + if (functionName.equals(unqualified(className))) { + functionName = ""; + } + } else { + // Standalone functions use the same synthetic scope name as + // CxxLanguageTranslation#getInvokedObjectTypeString for a call site, and are + // named with the namespaces they are declared in, as a call names them (see + // CxxScopes#lookupNames) + invocationObjectName = CxxLanguageTranslation.GLOBAL_SCOPE; + final List namespaces = CxxScopes.enclosingNamespaces(methodDefinition); + if (!namespaces.isEmpty()) { + functionName = String.join("::", namespaces) + "::" + functionName; + } + } + + // Parameter types are wildcards: CxxLanguageTranslation#getMethodParameterTypes + // derives a call site's argument types from literal/identifier content, not from + // declared C++ types, so the two are not comparable. Only parameter count is matched. + List parameters = + CxxAstNodeHelper.getFunctionDefinitionParameters(methodDefinition); + LinkedList parameterTypeList = new LinkedList<>(); + for (int i = 0; i < parameters.size(); i++) { + parameterTypeList.add(MethodMatcher.ANY); + } + + return new MethodMatcher<>(invocationObjectName, functionName, parameterTypeList); + } catch (Exception e) { + LOGGER.error(e.getLocalizedMessage(), e); + return null; + } + } + + /** The last component of a qualified name, {@code reset} of {@code Hasher::reset}. */ + @Nonnull + private static String unqualified(@Nonnull String name) { + final int separator = name.lastIndexOf("::"); + return separator < 0 ? name : name.substring(separator + 2); + } + + @Nullable @Override + public EnumMatcher createSimpleEnumMatcherFor( + @Nonnull AstNode enumIdentifier, @Nonnull MatchContext matchContext) { + Optional enumIdentifierName = + translation().getEnumIdentifierName(matchContext, enumIdentifier); + return enumIdentifierName.>map(EnumMatcher::new).orElse(null); + } + + @Override + public void notifyLeaveFile(@Nonnull InputFile inputFile) { + this.handler.detachCallsForFile(inputFile); + } + + @Nonnull + @Override + public CallContextStats callContextStats() { + return this.handler.callContextStats(); + } + + @Override + public boolean isDetachableCall(@Nonnull AstNode tree) { + // the values of the arguments of a call of a function or a constructor hold no syntax + // tree, a braced-init-list included (see CxxSemantic#resolveValues) + return CxxAstNodeHelper.isFunctionCall(tree) || CxxConstructorCalls.isConstructorCall(tree); + } + + @Override + public int parameterIndexOf( + @Nonnull AstNode methodDefinition, @Nonnull AstNode methodParameter) { + if (!methodDefinition.is(CxxGrammarImpl.functionDefinition)) { + return -1; + } + final Optional targetName = + translation() + .resolveIdentifierAsString( + MatchContext.createForHookContext(), methodParameter); + if (targetName.isEmpty()) { + return -1; + } + final List parameters = + CxxAstNodeHelper.getFunctionDefinitionParameters(methodDefinition); + for (int i = 0; i < parameters.size(); i++) { + final String paramName = CxxAstNodeHelper.getIdentifierName(parameters.get(i)); + if (targetName.get().equals(paramName)) { + return i; + } + } + return -1; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageSupporter.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageSupporter.java new file mode 100644 index 000000000..f93cfaa50 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageSupporter.java @@ -0,0 +1,52 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.cxx; + +import com.ibm.engine.language.ILanguageSupport; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import javax.annotation.Nonnull; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +/** + * Holder for the C/C++ {@link ILanguageSupport} factory method. The equivalent factory methods for + * Java, Python, Go and C# are in {@link com.ibm.engine.language.LanguageSupporter}. + * + *

The sonar-cxx API is a {@code provided} dependency of the engine, so it is not on the + * classpath of the other language modules. Keeping this factory out of {@code LanguageSupporter} + * keeps the sonar-cxx types out of a class those modules load. + */ +public final class CxxLanguageSupporter { + + private CxxLanguageSupporter() { + // nothing + } + + @Nonnull + public static ILanguageSupport< + SquidCheck, + AstNode, + org.sonar.cxx.squidbridge.api.Symbol, + SquidAstVisitorContext> + cxxLanguageSupporter() { + return new CxxLanguageSupport(); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageTranslation.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageTranslation.java new file mode 100644 index 000000000..52d4db2bf --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxLanguageTranslation.java @@ -0,0 +1,486 @@ +/* + * 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.engine.language.cxx; + +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.ibm.engine.language.ILanguageTranslation; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.parser.CxxTokenType; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.AstNodeTypeExtension; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.api.Type; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +public class CxxLanguageTranslation implements ILanguageTranslation { + @Nonnull + private static final Logger LOGGER = LoggerFactory.getLogger(CxxLanguageTranslation.class); + + /** + * Synthetic type name used for standalone C/C++ function calls that have no object qualifier. + * Detection rules for C-style functions (e.g., OpenSSL's EVP_DigestInit_ex) use {@code + * forObjectTypes(GLOBAL_SCOPE)}, so that a C++ member function of the same name (e.g. {@code + * hasher.MD5(...)}) does not match; {@code forObjectTypes("*")} matches any call. + */ + public static final String GLOBAL_SCOPE = ""; + + @Nonnull + @Override + public Optional getMethodName( + @Nonnull MatchContext matchContext, @Nonnull AstNode methodInvocation) { + if (CxxConstructorCalls.isConstructorCall(methodInvocation)) { + return Optional.of(""); + } else if (CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + String name = CxxAstNodeHelper.getFunctionCallName(methodInvocation); + if (name != null) { + // a static member function is named in its class, Hasher::make(...) calls make + final String qualifyingClass = qualifyingClassOf(methodInvocation, name); + return Optional.of( + qualifyingClass == null + ? name + : name.substring(qualifyingClass.length() + 2)); + } + } + return Optional.empty(); + } + + /** + * The functions a call through a function pointer calls (see {@link + * CxxSemantic#resolveCalledFunctions}), or the one function any other call names. + */ + @Nonnull + @Override + public List getMethodNames( + @Nonnull MatchContext matchContext, @Nonnull AstNode methodInvocation) { + final List calledFunctions = CxxSemantic.resolveCalledFunctions(methodInvocation); + if (!calledFunctions.isEmpty()) { + return calledFunctions; + } + final Optional name = getMethodName(matchContext, methodInvocation); + if (name.isEmpty() || !callsAFreeFunction(methodInvocation, name.get())) { + return name.stream().toList(); + } + // a free function is named with the namespaces it is declared in, which a call inside + // them may leave out + return CxxScopes.lookupNames(methodInvocation, name.get()); + } + + /** The key of a method name: its last component, {@code digest} of {@code util::digest}. */ + @Nonnull + @Override + public String getMethodNameKey(@Nonnull String methodName) { + return methodName.substring(methodName.lastIndexOf(':') + 1); + } + + /** + * Whether a call calls a free function: it is neither a constructor call, a member access, a + * call of a static member function, nor a call of a member function of the class of the + * function it is in. + */ + private static boolean callsAFreeFunction(@Nonnull AstNode call, @Nonnull String name) { + if (!CxxAstNodeHelper.isFunctionCall(call) + || CxxConstructorCalls.isConstructorCall(call) + || CxxAstNodeHelper.isMemberAccess(call)) { + return false; + } + final String writtenName = CxxAstNodeHelper.getFunctionCallName(call); + return writtenName != null + && qualifyingClassOf(call, writtenName) == null + && memberClassOf(call, name) == null; + } + + /** + * The class of the member function a call without an object is made in, when the class declares + * a member of the name called, e.g. {@code Hasher} for {@code reset(name)} in {@code + * Hasher::start}, or null. + */ + @Nullable private static String memberClassOf(@Nonnull AstNode call, @Nonnull String name) { + if (name.contains("::")) { + return null; + } + final String className = CxxScopes.classOfEnclosingFunction(call); + return className != null && CxxScopes.declaresMember(call, className, name) + ? className + : null; + } + + @Nonnull + @Override + public Optional getInvokedObjectTypeString( + @Nonnull MatchContext matchContext, @Nonnull AstNode methodInvocation) { + if (CxxConstructorCalls.isConstructorCall(methodInvocation)) { + final String className = CxxConstructorCalls.getClassName(methodInvocation); + return className == null + ? Optional.empty() + : Optional.of(createTypeFromClassName(className)); + } else if (CxxAstNodeHelper.isMemberAccess(methodInvocation)) { + final Optional qualifierType = getQualifierType(matchContext, methodInvocation); + if (callsMemberFunction(methodInvocation)) { + return qualifierType; + } + // a call through a function pointer member, e.g. api->EVP_sha256(), calls a C + // function, as a standalone call does + final IType globalScope = createTypeFromFqn(GLOBAL_SCOPE, matchContext); + return Optional.of( + qualifierType + .map( + type -> + typeString -> + type.is(typeString) + || globalScope.is(typeString)) + .orElse(globalScope)); + } else if (CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + final String name = CxxAstNodeHelper.getFunctionCallName(methodInvocation); + final String qualifyingClass = + name == null ? null : qualifyingClassOf(methodInvocation, name); + if (qualifyingClass != null) { + return Optional.of(createTypeFromClassName(qualifyingClass)); + } + // a member function called without an object in another member function of its class + final String memberClass = name == null ? null : memberClassOf(methodInvocation, name); + if (memberClass != null) { + return Optional.of(createTypeFromClassName(memberClass)); + } + // Standalone C/C++ function call (no member access qualifier). + // Return a synthetic global scope type so MethodMatcher.match() doesn't + // short-circuit on empty. Detection rules match it with forObjectTypes(GLOBAL_SCOPE). + return Optional.of(createTypeFromFqn(GLOBAL_SCOPE, matchContext)); + } + return Optional.empty(); + } + + /** + * The class a call of a static member function names it in, {@code Hasher} of {@code + * Hasher::make(...)}, or null when the function is not named in a class. + */ + @Nullable private static String qualifyingClassOf(@Nonnull AstNode call, @Nonnull String name) { + final int separator = name.lastIndexOf("::"); + if (separator <= 0 || CxxAstNodeHelper.isMemberAccess(call)) { + return null; + } + final List identifiers = + call.getFirstChild().getDescendants(GenericTokenType.IDENTIFIER); + if (identifiers.size() < 2) { + return null; + } + final String qualifier = name.substring(0, separator); + return CxxConstructorCalls.namesAClass(identifiers.get(identifiers.size() - 2), qualifier) + ? qualifier + : null; + } + + /** The type of the object a member is accessed on, e.g. of {@code obj} in {@code obj.f()}. */ + @Nonnull + private Optional getQualifierType( + @Nonnull MatchContext matchContext, @Nonnull AstNode memberAccess) { + AstNode qualifier = CxxAstNodeHelper.getMemberAccessQualifier(memberAccess); + if (qualifier == null) { + return Optional.empty(); + } + // this is a pointer to the object of the member function it is used in + if ("this".equals(qualifier.getTokenValue()) && qualifier.getNumberOfChildren() <= 1) { + final String className = CxxScopes.classOfEnclosingFunction(memberAccess); + if (className != null) { + return Optional.of(createTypeFromClassName(className)); + } + } + // an object, a pointer or a reference is of the class it is declared with + if (AstNodeSymbolExtension.getSymbol(qualifier) instanceof Symbol.VariableSymbol variable + && !variable.isUnknown()) { + final String className = CxxConstructorCalls.getDeclaredClassName(variable); + if (className != null) { + return Optional.of(createTypeFromClassName(className)); + } + } + Type cxxType = AstNodeTypeExtension.getType(qualifier); + if (cxxType != null && !cxxType.isUnknown()) { + return Optional.of(createTypeFromCxxType(cxxType, matchContext)); + } + + Symbol symbol = AstNodeSymbolExtension.getSymbol(qualifier); + if (symbol != null && !symbol.isUnknown()) { + String fqn = symbol.fullyQualifiedName(); + if (fqn != null) { + return Optional.of(createTypeFromFqn(fqn, matchContext)); + } + } + + String identifierName = CxxAstNodeHelper.getIdentifierName(qualifier); + if (identifierName != null) { + return Optional.of(createTypeFromFqn(identifierName, matchContext)); + } + return Optional.empty(); + } + + /** + * Whether a member access calls a member function, i.e. its member resolves to a function of a + * class rather than to a function pointer member or an unresolved member. + */ + private static boolean callsMemberFunction(@Nonnull AstNode memberAccess) { + final List children = memberAccess.getChildren(); + for (int i = children.size() - 2; i >= 0; i--) { + final String operator = children.get(i).getTokenValue(); + if (".".equals(operator) || "->".equals(operator)) { + AstNode member = children.get(i + 1); + if (!member.is(GenericTokenType.IDENTIFIER)) { + member = member.getFirstDescendant(GenericTokenType.IDENTIFIER); + } + final Symbol symbol = + member != null ? AstNodeSymbolExtension.getSymbol(member) : null; + return symbol != null && !symbol.isUnknown() && symbol.isFunctionSymbol(); + } + } + return false; + } + + @Nonnull + @Override + public Optional getMethodReturnTypeString( + @Nonnull MatchContext matchContext, @Nonnull AstNode methodInvocation) { + if (CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + Type cxxType = AstNodeTypeExtension.getType(methodInvocation); + if (cxxType != null && !cxxType.isUnknown()) { + return Optional.of(createTypeFromCxxType(cxxType, matchContext)); + } + + AstNode idExpr = methodInvocation.getFirstDescendant(CxxGrammarImpl.idExpression); + if (idExpr != null) { + Symbol symbol = AstNodeSymbolExtension.getSymbol(idExpr); + if (symbol != null + && symbol.isFunctionSymbol() + && symbol instanceof Symbol.FunctionSymbol funcSym) { + Type returnType = funcSym.returnType(); + if (returnType != null && !returnType.isUnknown()) { + return Optional.of(createTypeFromCxxType(returnType, matchContext)); + } + } + } + } + return Optional.empty(); + } + + @Nonnull + @Override + public List getMethodParameterTypes( + @Nonnull MatchContext matchContext, @Nonnull AstNode methodInvocation) { + List arguments; + + if (CxxConstructorCalls.isConstructorCall(methodInvocation)) { + arguments = CxxConstructorCalls.getArguments(methodInvocation); + } else if (CxxAstNodeHelper.isFunctionCall(methodInvocation)) { + arguments = CxxAstNodeHelper.getFunctionCallArguments(methodInvocation); + } else { + return Collections.emptyList(); + } + + if (arguments.isEmpty()) { + return Collections.emptyList(); + } + + List types = new ArrayList<>(); + List parameterMatchExactTypes = matchContext.parametersShouldMatchExactTypes(); + List matchMatrix = parameterMatchExactTypes; + + if (parameterMatchExactTypes.size() != arguments.size()) { + Boolean[] defaults = new Boolean[arguments.size()]; + java.util.Arrays.fill(defaults, Boolean.FALSE); + matchMatrix = java.util.Arrays.asList(defaults); + } + + for (int i = 0; i < arguments.size(); i++) { + AstNode argument = arguments.get(i); + boolean exactMatch = matchMatrix.get(i); + + final String argumentText = argumentText(argument); + if (argumentText != null) { + types.add(createTypeFromArgumentText(argumentText)); + continue; + } + + Type argType = AstNodeTypeExtension.getType(argument); + if (argType != null && !argType.isUnknown()) { + types.add( + createTypeFromCxxType( + argType, + new MatchContext( + matchContext.isHookContext(), + exactMatch, + Collections.emptyList()))); + continue; + } + + Symbol argSymbol = AstNodeSymbolExtension.getSymbol(argument); + if (argSymbol != null && !argSymbol.isUnknown()) { + String fqn = argSymbol.fullyQualifiedName(); + if (fqn != null) { + types.add( + createTypeFromFqn( + fqn, + new MatchContext( + matchContext.isHookContext(), + exactMatch, + Collections.emptyList()))); + continue; + } + } + + types.add(createUnknownType()); + } + return types; + } + + @Nonnull + @Override + public Optional resolveIdentifierAsString( + @Nonnull MatchContext matchContext, @Nonnull AstNode identifier) { + if (identifier.is(GenericTokenType.IDENTIFIER)) { + return Optional.of(identifier.getTokenValue()); + } + String name = CxxAstNodeHelper.getIdentifierName(identifier); + if (name != null) { + return Optional.of(name); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getEnumIdentifierName( + @Nonnull MatchContext matchContext, @Nonnull AstNode enumIdentifier) { + if (enumIdentifier.is(GenericTokenType.IDENTIFIER)) { + return Optional.of(enumIdentifier.getTokenValue()); + } + String name = CxxAstNodeHelper.getIdentifierName(enumIdentifier); + if (name != null) { + return Optional.of(name); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getEnumClassName( + @Nonnull MatchContext matchContext, @Nonnull AstNode enumClass) { + if (enumClass.is(CxxGrammarImpl.enumSpecifier)) { + AstNode enumHead = enumClass.getFirstChild(CxxGrammarImpl.enumHead); + if (enumHead != null) { + AstNode enumHeadName = enumHead.getFirstChild(CxxGrammarImpl.enumHeadName); + if (enumHeadName != null) { + String name = CxxAstNodeHelper.getIdentifierName(enumHeadName); + if (name != null) { + return Optional.of(name); + } + } + } + } + return Optional.empty(); + } + + /** + * The source text of an argument that is a literal or an identifier, e.g. {@code "digest"} + * (with its quotes), {@code 32} or {@code OSSL_KDF_PARAM_DIGEST}, or null for any other + * expression. Rules match such an argument by its text, as the key of an {@code OSSL_PARAM} + * entry is matched by {@code withMethodParameter("\"digest\"")}. + */ + @Nullable private static String argumentText(@Nonnull AstNode argument) { + AstNode node = argument; + while (!node.is(CxxGrammarImpl.LITERAL) && node.getNumberOfChildren() == 1) { + node = node.getFirstChild(); + } + if (node.is(CxxGrammarImpl.LITERAL)) { + final StringBuilder text = new StringBuilder(); + node.getTokens().forEach(token -> text.append(token.getValue())); + return text.toString(); + } + if (node.hasChildren()) { + return null; + } + if (node.is( + CxxTokenType.STRING, + CxxTokenType.NUMBER, + CxxTokenType.CHARACTER, + GenericTokenType.IDENTIFIER)) { + return node.getTokenValue(); + } + return null; + } + + @Nonnull + private static IType createTypeFromArgumentText(@Nonnull String argumentText) { + return argumentText::equals; + } + + IType createTypeFromCxxType(@Nonnull Type cxxType, @Nonnull MatchContext matchContext) { + final String fqn = cxxType.fullyQualifiedName(); + final boolean exactOnly = + matchContext.isHookContext() || matchContext.objectShouldMatchExactTypes(); + if (exactOnly) { + return typeString -> fqn != null && fqn.equals(typeString); + } + + final Symbol.TypeSymbol typeSymbol = cxxType.symbol(); + final List baseFqns = new ArrayList<>(); + if (typeSymbol != null) { + for (Symbol.TypeSymbol base : typeSymbol.baseClasses()) { + String baseFqn = base.fullyQualifiedName(); + if (baseFqn != null) { + baseFqns.add(baseFqn); + } + } + } + return typeString -> + (fqn != null && fqn.equals(typeString)) || baseFqns.contains(typeString); + } + + private IType createTypeFromFqn(@Nonnull String fqn, @Nonnull MatchContext matchContext) { + return typeString -> { + if (matchContext.isHookContext() || matchContext.objectShouldMatchExactTypes()) { + return fqn.equals(typeString); + } + return fqn.equals(typeString) + || fqn.endsWith("::" + typeString) + || typeString.endsWith("::" + fqn); + }; + } + + /** + * The type of an object of a class named as it is written, which may leave out the namespaces + * and classes the code naming it is in (see {@link CxxConstructorCalls#isSameClass}). + */ + @Nonnull + private static IType createTypeFromClassName(@Nonnull String className) { + return typeString -> CxxConstructorCalls.isSameClass(className, typeString); + } + + private IType createUnknownType() { + return typeString -> false; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxScanContext.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxScanContext.java new file mode 100644 index 000000000..e4b40838b --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxScanContext.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.engine.language.cxx; + +import com.ibm.engine.language.IScanContext; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import javax.annotation.Nonnull; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +public record CxxScanContext(@Nonnull SquidAstVisitorContext cxxVisitorContext) + implements IScanContext, AstNode> { + + /** + * Reports an issue through {@code createLineViolation}, which writes a {@code CheckMessage} - + * the API {@code CxxSquidSensor.saveViolations} actually reads back into a SonarQube issue. + * {@code SquidCheck.addIssue} looks like the natural counterpart, but it populates a separate + * {@code PreciseIssue} list that sonar-cxx's own sensor never reads: using it here would build + * every finding correctly and then silently drop it at the very last step, with no error. + */ + @Override + public void reportIssue( + @Nonnull SquidCheck currentRule, @Nonnull AstNode tree, @Nonnull String message) { + this.cxxVisitorContext.createLineViolation(currentRule, message, tree); + } + + @Nonnull + @Override + public InputFile getInputFile() { + return this.cxxVisitorContext.getInputFile(); + } + + @Nonnull + @Override + public String getFilePath() { + return this.cxxVisitorContext.getInputFile().uri().getPath(); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxScopes.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxScopes.java new file mode 100644 index 000000000..093f9bc30 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxScopes.java @@ -0,0 +1,165 @@ +/* + * 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.engine.language.cxx; + +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.utils.CxxAstNodeHelper; + +/** + * The namespaces and classes C++ code is in, by which a call and the function it calls are named + * alike: a function is named with the namespaces it is declared in, e.g. {@code util::digest}, and + * a call by the names C++ name lookup tries for it, the name as written qualified by each namespace + * the call is in, innermost first, and as written. + */ +public final class CxxScopes { + + private CxxScopes() { + // private + } + + /** + * The namespaces a node is in, outermost first, e.g. {@code [util, inner]} inside {@code + * namespace util { namespace inner { ... } }} or {@code namespace util::inner { ... }}. + */ + @Nonnull + public static List enclosingNamespaces(@Nonnull AstNode node) { + final List namespaces = new LinkedList<>(); + for (AstNode scope = node.getParent(); scope != null; scope = scope.getParent()) { + if (scope.is( + CxxGrammarImpl.namedNamespaceDefinition, + CxxGrammarImpl.nestedNamespaceDefinition)) { + final AstNode name = scope.getFirstChild(GenericTokenType.IDENTIFIER); + if (name != null) { + namespaces.add(0, name.getTokenValue()); + } + final AstNode enclosing = + scope.getFirstChild(CxxGrammarImpl.enclosingNamespaceSpecifier); + if (enclosing != null) { + namespaces.add(0, CxxConstructorCalls.textOf(enclosing)); + } + } + } + return namespaces; + } + + /** + * The names C++ name lookup tries for a call of a function named {@code name} as written at the + * call: the name qualified by each namespace the call is in, innermost first, then the name as + * written, e.g. {@code util::inner::digest}, {@code util::digest} and {@code digest} for {@code + * digest(...)} inside {@code namespace util { namespace inner { ... } }}. + */ + @Nonnull + public static List lookupNames(@Nonnull AstNode call, @Nonnull String name) { + final List namespaces = enclosingNamespaces(call); + final List names = new ArrayList<>(); + for (int i = namespaces.size(); i > 0; i--) { + names.add(String.join("::", namespaces.subList(0, i)) + "::" + name); + } + names.add(name); + return names; + } + + /** + * The class a function definition defines a member function of, named with the namespaces and + * classes it is declared in, e.g. {@code crypto::Digest}, or null for a function that is not a + * member of a class: a function defined in its class, or outside it with a name qualified by a + * class, e.g. {@code Hasher::reset}. + */ + @CheckForNull + public static String classOfFunction(@Nonnull AstNode functionDefinition) { + final String functionName = CxxAstNodeHelper.getFunctionDefinitionName(functionDefinition); + if (functionName == null) { + return null; + } + final List scopes = new LinkedList<>(enclosingNamespaces(functionDefinition)); + int classes = 0; + for (AstNode scope = functionDefinition.getParent(); + scope != null; + scope = scope.getParent()) { + if (scope.is(CxxGrammarImpl.classSpecifier)) { + final String name = CxxAstNodeHelper.getClassName(scope); + if (name == null) { + return null; + } + // the enclosing classes are met innermost first + scopes.add(scopes.size() - classes, name); + classes++; + } + } + final int separator = functionName.lastIndexOf("::"); + if (separator > 0) { + final String qualifier = functionName.substring(0, separator); + final AstNode declaratorId = + functionDefinition + .getFirstChild(CxxGrammarImpl.declarator) + .getFirstDescendant(CxxGrammarImpl.declaratorId); + final List identifiers = + declaratorId.getDescendants(GenericTokenType.IDENTIFIER); + if (identifiers.size() >= 2 + && CxxConstructorCalls.namesAClass( + identifiers.get(identifiers.size() - 2), qualifier)) { + scopes.add(qualifier); + return String.join("::", scopes); + } + } + return classes > 0 ? String.join("::", scopes) : null; + } + + /** The class of the member function a node is in, see {@link #classOfFunction}, or null. */ + @CheckForNull + public static String classOfEnclosingFunction(@Nonnull AstNode node) { + final AstNode function = CxxAstNodeHelper.getEnclosingFunction(node); + return function == null ? null : classOfFunction(function); + } + + /** + * Whether the class of the given name, declared in the translation unit of the node, declares a + * member of the given name, e.g. a member function. + */ + public static boolean declaresMember( + @Nonnull AstNode node, @Nonnull String className, @Nonnull String member) { + AstNode root = node; + while (root.getParent() != null) { + root = root.getParent(); + } + final String simpleName = className.substring(className.lastIndexOf(':') + 1); + for (AstNode specifier : root.getDescendants(CxxGrammarImpl.classSpecifier)) { + if (!simpleName.equals(CxxAstNodeHelper.getClassName(specifier))) { + continue; + } + for (AstNode declaratorId : specifier.getDescendants(CxxGrammarImpl.declaratorId)) { + if (declaratorId.getFirstAncestor(CxxGrammarImpl.classSpecifier) == specifier + && !declaratorId.hasAncestor(CxxGrammarImpl.parameterDeclaration) + && !declaratorId.hasAncestor(CxxGrammarImpl.functionBody) + && member.equals(CxxConstructorCalls.textOf(declaratorId))) { + return true; + } + } + } + return false; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/cxx/CxxSemantic.java b/engine/src/main/java/com/ibm/engine/language/cxx/CxxSemantic.java new file mode 100644 index 000000000..25b1ab1ba --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/cxx/CxxSemantic.java @@ -0,0 +1,1413 @@ +/* + * 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.engine.language.cxx; + +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.model.factory.IValueFactory; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.AstNodeType; +import com.sonar.cxx.sslr.api.GenericTokenType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.parser.CxxKeyword; +import org.sonar.cxx.parser.CxxPunctuator; +import org.sonar.cxx.parser.CxxTokenType; +import org.sonar.cxx.squidbridge.api.AstNodeSymbolExtension; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.api.SymbolTable; +import org.sonar.cxx.utils.CxxAstNodeHelper; +import org.sonar.cxx.utils.CxxConstantUtils; + +public final class CxxSemantic { + private static final Logger LOGGER = LoggerFactory.getLogger(CxxSemantic.class); + + /** + * Constant-style name: an uppercase prefix followed by at least one underscore-separated part, + * e.g. {@code TLS1_2_VERSION}, {@code NID_sha256} or {@code OSSL_KDF_NAME_HKDF}. + */ + private static final Pattern MACRO_NAME = Pattern.compile("[A-Z][A-Z0-9]*(_[A-Za-z0-9]+)+"); + + /** A name a function can have. */ + private static final Pattern FUNCTION_NAME = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*"); + + /** Arithmetic, shift, bitwise and unary operator expressions. */ + private static final AstNodeType[] OPERATOR_EXPRESSIONS = { + CxxGrammarImpl.multiplicativeExpression, + CxxGrammarImpl.additiveExpression, + CxxGrammarImpl.shiftExpression, + CxxGrammarImpl.andExpression, + CxxGrammarImpl.exclusiveOrExpression, + CxxGrammarImpl.inclusiveOrExpression, + CxxGrammarImpl.unaryExpression + }; + + /** The C++ cast keywords, e.g. {@code static_cast} in {@code static_cast(x)}. */ + private static final AstNodeType[] NAMED_CASTS = { + CxxKeyword.STATIC_CAST, + CxxKeyword.REINTERPRET_CAST, + CxxKeyword.CONST_CAST, + CxxKeyword.DYNAMIC_CAST + }; + + private static final Pattern INTEGER_SUFFIX_PATTERN = Pattern.compile("[uUlL]+$"); + private static final Pattern FLOAT_SUFFIX_PATTERN = Pattern.compile("[fFlL]+$"); + + private CxxSemantic() { + // private + } + + @Nonnull + public static List> resolveValues( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine) { + return resolveValuesInternal( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + 0, + new Resolution()); + } + + @Nonnull + private static List> resolveValuesInternal( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + if (depth > 15) { + resolution.cutOffs++; + return Collections.emptyList(); + } + + if (tree.is(CxxTokenType.STRING)) { + return resolveStringLiteral(clazz, tree); + } else if (tree.is(CxxTokenType.NUMBER)) { + return resolveNumberLiteral(clazz, tree); + } else if (tree.is(CxxTokenType.CHARACTER)) { + return resolveCharLiteral(clazz, tree); + } else if (tree.is(GenericTokenType.IDENTIFIER)) { + return resolveIdentifier( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.primaryExpression)) { + return resolvePrimaryExpression( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.BOOL)) { + return castValue(clazz, "true".equals(tree.getTokenValue())) + .map(value -> List.of(new ResolvedValue<>(value, tree))) + .orElse(Collections.emptyList()); + } else if (tree.is(CxxGrammarImpl.LITERAL)) { + return resolveLiteral( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.assignmentExpression)) { + return resolveAssignmentExpression( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.initializerClause)) { + return resolveInitializerClause( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.expression)) { + return resolveExpression( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.bracedInitList)) { + return resolveBracedInitList(clazz, tree); + } else if (tree.is(CxxGrammarImpl.qualifiedId)) { + // idExpression is .skip()-annotated, so a qualified reference (Type::CONSTANT) + // surfaces its qualifiedId node directly, with no dedicated wrapper. The referenced + // identifier is qualifiedId's last child (unqualifiedId collapses to a bare + // IDENTIFIER when it has only one child), not its first child (the LHS type name). + AstNode referencedId = tree.getLastChild(); + if (referencedId != null) { + return resolveValuesInternal( + clazz, + referencedId, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + } else if (tree.is(CxxGrammarImpl.postfixExpression) + && tree.getFirstChild().is(NAMED_CASTS)) { + // "static_cast(operand)" and the other C++ casts: the value of the operand + final AstNode open = tree.getFirstChild(CxxPunctuator.BR_LEFT); + final AstNode operand = open != null ? open.getNextSibling() : null; + if (operand != null) { + return resolveValuesInternal( + clazz, + operand, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + } else if (isArrayElement(tree)) { + return resolveArrayElement( + clazz, + tree.getFirstChild(), + indicesOf(tree.getChildren()), + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (CxxAstNodeHelper.isFunctionCall(tree)) { + return resolveFunctionCall(clazz, tree, returnEnclosingParam, detectionEngine, depth); + } else if (tree.is(CxxGrammarImpl.conditionalExpression)) { + return resolveConditionalExpression( + clazz, + tree, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } else if (tree.is(CxxGrammarImpl.castExpression)) { + // "(type) operand": the value of the operand + return resolveValuesInternal( + clazz, + tree.getLastChild(), + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } else if (isAddressOfAFunction(tree)) { + // "&f" designates the function f, as "f" does + return resolveValuesInternal( + clazz, + tree.getLastChild(), + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } else if (tree.is(OPERATOR_EXPRESSIONS)) { + // an operator expression has a value only when it is a compile-time constant, or when + // it combines flags named by macros of a header that is not part of the analyzed code, + // e.g. SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1, whose value is the flags it combines, as a + // macro's name is its value + final Object constant = CxxConstantUtils.resolveAsConstant(tree); + return castValue(clazz, constant != null ? constant : combinedFlags(tree)) + .map(value -> List.of(new ResolvedValue<>(value, tree))) + .orElse(Collections.emptyList()); + } else { + AstNode firstChild = tree.getFirstChild(); + if (firstChild != null) { + return resolveValuesInternal( + clazz, + firstChild, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + } + + return Collections.emptyList(); + } + + /** + * Whether an expression takes the address of a function, {@code &f}: of a function of the + * analyzed code or of an undeclared name written in constant style, as a library function such + * as {@code EVP_sha256} is, rather than of a variable. + */ + private static boolean isAddressOfAFunction(@Nonnull AstNode tree) { + if (!tree.is(CxxGrammarImpl.unaryExpression) + || tree.getNumberOfChildren() != 2 + || !"&".equals(tree.getFirstChild().getTokenValue()) + || !tree.getLastChild().is(GenericTokenType.IDENTIFIER)) { + return false; + } + final Symbol symbol = AstNodeSymbolExtension.getSymbol(tree.getLastChild()); + return symbol == null + ? MACRO_NAME.matcher(tree.getLastChild().getTokenValue()).matches() + : symbol instanceof Symbol.FunctionSymbol; + } + + /** + * The names of the functions a call through a function pointer calls: the functions the pointer + * is assigned, by name or by address, for a call of a pointer variable ({@code get()} or {@code + * (*get)()}) or of an element of an array of pointers ({@code getters[i]()}). Empty for a call + * that is not made through a pointer of the analyzed code. + */ + @Nonnull + public static List resolveCalledFunctions(@Nonnull AstNode call) { + if (!CxxAstNodeHelper.isFunctionCall(call)) { + return List.of(); + } + final List children = call.getChildren(); + int open = children.size() - 1; + while (open >= 0 && !children.get(open).is(CxxPunctuator.BR_LEFT)) { + open--; + } + if (open < 1) { + return List.of(); + } + List callee = children.subList(0, open); + // (*get)() and (*getters[i])() call the function the pointer points to + if (callee.size() == 1 + && callee.get(0).is(CxxGrammarImpl.primaryExpression) + && callee.get(0).getNumberOfChildren() == 3) { + AstNode dereference = callee.get(0).getChildren().get(1); + if (dereference.is(CxxGrammarImpl.expression) + && dereference.getNumberOfChildren() == 1) { + dereference = dereference.getFirstChild(); + } + if (dereference.is(CxxGrammarImpl.unaryExpression) + && dereference.getNumberOfChildren() == 2 + && "*".equals(dereference.getFirstChild().getTokenValue())) { + final AstNode pointer = dereference.getLastChild(); + callee = + pointer.is(CxxGrammarImpl.postfixExpression) + ? pointer.getChildren() + : List.of(pointer); + } + } + // a name alone may be read as a type name, as in "get()" + final AstNode first = callee.get(0); + final AstNode name = + first.getToken() == first.getLastToken() && !first.is(GenericTokenType.IDENTIFIER) + ? first.getFirstDescendant(GenericTokenType.IDENTIFIER) + : first; + if (name == null + || !name.is(GenericTokenType.IDENTIFIER) + || !(AstNodeSymbolExtension.getSymbol(name) instanceof Symbol.VariableSymbol)) { + return List.of(); + } + final List> values; + if (callee.size() == 1) { + values = + resolveValuesInternal( + Object.class, + name, + new LinkedList<>(), + null, + false, + null, + 0, + new Resolution()); + } else if (isArrayElement(callee)) { + values = + resolveArrayElement( + Object.class, + name, + indicesOf(callee), + call, + new LinkedList<>(), + null, + false, + null, + 0, + new Resolution()); + } else { + return List.of(); + } + return values.stream() + .map(ResolvedValue::value) + .filter(String.class::isInstance) + .map(String.class::cast) + .filter(function -> FUNCTION_NAME.matcher(function).matches()) + .distinct() + .toList(); + } + + /** + * Whether a postfix expression is an element of an array variable, a name followed by + * subscripts only: {@code names[i]} or {@code names[i][1]}. + */ + private static boolean isArrayElement(@Nonnull AstNode tree) { + return tree.is(CxxGrammarImpl.postfixExpression) && isArrayElement(tree.getChildren()); + } + + /** Whether nodes are a name followed by subscripts only, see {@link #isArrayElement}. */ + private static boolean isArrayElement(@Nonnull List children) { + if (children.size() < 4 + || (children.size() - 1) % 3 != 0 + || !children.get(0).is(GenericTokenType.IDENTIFIER)) { + return false; + } + for (int i = 1; i < children.size(); i += 3) { + if (!children.get(i).is(CxxPunctuator.SQBR_LEFT) + || !children.get(i + 2).is(CxxPunctuator.SQBR_RIGHT)) { + return false; + } + } + return true; + } + + /** The index expressions of an {@link #isArrayElement array element}, in order. */ + @Nonnull + private static List indicesOf(@Nonnull List children) { + final List indices = new ArrayList<>(); + for (int i = 2; i < children.size(); i += 3) { + indices.add(children.get(i)); + } + return indices; + } + + /** The value of a constant index, or null when the index is not a compile-time constant. */ + @Nullable private static Long constantIndex(@Nonnull AstNode index) { + AstNode expression = index; + while (expression.is(CxxGrammarImpl.expressionList, CxxGrammarImpl.initializerList) + && expression.getNumberOfChildren() == 1) { + expression = expression.getFirstChild(); + } + return CxxConstantUtils.resolveAsConstant(expression) instanceof Number number + ? number.longValue() + : null; + } + + /** + * Resolves an element of an array variable to the elements of the array's initializer and the + * values assigned to its elements: at a constant index, the element at that index, counting a + * designated element ({@code [2] = "SM3"}) at its index; at an index that is not constant, + * every element. An element assigned with a constant index ({@code names[1] = v}) gives its + * value to the elements at that index, one assigned with an index that is not constant to every + * element. The array is guarded against cycles as a variable is. + */ + @Nonnull + private static List> resolveArrayElement( + @Nonnull Class clazz, + @Nonnull AstNode arrayName, + @Nonnull List indices, + @Nonnull AstNode element, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + if (!(AstNodeSymbolExtension.getSymbol(arrayName) instanceof Symbol.VariableSymbol array)) { + return Collections.emptyList(); + } + if (!resolution.inProgress.add(array)) { + resolution.cutOffs++; + return Collections.emptyList(); + } + try { + final List values = new ArrayList<>(); + final AstNode initializer = array.initializer(); + final AstNode list = initializer == null ? null : initializer.getLastChild(); + if (list != null && list.is(CxxGrammarImpl.bracedInitList)) { + List level = List.of(list); + for (int i = 0; i < indices.size(); i++) { + final Long index = constantIndex(indices.get(i)); + final List next = new ArrayList<>(); + for (AstNode levelList : level) { + for (AstNode value : elementsAt(levelList, index)) { + if (i == indices.size() - 1) { + next.add(value); + } else if (value.is(CxxGrammarImpl.bracedInitList)) { + next.add(value); + } + } + } + level = next; + } + values.addAll(level); + } + for (Symbol.Usage usage : array.usages()) { + final AstNode assignedElement = usage.node().getParent(); + if (assignedElement == null + || assignedElement == element + || assignedElement.getFirstChild() != usage.node() + || !isArrayElement(assignedElement)) { + continue; + } + final AstNode assignment = assignedElement.getParent(); + if (assignment == null + || !assignment.is(CxxGrammarImpl.assignmentExpression) + || assignment.getFirstChild() != assignedElement + || !"=".equals(assignment.getChildren().get(1).getTokenValue()) + || !sameElement(indicesOf(assignedElement.getChildren()), indices)) { + continue; + } + values.add(assignment.getLastChild()); + } + final List> result = new LinkedList<>(); + for (AstNode value : values) { + result.addAll( + resolveValuesInternal( + clazz, + value, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution)); + } + return List.copyOf(new LinkedHashSet<>(result)); + } finally { + resolution.inProgress.remove(array); + } + } + + /** + * Whether an element assigned with the given indices may be the element read with the other + * indices: of the same number of indices, each pair is equal or not constant. + */ + private static boolean sameElement( + @Nonnull List assigned, @Nonnull List read) { + if (assigned.size() != read.size()) { + return false; + } + for (int i = 0; i < assigned.size(); i++) { + final Long assignedIndex = constantIndex(assigned.get(i)); + final Long readIndex = constantIndex(read.get(i)); + if (assignedIndex != null && readIndex != null && !assignedIndex.equals(readIndex)) { + return false; + } + } + return true; + } + + /** + * The values of the elements of a brace-enclosed initializer list at an index, or of every + * element for a null index. A designated element, {@code [2] = "SM3"}, is at the index it + * names, and the elements after it follow from there. + */ + @Nonnull + private static List elementsAt(@Nonnull AstNode list, @Nullable Long index) { + final List values = new ArrayList<>(); + long position = 0; + for (AstNode element : elementsOf(list)) { + AstNode value = element; + if (element.is(CxxGrammarImpl.designatedInitializerClause)) { + final AstNode designator = element.getFirstChild(CxxGrammarImpl.designator); + final AstNode at = + designator == null + ? null + : designator.getFirstChild(CxxGrammarImpl.constantExpression); + if (at != null && CxxConstantUtils.resolveAsConstant(at) instanceof Number number) { + position = number.longValue(); + } + value = element.getLastChild(); + } + if (index == null || index == position) { + values.add(value); + } + position++; + } + return values; + } + + /** + * The elements of a brace-enclosed initializer list, without the braces and commas: the + * initializer clauses, or the designated initializer clauses of a designated list. + */ + @Nonnull + static List elementsOf(@Nonnull AstNode bracedInitList) { + final List elements = new ArrayList<>(); + for (AstNode child : bracedInitList.getChildren()) { + if (child.is( + CxxGrammarImpl.initializerList, CxxGrammarImpl.designatedInitializerList)) { + child.getChildren().stream() + .filter(element -> !element.is(CxxPunctuator.COMMA)) + .forEach(elements::add); + } else if (!child.is( + CxxPunctuator.CURLBR_LEFT, CxxPunctuator.CURLBR_RIGHT, CxxPunctuator.COMMA)) { + elements.add(child); + } + } + return elements; + } + + /** + * The flags a bitwise or of macro names combines, written {@code + * SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1} in the order they are written, or null when an operand is + * not the name of a macro that is not declared in the analyzed code. + */ + @Nullable private static String combinedFlags(@Nonnull AstNode tree) { + final List flags = new ArrayList<>(); + return collectFlags(tree, flags) && flags.size() > 1 ? String.join("|", flags) : null; + } + + private static boolean collectFlags(@Nonnull AstNode operand, @Nonnull List flags) { + if (operand.is(CxxGrammarImpl.inclusiveOrExpression)) { + for (AstNode child : operand.getChildren()) { + if (!child.is(CxxPunctuator.BW_OR) && !collectFlags(child, flags)) { + return false; + } + } + return true; + } + if (operand.is(CxxGrammarImpl.primaryExpression) + && operand.getNumberOfChildren() == 3 + && operand.getFirstChild().is(CxxPunctuator.BR_LEFT)) { + return collectFlags(operand.getChildren().get(1), flags); + } + if (operand.is(CxxGrammarImpl.expression) && operand.getNumberOfChildren() == 1) { + return collectFlags(operand.getFirstChild(), flags); + } + if (operand.is(GenericTokenType.IDENTIFIER) + && AstNodeSymbolExtension.getSymbol(operand) == null + && MACRO_NAME.matcher(operand.getTokenValue()).matches()) { + flags.add(operand.getTokenValue()); + return true; + } + return false; + } + + /** True for the member name of a member access, e.g. {@code field} in {@code s.field}. */ + private static boolean isMemberAccessName(@Nonnull AstNode identifier) { + AstNode previous = identifier.getPreviousSibling(); + return previous != null + && (".".equals(previous.getTokenValue()) || "->".equals(previous.getTokenValue())); + } + + /** + * Resolves {@code condition ? whenTrue : whenFalse} to the selected branch when the condition + * is a compile-time constant, and to the values of both branches otherwise. + */ + @Nonnull + private static List> resolveConditionalExpression( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + List children = tree.getChildren(); + if (children.size() != 5) { + return Collections.emptyList(); + } + List branches; + Object condition = CxxConstantUtils.resolveAsConstant(children.get(0)); + if (condition instanceof Boolean bool) { + branches = List.of(bool ? children.get(2) : children.get(4)); + } else if (condition instanceof Number number) { + branches = List.of(number.longValue() != 0 ? children.get(2) : children.get(4)); + } else { + branches = List.of(children.get(2), children.get(4)); + } + List> result = new LinkedList<>(); + for (AstNode branch : branches) { + result.addAll( + resolveValuesInternal( + clazz, + branch, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution)); + } + return result; + } + + @Nonnull + private static List> resolveStringLiteral( + @Nonnull Class clazz, @Nonnull AstNode tree) { + String value = tree.getTokenValue(); + if (value.startsWith("\"") && value.endsWith("\"") && value.length() >= 2) { + value = value.substring(1, value.length() - 1); + } else if (value.startsWith("L\"") && value.endsWith("\"") && value.length() >= 3) { + value = value.substring(2, value.length() - 1); + } else if (value.startsWith("u\"") && value.endsWith("\"") && value.length() >= 3) { + value = value.substring(2, value.length() - 1); + } else if (value.startsWith("U\"") && value.endsWith("\"") && value.length() >= 3) { + value = value.substring(2, value.length() - 1); + } else if (value.startsWith("u8\"") && value.endsWith("\"") && value.length() >= 4) { + value = value.substring(3, value.length() - 1); + } else if (value.startsWith("R\"") && value.endsWith("\"")) { + int parenOpen = value.indexOf('('); + int parenClose = value.lastIndexOf(')'); + if (parenOpen >= 0 && parenClose > parenOpen) { + value = value.substring(parenOpen + 1, parenClose); + } + } + Optional result = castValue(clazz, value); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + @Nonnull + private static List> resolveNumberLiteral( + @Nonnull Class clazz, @Nonnull AstNode tree) { + String value = tree.getTokenValue(); + if ("nullptr".equals(value)) { + // the lexer reads the null pointer literal as a number + return castValue(clazz, "nullptr") + .map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + value = value.replace("'", ""); + boolean isHex = value.startsWith("0x") || value.startsWith("0X"); + // Hex literals never carry an f/F suffix (f/F there are hex digits), so only strip the + // u/U/l/L integer suffix; non-hex literals may also carry a float f/F suffix. + value = + isHex + ? INTEGER_SUFFIX_PATTERN.matcher(value).replaceAll("") + : FLOAT_SUFFIX_PATTERN + .matcher(INTEGER_SUFFIX_PATTERN.matcher(value).replaceAll("")) + .replaceAll(""); + + Object result; + try { + // an integer literal is an int when its value fits, a long otherwise, e.g. 0x80000000 + if (isHex) { + result = integerValue(Long.parseUnsignedLong(value.substring(2), 16)); + } else if (value.startsWith("0b") || value.startsWith("0B")) { + result = integerValue(Long.parseUnsignedLong(value.substring(2), 2)); + } else if (value.contains(".") || value.contains("e") || value.contains("E")) { + result = Double.parseDouble(value); + } else if (value.startsWith("0") && value.length() > 1) { + result = integerValue(Long.parseUnsignedLong(value.substring(1), 8)); + } else { + result = integerValue(Long.parseLong(value)); + } + } catch (NumberFormatException e) { + // a literal out of the range of the integer types, or not a valid number (e.g. the + // octal 0999), has no value the compiler would accept + return Collections.emptyList(); + } + Optional castResult = castValue(clazz, result); + return castResult + .map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + /** The value of an integer literal: an {@code int} when it fits, a {@code long} otherwise. */ + @Nonnull + private static Object integerValue(long value) { + return value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE + ? (Object) (int) value + : value; + } + + /** + * A brace-enclosed initializer list, e.g. an {@code OSSL_HPKE_SUITE} given as {@code + * {OSSL_HPKE_KEM_ID_P256, OSSL_HPKE_KDF_ID_HKDF_SHA256, OSSL_HPKE_AEAD_ID_AES_GCM_256}}, has no + * single scalar value: its value is the list of the source texts of its elements, which a value + * factory that understands the structure it initializes reads. The value holds no syntax tree, + * so that a call given such an argument can be kept without its file's tree. + */ + @Nonnull + private static List> resolveBracedInitList( + @Nonnull Class clazz, @Nonnull AstNode tree) { + final List elements = + elementsOf(tree).stream().map(CxxConstructorCalls::textOf).toList(); + return castValue(clazz, elements) + .map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + @Nonnull + private static List> resolveCharLiteral( + @Nonnull Class clazz, @Nonnull AstNode tree) { + String value = tree.getTokenValue(); + if (value.startsWith("'") && value.endsWith("'") && value.length() >= 3) { + value = value.substring(1, value.length() - 1); + } + Optional result = castValue(clazz, value); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + @Nonnull + private static List> resolveIdentifier( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + String name = tree.getTokenValue(); + if ("true".equals(name)) { + Optional result = castValue(clazz, Boolean.TRUE); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } else if ("false".equals(name)) { + Optional result = castValue(clazz, Boolean.FALSE); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } else if ("nullptr".equals(name)) { + Optional result = castValue(clazz, "nullptr"); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + // Try to resolve as compile-time constant using CxxConstantUtils + try { + Object constantValue = CxxConstantUtils.resolveAsConstant(tree); + if (constantValue != null) { + LOGGER.debug("Resolved identifier '{}' to constant value: {}", name, constantValue); + Optional result = castValue(clazz, constantValue); + if (result.isPresent()) { + return List.of(new ResolvedValue<>(result.get(), tree)); + } + } + } catch (Exception e) { + LOGGER.debug("Could not resolve identifier '{}' as constant: {}", name, e.getMessage()); + } + + // A field's or parameter's own name is never a resolved value. Parameters are still + // returned when returnEnclosingParam is set, since that caller needs the node to hook the + // enclosing function; a field has nothing to hook. A scoped enum's constants live only in + // its qualified-only member scope (see Symbol.TypeSymbol#memberScope) and are never + // registered in an enclosing scope, so the cheap direct lookup below always misses for + // them; the qualified-reference (Mode::STRICT) fallback only runs on that miss, sparing the + // unbounded ancestor walk it does for the overwhelmingly common non-scoped-enum identifier. + Symbol symbol = AstNodeSymbolExtension.getSymbol(tree); + if (symbol == null) { + symbol = resolveScopedEnumConstant(tree); + } + if (symbol instanceof Symbol.VariableSymbol variableSymbol && !symbol.isUnknown()) { + boolean skipField = variableSymbol.isField(); + boolean skipParameter = variableSymbol.isParameter() && !returnEnclosingParam; + if (skipField || skipParameter) { + return Collections.emptyList(); + } + + List> chased = + chaseVariableValues( + clazz, + tree, + variableSymbol, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + if (!chased.isEmpty()) { + return chased; + } + // A bare parameter with no local reassignment/initializer has nothing to chase, so + // fall back to the parameter node itself, per the returnEnclosingParam contract above. + if (variableSymbol.isParameter() && returnEnclosingParam) { + Optional result = castValue(clazz, name); + if (result.isPresent()) { + return List.of(new ResolvedValue<>(result.get(), tree)); + } + } + } else if (symbol != null + && symbol.kind() == Symbol.Kind.ENUM_CONSTANT + && !symbol.isUnknown()) { + List> resolved = resolveEnumConstant(clazz, symbol); + if (!resolved.isEmpty()) { + return resolved; + } + } + + // the name of a function used as a value designates the function, e.g. the function a + // function pointer is assigned + if (symbol instanceof Symbol.FunctionSymbol && !symbol.isUnknown()) { + return castValue(clazz, name) + .map(value -> List.of(new ResolvedValue<>(value, tree))) + .orElse(Collections.emptyList()); + } + + // An undeclared name written in constant style (e.g. TLS1_2_VERSION, NID_sha256) is a macro + // or constant defined in a header that is not part of the analyzed code; its name is its + // value, which library rules map to what it stands for, as for an unresolved Python name. + if (symbol == null && !isMemberAccessName(tree) && MACRO_NAME.matcher(name).matches()) { + return castValue(clazz, name) + .map(value -> List.of(new ResolvedValue<>(value, tree))) + .orElse(Collections.emptyList()); + } + + // Any other identifier with no attached symbol (something declared in another translation + // unit) is not itself a resolved value; resolution of such names is deferred to the + // separate outer-scope resolution mechanism. + return Collections.emptyList(); + } + + /** + * Resolves a variable to its declaration-time initializer value and every subsequent + * reassignment's value, matching how the Java engine chases {@code VariableTree.initializer()} + * and assignment-site usages. The initializer's result (if any) comes first, followed by + * reassignments in source order; an empty list means neither yielded a resolved value. A value + * reached through several assignments is listed once. + * + *

The assignment graph can contain cycles (e.g. {@code a = b;} together with {@code b = + * a;}): a variable that is already being resolved further up the call chain is not followed + * again, since it cannot contribute a new value and following it would recurse forever, + * matching the guard the Java engine carries for the same reason (issue #525). + * + *

A variable reached again through another assignment of the same resolution, e.g. {@code + * v1} in {@code v2 = v1; if (c) v2 = v1;}, takes the values it was resolved to the first time, + * so that a chain of such variables is resolved once per variable rather than once per path. + * Only complete values are reused: values from which the depth limit or the cycle guard left + * something out depend on where the variable was reached from. + */ + @Nonnull + private static List> chaseVariableValues( + @Nonnull Class clazz, + @Nonnull AstNode currentOccurrence, + @Nonnull Symbol.VariableSymbol variableSymbol, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + // the values of a variable do not include the assignment being resolved, so they can only + // be reused when the occurrence is not one of the variable's own assignments + final boolean reusable = !isWrittenAt(variableSymbol, currentOccurrence); + if (reusable) { + final List> known = resolution.valuesOf(variableSymbol); + if (known != null) { + return known; + } + } + if (!resolution.inProgress.add(variableSymbol)) { + // cycle: this variable is already being resolved further up the call chain + resolution.cutOffs++; + return Collections.emptyList(); + } + final int cutOffsBefore = resolution.cutOffs; + try { + LinkedList> result = new LinkedList<>(); + + for (Symbol.Usage usage : variableSymbol.usages()) { + if (usage.node() == currentOccurrence) { + continue; + } + // a compound assignment (x += 16) combines the variable's value with another one, + // so only a plain assignment of the variable itself gives it a value, as for Java's + // Tree.Kind.ASSIGNMENT + if (usage.kind() != Symbol.Usage.UsageKind.WRITE) { + continue; + } + AstNode assignmentExpr = + usage.node().getFirstAncestor(CxxGrammarImpl.assignmentExpression); + if (assignmentExpr == null) { + continue; + } + AstNode assignedValue = assignmentExpr.getLastChild(); + if (assignedValue == null || assignedValue == usage.node()) { + continue; + } + result.addAll( + resolveValuesInternal( + clazz, + assignedValue, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution)); + } + + AstNode initializer = variableSymbol.initializer(); + if (initializer != null) { + // braceOrEqualInitializer is declared with .skip() and initializerClause with + // .skipIfOneChild(), so for a plain "= " declarator neither node survives in + // the tree: initializer ends up with the "=" token as its first child and the + // (possibly further-collapsed) value expression as its last child. Taking the + // structurally last child, as with assignmentExpression above, reaches the value + // regardless of how far it collapsed. + AstNode initializerTarget = initializer.getLastChild(); + if (initializerTarget == null) { + initializerTarget = initializer; + } + List> initializerResults = + resolveValuesInternal( + clazz, + initializerTarget, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + result.addAll(0, initializerResults); + } + + final List> values = List.copyOf(new LinkedHashSet<>(result)); + if (reusable && resolution.cutOffs == cutOffsBefore) { + resolution.resolvedValues.put(variableSymbol, values); + } + return values; + } finally { + resolution.inProgress.remove(variableSymbol); + } + } + + /** Whether {@code occurrence} is one of the places where {@code variableSymbol} is assigned. */ + private static boolean isWrittenAt( + @Nonnull Symbol.VariableSymbol variableSymbol, @Nonnull AstNode occurrence) { + for (Symbol.Usage usage : variableSymbol.usages()) { + if (usage.node() == occurrence && usage.kind() != Symbol.Usage.UsageKind.READ) { + return true; + } + } + return false; + } + + /** + * Resolves an enum constant to its explicit {@code = constantExpression} value when present, + * and otherwise to its implicit value: the value of the enumerator before it plus one, or 0 for + * the first enumerator. When that value is not known, e.g. an enumerator before it is given an + * undeclared macro, the constant resolves to its own declared name. + */ + @Nonnull + private static List> resolveEnumConstant( + @Nonnull Class clazz, @Nonnull Symbol constantSymbol) { + AstNode enumeratorNode = constantSymbol.declaration(); + if (enumeratorNode == null) { + return Collections.emptyList(); + } + // The symbol's declaration() is the "enumerator" node (just the IDENTIFIER, plus an + // optional attribute-specifier-seq); its optional "= constantExpression" is a sibling + // under the enclosing "enumeratorDefinition" rule, fetched from the parent rather than + // from the enumerator node itself. + AstNode enumeratorDefinition = enumeratorNode.getParent(); + if (enumeratorDefinition != null + && enumeratorDefinition.is(CxxGrammarImpl.enumeratorDefinition)) { + AstNode constantExpr = + enumeratorDefinition.getFirstChild(CxxGrammarImpl.constantExpression); + if (constantExpr != null) { + List> explicitValue = + resolveValuesInternal( + clazz, + constantExpr, + new LinkedList<>(), + null, + false, + null, + 0, + new Resolution()); + if (!explicitValue.isEmpty()) { + return explicitValue; + } + } else { + final Long implicitValue = implicitEnumeratorValue(enumeratorDefinition); + if (implicitValue != null) { + return castValue(clazz, integerValue(implicitValue)) + .map(v -> List.of(new ResolvedValue<>(v, enumeratorNode))) + .orElse(Collections.emptyList()); + } + } + } + Optional nameValue = castValue(clazz, constantSymbol.name()); + return nameValue + .map(v -> List.of(new ResolvedValue<>(v, enumeratorNode))) + .orElse(Collections.emptyList()); + } + + /** + * The value of an enumerator without a constant expression: the value of the enumerator before + * it plus one, or 0 for the first enumerator, or null when the value of an enumerator before it + * is not a known integer. + */ + @Nullable private static Long implicitEnumeratorValue(@Nonnull AstNode enumeratorDefinition) { + final AstNode list = enumeratorDefinition.getParent(); + if (list == null) { + return null; + } + long value = -1; + for (AstNode definition : list.getChildren(CxxGrammarImpl.enumeratorDefinition)) { + final AstNode constantExpr = + definition.getFirstChild(CxxGrammarImpl.constantExpression); + if (constantExpr == null) { + value++; + } else { + final List> explicitValue = + resolveValuesInternal( + Object.class, + constantExpr, + new LinkedList<>(), + null, + false, + null, + 0, + new Resolution()); + if (explicitValue.size() != 1 + || !(explicitValue.get(0).value() instanceof Number number)) { + return null; + } + value = number.longValue(); + } + if (definition == enumeratorDefinition) { + return value; + } + } + return null; + } + + /** + * For an identifier that is the right-hand side of a qualified reference ({@code + * Type::CONSTANT}), looks it up inside the left-hand type's qualified-only member scope when + * that type is a scoped enum, since a scoped enum's constants are reachable only through their + * own member scope, not the normal enclosing-scope chain. Returns null for any other {@code + * nestedNameSpecifier} shape (namespace, class, unresolved type, or an unscoped enum). + */ + @Nullable private static Symbol resolveScopedEnumConstant(@Nonnull AstNode identifierNode) { + AstNode qualifiedId = identifierNode.getFirstAncestor(CxxGrammarImpl.qualifiedId); + if (qualifiedId == null) { + return null; + } + AstNode nestedNameSpecifier = qualifiedId.getFirstChild(CxxGrammarImpl.nestedNameSpecifier); + if (nestedNameSpecifier == null) { + return null; + } + // "Mode::" parses as nestedNameSpecifier -> typeName -> className -> IDENTIFIER, so the + // type name token is a descendant, not a direct child + AstNode typeNameNode = nestedNameSpecifier.getFirstDescendant(GenericTokenType.IDENTIFIER); + if (typeNameNode == null + || !(AstNodeSymbolExtension.getSymbol(typeNameNode) + instanceof Symbol.TypeSymbol typeSymbol) + || !typeSymbol.isScopedEnum()) { + return null; + } + SymbolTable memberScope = typeSymbol.memberScope(); + if (memberScope == null) { + return null; + } + return memberScope.getSymbol(identifierNode.getTokenValue()); + } + + @Nonnull + private static List> resolvePrimaryExpression( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + // "( expression )": the value of the expression + if (tree.getNumberOfChildren() == 3 && tree.getFirstChild().is(CxxPunctuator.BR_LEFT)) { + return resolveValuesInternal( + clazz, + tree.getChildren().get(1), + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + + AstNode literal = tree.getFirstChild(CxxGrammarImpl.LITERAL); + if (literal != null) { + return resolveLiteral( + clazz, + literal, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } + + AstNode firstChild = tree.getFirstChild(); + if (firstChild != null) { + return resolveValuesInternal( + clazz, + firstChild, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + + return Collections.emptyList(); + } + + @Nonnull + private static List> resolveLiteral( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + AstNode stringLiteral = tree.getFirstChild(CxxTokenType.STRING); + if (stringLiteral != null) { + return resolveStringLiteral(clazz, stringLiteral); + } + + AstNode numberLiteral = tree.getFirstChild(CxxTokenType.NUMBER); + if (numberLiteral != null) { + return resolveNumberLiteral(clazz, numberLiteral); + } + + AstNode charLiteral = tree.getFirstChild(CxxTokenType.CHARACTER); + if (charLiteral != null) { + return resolveCharLiteral(clazz, charLiteral); + } + + AstNode boolLiteral = tree.getFirstChild(CxxGrammarImpl.BOOL); + if (boolLiteral != null) { + String value = boolLiteral.getTokenValue(); + Boolean boolValue = "true".equals(value); + Optional result = castValue(clazz, boolValue); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + AstNode nullptrLiteral = tree.getFirstChild(CxxGrammarImpl.NULLPTR); + if (nullptrLiteral != null) { + Optional result = castValue(clazz, "nullptr"); + return result.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + AstNode firstChild = tree.getFirstChild(); + if (firstChild != null) { + return resolveValuesInternal( + clazz, + firstChild, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + + return Collections.emptyList(); + } + + @Nonnull + private static List> resolveAssignmentExpression( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + List children = tree.getChildren(); + if (!children.isEmpty()) { + AstNode lastChild = children.get(children.size() - 1); + return resolveValuesInternal( + clazz, + lastChild, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + return Collections.emptyList(); + } + + @Nonnull + private static List> resolveInitializerClause( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + AstNode assignmentExpression = tree.getFirstChild(CxxGrammarImpl.assignmentExpression); + if (assignmentExpression != null) { + return resolveAssignmentExpression( + clazz, + assignmentExpression, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth, + resolution); + } + AstNode firstChild = tree.getFirstChild(); + if (firstChild != null) { + return resolveValuesInternal( + clazz, + firstChild, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + return Collections.emptyList(); + } + + @Nonnull + private static List> resolveExpression( + @Nonnull Class clazz, + @Nonnull AstNode tree, + @Nonnull LinkedList selections, + @Nullable IValueFactory valueFactory, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth, + @Nonnull Resolution resolution) { + AstNode firstChild = tree.getFirstChild(); + if (firstChild != null) { + return resolveValuesInternal( + clazz, + firstChild, + selections, + valueFactory, + returnEnclosingParam, + detectionEngine, + depth + 1, + resolution); + } + return Collections.emptyList(); + } + + @Nonnull + private static List> resolveFunctionCall( + @Nonnull Class clazz, + @Nonnull AstNode tree, + boolean returnEnclosingParam, + @Nullable CxxDetectionEngine detectionEngine, + int depth) { + // A function call's callee name is not itself a resolved value. A plain function-call + // identifier has a method symbol, not a variable or enum symbol, so none of the + // IDENTIFIER sub-cases in resolveIdentifier apply to it, and resolution yields nothing. + return Collections.emptyList(); + } + + @Nonnull + private static Optional castValue(@Nonnull Class clazz, @Nullable Object value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(clazz.cast(value)); + } catch (ClassCastException e) { + if (clazz == String.class) { + @SuppressWarnings("unchecked") + O stringValue = (O) value.toString(); + return Optional.of(stringValue); + } + return Optional.empty(); + } + } + + /** + * The state of resolving one expression: the variables being resolved, the values of the + * variables resolved completely, and how often the depth limit or the cycle guard left a value + * out. + */ + private static final class Resolution { + @Nonnull private final Set inProgress = new HashSet<>(); + + @Nonnull + private final Map>> + resolvedValues = new HashMap<>(); + + private int cutOffs; + + /** + * The values {@code variableSymbol} was resolved to, or null if it was not resolved + * completely yet. All values of a resolution are of the class it was started with. + */ + @SuppressWarnings("unchecked") + @Nullable private List> valuesOf( + @Nonnull Symbol.VariableSymbol variableSymbol) { + return (List>) resolvedValues.get(variableSymbol); + } + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/go/tree/CompositeLiteralWithBlockTree.java b/engine/src/main/java/com/ibm/engine/language/go/tree/CompositeLiteralWithBlockTree.java index 91daae336..2cbb24a53 100644 --- a/engine/src/main/java/com/ibm/engine/language/go/tree/CompositeLiteralWithBlockTree.java +++ b/engine/src/main/java/com/ibm/engine/language/go/tree/CompositeLiteralWithBlockTree.java @@ -23,7 +23,6 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.checkerframework.checker.nullness.qual.NonNull; import org.sonar.go.impl.BaseTreeImpl; import org.sonar.plugins.go.api.BlockTree; import org.sonar.plugins.go.api.CompositeLiteralTree; @@ -66,7 +65,7 @@ public List identifiers() { } @Override - public @NonNull BlockTree blockTree() { + public @Nonnull BlockTree blockTree() { return blockTree; } diff --git a/engine/src/main/java/com/ibm/engine/language/go/tree/FunctionInvocationWIthIdentifiersTree.java b/engine/src/main/java/com/ibm/engine/language/go/tree/FunctionInvocationWIthIdentifiersTree.java index e73939f30..b04060d0c 100644 --- a/engine/src/main/java/com/ibm/engine/language/go/tree/FunctionInvocationWIthIdentifiersTree.java +++ b/engine/src/main/java/com/ibm/engine/language/go/tree/FunctionInvocationWIthIdentifiersTree.java @@ -24,7 +24,6 @@ import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.checkerframework.checker.nullness.qual.NonNull; import org.sonar.go.impl.FunctionInvocationTreeImpl; import org.sonar.plugins.go.api.BlockTree; import org.sonar.plugins.go.api.FunctionInvocationTree; @@ -54,7 +53,7 @@ public List identifiers() { } @Override - public @NonNull BlockTree blockTree() { + public @Nonnull BlockTree blockTree() { return blockTree; } } diff --git a/engine/src/main/java/com/ibm/engine/language/go/tree/IdentifierWithBlockTree.java b/engine/src/main/java/com/ibm/engine/language/go/tree/IdentifierWithBlockTree.java index 47b12dc31..0a09f59d4 100644 --- a/engine/src/main/java/com/ibm/engine/language/go/tree/IdentifierWithBlockTree.java +++ b/engine/src/main/java/com/ibm/engine/language/go/tree/IdentifierWithBlockTree.java @@ -21,7 +21,6 @@ import java.util.List; import javax.annotation.Nonnull; -import org.checkerframework.checker.nullness.qual.NonNull; import org.sonar.go.impl.BaseTreeImpl; import org.sonar.plugins.go.api.BlockTree; import org.sonar.plugins.go.api.IdentifierTree; @@ -54,7 +53,7 @@ public IdentifierTree identifierTree() { } @Override - public @NonNull BlockTree blockTree() { + public @Nonnull BlockTree blockTree() { return blockTree; } diff --git a/engine/src/main/java/com/ibm/engine/language/go/tree/TreeWithBlock.java b/engine/src/main/java/com/ibm/engine/language/go/tree/TreeWithBlock.java index 87e05c45e..2f36f1b32 100644 --- a/engine/src/main/java/com/ibm/engine/language/go/tree/TreeWithBlock.java +++ b/engine/src/main/java/com/ibm/engine/language/go/tree/TreeWithBlock.java @@ -21,7 +21,6 @@ import java.util.List; import javax.annotation.Nonnull; -import org.checkerframework.checker.nullness.qual.NonNull; import org.sonar.go.impl.BaseTreeImpl; import org.sonar.plugins.go.api.BlockTree; import org.sonar.plugins.go.api.Tree; @@ -42,7 +41,7 @@ public List children() { } @Override - public @NonNull BlockTree blockTree() { + public @Nonnull BlockTree blockTree() { return blockTree; } } diff --git a/engine/src/main/java/com/ibm/engine/language/java/JavaDetachedIssueReporter.java b/engine/src/main/java/com/ibm/engine/language/java/JavaDetachedIssueReporter.java index d7169476d..32b694d41 100644 --- a/engine/src/main/java/com/ibm/engine/language/java/JavaDetachedIssueReporter.java +++ b/engine/src/main/java/com/ibm/engine/language/java/JavaDetachedIssueReporter.java @@ -32,6 +32,7 @@ import org.sonar.java.reporting.AnalyzerMessage; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.JavaFileScannerContext; +import org.sonar.plugins.java.api.tree.Tree; /** * Raises a SonarQube issue for a detached (tree-free) cross-file detection through {@link @@ -45,7 +46,7 @@ * (extended by both the production context and the CheckVerifier test context). If it cannot be * obtained, {@link #create} returns {@code null} and issue reporting degrades to CBOM-only. */ -public final class JavaDetachedIssueReporter implements IDetachedIssueReporter { +public final class JavaDetachedIssueReporter implements IDetachedIssueReporter { private static final Logger LOGGER = LoggerFactory.getLogger(JavaDetachedIssueReporter.class); @@ -67,16 +68,17 @@ private JavaDetachedIssueReporter( } @Override - public void report( - @Nonnull JavaCheck rule, - @Nonnull DetachedSyntaxToken location, - @Nonnull String message) { + public void report(@Nonnull JavaCheck rule, @Nonnull Tree location, @Nonnull String message) { + if (!(location instanceof DetachedSyntaxToken detachedLocation)) { + LOGGER.debug("Skipping issue on non-detached location in {}: {}", inputFile, message); + return; + } final AnalyzerMessage.TextSpan span = new AnalyzerMessage.TextSpan( - location.line(), - location.offset(), - location.endLine(), - location.endOffset()); + detachedLocation.line(), + detachedLocation.offset(), + detachedLocation.endLine(), + detachedLocation.endOffset()); sonarComponents.reportIssue(new AnalyzerMessage(rule, inputFile, span, message, 0)); } diff --git a/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java b/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java index 72a9a0069..4f383a2cf 100644 --- a/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java +++ b/engine/src/main/java/com/ibm/engine/language/java/JavaDetectionEngine.java @@ -133,8 +133,16 @@ public void run(@Nonnull TraceSymbol traceSymbol, @Nonnull Tree tree) { * when possible (its arguments are pre-resolved here while the file is live). Falls back to * retaining the tree when the call is not detachable or an argument cannot be faithfully * snapshotted. + * + *

{@code run} is invoked once per detection rule for the same call node, so this same {@code + * invocation} reaches here once per rule too; only the first such call actually needs + * recording, so every later one exits before doing the argument-resolution and detached-call + * construction work below. */ private void recordCall(@Nonnull MethodInvocationTree invocation) { + if (handler.isCallAlreadyRecorded(invocation)) { + return; + } final IScanContext scanContext = detectionStore.getScanContext(); // Record retained (with the live tree) so same-file hook detections resolve and report // through the live context. If detachable, pre-build the tree-free form now, while the file @@ -165,12 +173,12 @@ private void recordCall(@Nonnull MethodInvocationTree invocation) { final List parameterTypes = translation.getMethodParameterTypes(matchContext, invocation); - final List arguments = new ArrayList<>(); + final List> arguments = new ArrayList<>(); final List actualArguments = invocation.arguments(); for (int i = 0; i < actualArguments.size(); i++) { final List> resolved = resolveValuesInInnerScope(Object.class, actualArguments.get(i), null); - final List snapshots = new ArrayList<>(); + final List> snapshots = new ArrayList<>(); for (ResolvedValue resolvedValue : resolved) { final DetachedSyntaxToken location = captureLocation(resolvedValue.tree(), resolvedValue.value().toString()); @@ -178,9 +186,9 @@ private void recordCall(@Nonnull MethodInvocationTree invocation) { return null; // cannot faithfully snapshot -> fall back to retaining the tree } snapshots.add( - new ArgSnapshot.ResolvedSnapshotValue(resolvedValue.value(), location)); + new ArgSnapshot.ResolvedSnapshotValue<>(resolvedValue.value(), location)); } - arguments.add(new ArgSnapshot(i, snapshots)); + arguments.add(new ArgSnapshot<>(i, snapshots)); } final JavaDetachedIssueReporter issueReporter = diff --git a/engine/src/main/java/com/ibm/engine/model/context/ProtocolContext.java b/engine/src/main/java/com/ibm/engine/model/context/ProtocolContext.java index 600f25fc0..0cf534a53 100644 --- a/engine/src/main/java/com/ibm/engine/model/context/ProtocolContext.java +++ b/engine/src/main/java/com/ibm/engine/model/context/ProtocolContext.java @@ -26,6 +26,19 @@ public class ProtocolContext extends DetectionContext { public enum Kind { TLS, + // TLS signature-algorithm list configuration (e.g. SSL_CTX_set1_sigalgs_list). + TLS_SIGNATURE_ALGORITHMS, + // TLS supported-groups (key-exchange) list configuration (e.g. SSL_CTX_set1_groups_list). + TLS_GROUPS, + // TLS protocol versions disabled by options (e.g. SSL_CTX_set_options with + // SSL_OP_NO_TLSv1), the minimum version (e.g. SSL_CTX_set_min_proto_version) and the + // maximum version (e.g. SSL_CTX_set_max_proto_version): the settings made on a context + // together bound the range of versions it uses. + TLS_DISABLED_VERSIONS, + TLS_MINIMUM_VERSION, + TLS_MAXIMUM_VERSION, + // SRTP protection profile list configuration (e.g. SSL_CTX_set_tlsext_use_srtp). + SRTP, NONE, } 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 055cf8622..285c534f6 100644 --- a/engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java +++ b/engine/src/main/java/com/ibm/engine/rule/IDetectionRule.java @@ -98,6 +98,13 @@ ParametersFactoryBuilder withNamedMethodParameter( @Nonnull ParametersFactoryBuilder withOptionalNamedMethodParameter( @Nonnull String name, @Nonnull String type); + + /** + * A call without arguments and without a value of its own, e.g. the creation of an object + * whose operations are detected by the depending rules. + */ + @Nonnull + FinalDetectionRuleBuilder withoutParameters(); } interface ParametersTypeBuilder { diff --git a/engine/src/test/java/com/ibm/engine/callstack/CallStackAgentTest.java b/engine/src/test/java/com/ibm/engine/callstack/CallStackAgentTest.java new file mode 100644 index 000000000..14f6f7b2c --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/callstack/CallStackAgentTest.java @@ -0,0 +1,204 @@ +/* + * 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.engine.callstack; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.EnumMatcher; +import com.ibm.engine.detection.IBaseMethodVisitorFactory; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.ibm.engine.detection.MethodMatcher; +import com.ibm.engine.executive.DetectionExecutive; +import com.ibm.engine.language.ILanguageSupport; +import com.ibm.engine.language.ILanguageTranslation; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.rule.IDetectionRule; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * A single call node is visited once per detection rule ({@code CxxDetectionEngine}/{@code + * JavaDetectionEngine}'s {@code run} is invoked once per rule for the same tree), so {@code + * recordCall} calls {@link CallStackAgent#add} once per rule for the same node too. {@link + * CallStackAgent#isRecorded} lets a caller check, before doing any expensive work, whether an + * earlier call already added that same node - this test verifies that check reflects {@link + * CallStackAgent#add}'s own dedup key and tree-identity check exactly. + */ +class CallStackAgentTest { + + /** A tree stand-in with its own identity, distinct from its (possibly shared) method name. */ + private record CallSite(String methodName, int siteId) {} + + @Test + void isRecordedIsFalseBeforeTheFirstAddForATree() { + CallStackAgent agent = + new CallStackAgent<>(fakeLanguageSupport()); + + assertThat(agent.isRecorded(new CallSite("EVP_EncryptInit_ex", 1))).isFalse(); + } + + @Test + void isRecordedIsTrueAfterTheFirstAddForThatTree() { + CallStackAgent agent = + new CallStackAgent<>(fakeLanguageSupport()); + CallSite tree = new CallSite("EVP_EncryptInit_ex", 1); + + agent.add(new RetainedCall<>(tree, fakeScanContext(), null)); + + assertThat(agent.isRecorded(tree)).isTrue(); + } + + @Test + void isRecordedStaysTrueAcrossRepeatedAddsForTheSameTree_simulatingOneRulePerCallNode() { + // Mirrors production: run() is invoked once per detection rule for the same call node, so + // recordCall (and its handler.addRecordedCall -> CallStackAgent.add) is called once per + // rule too. isRecorded must reflect the very first add so every later rule's pass can skip + // rebuilding the detached call. + CallStackAgent agent = + new CallStackAgent<>(fakeLanguageSupport()); + CallSite tree = new CallSite("EVP_EncryptInit_ex", 1); + + for (int rule = 0; rule < 545; rule++) { + assertThat(agent.isRecorded(tree)).isEqualTo(rule > 0); + agent.add(new RetainedCall<>(tree, fakeScanContext(), null)); + } + + assertThat(agent.isRecorded(tree)).isTrue(); + } + + @Test + void isRecordedDistinguishesDifferentTreesWithTheSameMethodName() { + // Two distinct call sites that invoke the same method name: both hash to the same bucket + // (keyed by method name), so isRecorded must fall back to tree identity/equality within + // that bucket rather than treating the bucket's mere existence as "recorded". + CallStackAgent agent = + new CallStackAgent<>(fakeLanguageSupport()); + CallSite firstCallSite = new CallSite("EVP_EncryptInit_ex", 1); + CallSite secondCallSite = new CallSite("EVP_EncryptInit_ex", 2); + + agent.add(new RetainedCall<>(firstCallSite, fakeScanContext(), null)); + + assertThat(agent.isRecorded(firstCallSite)).isTrue(); + assertThat(agent.isRecorded(secondCallSite)).isFalse(); + } + + @SuppressWarnings("unchecked") + private static IScanContext fakeScanContext() { + return Mockito.mock(IScanContext.class); + } + + /** + * A minimal {@link ILanguageSupport} whose {@code T} is {@link CallSite}: only {@link + * ILanguageSupport#translation()} is ever called by {@link CallStackAgent#add}/{@link + * CallStackAgent#isRecorded}, via {@link ILanguageTranslation#getMethodName}, so every other + * method throws if reached - which would mean this test's understanding of {@link + * CallStackAgent}'s dependencies is wrong. + */ + private static ILanguageSupport fakeLanguageSupport() { + return new ILanguageSupport<>() { + @Override + public ILanguageTranslation translation() { + return new ILanguageTranslation<>() { + @Override + public Optional getMethodName( + MatchContext matchContext, CallSite methodInvocation) { + return Optional.of(methodInvocation.methodName()); + } + + @Override + public Optional getInvokedObjectTypeString( + MatchContext matchContext, CallSite methodInvocation) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getMethodReturnTypeString( + MatchContext matchContext, CallSite methodInvocation) { + throw new UnsupportedOperationException(); + } + + @Override + public List getMethodParameterTypes( + MatchContext matchContext, CallSite methodInvocation) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional resolveIdentifierAsString( + MatchContext matchContext, CallSite identifier) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getEnumIdentifierName( + MatchContext matchContext, CallSite enumConstant) { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getEnumClassName( + MatchContext matchContext, CallSite enumClass) { + return Optional.empty(); + } + }; + } + + @Override + public DetectionExecutive createDetectionExecutive( + CallSite tree, + IDetectionRule detectionRule, + IScanContext scanContext) { + throw new UnsupportedOperationException(); + } + + @Override + public IDetectionEngine createDetectionEngineInstance( + DetectionStore detectionStore) { + throw new UnsupportedOperationException(); + } + + @Override + public IBaseMethodVisitorFactory getBaseMethodVisitorFactory() { + throw new UnsupportedOperationException(); + } + + @Override + public Optional getEnclosingMethod(CallSite expression) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodMatcher createMethodMatcherBasedOn(CallSite methodDefinition) { + throw new UnsupportedOperationException(); + } + + @Override + public EnumMatcher createSimpleEnumMatcherFor( + CallSite enumIdentifier, MatchContext matchContext) { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/engine/src/test/java/com/ibm/engine/callstack/DetachedScanContextTest.java b/engine/src/test/java/com/ibm/engine/callstack/DetachedScanContextTest.java index 1e8a33192..dde58d57e 100644 --- a/engine/src/test/java/com/ibm/engine/callstack/DetachedScanContextTest.java +++ b/engine/src/test/java/com/ibm/engine/callstack/DetachedScanContextTest.java @@ -21,13 +21,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import org.junit.jupiter.api.Test; import org.sonar.api.batch.fs.InputFile; class DetachedScanContextTest { + private record FakeDetachedLocation() implements DetachedLocation {} + @Test void exposesFilePathAndInputFileWithoutPinningAst() { InputFile inputFile = mock(InputFile.class); @@ -39,7 +45,7 @@ void exposesFilePathAndInputFileWithoutPinningAst() { } @Test - void reportIssueIsANoOp() { + void reportIssueIsANoOpWhenNoReporterIsAvailable() { // A detached context has no live scanner context, so it cannot raise a tree-based issue; it // silently skips rather than failing the analysis. The CBOM node is still produced. DetachedScanContext ctx = @@ -47,4 +53,32 @@ void reportIssueIsANoOp() { assertThatCode(() -> ctx.reportIssue(new Object(), new Object(), "msg")) .doesNotThrowAnyException(); } + + @Test + @SuppressWarnings("unchecked") + void reportIssueDelegatesWhenLocationIsDetached() { + IDetachedIssueReporter reporter = mock(IDetachedIssueReporter.class); + DetachedScanContext ctx = + new DetachedScanContext<>(mock(InputFile.class), "/p/F.java", reporter); + Object rule = new Object(); + DetachedLocation location = new FakeDetachedLocation(); + + ctx.reportIssue(rule, location, "msg"); + + verify(reporter).report(rule, location, "msg"); + } + + @Test + @SuppressWarnings("unchecked") + void reportIssueSkipsWhenLocationIsNotDetached() { + // T is shared with the non-detached path, so a plain Object (standing in for a live tree + // node) reaches reportIssue here without failing to compile. + IDetachedIssueReporter reporter = mock(IDetachedIssueReporter.class); + DetachedScanContext ctx = + new DetachedScanContext<>(mock(InputFile.class), "/p/F.java", reporter); + + assertThatCode(() -> ctx.reportIssue(new Object(), new Object(), "msg")) + .doesNotThrowAnyException(); + verify(reporter, never()).report(any(), any(), anyString()); + } } diff --git a/engine/src/test/java/com/ibm/engine/detection/DetectionStoreTest.java b/engine/src/test/java/com/ibm/engine/detection/DetectionStoreTest.java new file mode 100644 index 000000000..c47d04dd8 --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/detection/DetectionStoreTest.java @@ -0,0 +1,128 @@ +/* + * 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.engine.detection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; + +import com.ibm.engine.executive.IStatusReporting; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +/** The calls a detection store records, and the tree a finding is reported on. */ +class DetectionStoreTest { + + private final IDetectionRule contextRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("ctx_new") + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules(); + + private final IDetectionRule initRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("init") + .shouldBeDetectedAs(new ValueActionFactory<>("AES")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules(); + + @SuppressWarnings("unchecked") + private final Handler handler = + mock(Handler.class, RETURNS_DEEP_STUBS); + + @SuppressWarnings("unchecked") + private final IScanContext scanContext = mock(IScanContext.class); + + @SuppressWarnings("unchecked") + private final IStatusReporting statusReporting = + mock(IStatusReporting.class); + + @Test + void recordsEveryCallTheRuleMatchesOnce() { + final String first = "init(ctx, cipher)"; + final String second = "init(ctx, NULL)"; + final DetectionStore store = store(initRule); + + store.onReceivingNewDetection(new MethodDetection<>(first, null)); + store.onReceivingNewDetection(new MethodDetection<>(second, null)); + store.onReceivingNewDetection(new MethodDetection<>(first, null)); + + assertThat(store.getDetectedExpressions()).containsExactly(first, second); + assertThat(store.getDetectedExpression()).contains(first); + } + + @Test + void recordsNoCallBeforeAMatch() { + final DetectionStore store = store(initRule); + + assertThat(store.getDetectedExpressions()).isEmpty(); + assertThat(store.getDetectedExpression()).isEmpty(); + } + + @Test + void findingIsReportedOnItsFirstValue() { + final DetectionStore store = store(initRule); + store.onReceivingNewDetection(new MethodDetection<>("init()", "init")); + + assertThat(new Finding<>(store).getMarkerTree()).isEqualTo("init"); + } + + @Test + void findingWithoutValueIsReportedOnTheCallItMatched() { + final DetectionStore context = store(contextRule); + context.onReceivingNewDetection(new MethodDetection<>("ctx_new()", null)); + final DetectionStore init = store(initRule); + init.onReceivingNewDetection(new MethodDetection<>("init()", null)); + context.attach(init); + + assertThat(new Finding<>(context).getMarkerTree()).isEqualTo("ctx_new()"); + } + + @Test + void findingWithoutValueOrCallIsReportedOnTheFirstValueBelowIt() { + final DetectionStore root = store(contextRule); + final DetectionStore empty = store(contextRule); + final DetectionStore init = store(initRule); + init.onReceivingNewDetection(new MethodDetection<>("init()", null)); + empty.attach(init); + root.attach(empty); + + assertThat(new Finding<>(root).getMarkerTree()).isEqualTo("init()"); + } + + @Nonnull + private DetectionStore store( + @Nonnull IDetectionRule rule) { + return new DetectionStore<>(0, rule, scanContext, handler, statusReporting); + } +} diff --git a/engine/src/test/java/com/ibm/engine/detection/MethodMatcherBehaviorTest.java b/engine/src/test/java/com/ibm/engine/detection/MethodMatcherBehaviorTest.java index 6b705c1f0..dbb1a64b7 100644 --- a/engine/src/test/java/com/ibm/engine/detection/MethodMatcherBehaviorTest.java +++ b/engine/src/test/java/com/ibm/engine/detection/MethodMatcherBehaviorTest.java @@ -211,6 +211,7 @@ private ILanguageTranslation translation( when(translation.getInvokedObjectTypeString(context, this)) .thenReturn(Optional.of(type("Cipher"))); when(translation.getMethodName(context, this)).thenReturn(Optional.of(name)); + when(translation.getMethodNames(context, this)).thenCallRealMethod(); when(translation.getMethodParameterTypes(context, this)).thenReturn(parameters); when(translation.supportsSubsetParameterMatching()).thenReturn(subset); return translation; diff --git a/engine/src/test/java/com/ibm/engine/executive/DetectionExecutiveTest.java b/engine/src/test/java/com/ibm/engine/executive/DetectionExecutiveTest.java new file mode 100644 index 000000000..d742b5993 --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/executive/DetectionExecutiveTest.java @@ -0,0 +1,209 @@ +/* + * 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.engine.executive; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.Finding; +import com.ibm.engine.detection.Handler; +import com.ibm.engine.detection.MethodDetection; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** The findings a detection executive reports for a detection store tree. */ +class DetectionExecutiveTest { + + private final IDetectionRule initRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("init") + .shouldBeDetectedAs(new ValueActionFactory<>("AES")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules(); + + /** Creates an object, followed to the calls made on it. */ + private final IDetectionRule contextRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("ctx_new") + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withDependingDetectionRules(List.of(initRule)); + + /** Detects a value of its own, followed to the calls made on the object it creates. */ + private final IDetectionRule getInstanceRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("getInstance") + .shouldBeDetectedAs(new ValueActionFactory<>("AES")) + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withDependingDetectionRules(List.of(initRule)); + + /** Creates an object, not followed. */ + private final IDetectionRule notFollowedRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("create") + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules(); + + @SuppressWarnings("unchecked") + private final Handler handler = + mock(Handler.class, RETURNS_DEEP_STUBS); + + @SuppressWarnings("unchecked") + private final IScanContext scanContext = mock(IScanContext.class); + + @SuppressWarnings("unchecked") + private final IStatusReporting statusReporting = + mock(IStatusReporting.class); + + private final List> findings = new ArrayList<>(); + private DetectionExecutive executive; + + @BeforeEach + void setUp() { + executive = new DetectionExecutive<>("ctx_new()", contextRule, scanContext, handler); + executive.subscribe(findings::add); + executive.incrementVisitedRules(); + } + + @Test + void storeWithValueIsOneFinding() { + final DetectionStore init = matched(initRule, "init()"); + + executive.emitFinding(init); + + assertThat(findings).extracting(Finding::detectionStore).containsExactly(init); + } + + @Test + void storeWithoutValueReportsTheCallsMadeOnItsObjectTogether() { + final DetectionStore context = + matched(contextRule, "ctx_new()"); + final DetectionStore init = matched(initRule, "init()"); + context.attach(init); + + executive.emitFinding(context); + + assertThat(findings).extracting(Finding::detectionStore).containsExactly(context); + } + + @Test + void storeWithoutValueReportsTheArgumentsOfItsCallAndTheCallsMadeOnItsObjectTogether() { + final DetectionStore context = + matched(contextRule, "ctx_new(cipher())"); + final DetectionStore argument = matched(initRule, "a()"); + final DetectionStore operation = matched(initRule, "b()"); + context.attach(0, argument); + context.attach(operation); + + executive.emitFinding(context); + + assertThat(findings).extracting(Finding::detectionStore).containsExactly(context); + } + + @Test + void storeWhoseOwnValueWasFoundElsewhereReportsTheValuesBelowItSeparately() { + // getInstance(algorithm()): the value of the call is resolved in the called function + final DetectionStore getInstance = store(getInstanceRule); + final DetectionStore ownValue = + matched(getInstanceRule, "algorithm()"); + final DetectionStore operation = matched(initRule, "b()"); + getInstance.attach(0, ownValue); + getInstance.attach(operation); + + executive.emitFinding(getInstance); + + assertThat(findings) + .extracting(Finding::detectionStore) + .containsExactly(operation, ownValue); + } + + @Test + void storeWithoutValueNotFollowedReportsTheValuesBelowItSeparately() { + final DetectionStore created = + matched(notFollowedRule, "create()"); + final DetectionStore first = matched(initRule, "a()"); + final DetectionStore second = matched(initRule, "b()"); + created.attach(0, first); + created.attach(1, second); + + executive.emitFinding(created); + + assertThat(findings).extracting(Finding::detectionStore).containsExactly(first, second); + } + + @Test + void storeWithoutAnyValueIsNoFinding() { + final DetectionStore context = + matched(contextRule, "ctx_new()"); + context.attach(matched(contextRule, "ctx_new()")); + + executive.emitFinding(context); + + assertThat(findings).isEmpty(); + } + + @Test + void noFindingBeforeEveryRuleIsVisited() { + executive.addAdditionalExpectedRuleVisits(1); + + executive.emitFinding(matched(initRule, "init()")); + + assertThat(findings).isEmpty(); + } + + @Nonnull + private DetectionStore matched( + @Nonnull IDetectionRule rule, @Nonnull String call) { + final DetectionStore store = store(rule); + store.onReceivingNewDetection(new MethodDetection<>(call, null)); + return store; + } + + @Nonnull + private DetectionStore store( + @Nonnull IDetectionRule rule) { + return new DetectionStore<>(0, rule, scanContext, handler, statusReporting); + } +} diff --git a/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageSupportStatsTest.java b/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageSupportStatsTest.java new file mode 100644 index 000000000..02d8c06f3 --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageSupportStatsTest.java @@ -0,0 +1,57 @@ +/* + * 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.engine.language.cxx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.callstack.CallContextStats; +import com.ibm.engine.language.ILanguageSupport; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +class CxxLanguageSupportStatsTest { + + @Test + void freshSupportReportsEmptyStatsThroughTheChain() { + ILanguageSupport, AstNode, Symbol, SquidAstVisitorContext> + support = CxxLanguageSupporter.cxxLanguageSupporter(); + + CallContextStats stats = support.callContextStats(); + + assertThat(stats).isNotNull(); + assertThat(stats.total()).isZero(); + assertThat(stats.retainedWithTree()).isZero(); + assertThat(stats.detached()).isZero(); + } + + @Test + void translationReturnsTheSameCachedInstanceOnEveryCall() { + CxxLanguageSupport support = new CxxLanguageSupport(); + + Object first = support.translation(); + Object second = support.translation(); + + assertThat(second).isSameAs(first); + } +} diff --git a/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationAstDetachTest.java b/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationAstDetachTest.java new file mode 100644 index 000000000..828e82636 --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationAstDetachTest.java @@ -0,0 +1,122 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.cxx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.AstNodeType; +import java.lang.ref.WeakReference; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.api.Symbol; +import org.sonar.cxx.squidbridge.api.Type; + +/** + * Verifies that {@code createTypeFromCxxType}'s returned {@link IType} does not capture the + * sonar-cxx {@link Type} it was built from. A {@code Type}'s {@code TypeSymbol} can hold a live + * {@link AstNode} via {@code declaration()}, and any reachable {@code AstNode} keeps its whole + * file's parse tree alive through parent/child/sibling links. + * + *

Builds a {@code Type} backed by a real {@code AstNode}, drops every strong reference except + * the produced {@link IType}, forces GC, and asserts the node is collected. + */ +class CxxLanguageTranslationAstDetachTest { + + private final CxxLanguageTranslation translation = new CxxLanguageTranslation(); + + @Test + void hookContextTypeDoesNotPinTheDeclarationAstNode() { + assertDeclarationNodeIsCollectable(MatchContext.createForHookContext()); + } + + @Test + void exactMatchTypeDoesNotPinTheDeclarationAstNode() { + assertDeclarationNodeIsCollectable(new MatchContext(false, true, List.of())); + } + + @Test + void subtypeMatchTypeDoesNotPinTheDeclarationAstNode() { + assertDeclarationNodeIsCollectable(new MatchContext(false, false, List.of())); + } + + private void assertDeclarationNodeIsCollectable(MatchContext matchContext) { + Built built = buildTypeAndDropAstReferences(matchContext); + + assertThat(built.type.is("MyNamespace::MyClass")).isTrue(); + assertThat(built.type.is("Something::Else")).isFalse(); + + forceGc(); + assertThat(built.declarationRef.get()) + .as( + "createTypeFromCxxType must not capture the sonar-cxx Type/TypeSymbol chain: " + + "doing so keeps the declaration AstNode - and via getParent(), its " + + "whole file's AST - reachable for as long as the IType survives") + .isNull(); + } + + private Built buildTypeAndDropAstReferences(MatchContext matchContext) { + AstNode wholeFileTree = new AstNode(new FakeAstNodeType(), "translationUnit", null); + AstNode declarationNode = new AstNode(new FakeAstNodeType(), "classSpecifier", null); + wholeFileTree.addChild(declarationNode); + WeakReference declarationRef = new WeakReference<>(declarationNode); + + Symbol.TypeSymbol typeSymbol = new FakeTypeSymbol(declarationNode); + Type cxxType = new Type.CxxType("MyNamespace::MyClass", typeSymbol); + + IType type = translation.createTypeFromCxxType(cxxType, matchContext); + return new Built(type, declarationRef); + } + + private record Built(IType type, WeakReference declarationRef) {} + + private static void forceGc() { + for (int i = 0; i < 10; i++) { + System.gc(); + byte[] pressure = new byte[4 * 1024 * 1024]; + assertThat(pressure).isNotNull(); + } + } + + private static final class FakeAstNodeType implements AstNodeType {} + + /** + * Minimal {@link Symbol.TypeSymbol} whose only real behavior is a fixed {@code declaration()}. + */ + private static final class FakeTypeSymbol extends Symbol.UnknownTypeSymbol { + private final AstNode declarationNode; + + FakeTypeSymbol(AstNode declarationNode) { + this.declarationNode = declarationNode; + } + + @Override + public AstNode declaration() { + return declarationNode; + } + + @Override + public String fullyQualifiedName() { + return "MyNamespace::MyClass"; + } + } +} diff --git a/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationLiteralArgumentTest.java b/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationLiteralArgumentTest.java new file mode 100644 index 000000000..beb2f7b9d --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/language/cxx/CxxLanguageTranslationLiteralArgumentTest.java @@ -0,0 +1,139 @@ +/* + * 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.engine.language.cxx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.GenericTokenType; +import com.sonar.cxx.sslr.api.Token; +import com.sonar.cxx.sslr.api.TokenType; +import java.net.URI; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.parser.CxxPunctuator; +import org.sonar.cxx.parser.CxxTokenType; + +/** + * {@link CxxLanguageTranslation#getMethodParameterTypes} gives an argument that is a literal its + * source text as its type (e.g. matched by {@code withMethodParameter("\"AES-256-GCM\"")}), also + * when the literal is not wrapped in a {@code LITERAL} grammar node but is reached through nodes of + * one child each. A literal that is only part of the argument, as {@code 2} in {@code a[2]}, does + * not give the argument its text. + */ +class CxxLanguageTranslationLiteralArgumentTest { + + private final CxxLanguageTranslation translation = new CxxLanguageTranslation(); + + @Test + void matchesAStringLiteralNestedBelowTheArgumentNodeWithNoLiteralWrapper() { + AstNode methodInvocation = buildFunctionCall(buildNestedStringToken("\"AES-256-GCM\"")); + + List types = + translation.getMethodParameterTypes( + MatchContext.createForHookContext(), methodInvocation); + + assertThat(types).hasSize(1); + assertThat(types.get(0).is("\"AES-256-GCM\"")).isTrue(); + assertThat(types.get(0).is("\"something-else\"")).isFalse(); + } + + @Test + void doesNotMatchALiteralThatIsOnlyPartOfTheArgument() { + AstNode subscript = new AstNode(new PlainAstNodeType(), "postfixExpression", null); + subscript.addChild(new AstNode(fakeToken(GenericTokenType.IDENTIFIER, "a"))); + subscript.addChild(new AstNode(fakeToken(CxxPunctuator.BR_LEFT, "["))); + subscript.addChild(new AstNode(fakeToken(CxxTokenType.NUMBER, "2"))); + subscript.addChild(new AstNode(fakeToken(CxxPunctuator.BR_RIGHT, "]"))); + AstNode methodInvocation = buildFunctionCall(subscript); + + List types = + translation.getMethodParameterTypes( + MatchContext.createForHookContext(), methodInvocation); + + assertThat(types).hasSize(1); + assertThat(types.get(0).is("2")).isFalse(); + } + + /** + * A raw {@code STRING} token nested two levels under a wrapper node that is not itself + * grammar-typed as {@code LITERAL} - so {@code getFirstDescendant(CxxGrammarImpl.LITERAL)} + * finds nothing, and only a token-type-aware descendant walk reaches it. + */ + private static AstNode buildNestedStringToken(String text) { + AstNode stringToken = new AstNode(fakeToken(CxxTokenType.STRING, text)); + AstNode innerWrapper = new AstNode(new PlainAstNodeType(), "innerWrapper", null); + innerWrapper.addChild(stringToken); + AstNode outerWrapper = new AstNode(new PlainAstNodeType(), "initializerClause", null); + outerWrapper.addChild(innerWrapper); + return outerWrapper; + } + + /** + * Builds a minimal {@code postfixExpression}-shaped function call whose single argument is + * {@code argument}: {@code CxxAstNodeHelper.isFunctionCall} requires a {@code + * postfixExpression} child token {@code "("}; {@code extractActualArguments} then requires a + * descendant {@code expressionList} node whose children (no {@code initializerList} child) are + * the arguments. + */ + private static AstNode buildFunctionCall(AstNode argument) { + AstNode postfixExpression = + new AstNode(CxxGrammarImpl.postfixExpression, "postfixExpression", null); + AstNode openParen = new AstNode(fakeToken(CxxPunctuator.BR_LEFT, "(")); + AstNode expressionList = new AstNode(CxxGrammarImpl.expressionList, "expressionList", null); + expressionList.addChild(argument); + AstNode closeParen = new AstNode(fakeToken(CxxPunctuator.BR_RIGHT, ")")); + + postfixExpression.addChild(openParen); + postfixExpression.addChild(expressionList); + postfixExpression.addChild(closeParen); + return postfixExpression; + } + + private static Token fakeToken(TokenType type, String value) { + return Token.builder() + .setLine(1) + .setColumn(0) + .setValueAndOriginalValue(value) + .setType(type) + .setURI(URI.create("file:///test.cpp")) + .build(); + } + + private static final class PlainAstNodeType implements TokenType { + @Override + public String getName() { + return "PLAIN"; + } + + @Override + public String getValue() { + return "PLAIN"; + } + + @Override + public boolean hasToBeSkippedFromAst(AstNode node) { + return false; + } + } +} diff --git a/engine/src/test/java/com/ibm/engine/language/cxx/CxxScanContextTest.java b/engine/src/test/java/com/ibm/engine/language/cxx/CxxScanContextTest.java new file mode 100644 index 000000000..963cd15e9 --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/language/cxx/CxxScanContextTest.java @@ -0,0 +1,55 @@ +/* + * 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.engine.language.cxx; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.squidbridge.SquidAstVisitorContext; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +class CxxScanContextTest { + + // Regression test: sonar-cxx's own sensor (CxxSquidSensor.saveViolations) reads issues back + // from SquidAstVisitorContext's CheckMessage list, populated only by createLineViolation - + // never from the separate PreciseIssue list SquidCheck.addIssue populates. Calling addIssue + // here silently drops every finding: no exception, no log, just zero issues in SonarQube + // despite the CBOM being generated correctly. reportIssue must go through createLineViolation. + @Test + void reportIssueGoesThroughCreateLineViolationNotAddIssue() { + @SuppressWarnings("unchecked") + SquidAstVisitorContext visitorContext = mock(SquidAstVisitorContext.class); + SquidCheck check = mock(SquidCheck.class); + AstNode node = mock(AstNode.class); + + CxxScanContext scanContext = new CxxScanContext(visitorContext); + scanContext.reportIssue(check, node, "(BlockCipher) AES-256-GCM"); + + verify(visitorContext) + .createLineViolation(eq(check), eq("(BlockCipher) AES-256-GCM"), eq(node)); + verify(check, never()).addIssue(any(), any()); + } +} diff --git a/engine/src/test/java/com/ibm/engine/rule/builder/DetectionRuleBuilderTest.java b/engine/src/test/java/com/ibm/engine/rule/builder/DetectionRuleBuilderTest.java new file mode 100644 index 000000000..ebe8fd612 --- /dev/null +++ b/engine/src/test/java/com/ibm/engine/rule/builder/DetectionRuleBuilderTest.java @@ -0,0 +1,62 @@ +/* + * 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.engine.rule.builder; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.rule.DetectionRule; +import com.ibm.engine.rule.IDetectionRule; +import java.util.List; +import org.junit.jupiter.api.Test; + +class DetectionRuleBuilderTest { + + @Test + void ruleWithoutParametersOrValue() { + final IDetectionRule dependingRule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("init") + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withoutDependingDetectionRules(); + + final IDetectionRule rule = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("*") + .forMethods("ctx_new") + .withoutParameters() + .buildForContext(new CipherContext()) + .inBundle(() -> "Test") + .withDependingDetectionRules(List.of(dependingRule)); + + assertThat(rule).isInstanceOf(DetectionRule.class); + final DetectionRule detectionRule = (DetectionRule) rule; + assertThat(detectionRule.parameters()).isEmpty(); + assertThat(detectionRule.actionFactory()).isNull(); + assertThat(detectionRule.detectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(detectionRule.bundle().getIdentifier()).isEqualTo("Test"); + assertThat(detectionRule.nextDetectionRules()).containsExactly(dependingRule); + } +} diff --git a/enricher/pom.xml b/enricher/pom.xml index 465b649ef..a929efa29 100644 --- a/enricher/pom.xml +++ b/enricher/pom.xml @@ -12,8 +12,6 @@ enricher - 17 - 17 UTF-8 diff --git a/enricher/src/main/java/com/ibm/enricher/algorithm/AESEnricher.java b/enricher/src/main/java/com/ibm/enricher/algorithm/AESEnricher.java index 4619af363..a827aeaf7 100644 --- a/enricher/src/main/java/com/ibm/enricher/algorithm/AESEnricher.java +++ b/enricher/src/main/java/com/ibm/enricher/algorithm/AESEnricher.java @@ -21,8 +21,10 @@ import com.ibm.enricher.IEnricher; import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Mode; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.algorithms.AES; @@ -31,10 +33,13 @@ import com.ibm.mapper.model.mode.CFB; import com.ibm.mapper.model.mode.ECB; import com.ibm.mapper.model.mode.GCM; +import com.ibm.mapper.model.mode.GMAC; import com.ibm.mapper.model.mode.KW; import com.ibm.mapper.model.mode.KWP; import com.ibm.mapper.model.mode.OFB; +import com.ibm.mapper.utils.Utils; import java.util.Map; +import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -50,7 +55,9 @@ public class AESEnricher implements IEnricher, IEnrichWithDefaultKeySize { KW.class, 5, GCM.class, 6, CCM.class, 7, - KWP.class, 8); + KWP.class, 8, + // RFC 9044 + GMAC.class, 9); private static final Map KEYSIZE_OID_MAP = Map.of( @@ -71,6 +78,10 @@ private INode enrich(@Nonnull AES aes) { @Nullable KeyLength keyLength = aes.getKeyLength().orElse(null); @Nullable final Mode mode = aes.getMode().orElse(null); this.applyDefaultKeySizeForJca(aes, 128); + if (aes.is(Mac.class)) { + aes.put(new Oid(buildMacOid(keyLength, mode), aes.getDetectionContext())); + return aes; + } // add oid final Oid oid = new Oid(buildOid(keyLength, mode), aes.getDetectionContext()); aes.put(oid); @@ -82,6 +93,18 @@ private INode enrich(@Nonnull AES aes) { return aes; } + /** + * The OID of AES as a MAC: of the GMAC with its key length (RFC 9044), otherwise of AES. The + * OIDs of the other modes are those of AES encryption in that mode. + */ + @Nonnull + private String buildMacOid(@Nullable KeyLength keyLength, @Nullable Mode mode) { + if (mode instanceof GMAC && keyLength != null) { + return buildOid(keyLength, mode); + } + return BASE_OID; + } + @Nonnull private String buildOid(@Nullable KeyLength keyLength, @Nullable Mode mode) { StringBuilder builder = new StringBuilder(BASE_OID); @@ -97,7 +120,7 @@ private String buildOid(@Nullable KeyLength keyLength, @Nullable Mode mode) { return builder.toString(); } Integer modeOidNumber = MODE_OID_MAP.get(mode.getClass()); - if (modeOidNumber != null) { + if (modeOidNumber != null && hasFullBlockFeedback(mode)) { if (keySizeOidNumber == null) { builder.append("."); } @@ -105,4 +128,22 @@ private String buildOid(@Nullable KeyLength keyLength, @Nullable Mode mode) { } return builder.toString(); } + + /** + * The OIDs of AES in CFB and OFB mode are for a feedback of one block (128 bits). The feedback + * size is given in the name of the mode (e.g. {@code CFB8}) or as its block size. + */ + private static boolean hasFullBlockFeedback(@Nonnull Mode mode) { + if (!(mode instanceof CFB) && !(mode instanceof OFB)) { + return true; + } + final Optional feedbackSize = + mode.getBlockSize() + .map(BlockSize::getValue) + .or( + () -> + Utils.extractNumberFormString(mode.getName()) + .map(Integer::valueOf)); + return feedbackSize.map(size -> size == 128).orElse(true); + } } diff --git a/enricher/src/main/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricher.java b/enricher/src/main/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricher.java index 9653833f8..69007b7d3 100644 --- a/enricher/src/main/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricher.java +++ b/enricher/src/main/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricher.java @@ -24,6 +24,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.collections.AbstractAssetCollection; import java.util.ArrayList; +import java.util.List; import javax.annotation.Nonnull; public class AbstractAssetCollectionEnricher implements IEnricher { @@ -31,9 +32,34 @@ public class AbstractAssetCollectionEnricher implements IEnricher { @Nonnull @Override public INode enrich(@Nonnull INode node) { - if (node instanceof AbstractAssetCollection aac) { - Enricher.enrich(new ArrayList(aac.getCollection())); + if (node instanceof AbstractAssetCollection collection) { + return withEnrichedAssets(collection); } return node; } + + /** + * A collection holding the enriched assets of the given one, and its children. An enricher can + * return a new node for an asset, e.g. AES in GCM mode as authenticated encryption; an asset + * that would change class keeps the one it has, so the collection keeps its element type. + */ + @Nonnull + private static AbstractAssetCollection withEnrichedAssets( + @Nonnull AbstractAssetCollection collection) { + final List enrichedAssets = new ArrayList<>(); + for (final K asset : collection.getCollection()) { + final INode enriched = Enricher.enrich(List.of(asset)).iterator().next(); + enrichedAssets.add(sameClassOr(asset, enriched)); + } + final AbstractAssetCollection enrichedCollection = + collection.createMerged(enrichedAssets); + collection.getChildren().values().forEach(enrichedCollection::put); + return enrichedCollection; + } + + @Nonnull + @SuppressWarnings("unchecked") + private static K sameClassOr(@Nonnull K asset, @Nonnull INode enriched) { + return asset.getClass().isInstance(enriched) ? (K) enriched : asset; + } } diff --git a/enricher/src/test/java/com/ibm/enricher/algorithm/AESEnricherTest.java b/enricher/src/test/java/com/ibm/enricher/algorithm/AESEnricherTest.java index 52abe1beb..bb23e0462 100644 --- a/enricher/src/test/java/com/ibm/enricher/algorithm/AESEnricherTest.java +++ b/enricher/src/test/java/com/ibm/enricher/algorithm/AESEnricherTest.java @@ -24,12 +24,17 @@ import com.ibm.enricher.TestBase; import com.ibm.mapper.model.AuthenticatedEncryption; import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.algorithms.AES; import com.ibm.mapper.model.mode.CBC; +import com.ibm.mapper.model.mode.CFB; import com.ibm.mapper.model.mode.ECB; import com.ibm.mapper.model.mode.GCM; +import com.ibm.mapper.model.mode.GMAC; +import com.ibm.mapper.model.mode.OFB; import com.ibm.mapper.model.padding.PKCS1; import com.ibm.mapper.utils.DetectionLocation; import java.util.List; @@ -136,4 +141,71 @@ void defaultKeyLengthForJca() { assertThat(enrichedAES.getKeyLength()).isPresent(); assertThat(enrichedAES.getKeyLength().get().asString()).isEqualTo("128"); } + + @Test + void cfbAndOfbOidsOnlyForFullBlockFeedback() { + DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + final AESEnricher aesEnricher = new AESEnricher(); + + // the NIST OIDs for AES in CFB and OFB mode are for a 128-bit feedback + assertThat(oidOf(aesEnricher.enrich(new AES(128, new CFB(location), location)))) + .isEqualTo("2.16.840.1.101.3.4.1.4"); + assertThat(oidOf(aesEnricher.enrich(new AES(256, new CFB(128, location), location)))) + .isEqualTo("2.16.840.1.101.3.4.1.44"); + assertThat(oidOf(aesEnricher.enrich(new AES(192, new OFB(location), location)))) + .isEqualTo("2.16.840.1.101.3.4.1.23"); + // CFB1 and CFB8 have no OID of their own, whether the width is in the name or given as the + // block size of the mode + assertThat(oidOf(aesEnricher.enrich(new AES(128, new CFB(8, location), location)))) + .isEqualTo("2.16.840.1.101.3.4.1"); + assertThat(oidOf(aesEnricher.enrich(new AES(256, new CFB(1, location), location)))) + .isEqualTo("2.16.840.1.101.3.4.1.4"); + final CFB cfbWithBlockSize = new CFB(location); + cfbWithBlockSize.put(new BlockSize(8, location)); + assertThat(oidOf(aesEnricher.enrich(new AES(128, cfbWithBlockSize, location)))) + .isEqualTo("2.16.840.1.101.3.4.1"); + } + + @Test + void gmacOids() { + DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + final AESEnricher aesEnricher = new AESEnricher(); + + // RFC 9044: aes128-GMAC, aes192-GMAC and aes256-GMAC + for (int[] keyLengthAndOid : new int[][] {{128, 9}, {192, 29}, {256, 49}}) { + final INode enriched = + aesEnricher.enrich( + new AES(keyLengthAndOid[0], new GMAC(location), location) + .asKind(Mac.class)); + assertThat(enriched.getKind()).isEqualTo(Mac.class); + assertThat(oidOf(enriched)).isEqualTo("2.16.840.1.101.3.4.1." + keyLengthAndOid[1]); + } + } + + @Test + void aMacOtherThanGmacHasTheOidOfAes() { + DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + final AESEnricher aesEnricher = new AESEnricher(); + + // the OIDs of a mode are those of AES encryption in that mode, not of a MAC + assertThat( + oidOf( + aesEnricher.enrich( + new AES(128, new CBC(location), location) + .asKind(Mac.class)))) + .isEqualTo("2.16.840.1.101.3.4.1"); + // the OID of a GMAC depends on the key length + assertThat( + oidOf( + aesEnricher.enrich( + new AES(new GMAC(location), location).asKind(Mac.class)))) + .isEqualTo("2.16.840.1.101.3.4.1"); + } + + private static String oidOf(INode node) { + return node.hasChildOfType(Oid.class).map(INode::asString).orElseThrow(); + } } diff --git a/enricher/src/test/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricherTest.java b/enricher/src/test/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricherTest.java new file mode 100644 index 000000000..5aaeab211 --- /dev/null +++ b/enricher/src/test/java/com/ibm/enricher/algorithm/AbstractAssetCollectionEnricherTest.java @@ -0,0 +1,84 @@ +/* + * 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.enricher.algorithm; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.enricher.Enricher; +import com.ibm.enricher.TestBase; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.CipherSuite; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.collections.AssetCollection; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.mode.GCM; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** The assets held by a collection are enriched like any other asset. */ +class AbstractAssetCollectionEnricherTest extends TestBase { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + @Test + void assetOfATopLevelCollection() { + final AssetCollection collection = + new AssetCollection(List.of(new AES(128, new GCM(location), location))); + + final INode enriched = Enricher.enrich(List.of(collection)).iterator().next(); + + assertThat(enriched).isInstanceOf(AssetCollection.class); + assertAuthenticatedAesGcm(((AssetCollection) enriched).getCollection().get(0)); + } + + @Test + void assetOfACipherSuite() { + final TLS tls = new TLS(location); + tls.put( + new CipherSuiteCollection( + List.of( + new CipherSuite( + "TLS_AES_128_GCM_SHA256", + new AssetCollection( + List.of(new AES(128, new GCM(location), location))), + location)))); + + final INode enriched = Enricher.enrich(List.of(tls)).iterator().next(); + + final CipherSuite suite = + ((CipherSuiteCollection) enriched.getChildren().get(CipherSuiteCollection.class)) + .getCollection() + .get(0); + assertAuthenticatedAesGcm(suite.getAssetCollection().orElseThrow().getCollection().get(0)); + } + + private static void assertAuthenticatedAesGcm(INode node) { + assertThat(node).isInstanceOf(AES.class); + assertThat(node.getKind()).isEqualTo(AuthenticatedEncryption.class); + assertThat(node.hasChildOfType(Oid.class)) + .map(INode::asString) + .contains("2.16.840.1.101.3.4.1.6"); + } +} diff --git a/go/pom.xml b/go/pom.xml index d88f8ec34..d699e3ba2 100644 --- a/go/pom.xml +++ b/go/pom.xml @@ -12,8 +12,6 @@ go - 17 - 17 UTF-8 diff --git a/go/src/main/java/com/ibm/plugin/translation/reorganizer/GoReorganizerRules.java b/go/src/main/java/com/ibm/plugin/translation/reorganizer/GoReorganizerRules.java index ff06c2cff..f17ec4458 100644 --- a/go/src/main/java/com/ibm/plugin/translation/reorganizer/GoReorganizerRules.java +++ b/go/src/main/java/com/ibm/plugin/translation/reorganizer/GoReorganizerRules.java @@ -26,8 +26,7 @@ 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.UsualPerformActions; -import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import com.ibm.mapper.reorganizer.rules.AsymmetricBlockCipherReorganizer; import com.ibm.mapper.reorganizer.rules.CipherSuiteReorganizer; import com.ibm.mapper.reorganizer.rules.KeyAgreementReorganizer; import com.ibm.mapper.reorganizer.rules.KeyDerivationReorganizer; @@ -43,17 +42,6 @@ private GoReorganizerRules() { // private } - private static final IReorganizerRule MERGE_PKE_PARENT_AND_CHILD = - new ReorganizerRuleBuilder() - .createReorganizerRule("MERGE_PKE_PARENT_AND_CHILD") - .forNodeKind(PublicKeyEncryption.class) - .withDetectionCondition( - (node, parent, roots) -> - node.hasChildOfType(PublicKeyEncryption.class).isPresent()) - .perform( - UsualPerformActions.performMergeParentAndChildOfSameKind( - PublicKeyEncryption.class)); - @Nonnull public static List rules() { return Stream.of( @@ -71,7 +59,9 @@ public static List rules() { SignatureReorganizer.MERGE_SIGNATURE_WITH_PKE_UNDER_PRIVATE_KEY, SignatureReorganizer.MOVE_PSS_FROM_UNDER_SIGN_FUNCTION_TO_UNDER_KEY, SignatureReorganizer.MAKE_RSA_TO_SIGNATURE, - MERGE_PKE_PARENT_AND_CHILD, + AsymmetricBlockCipherReorganizer.MERGE_PKE_PARENT_AND_CHILD, + AsymmetricBlockCipherReorganizer.mergeParentAndChildOfSameAlgorithm( + PublicKeyEncryption.class), KeyDerivationReorganizer.moveModeFromParentToNode(BlockCipher.class), KeyDerivationReorganizer.moveModeFromParentToNode(MessageDigest.class), KeyAgreementReorganizer.MERGE_KEYAGREEMENT_WITH_PKE_UNDER_PRIVATE_KEY, diff --git a/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoProtocolContextTranslator.java b/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoProtocolContextTranslator.java index aa1146ef4..002084926 100644 --- a/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoProtocolContextTranslator.java +++ b/go/src/main/java/com/ibm/plugin/translation/translator/contexts/GoProtocolContextTranslator.java @@ -54,7 +54,9 @@ public Optional translate( }; } else if (value instanceof Protocol protocol) { final GoCryptoTLSVersionMapper versionMapper = new GoCryptoTLSVersionMapper(); - return versionMapper.parse(protocol.asString(), detectionLocation).map(TLS::new); + return versionMapper + .parse(protocol.asString(), detectionLocation) + .map(version -> new TLS(protocol.asString(), version)); } else if (value instanceof CipherSuite cipherSuite && detectionContext instanceof ProtocolContext protocolContext) { return switch (protocolContext.get("kind").orElse("")) { diff --git a/go/src/test/files/rules/detection/gocrypto/GoCryptoTLSTestFile.go b/go/src/test/files/rules/detection/gocrypto/GoCryptoTLSTestFile.go index 2e3f5da3a..3117e2b93 100644 --- a/go/src/test/files/rules/detection/gocrypto/GoCryptoTLSTestFile.go +++ b/go/src/test/files/rules/detection/gocrypto/GoCryptoTLSTestFile.go @@ -20,3 +20,15 @@ func main() { } defer conn.Close() } + +// VersionSSL30: the family (SSL, not TLS) must be preserved in the translated node's name. +func dialSsl30() { + confSsl30 := tls.Config{ // Noncompliant {{(TLS) SSLv3.0}} + MinVersion: tls.VersionSSL30, + } + connSsl30, errSsl30 := tls.Dial("tcp", "example.com:443", &confSsl30) + if errSsl30 != nil { + panic(errSsl30) + } + defer connSsl30.Close() +} diff --git a/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoHKDFExpandTest.java b/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoHKDFExpandTest.java index 29827c6aa..fc4c58c19 100644 --- a/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoHKDFExpandTest.java +++ b/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoHKDFExpandTest.java @@ -151,7 +151,7 @@ public void asserts( INode keyDerivationFunctionNode1 = nodes.get(1); assertThat(keyDerivationFunctionNode1).isInstanceOf(HKDF.class); assertThat(keyDerivationFunctionNode1.getKind()).isEqualTo(KeyDerivationFunction.class); - assertThat(keyDerivationFunctionNode1.getChildren()).hasSize(2); + assertThat(keyDerivationFunctionNode1.getChildren()).hasSize(3); assertThat(keyDerivationFunctionNode1.asString()).isEqualTo("HKDF-SHA-256"); // KeyLength under KeyDerivationFunction @@ -190,6 +190,12 @@ public void asserts( assertThat(blockSizeNode1).isNotNull(); assertThat(blockSizeNode1.getChildren()).isEmpty(); assertThat(blockSizeNode1.asString()).isEqualTo("512"); + + // SaltLength under KeyDerivationFunction + INode saltLengthNode1 = keyDerivationFunctionNode1.getChildren().get(SaltLength.class); + assertThat(saltLengthNode1).isNotNull(); + assertThat(saltLengthNode1.getChildren()).isEmpty(); + assertThat(saltLengthNode1.asString()).isEqualTo("48"); } } } diff --git a/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoTLSTest.java b/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoTLSTest.java index e745f07af..adee92003 100644 --- a/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoTLSTest.java +++ b/go/src/test/java/com/ibm/plugin/rules/detection/gocrypto/GoCryptoTLSTest.java @@ -57,9 +57,6 @@ public void asserts( int findingId, @Nonnull DetectionStore detectionStore, @Nonnull List nodes) { - /* - * Detection Store - */ assertThat(detectionStore).isNotNull(); assertThat(detectionStore.getDetectionValues()).hasSize(1); assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(ProtocolContext.class); @@ -67,6 +64,16 @@ public void asserts( assertThat(value0).isInstanceOf(ValueAction.class); assertThat(value0.asString()).isEqualTo("TLS"); + switch (findingId) { + case 0 -> assertTls12Config(detectionStore, nodes); + case 1 -> assertSsl30Config(detectionStore, nodes); + default -> throw new AssertionError("Unexpected findingId: " + findingId); + } + } + + private void assertTls12Config( + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { DetectionStore store1 = getStoreOfValueType(Protocol.class, detectionStore.getChildren()); assertThat(store1).isNotNull(); @@ -76,9 +83,6 @@ public void asserts( assertThat(value01).isInstanceOf(Protocol.class); assertThat(value01.asString()).isEqualTo("VersionTLS12"); - /* - * Translation - */ assertThat(nodes).hasSize(1); // TLS @@ -101,4 +105,28 @@ public void asserts( assertThat(versionNode.getChildren()).isEmpty(); assertThat(versionNode.asString()).isEqualTo("1.2"); } + + private void assertSsl30Config( + @Nonnull DetectionStore detectionStore, + @Nonnull List nodes) { + DetectionStore store1 = + getStoreOfValueType(Protocol.class, detectionStore.getChildren()); + assertThat(store1).isNotNull(); + assertThat(store1.getDetectionValues()).hasSize(1); + IValue value01 = store1.getDetectionValues().get(0); + assertThat(value01).isInstanceOf(Protocol.class); + assertThat(value01.asString()).isEqualTo("VersionSSL30"); + + assertThat(nodes).hasSize(1); + + // TLS node, but named after the detected family (SSL), not hardcoded "TLS". + INode tLSNode = nodes.get(0); + assertThat(tLSNode.getKind()).isEqualTo(TLS.class); + assertThat(tLSNode.asString()).isEqualTo("SSLv3.0"); + + // Version under TLS + INode versionNode = tLSNode.getChildren().get(Version.class); + assertThat(versionNode).isNotNull(); + assertThat(versionNode.asString()).isEqualTo("3.0"); + } } diff --git a/java/pom.xml b/java/pom.xml index 404423534..f46455e51 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -12,8 +12,6 @@ java - 17 - 17 UTF-8 diff --git a/java/src/main/java/com/ibm/plugin/translation/translator/contexts/JavaProtocolContextTranslator.java b/java/src/main/java/com/ibm/plugin/translation/translator/contexts/JavaProtocolContextTranslator.java index 8e2710c9a..fcec25651 100644 --- a/java/src/main/java/com/ibm/plugin/translation/translator/contexts/JavaProtocolContextTranslator.java +++ b/java/src/main/java/com/ibm/plugin/translation/translator/contexts/JavaProtocolContextTranslator.java @@ -60,7 +60,9 @@ public Optional translate( new SSLVersionMapper(); return sslVersionMapper .parse(p.asString(), detectionLocation) - .map(TLS::new) + .map( + version -> + new TLS(p.asString(), version)) .orElse(new TLS(detectionLocation)); }); default -> diff --git a/java/src/test/files/rules/detection/bc/mac/BcGMacTestFile.java b/java/src/test/files/rules/detection/bc/mac/BcGMacTestFile.java index 11bd8ac65..7e9295e76 100644 --- a/java/src/test/files/rules/detection/bc/mac/BcGMacTestFile.java +++ b/java/src/test/files/rules/detection/bc/mac/BcGMacTestFile.java @@ -36,7 +36,7 @@ public static void exampleGMAC() { MultiBlockCipher aesEngine = AESEngine.newInstance(); // Noncompliant {{(BlockCipher) AES}} GCMModeCipher blockCipher = GCMBlockCipher.newInstance(aesEngine); // Noncompliant {{(AuthenticatedEncryption) AES-GCM}} - GMac gmac = new GMac(blockCipher, 128); // Noncompliant {{(Mac) AES}} + GMac gmac = new GMac(blockCipher, 128); // Noncompliant {{(Mac) AES-GMAC}} CipherParameters params = new KeyParameter(key); gmac.init(params); diff --git a/java/src/test/files/rules/detection/ssl/SSLContextGetInstanceTestFile.java b/java/src/test/files/rules/detection/ssl/SSLContextGetInstanceTestFile.java index 7793e8b89..cffe39d7c 100644 --- a/java/src/test/files/rules/detection/ssl/SSLContextGetInstanceTestFile.java +++ b/java/src/test/files/rules/detection/ssl/SSLContextGetInstanceTestFile.java @@ -12,4 +12,16 @@ void test() throws KeyManagementException, NoSuchAlgorithmException { SSLSocketFactory socketFactory = sslContext.getSocketFactory(); } + void testSslv3() throws KeyManagementException, NoSuchAlgorithmException { + // Noncompliant@+1 {{(TLS) SSLv3.0}} + SSLContext sslContext = SSLContext.getInstance("SSLv3"); + sslContext.init(null, null, new SecureRandom()); + } + + void testDtlsv12() throws KeyManagementException, NoSuchAlgorithmException { + // Noncompliant@+1 {{(TLS) DTLSv1.2}} + SSLContext sslContext = SSLContext.getInstance("DTLSv1.2"); + sslContext.init(null, null, new SecureRandom()); + } + } \ No newline at end of file diff --git a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcBlockCipherMacTest.java b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcBlockCipherMacTest.java index bfd38e338..5c53a3065 100644 --- a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcBlockCipherMacTest.java +++ b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcBlockCipherMacTest.java @@ -30,6 +30,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.model.functionality.Tag; import com.ibm.plugin.TestBase; @@ -101,9 +102,15 @@ public void asserts( // Mac INode macNode = nodes.get(0); assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(4); + assertThat(macNode.getChildren()).hasSize(5); assertThat(macNode.asString()).isEqualTo("AES"); + // Oid under Mac + INode oidNode = macNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + // Tag under Mac INode tagNode = macNode.getChildren().get(Tag.class); assertThat(tagNode).isNotNull(); diff --git a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCBCBlockCipherMacTest.java b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCBCBlockCipherMacTest.java index 9c4a89564..53b574e1c 100644 --- a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCBCBlockCipherMacTest.java +++ b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCBCBlockCipherMacTest.java @@ -32,6 +32,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Padding; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.model.functionality.Encrypt; @@ -138,9 +139,15 @@ public void asserts( // Mac INode macNode3 = nodes.get(0); assertThat(macNode3.getKind()).isEqualTo(Mac.class); - assertThat(macNode3.getChildren()).hasSize(findingId == 7 ? 6 : 5); + assertThat(macNode3.getChildren()).hasSize(findingId == 7 ? 7 : 6); assertThat(macNode3.asString()).isEqualTo("AES"); + // Oid under Mac + INode oidNode3 = macNode3.getChildren().get(Oid.class); + assertThat(oidNode3).isNotNull(); + assertThat(oidNode3.getChildren()).isEmpty(); + assertThat(oidNode3.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + if (findingId == 1 || findingId == 5 || findingId == 7) { // TagLength under Mac INode tagLengthNode2 = macNode3.getChildren().get(TagLength.class); diff --git a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCFBBlockCipherMacTest.java b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCFBBlockCipherMacTest.java index f84a3f5f3..44f014212 100644 --- a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCFBBlockCipherMacTest.java +++ b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcCFBBlockCipherMacTest.java @@ -31,6 +31,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Padding; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.model.functionality.Tag; @@ -122,9 +123,15 @@ public void asserts( // Mac INode macNode = nodes.get(0); assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(5); + assertThat(macNode.getChildren()).hasSize(6); assertThat(macNode.asString()).isEqualTo("AES"); + // Oid under Mac + INode oidNode = macNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + // Tag under Mac INode tagNode = macNode.getChildren().get(Tag.class); assertThat(tagNode).isNotNull(); diff --git a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcGMACTest.java b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcGMACTest.java index 1307b2bba..02a4085bf 100644 --- a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcGMACTest.java +++ b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcGMACTest.java @@ -30,6 +30,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.model.functionality.Tag; import com.ibm.plugin.TestBase; @@ -101,8 +102,14 @@ public void asserts( // Mac INode macNode = nodes.get(0); assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(4); - assertThat(macNode.asString()).isEqualTo("AES"); + assertThat(macNode.getChildren()).hasSize(5); + assertThat(macNode.asString()).isEqualTo("AES-GMAC"); + + // Oid under Mac + INode oidNode = macNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); // TagLength under Mac INode tagLengthNode = macNode.getChildren().get(TagLength.class); diff --git a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcISO9797Alg3MacTest.java b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcISO9797Alg3MacTest.java index 79820b688..858caaf01 100644 --- a/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcISO9797Alg3MacTest.java +++ b/java/src/test/java/com/ibm/plugin/rules/detection/bc/mac/BcISO9797Alg3MacTest.java @@ -30,6 +30,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Padding; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.model.functionality.Tag; @@ -112,9 +113,15 @@ public void asserts( // Mac INode macNode = nodes.get(0); assertThat(macNode.getKind()).isEqualTo(Mac.class); - assertThat(macNode.getChildren()).hasSize(5); + assertThat(macNode.getChildren()).hasSize(6); assertThat(macNode.asString()).isEqualTo("AES"); + // Oid under Mac + INode oidNode = macNode.getChildren().get(Oid.class); + assertThat(oidNode).isNotNull(); + assertThat(oidNode.getChildren()).isEmpty(); + assertThat(oidNode.asString()).isEqualTo("2.16.840.1.101.3.4.1"); + // Mode under Mac INode modeNode = macNode.getChildren().get(Mode.class); assertThat(modeNode).isNotNull(); diff --git a/java/src/test/java/com/ibm/plugin/rules/detection/ssl/SSLContextGetInstanceTest.java b/java/src/test/java/com/ibm/plugin/rules/detection/ssl/SSLContextGetInstanceTest.java index ca2be7da8..08c5f5e7e 100644 --- a/java/src/test/java/com/ibm/plugin/rules/detection/ssl/SSLContextGetInstanceTest.java +++ b/java/src/test/java/com/ibm/plugin/rules/detection/ssl/SSLContextGetInstanceTest.java @@ -58,28 +58,41 @@ public void asserts( int findingId, @Nonnull DetectionStore detectionStore, @Nonnull List nodes) { - /* - * Detection Store - */ assertThat(detectionStore.getDetectionValues()).hasSize(1); assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(ProtocolContext.class); IValue value0 = detectionStore.getDetectionValues().get(0); assertThat(value0).isInstanceOf(Protocol.class); - assertThat(value0.asString()).isEqualTo("TLSv1.2"); - /* - * Translation - */ + switch (findingId) { + case 0 -> { + assertThat(value0.asString()).isEqualTo("TLSv1.2"); + assertTlsVersion(nodes, "TLSv1.2", "1.2"); + } + // SSLVersionMapper's regex also matches ssl/dtls prefixes (not just tls), so + // SSLContext.getInstance("SSLv3") and ("DTLSv1.2") resolve to a versioned TLS + // node too, the same as a TLSv* algorithm name. + case 1 -> { + assertThat(value0.asString()).isEqualTo("SSLv3"); + assertTlsVersion(nodes, "SSLv3.0", "3.0"); + } + case 2 -> { + assertThat(value0.asString()).isEqualTo("DTLSv1.2"); + assertTlsVersion(nodes, "DTLSv1.2", "1.2"); + } + default -> throw new AssertionError("Unexpected findingId: " + findingId); + } + } + + private static void assertTlsVersion( + @Nonnull List nodes, @Nonnull String asString, @Nonnull String version) { assertThat(nodes).hasSize(1); - // TLSProtocol - INode tLSProtocolNode = nodes.get(0); - assertThat(tLSProtocolNode.getKind()).isEqualTo(TLS.class); - assertThat(tLSProtocolNode.getChildren()).hasSize(1); - assertThat(tLSProtocolNode.asString()).isEqualTo("TLSv1.2"); - // Version under TLSProtocol - INode versionNode = tLSProtocolNode.getChildren().get(Version.class); + INode tlsProtocolNode = nodes.get(0); + assertThat(tlsProtocolNode.getKind()).isEqualTo(TLS.class); + assertThat(tlsProtocolNode.getChildren()).hasSize(1); + assertThat(tlsProtocolNode.asString()).isEqualTo(asString); + INode versionNode = tlsProtocolNode.getChildren().get(Version.class); assertThat(versionNode).isNotNull(); assertThat(versionNode.getChildren()).isEmpty(); - assertThat(versionNode.asString()).isEqualTo("1.2"); + assertThat(versionNode.asString()).isEqualTo(version); } } diff --git a/mapper/ciphersuites.json b/mapper/ciphersuites.json index 44542d946..d904cf832 100644 --- a/mapper/ciphersuites.json +++ b/mapper/ciphersuites.json @@ -1 +1,6696 @@ -{"ciphersuites": [{"TLS_AES_128_CCM_8_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x13", "hex_byte_2": "0x05", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.3"]}}, {"TLS_AES_128_CCM_SHA256": {"gnutls_name": "", "openssl_name": "TLS_AES_128_CCM_SHA256", "hex_byte_1": "0x13", "hex_byte_2": "0x04", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.3"]}}, {"TLS_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "TLS_AES_128_GCM_SHA256", "hex_byte_1": "0x13", "hex_byte_2": "0x01", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.3"]}}, {"TLS_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "TLS_AES_256_GCM_SHA384", "hex_byte_1": "0x13", "hex_byte_2": "0x02", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "recommended", "tls_version": ["TLS1.3"]}}, {"TLS_CHACHA20_POLY1305_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x13", "hex_byte_2": "0x03", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.3"]}}, {"TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x19", "protocol_version": "TLS EXPORT", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "DES40 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_EXPORT_WITH_RC4_40_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x17", "protocol_version": "TLS EXPORT", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "RC4 40", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_DH_ANON_3DES_EDE_CBC_SHA1", "openssl_name": "ADH-DES-CBC3-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x1B", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_DH_ANON_AES_128_CBC_SHA1", "openssl_name": "ADH-AES128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x34", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_DH_ANON_AES_128_CBC_SHA256", "openssl_name": "ADH-AES128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x6C", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_DH_ANON_AES_128_GCM_SHA256", "openssl_name": "ADH-AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xA6", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_DH_ANON_AES_256_CBC_SHA1", "openssl_name": "ADH-AES256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x3A", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_AES_256_CBC_SHA256": {"gnutls_name": "TLS_DH_ANON_AES_256_CBC_SHA256", "openssl_name": "ADH-AES256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x6D", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_DH_ANON_AES_256_GCM_SHA384", "openssl_name": "ADH-AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xA7", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x46", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x5A", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x47", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x5B", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": {"gnutls_name": "TLS_DH_ANON_CAMELLIA_128_CBC_SHA1", "openssl_name": "ADH-CAMELLIA128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x46", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_DH_ANON_CAMELLIA_128_CBC_SHA256", "openssl_name": "ADH-CAMELLIA128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xBF", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_DH_ANON_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x84", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": {"gnutls_name": "TLS_DH_ANON_CAMELLIA_256_CBC_SHA1", "openssl_name": "ADH-CAMELLIA256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x89", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256": {"gnutls_name": "TLS_DH_ANON_CAMELLIA_256_CBC_SHA256", "openssl_name": "ADH-CAMELLIA256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xC5", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_DH_ANON_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x85", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x1A", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_RC4_128_MD5": {"gnutls_name": "TLS_DH_ANON_ARCFOUR_128_MD5", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x18", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "RC4 128", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_anon_WITH_SEED_CBC_SHA": {"gnutls_name": "", "openssl_name": "ADH-SEED-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x9B", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "anon", "enc_algorithm": "SEED CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x0B", "protocol_version": "TLS EXPORT", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "DES40 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x0D", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_AES_128_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x30", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_AES_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x3E", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xA4", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_AES_256_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x36", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_AES_256_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x68", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xA5", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x3E", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x58", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x3F", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x59", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x42", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xBB", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x82", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x85", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xC1", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x83", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x0C", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_DSS_WITH_SEED_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x97", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "DSS", "enc_algorithm": "SEED CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x11", "protocol_version": "TLS EXPORT", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "DES40 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_DHE_DSS_3DES_EDE_CBC_SHA1", "openssl_name": "DHE-DSS-DES-CBC3-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x13", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_DHE_DSS_AES_128_CBC_SHA1", "openssl_name": "DHE-DSS-AES128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x32", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_DHE_DSS_AES_128_CBC_SHA256", "openssl_name": "DHE-DSS-AES128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x40", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_DHE_DSS_AES_128_GCM_SHA256", "openssl_name": "DHE-DSS-AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xA2", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_DHE_DSS_AES_256_CBC_SHA1", "openssl_name": "DHE-DSS-AES256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x38", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": {"gnutls_name": "TLS_DHE_DSS_AES_256_CBC_SHA256", "openssl_name": "DHE-DSS-AES256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x6A", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_DHE_DSS_AES_256_GCM_SHA384", "openssl_name": "DHE-DSS-AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xA3", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x42", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x56", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x43", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x57", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": {"gnutls_name": "TLS_DHE_DSS_CAMELLIA_128_CBC_SHA1", "openssl_name": "DHE-DSS-CAMELLIA128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x44", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_DHE_DSS_CAMELLIA_128_CBC_SHA256", "openssl_name": "DHE-DSS-CAMELLIA128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xBD", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_DHE_DSS_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x80", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": {"gnutls_name": "TLS_DHE_DSS_CAMELLIA_256_CBC_SHA1", "openssl_name": "DHE-DSS-CAMELLIA256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x87", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256": {"gnutls_name": "TLS_DHE_DSS_CAMELLIA_256_CBC_SHA256", "openssl_name": "DHE-DSS-CAMELLIA256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xC3", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_DHE_DSS_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x81", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x12", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_DSS_WITH_SEED_CBC_SHA": {"gnutls_name": "", "openssl_name": "DHE-DSS-SEED-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x99", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "DSS", "enc_algorithm": "SEED CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_DHE_PSK_3DES_EDE_CBC_SHA1", "openssl_name": "DHE-PSK-3DES-EDE-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x8F", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_DHE_PSK_AES_128_CBC_SHA1", "openssl_name": "DHE-PSK-AES128-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x90", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_DHE_PSK_AES_128_CBC_SHA256", "openssl_name": "DHE-PSK-AES128-CBC-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xB2", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_128_CCM": {"gnutls_name": "TLS_DHE_PSK_AES_128_CCM", "openssl_name": "DHE-PSK-AES128-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0xA6", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_DHE_PSK_AES_128_GCM_SHA256", "openssl_name": "DHE-PSK-AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xAA", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_DHE_PSK_AES_256_CBC_SHA1", "openssl_name": "DHE-PSK-AES256-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x91", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_256_CBC_SHA384": {"gnutls_name": "TLS_DHE_PSK_AES_256_CBC_SHA384", "openssl_name": "DHE-PSK-AES256-CBC-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xB3", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_256_CCM": {"gnutls_name": "TLS_DHE_PSK_AES_256_CCM", "openssl_name": "DHE-PSK-AES256-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0xA7", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_DHE_PSK_AES_256_GCM_SHA384", "openssl_name": "DHE-PSK-AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xAB", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x66", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x6C", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x67", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x6D", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_DHE_PSK_CAMELLIA_128_CBC_SHA256", "openssl_name": "DHE-PSK-CAMELLIA128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x96", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_DHE_PSK_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x90", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "TLS_DHE_PSK_CAMELLIA_256_CBC_SHA384", "openssl_name": "DHE-PSK-CAMELLIA256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x97", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_DHE_PSK_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x91", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_DHE_PSK_CHACHA20_POLY1305", "openssl_name": "DHE-PSK-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xAD", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_NULL_SHA": {"gnutls_name": "TLS_DHE_PSK_NULL_SHA1", "openssl_name": "DHE-PSK-NULL-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x2D", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_NULL_SHA256": {"gnutls_name": "TLS_DHE_PSK_NULL_SHA256", "openssl_name": "DHE-PSK-NULL-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xB4", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_NULL_SHA384": {"gnutls_name": "TLS_DHE_PSK_NULL_SHA384", "openssl_name": "DHE-PSK-NULL-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xB5", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_PSK_WITH_RC4_128_SHA": {"gnutls_name": "TLS_DHE_PSK_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x8E", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x14", "protocol_version": "TLS EXPORT", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "DES40 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_DHE_RSA_3DES_EDE_CBC_SHA1", "openssl_name": "DHE-RSA-DES-CBC3-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x16", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_DHE_RSA_AES_128_CBC_SHA1", "openssl_name": "DHE-RSA-AES128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x33", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_DHE_RSA_AES_128_CBC_SHA256", "openssl_name": "DHE-RSA-AES128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x67", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_128_CCM": {"gnutls_name": "TLS_DHE_RSA_AES_128_CCM", "openssl_name": "DHE-RSA-AES128-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0x9E", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_128_CCM_8": {"gnutls_name": "TLS_DHE_RSA_AES_128_CCM_8", "openssl_name": "DHE-RSA-AES128-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xA2", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_DHE_RSA_AES_128_GCM_SHA256", "openssl_name": "DHE-RSA-AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x9E", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_DHE_RSA_AES_256_CBC_SHA1", "openssl_name": "DHE-RSA-AES256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x39", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": {"gnutls_name": "TLS_DHE_RSA_AES_256_CBC_SHA256", "openssl_name": "DHE-RSA-AES256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x6B", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_256_CCM": {"gnutls_name": "TLS_DHE_RSA_AES_256_CCM", "openssl_name": "DHE-RSA-AES256-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0x9F", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_256_CCM_8": {"gnutls_name": "TLS_DHE_RSA_AES_256_CCM_8", "openssl_name": "DHE-RSA-AES256-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xA3", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_DHE_RSA_AES_256_GCM_SHA384", "openssl_name": "DHE-RSA-AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0x9F", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x44", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x52", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x45", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x53", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": {"gnutls_name": "TLS_DHE_RSA_CAMELLIA_128_CBC_SHA1", "openssl_name": "DHE-RSA-CAMELLIA128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x45", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_DHE_RSA_CAMELLIA_128_CBC_SHA256", "openssl_name": "DHE-RSA-CAMELLIA128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xBE", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_DHE_RSA_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x7C", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": {"gnutls_name": "TLS_DHE_RSA_CAMELLIA_256_CBC_SHA1", "openssl_name": "DHE-RSA-CAMELLIA256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x88", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256": {"gnutls_name": "TLS_DHE_RSA_CAMELLIA_256_CBC_SHA256", "openssl_name": "DHE-RSA-CAMELLIA256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xC4", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_DHE_RSA_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x7D", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_DHE_RSA_CHACHA20_POLY1305", "openssl_name": "DHE-RSA-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xAA", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x15", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DHE_RSA_WITH_SEED_CBC_SHA": {"gnutls_name": "", "openssl_name": "DHE-RSA-SEED-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x9A", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "RSA", "enc_algorithm": "SEED CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x0E", "protocol_version": "TLS EXPORT", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "DES40 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x10", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x31", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x3F", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xA0", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x37", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_AES_256_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x69", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xA1", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x40", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x54", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x41", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x55", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x43", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xBC", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x7E", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x86", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xC2", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x7F", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x0F", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_DH_RSA_WITH_SEED_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x98", "protocol_version": "TLS", "kex_algorithm": "DH", "auth_algorithm": "RSA", "enc_algorithm": "SEED CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECCPWD_WITH_AES_128_CCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0xB2", "protocol_version": "TLS", "kex_algorithm": "ECCPWD", "auth_algorithm": "ECCPWD", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECCPWD_WITH_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0xB0", "protocol_version": "TLS", "kex_algorithm": "ECCPWD", "auth_algorithm": "ECCPWD", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECCPWD_WITH_AES_256_CCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0xB3", "protocol_version": "TLS", "kex_algorithm": "ECCPWD", "auth_algorithm": "ECCPWD", "enc_algorithm": "AES 256 CCM", "hash_algorithm": "SHA384", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECCPWD_WITH_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0xB1", "protocol_version": "TLS", "kex_algorithm": "ECCPWD", "auth_algorithm": "ECCPWD", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_ECDH_ANON_3DES_EDE_CBC_SHA1", "openssl_name": "AECDH-DES-CBC3-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x17", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "anon", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_anon_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_ECDH_ANON_AES_128_CBC_SHA1", "openssl_name": "AECDH-AES128-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x18", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "anon", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_anon_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_ECDH_ANON_AES_256_CBC_SHA1", "openssl_name": "AECDH-AES256-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x19", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "anon", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_anon_WITH_NULL_SHA": {"gnutls_name": "TLS_ECDH_ANON_NULL_SHA1", "openssl_name": "AECDH-NULL-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x15", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "anon", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_anon_WITH_RC4_128_SHA": {"gnutls_name": "TLS_ECDH_ANON_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x16", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "anon", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x03", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x04", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x25", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x2D", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x05", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x26", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x2E", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x4A", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x5E", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x4B", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x5F", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x74", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x88", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x75", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x89", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_NULL_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x01", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_ECDSA_WITH_RC4_128_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x02", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "ECDSA", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_ECDHE_ECDSA_3DES_EDE_CBC_SHA1", "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x08", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CBC_SHA1", "openssl_name": "ECDHE-ECDSA-AES128-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x09", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CBC_SHA256", "openssl_name": "ECDHE-ECDSA-AES128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x23", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_128_CCM": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CCM", "openssl_name": "ECDHE-ECDSA-AES128-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0xAC", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CCM_8", "openssl_name": "ECDHE-ECDSA-AES128-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xAE", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_128_GCM_SHA256", "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x2B", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CBC_SHA1", "openssl_name": "ECDHE-ECDSA-AES256-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x0A", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CBC_SHA384", "openssl_name": "ECDHE-ECDSA-AES256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x24", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_256_CCM": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CCM", "openssl_name": "ECDHE-ECDSA-AES256-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0xAD", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 CCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CCM_8", "openssl_name": "ECDHE-ECDSA-AES256-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xAF", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 CCM 8", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_ECDHE_ECDSA_AES_256_GCM_SHA384", "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x2C", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x48", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x5C", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x49", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x5D", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_128_CBC_SHA256", "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x72", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x86", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_256_CBC_SHA384", "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x73", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x87", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_ECDHE_ECDSA_CHACHA20_POLY1305", "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xA9", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_NULL_SHA": {"gnutls_name": "TLS_ECDHE_ECDSA_NULL_SHA1", "openssl_name": "ECDHE-ECDSA-NULL-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x06", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": {"gnutls_name": "TLS_ECDHE_ECDSA_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x07", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "ECDSA", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_ECDHE_PSK_3DES_EDE_CBC_SHA1", "openssl_name": "ECDHE-PSK-3DES-EDE-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x34", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_ECDHE_PSK_AES_128_CBC_SHA1", "openssl_name": "ECDHE-PSK-AES128-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x35", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_ECDHE_PSK_AES_128_CBC_SHA256", "openssl_name": "ECDHE-PSK-AES128-CBC-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x37", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xD0", "hex_byte_2": "0x03", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xD0", "hex_byte_2": "0x05", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xD0", "hex_byte_2": "0x01", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_ECDHE_PSK_AES_256_CBC_SHA1", "openssl_name": "ECDHE-PSK-AES256-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x36", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384": {"gnutls_name": "TLS_ECDHE_PSK_AES_256_CBC_SHA384", "openssl_name": "ECDHE-PSK-AES256-CBC-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x38", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xD0", "hex_byte_2": "0x02", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x70", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x71", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_ECDHE_PSK_CAMELLIA_128_CBC_SHA256", "openssl_name": "ECDHE-PSK-CAMELLIA128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x9A", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "TLS_ECDHE_PSK_CAMELLIA_256_CBC_SHA384", "openssl_name": "ECDHE-PSK-CAMELLIA256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x9B", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_ECDHE_PSK_CHACHA20_POLY1305", "openssl_name": "ECDHE-PSK-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xAC", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "recommended", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_NULL_SHA": {"gnutls_name": "TLS_ECDHE_PSK_NULL_SHA1", "openssl_name": "ECDHE-PSK-NULL-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x39", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_NULL_SHA256": {"gnutls_name": "TLS_ECDHE_PSK_NULL_SHA256", "openssl_name": "ECDHE-PSK-NULL-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x3A", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_NULL_SHA384": {"gnutls_name": "TLS_ECDHE_PSK_NULL_SHA384", "openssl_name": "ECDHE-PSK-NULL-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x3B", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_PSK_WITH_RC4_128_SHA": {"gnutls_name": "TLS_ECDHE_PSK_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x33", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "PSK", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_ECDHE_RSA_3DES_EDE_CBC_SHA1", "openssl_name": "ECDHE-RSA-DES-CBC3-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x12", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_ECDHE_RSA_AES_128_CBC_SHA1", "openssl_name": "ECDHE-RSA-AES128-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x13", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_ECDHE_RSA_AES_128_CBC_SHA256", "openssl_name": "ECDHE-RSA-AES128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x27", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_ECDHE_RSA_AES_128_GCM_SHA256", "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x2F", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_ECDHE_RSA_AES_256_CBC_SHA1", "openssl_name": "ECDHE-RSA-AES256-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x14", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": {"gnutls_name": "TLS_ECDHE_RSA_AES_256_CBC_SHA384", "openssl_name": "ECDHE-RSA-AES256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x28", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_ECDHE_RSA_AES_256_GCM_SHA384", "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x30", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x4C", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x60", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x4D", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x61", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_128_CBC_SHA256", "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x76", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x8A", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_256_CBC_SHA384", "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x77", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x8B", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_ECDHE_RSA_CHACHA20_POLY1305", "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xA8", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "secure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_NULL_SHA": {"gnutls_name": "TLS_ECDHE_RSA_NULL_SHA1", "openssl_name": "ECDHE-RSA-NULL-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x10", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDHE_RSA_WITH_RC4_128_SHA": {"gnutls_name": "TLS_ECDHE_RSA_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x11", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "RSA", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x0D", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x0E", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x29", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x31", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x0F", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x2A", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x32", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x4E", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x62", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x4F", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x63", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x78", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x8C", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x79", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x8D", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_NULL_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x0B", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_ECDH_RSA_WITH_RC4_128_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x0C", "protocol_version": "TLS", "kex_algorithm": "ECDH", "auth_algorithm": "RSA", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_28147_CNT_IMIT": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x02", "protocol_version": "TLS", "kex_algorithm": "GOSTR341112 256", "auth_algorithm": "GOSTR341012", "enc_algorithm": "28147 CNT", "hash_algorithm": "GOSTR341112", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x00", "protocol_version": "TLS", "kex_algorithm": "GOSTR341112 256", "auth_algorithm": "GOSTR341012", "enc_algorithm": "KUZNYECHIK CTR", "hash_algorithm": "GOSTR341112", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x03", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "-", "enc_algorithm": "KUZNYECHIK MGM L", "hash_algorithm": "-", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x05", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "-", "enc_algorithm": "KUZNYECHIK MGM S", "hash_algorithm": "-", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x01", "protocol_version": "TLS", "kex_algorithm": "GOSTR341112 256", "auth_algorithm": "GOSTR341012", "enc_algorithm": "MAGMA CTR", "hash_algorithm": "GOSTR341112", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_MAGMA_MGM_L": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x04", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "-", "enc_algorithm": "MAGMA MGM L", "hash_algorithm": "-", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_GOSTR341112_256_WITH_MAGMA_MGM_S": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC1", "hex_byte_2": "0x06", "protocol_version": "TLS", "kex_algorithm": "ECDHE", "auth_algorithm": "-", "enc_algorithm": "MAGMA MGM S", "hash_algorithm": "-", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x29", "protocol_version": "TLS EXPORT", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "DES CBC 40", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x26", "protocol_version": "TLS EXPORT", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "DES CBC 40", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x2A", "protocol_version": "TLS EXPORT", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "RC2 CBC 40", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x27", "protocol_version": "TLS EXPORT", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "RC2 CBC 40", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_EXPORT_WITH_RC4_40_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x2B", "protocol_version": "TLS EXPORT", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "RC4 40", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_EXPORT_WITH_RC4_40_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x28", "protocol_version": "TLS EXPORT", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "RC4 40", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_3DES_EDE_CBC_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x23", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x1F", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_DES_CBC_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x22", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "DES CBC", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x1E", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_IDEA_CBC_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x25", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "IDEA CBC", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_IDEA_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x21", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "IDEA CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_RC4_128_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x24", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "RC4 128", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_KRB5_WITH_RC4_128_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x20", "protocol_version": "TLS", "kex_algorithm": "KRB5", "auth_algorithm": "KRB5", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_NULL_WITH_NULL_NULL": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x00", "protocol_version": "TLS", "kex_algorithm": "NULL", "auth_algorithm": "NULL", "enc_algorithm": "NULL", "hash_algorithm": "NULL", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_DHE_WITH_AES_128_CCM_8": {"gnutls_name": "TLS_DHE_PSK_AES_128_CCM_8", "openssl_name": "DHE-PSK-AES128-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xAA", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_DHE_WITH_AES_256_CCM_8": {"gnutls_name": "TLS_DHE_PSK_AES_256_CCM_8", "openssl_name": "DHE-PSK-AES256-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xAB", "protocol_version": "TLS", "kex_algorithm": "DHE", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_PSK_3DES_EDE_CBC_SHA1", "openssl_name": "PSK-3DES-EDE-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x8B", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_PSK_AES_128_CBC_SHA1", "openssl_name": "PSK-AES128-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x8C", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_PSK_AES_128_CBC_SHA256", "openssl_name": "PSK-AES128-CBC-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xAE", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_128_CCM": {"gnutls_name": "TLS_PSK_AES_128_CCM", "openssl_name": "PSK-AES128-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0xA4", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_128_CCM_8": {"gnutls_name": "TLS_PSK_AES_128_CCM_8", "openssl_name": "PSK-AES128-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xA8", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_PSK_AES_128_GCM_SHA256", "openssl_name": "PSK-AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xA8", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_PSK_AES_256_CBC_SHA1", "openssl_name": "PSK-AES256-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x8D", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_256_CBC_SHA384": {"gnutls_name": "TLS_PSK_AES_256_CBC_SHA384", "openssl_name": "PSK-AES256-CBC-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xAF", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_256_CCM": {"gnutls_name": "TLS_PSK_AES_256_CCM", "openssl_name": "PSK-AES256-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0xA5", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_256_CCM_8": {"gnutls_name": "TLS_PSK_AES_256_CCM_8", "openssl_name": "PSK-AES256-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xA9", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_PSK_AES_256_GCM_SHA384", "openssl_name": "PSK-AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xA9", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x64", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x6A", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x65", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x6B", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_PSK_CAMELLIA_128_CBC_SHA256", "openssl_name": "PSK-CAMELLIA128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x94", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_PSK_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x8E", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "TLS_PSK_CAMELLIA_256_CBC_SHA384", "openssl_name": "PSK-CAMELLIA256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x95", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_PSK_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x8F", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_PSK_CHACHA20_POLY1305", "openssl_name": "PSK-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xAB", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_NULL_SHA": {"gnutls_name": "TLS_PSK_NULL_SHA1", "openssl_name": "PSK-NULL-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x2C", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_NULL_SHA256": {"gnutls_name": "TLS_PSK_NULL_SHA256", "openssl_name": "PSK-NULL-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xB0", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_NULL_SHA384": {"gnutls_name": "TLS_PSK_NULL_SHA384", "openssl_name": "PSK-NULL-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xB1", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_PSK_WITH_RC4_128_SHA": {"gnutls_name": "TLS_PSK_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x8A", "protocol_version": "TLS", "kex_algorithm": "PSK", "auth_algorithm": "PSK", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_EXPORT_WITH_DES40_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x08", "protocol_version": "TLS EXPORT", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "DES40 CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x06", "protocol_version": "TLS EXPORT", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "RC2 CBC 40", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_EXPORT_WITH_RC4_40_MD5": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x03", "protocol_version": "TLS EXPORT", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "RC4 40", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_RSA_PSK_3DES_EDE_CBC_SHA1", "openssl_name": "RSA-PSK-3DES-EDE-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x93", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_RSA_PSK_AES_128_CBC_SHA1", "openssl_name": "RSA-PSK-AES128-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x94", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_RSA_PSK_AES_128_CBC_SHA256", "openssl_name": "RSA-PSK-AES128-CBC-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xB6", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_RSA_PSK_AES_128_GCM_SHA256", "openssl_name": "RSA-PSK-AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xAC", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_RSA_PSK_AES_256_CBC_SHA1", "openssl_name": "RSA-PSK-AES256-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x95", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_AES_256_CBC_SHA384": {"gnutls_name": "TLS_RSA_PSK_AES_256_CBC_SHA384", "openssl_name": "RSA-PSK-AES256-CBC-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xB7", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_RSA_PSK_AES_256_GCM_SHA384", "openssl_name": "RSA-PSK-AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xAD", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x68", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x6E", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x69", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x6F", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_RSA_PSK_CAMELLIA_128_CBC_SHA256", "openssl_name": "RSA-PSK-CAMELLIA128-SHA256", "hex_byte_1": "0xC0", "hex_byte_2": "0x98", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_RSA_PSK_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x92", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": {"gnutls_name": "TLS_RSA_PSK_CAMELLIA_256_CBC_SHA384", "openssl_name": "RSA-PSK-CAMELLIA256-SHA384", "hex_byte_1": "0xC0", "hex_byte_2": "0x99", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_RSA_PSK_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x93", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": {"gnutls_name": "TLS_RSA_PSK_CHACHA20_POLY1305", "openssl_name": "RSA-PSK-CHACHA20-POLY1305", "hex_byte_1": "0xCC", "hex_byte_2": "0xAE", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "CHACHA20 POLY1305", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_NULL_SHA": {"gnutls_name": "TLS_RSA_PSK_NULL_SHA1", "openssl_name": "RSA-PSK-NULL-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x2E", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_NULL_SHA256": {"gnutls_name": "TLS_RSA_PSK_NULL_SHA256", "openssl_name": "RSA-PSK-NULL-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xB8", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_NULL_SHA384": {"gnutls_name": "TLS_RSA_PSK_NULL_SHA384", "openssl_name": "RSA-PSK-NULL-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0xB9", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "NULL", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_PSK_WITH_RC4_128_SHA": {"gnutls_name": "TLS_RSA_PSK_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x92", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "PSK", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_RSA_3DES_EDE_CBC_SHA1", "openssl_name": "DES-CBC3-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x0A", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_RSA_AES_128_CBC_SHA1", "openssl_name": "AES128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x2F", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_128_CBC_SHA256": {"gnutls_name": "TLS_RSA_AES_128_CBC_SHA256", "openssl_name": "AES128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x3C", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_128_CCM": {"gnutls_name": "TLS_RSA_AES_128_CCM", "openssl_name": "AES128-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0x9C", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_128_CCM_8": {"gnutls_name": "TLS_RSA_AES_128_CCM_8", "openssl_name": "AES128-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xA0", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_128_GCM_SHA256": {"gnutls_name": "TLS_RSA_AES_128_GCM_SHA256", "openssl_name": "AES128-GCM-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x9C", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_RSA_AES_256_CBC_SHA1", "openssl_name": "AES256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x35", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_256_CBC_SHA256": {"gnutls_name": "TLS_RSA_AES_256_CBC_SHA256", "openssl_name": "AES256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x3D", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_256_CCM": {"gnutls_name": "TLS_RSA_AES_256_CCM", "openssl_name": "AES256-CCM", "hex_byte_1": "0xC0", "hex_byte_2": "0x9D", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_256_CCM_8": {"gnutls_name": "TLS_RSA_AES_256_CCM_8", "openssl_name": "AES256-CCM8", "hex_byte_1": "0xC0", "hex_byte_2": "0xA1", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 CCM 8", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_AES_256_GCM_SHA384": {"gnutls_name": "TLS_RSA_AES_256_GCM_SHA384", "openssl_name": "AES256-GCM-SHA384", "hex_byte_1": "0x00", "hex_byte_2": "0x9D", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "AES 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_ARIA_128_CBC_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x3C", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_ARIA_128_GCM_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x50", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_ARIA_256_CBC_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x3D", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 CBC", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_ARIA_256_GCM_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x51", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "ARIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": {"gnutls_name": "TLS_RSA_CAMELLIA_128_CBC_SHA1", "openssl_name": "CAMELLIA128-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x41", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256": {"gnutls_name": "TLS_RSA_CAMELLIA_128_CBC_SHA256", "openssl_name": "CAMELLIA128-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xBA", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256": {"gnutls_name": "TLS_RSA_CAMELLIA_128_GCM_SHA256", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x7A", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 128 GCM", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": {"gnutls_name": "TLS_RSA_CAMELLIA_256_CBC_SHA1", "openssl_name": "CAMELLIA256-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x84", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256": {"gnutls_name": "TLS_RSA_CAMELLIA_256_CBC_SHA256", "openssl_name": "CAMELLIA256-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0xC0", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 CBC", "hash_algorithm": "SHA256", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384": {"gnutls_name": "TLS_RSA_CAMELLIA_256_GCM_SHA384", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0x7B", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "CAMELLIA 256 GCM", "hash_algorithm": "SHA384", "security": "weak", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_DES_CBC_SHA": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x09", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "DES CBC", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_IDEA_CBC_SHA": {"gnutls_name": "", "openssl_name": "IDEA-CBC-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x07", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "IDEA CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_NULL_MD5": {"gnutls_name": "TLS_RSA_NULL_MD5", "openssl_name": "NULL-MD5", "hex_byte_1": "0x00", "hex_byte_2": "0x01", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "NULL", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_NULL_SHA": {"gnutls_name": "TLS_RSA_NULL_SHA1", "openssl_name": "NULL-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x02", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "NULL", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_NULL_SHA256": {"gnutls_name": "TLS_RSA_NULL_SHA256", "openssl_name": "NULL-SHA256", "hex_byte_1": "0x00", "hex_byte_2": "0x3B", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "NULL", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_RC4_128_MD5": {"gnutls_name": "TLS_RSA_ARCFOUR_128_MD5", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x04", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "RC4 128", "hash_algorithm": "MD5", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_RC4_128_SHA": {"gnutls_name": "TLS_RSA_ARCFOUR_128_SHA1", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0x05", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "RC4 128", "hash_algorithm": "SHA", "security": "insecure", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_RSA_WITH_SEED_CBC_SHA": {"gnutls_name": "", "openssl_name": "SEED-SHA", "hex_byte_1": "0x00", "hex_byte_2": "0x96", "protocol_version": "TLS", "kex_algorithm": "RSA", "auth_algorithm": "RSA", "enc_algorithm": "SEED CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SHA256_SHA256": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0xB4", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "SHA256", "enc_algorithm": "NULL", "hash_algorithm": "SHA256", "security": "insecure", "tls_version": ["TLS1.3"]}}, {"TLS_SHA384_SHA384": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0xC0", "hex_byte_2": "0xB5", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "SHA384", "enc_algorithm": "NULL", "hash_algorithm": "SHA384", "security": "insecure", "tls_version": ["TLS1.3"]}}, {"TLS_SM4_CCM_SM3": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xC7", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "SM4 CCM", "hash_algorithm": "SM3", "security": "insecure", "tls_version": ["TLS1.3"]}}, {"TLS_SM4_GCM_SM3": {"gnutls_name": "", "openssl_name": "", "hex_byte_1": "0x00", "hex_byte_2": "0xC6", "protocol_version": "TLS", "kex_algorithm": "-", "auth_algorithm": "-", "enc_algorithm": "SM4 GCM", "hash_algorithm": "SM3", "security": "insecure", "tls_version": ["TLS1.3"]}}, {"TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_DSS_3DES_EDE_CBC_SHA1", "openssl_name": "SRP-DSS-3DES-EDE-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x1C", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA DSS", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_DSS_AES_128_CBC_SHA1", "openssl_name": "SRP-DSS-AES-128-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x1F", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA DSS", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_DSS_AES_256_CBC_SHA1", "openssl_name": "SRP-DSS-AES-256-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x22", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA DSS", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_RSA_3DES_EDE_CBC_SHA1", "openssl_name": "SRP-RSA-3DES-EDE-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x1B", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA RSA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_RSA_AES_128_CBC_SHA1", "openssl_name": "SRP-RSA-AES-128-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x1E", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA RSA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_RSA_AES_256_CBC_SHA1", "openssl_name": "SRP-RSA-AES-256-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x21", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA RSA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_3DES_EDE_CBC_SHA1", "openssl_name": "SRP-3DES-EDE-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x1A", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA", "enc_algorithm": "3DES EDE CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_WITH_AES_128_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_AES_128_CBC_SHA1", "openssl_name": "SRP-AES-128-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x1D", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA", "enc_algorithm": "AES 128 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}, {"TLS_SRP_SHA_WITH_AES_256_CBC_SHA": {"gnutls_name": "TLS_SRP_SHA_AES_256_CBC_SHA1", "openssl_name": "SRP-AES-256-CBC-SHA", "hex_byte_1": "0xC0", "hex_byte_2": "0x20", "protocol_version": "TLS", "kex_algorithm": "SRP", "auth_algorithm": "SHA", "enc_algorithm": "AES 256 CBC", "hash_algorithm": "SHA", "security": "weak", "tls_version": ["TLS1.0", "TLS1.1", "TLS1.2", "TLS1.3"]}}]} \ No newline at end of file +{ + "ciphersuites": [ + { + "TLS_AES_128_CCM_8_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x13", + "hex_byte_2": "0x05", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_AES_128_CCM_ASCONHASH256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x71", + "protocol_version": "TLS", + "kex_algorithm": "AES", + "auth_algorithm": "128 CCM ASCONHASH256", + "enc_algorithm": "", + "hash_algorithm": "", + "security": "recommended", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_AES_128_CCM_SHA256": { + "gnutls_name": "", + "openssl_name": "TLS_AES_128_CCM_SHA256", + "hex_byte_1": "0x13", + "hex_byte_2": "0x04", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_AES_128_GCM_ASCONHASH256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x70", + "protocol_version": "TLS", + "kex_algorithm": "AES", + "auth_algorithm": "128 GCM ASCONHASH256", + "enc_algorithm": "", + "hash_algorithm": "", + "security": "recommended", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "TLS_AES_128_GCM_SHA256", + "hex_byte_1": "0x13", + "hex_byte_2": "0x01", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "TLS_AES_256_GCM_SHA384", + "hex_byte_1": "0x13", + "hex_byte_2": "0x02", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "recommended", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_ASCONAEAD128_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x6F", + "protocol_version": "TLS", + "kex_algorithm": "ASCONAEAD128", + "auth_algorithm": "SHA256", + "enc_algorithm": "", + "hash_algorithm": "", + "security": "recommended", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x13", + "hex_byte_2": "0x03", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x19", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "DES40 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x17", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "RC4 40", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_DH_ANON_3DES_EDE_CBC_SHA1", + "openssl_name": "ADH-DES-CBC3-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x1B", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_DH_ANON_AES_128_CBC_SHA1", + "openssl_name": "ADH-AES128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x34", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_DH_ANON_AES_128_CBC_SHA256", + "openssl_name": "ADH-AES128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x6C", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_DH_ANON_AES_128_GCM_SHA256", + "openssl_name": "ADH-AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA6", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_DH_ANON_AES_256_CBC_SHA1", + "openssl_name": "ADH-AES256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x3A", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_AES_256_CBC_SHA256": { + "gnutls_name": "TLS_DH_ANON_AES_256_CBC_SHA256", + "openssl_name": "ADH-AES256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x6D", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_DH_ANON_AES_256_GCM_SHA384", + "openssl_name": "ADH-AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA7", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x46", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x5A", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x47", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x5B", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA": { + "gnutls_name": "TLS_DH_ANON_CAMELLIA_128_CBC_SHA1", + "openssl_name": "ADH-CAMELLIA128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x46", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_DH_ANON_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ADH-CAMELLIA128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xBF", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_DH_ANON_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x84", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA": { + "gnutls_name": "TLS_DH_ANON_CAMELLIA_256_CBC_SHA1", + "openssl_name": "ADH-CAMELLIA256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x89", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256": { + "gnutls_name": "TLS_DH_ANON_CAMELLIA_256_CBC_SHA256", + "openssl_name": "ADH-CAMELLIA256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC5", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_DH_ANON_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x85", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x1A", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_RC4_128_MD5": { + "gnutls_name": "TLS_DH_ANON_ARCFOUR_128_MD5", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x18", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "RC4 128", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_anon_WITH_SEED_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "ADH-SEED-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x9B", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "anon", + "enc_algorithm": "SEED CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x0B", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "DES40 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x0D", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_AES_128_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x30", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x3E", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA4", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_AES_256_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x36", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x68", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA5", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x3E", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x58", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x3F", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x59", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x42", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xBB", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x82", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x85", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC1", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x83", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x0C", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_DSS_WITH_SEED_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x97", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "DSS", + "enc_algorithm": "SEED CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x11", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "DES40 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_DHE_DSS_3DES_EDE_CBC_SHA1", + "openssl_name": "DHE-DSS-DES-CBC3-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x13", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_DHE_DSS_AES_128_CBC_SHA1", + "openssl_name": "DHE-DSS-AES128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x32", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_DHE_DSS_AES_128_CBC_SHA256", + "openssl_name": "DHE-DSS-AES128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x40", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_DHE_DSS_AES_128_GCM_SHA256", + "openssl_name": "DHE-DSS-AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA2", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_DHE_DSS_AES_256_CBC_SHA1", + "openssl_name": "DHE-DSS-AES256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x38", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": { + "gnutls_name": "TLS_DHE_DSS_AES_256_CBC_SHA256", + "openssl_name": "DHE-DSS-AES256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x6A", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_DHE_DSS_AES_256_GCM_SHA384", + "openssl_name": "DHE-DSS-AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA3", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x42", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x56", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x43", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x57", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA": { + "gnutls_name": "TLS_DHE_DSS_CAMELLIA_128_CBC_SHA1", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x44", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_DHE_DSS_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xBD", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_DHE_DSS_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x80", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA": { + "gnutls_name": "TLS_DHE_DSS_CAMELLIA_256_CBC_SHA1", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x87", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256": { + "gnutls_name": "TLS_DHE_DSS_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-DSS-CAMELLIA256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC3", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_DHE_DSS_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x81", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x12", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_DSS_WITH_SEED_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "DHE-DSS-SEED-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x99", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "DSS", + "enc_algorithm": "SEED CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_DHE_PSK_3DES_EDE_CBC_SHA1", + "openssl_name": "DHE-PSK-3DES-EDE-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x8F", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_DHE_PSK_AES_128_CBC_SHA1", + "openssl_name": "DHE-PSK-AES128-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x90", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_DHE_PSK_AES_128_CBC_SHA256", + "openssl_name": "DHE-PSK-AES128-CBC-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB2", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_128_CCM": { + "gnutls_name": "TLS_DHE_PSK_AES_128_CCM", + "openssl_name": "DHE-PSK-AES128-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA6", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_DHE_PSK_AES_128_GCM_SHA256", + "openssl_name": "DHE-PSK-AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xAA", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_DHE_PSK_AES_256_CBC_SHA1", + "openssl_name": "DHE-PSK-AES256-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x91", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "TLS_DHE_PSK_AES_256_CBC_SHA384", + "openssl_name": "DHE-PSK-AES256-CBC-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB3", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_256_CCM": { + "gnutls_name": "TLS_DHE_PSK_AES_256_CCM", + "openssl_name": "DHE-PSK-AES256-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA7", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_DHE_PSK_AES_256_GCM_SHA384", + "openssl_name": "DHE-PSK-AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xAB", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x66", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x6C", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x67", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x6D", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_DHE_PSK_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-PSK-CAMELLIA128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x96", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_DHE_PSK_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x90", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "TLS_DHE_PSK_CAMELLIA_256_CBC_SHA384", + "openssl_name": "DHE-PSK-CAMELLIA256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x97", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_DHE_PSK_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x91", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_DHE_PSK_CHACHA20_POLY1305", + "openssl_name": "DHE-PSK-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xAD", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_NULL_SHA": { + "gnutls_name": "TLS_DHE_PSK_NULL_SHA1", + "openssl_name": "DHE-PSK-NULL-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x2D", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_NULL_SHA256": { + "gnutls_name": "TLS_DHE_PSK_NULL_SHA256", + "openssl_name": "DHE-PSK-NULL-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB4", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_NULL_SHA384": { + "gnutls_name": "TLS_DHE_PSK_NULL_SHA384", + "openssl_name": "DHE-PSK-NULL-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB5", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_PSK_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_DHE_PSK_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x8E", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x14", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "DES40 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_DHE_RSA_3DES_EDE_CBC_SHA1", + "openssl_name": "DHE-RSA-DES-CBC3-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x16", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_DHE_RSA_AES_128_CBC_SHA1", + "openssl_name": "DHE-RSA-AES128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x33", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_DHE_RSA_AES_128_CBC_SHA256", + "openssl_name": "DHE-RSA-AES128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x67", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_128_CCM": { + "gnutls_name": "TLS_DHE_RSA_AES_128_CCM", + "openssl_name": "DHE-RSA-AES128-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x9E", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_128_CCM_8": { + "gnutls_name": "TLS_DHE_RSA_AES_128_CCM_8", + "openssl_name": "DHE-RSA-AES128-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA2", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_DHE_RSA_AES_128_GCM_SHA256", + "openssl_name": "DHE-RSA-AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x9E", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_DHE_RSA_AES_256_CBC_SHA1", + "openssl_name": "DHE-RSA-AES256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x39", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": { + "gnutls_name": "TLS_DHE_RSA_AES_256_CBC_SHA256", + "openssl_name": "DHE-RSA-AES256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x6B", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_256_CCM": { + "gnutls_name": "TLS_DHE_RSA_AES_256_CCM", + "openssl_name": "DHE-RSA-AES256-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x9F", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_256_CCM_8": { + "gnutls_name": "TLS_DHE_RSA_AES_256_CCM_8", + "openssl_name": "DHE-RSA-AES256-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA3", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_DHE_RSA_AES_256_GCM_SHA384", + "openssl_name": "DHE-RSA-AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0x9F", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x44", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x52", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x45", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x53", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA": { + "gnutls_name": "TLS_DHE_RSA_CAMELLIA_128_CBC_SHA1", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x45", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_DHE_RSA_CAMELLIA_128_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xBE", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_DHE_RSA_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x7C", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA": { + "gnutls_name": "TLS_DHE_RSA_CAMELLIA_256_CBC_SHA1", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x88", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256": { + "gnutls_name": "TLS_DHE_RSA_CAMELLIA_256_CBC_SHA256", + "openssl_name": "DHE-RSA-CAMELLIA256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC4", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_DHE_RSA_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x7D", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_DHE_RSA_CHACHA20_POLY1305", + "openssl_name": "DHE-RSA-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xAA", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x15", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DHE_RSA_WITH_SEED_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "DHE-RSA-SEED-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x9A", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "RSA", + "enc_algorithm": "SEED CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x0E", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "DES40 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x10", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x31", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x3F", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA0", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x37", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x69", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA1", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x40", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x54", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x41", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x55", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x43", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xBC", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x7E", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x86", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC2", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x7F", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x0F", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_DH_RSA_WITH_SEED_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x98", + "protocol_version": "TLS", + "kex_algorithm": "DH", + "auth_algorithm": "RSA", + "enc_algorithm": "SEED CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECCPWD_WITH_AES_128_CCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xB2", + "protocol_version": "TLS", + "kex_algorithm": "ECCPWD", + "auth_algorithm": "ECCPWD", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECCPWD_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xB0", + "protocol_version": "TLS", + "kex_algorithm": "ECCPWD", + "auth_algorithm": "ECCPWD", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECCPWD_WITH_AES_256_CCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xB3", + "protocol_version": "TLS", + "kex_algorithm": "ECCPWD", + "auth_algorithm": "ECCPWD", + "enc_algorithm": "AES 256 CCM", + "hash_algorithm": "SHA384", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECCPWD_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xB1", + "protocol_version": "TLS", + "kex_algorithm": "ECCPWD", + "auth_algorithm": "ECCPWD", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_ECDH_ANON_3DES_EDE_CBC_SHA1", + "openssl_name": "AECDH-DES-CBC3-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x17", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "anon", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_anon_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_ECDH_ANON_AES_128_CBC_SHA1", + "openssl_name": "AECDH-AES128-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x18", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_ECDH_ANON_AES_256_CBC_SHA1", + "openssl_name": "AECDH-AES256-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x19", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "anon", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_anon_WITH_NULL_SHA": { + "gnutls_name": "TLS_ECDH_ANON_NULL_SHA1", + "openssl_name": "AECDH-NULL-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x15", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "anon", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_anon_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_ECDH_ANON_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x16", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "anon", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x03", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x04", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x25", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x2D", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x05", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x26", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x2E", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x4A", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x5E", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x4B", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x5F", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x74", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x88", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x75", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x89", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_NULL_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x01", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_ECDSA_WITH_RC4_128_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x02", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "ECDSA", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_ECDSA_3DES_EDE_CBC_SHA1", + "openssl_name": "ECDHE-ECDSA-DES-CBC3-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x08", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CBC_SHA1", + "openssl_name": "ECDHE-ECDSA-AES128-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x09", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x23", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CCM", + "openssl_name": "ECDHE-ECDSA-AES128-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xAC", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_128_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES128-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xAE", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-ECDSA-AES128-GCM-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x2B", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CBC_SHA1", + "openssl_name": "ECDHE-ECDSA-AES256-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x0A", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x24", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_256_CCM": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CCM", + "openssl_name": "ECDHE-ECDSA-AES256-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xAD", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 CCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_256_CCM_8", + "openssl_name": "ECDHE-ECDSA-AES256-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xAF", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 CCM 8", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_ECDHE_ECDSA_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-ECDSA-AES256-GCM-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x2C", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x48", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x5C", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x49", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x5D", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-ECDSA-CAMELLIA128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x72", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x86", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-ECDSA-CAMELLIA256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x73", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_ECDHE_ECDSA_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x87", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_ECDHE_ECDSA_CHACHA20_POLY1305", + "openssl_name": "ECDHE-ECDSA-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xA9", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_NULL_SHA": { + "gnutls_name": "TLS_ECDHE_ECDSA_NULL_SHA1", + "openssl_name": "ECDHE-ECDSA-NULL-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x06", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_ECDHE_ECDSA_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x07", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "ECDSA", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_PSK_3DES_EDE_CBC_SHA1", + "openssl_name": "ECDHE-PSK-3DES-EDE-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x34", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_PSK_AES_128_CBC_SHA1", + "openssl_name": "ECDHE-PSK-AES128-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x35", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_ECDHE_PSK_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-PSK-AES128-CBC-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x37", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xD0", + "hex_byte_2": "0x03", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xD0", + "hex_byte_2": "0x05", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xD0", + "hex_byte_2": "0x01", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_PSK_AES_256_CBC_SHA1", + "openssl_name": "ECDHE-PSK-AES256-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x36", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "TLS_ECDHE_PSK_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-PSK-AES256-CBC-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x38", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xD0", + "hex_byte_2": "0x02", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x70", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x71", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_ECDHE_PSK_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-PSK-CAMELLIA128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x9A", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "TLS_ECDHE_PSK_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-PSK-CAMELLIA256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x9B", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_ECDHE_PSK_CHACHA20_POLY1305", + "openssl_name": "ECDHE-PSK-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xAC", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "recommended", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_NULL_SHA": { + "gnutls_name": "TLS_ECDHE_PSK_NULL_SHA1", + "openssl_name": "ECDHE-PSK-NULL-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x39", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_NULL_SHA256": { + "gnutls_name": "TLS_ECDHE_PSK_NULL_SHA256", + "openssl_name": "ECDHE-PSK-NULL-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x3A", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_NULL_SHA384": { + "gnutls_name": "TLS_ECDHE_PSK_NULL_SHA384", + "openssl_name": "ECDHE-PSK-NULL-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x3B", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_PSK_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_ECDHE_PSK_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x33", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "PSK", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_RSA_3DES_EDE_CBC_SHA1", + "openssl_name": "ECDHE-RSA-DES-CBC3-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x12", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_RSA_AES_128_CBC_SHA1", + "openssl_name": "ECDHE-RSA-AES128-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x13", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_ECDHE_RSA_AES_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-AES128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x27", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_ECDHE_RSA_AES_128_GCM_SHA256", + "openssl_name": "ECDHE-RSA-AES128-GCM-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x2F", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_ECDHE_RSA_AES_256_CBC_SHA1", + "openssl_name": "ECDHE-RSA-AES256-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x14", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "TLS_ECDHE_RSA_AES_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-AES256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x28", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_ECDHE_RSA_AES_256_GCM_SHA384", + "openssl_name": "ECDHE-RSA-AES256-GCM-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x30", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x4C", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x60", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x4D", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x61", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_128_CBC_SHA256", + "openssl_name": "ECDHE-RSA-CAMELLIA128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x76", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x8A", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_256_CBC_SHA384", + "openssl_name": "ECDHE-RSA-CAMELLIA256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x77", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_ECDHE_RSA_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x8B", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_ECDHE_RSA_CHACHA20_POLY1305", + "openssl_name": "ECDHE-RSA-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xA8", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "secure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_NULL_SHA": { + "gnutls_name": "TLS_ECDHE_RSA_NULL_SHA1", + "openssl_name": "ECDHE-RSA-NULL-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x10", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDHE_RSA_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_ECDHE_RSA_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x11", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "RSA", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x0D", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x0E", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x29", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x31", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x0F", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x2A", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x32", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x4E", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x62", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x4F", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x63", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x78", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x8C", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x79", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x8D", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_NULL_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x0B", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_ECDH_RSA_WITH_RC4_128_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x0C", + "protocol_version": "TLS", + "kex_algorithm": "ECDH", + "auth_algorithm": "RSA", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_28147_CNT_IMIT": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x02", + "protocol_version": "TLS", + "kex_algorithm": "GOSTR341112 256", + "auth_algorithm": "GOSTR341012", + "enc_algorithm": "28147 CNT", + "hash_algorithm": "GOSTR341112", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x00", + "protocol_version": "TLS", + "kex_algorithm": "GOSTR341112 256", + "auth_algorithm": "GOSTR341012", + "enc_algorithm": "KUZNYECHIK CTR", + "hash_algorithm": "GOSTR341112", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x03", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "-", + "enc_algorithm": "KUZNYECHIK MGM L", + "hash_algorithm": "-", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x05", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "-", + "enc_algorithm": "KUZNYECHIK MGM S", + "hash_algorithm": "-", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x01", + "protocol_version": "TLS", + "kex_algorithm": "GOSTR341112 256", + "auth_algorithm": "GOSTR341012", + "enc_algorithm": "MAGMA CTR", + "hash_algorithm": "GOSTR341112", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_MAGMA_MGM_L": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x04", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "-", + "enc_algorithm": "MAGMA MGM L", + "hash_algorithm": "-", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_GOSTR341112_256_WITH_MAGMA_MGM_S": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC1", + "hex_byte_2": "0x06", + "protocol_version": "TLS", + "kex_algorithm": "ECDHE", + "auth_algorithm": "-", + "enc_algorithm": "MAGMA MGM S", + "hash_algorithm": "-", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x29", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "DES CBC 40", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x26", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "DES CBC 40", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x2A", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "RC2 CBC 40", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x27", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "RC2 CBC 40", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_EXPORT_WITH_RC4_40_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x2B", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "RC4 40", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_EXPORT_WITH_RC4_40_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x28", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "RC4 40", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_3DES_EDE_CBC_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x23", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x1F", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_DES_CBC_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x22", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "DES CBC", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x1E", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_IDEA_CBC_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x25", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "IDEA CBC", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_IDEA_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x21", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "IDEA CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_RC4_128_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x24", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "RC4 128", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_KRB5_WITH_RC4_128_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x20", + "protocol_version": "TLS", + "kex_algorithm": "KRB5", + "auth_algorithm": "KRB5", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_NULL_WITH_NULL_NULL": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x00", + "protocol_version": "TLS", + "kex_algorithm": "NULL", + "auth_algorithm": "NULL", + "enc_algorithm": "NULL", + "hash_algorithm": "NULL", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_DHE_WITH_AES_128_CCM_8": { + "gnutls_name": "TLS_DHE_PSK_AES_128_CCM_8", + "openssl_name": "DHE-PSK-AES128-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xAA", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_DHE_WITH_AES_256_CCM_8": { + "gnutls_name": "TLS_DHE_PSK_AES_256_CCM_8", + "openssl_name": "DHE-PSK-AES256-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xAB", + "protocol_version": "TLS", + "kex_algorithm": "DHE", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_PSK_3DES_EDE_CBC_SHA1", + "openssl_name": "PSK-3DES-EDE-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x8B", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_PSK_AES_128_CBC_SHA1", + "openssl_name": "PSK-AES128-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x8C", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_PSK_AES_128_CBC_SHA256", + "openssl_name": "PSK-AES128-CBC-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xAE", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_128_CCM": { + "gnutls_name": "TLS_PSK_AES_128_CCM", + "openssl_name": "PSK-AES128-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA4", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_128_CCM_8": { + "gnutls_name": "TLS_PSK_AES_128_CCM_8", + "openssl_name": "PSK-AES128-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA8", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_PSK_AES_128_GCM_SHA256", + "openssl_name": "PSK-AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA8", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_PSK_AES_256_CBC_SHA1", + "openssl_name": "PSK-AES256-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x8D", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "TLS_PSK_AES_256_CBC_SHA384", + "openssl_name": "PSK-AES256-CBC-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xAF", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_256_CCM": { + "gnutls_name": "TLS_PSK_AES_256_CCM", + "openssl_name": "PSK-AES256-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA5", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_256_CCM_8": { + "gnutls_name": "TLS_PSK_AES_256_CCM_8", + "openssl_name": "PSK-AES256-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA9", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_PSK_AES_256_GCM_SHA384", + "openssl_name": "PSK-AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xA9", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x64", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x6A", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x65", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x6B", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_PSK_CAMELLIA_128_CBC_SHA256", + "openssl_name": "PSK-CAMELLIA128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x94", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_PSK_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x8E", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "TLS_PSK_CAMELLIA_256_CBC_SHA384", + "openssl_name": "PSK-CAMELLIA256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x95", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_PSK_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x8F", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_PSK_CHACHA20_POLY1305", + "openssl_name": "PSK-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xAB", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_NULL_SHA": { + "gnutls_name": "TLS_PSK_NULL_SHA1", + "openssl_name": "PSK-NULL-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x2C", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_NULL_SHA256": { + "gnutls_name": "TLS_PSK_NULL_SHA256", + "openssl_name": "PSK-NULL-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB0", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_NULL_SHA384": { + "gnutls_name": "TLS_PSK_NULL_SHA384", + "openssl_name": "PSK-NULL-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB1", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_PSK_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_PSK_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x8A", + "protocol_version": "TLS", + "kex_algorithm": "PSK", + "auth_algorithm": "PSK", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x08", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "DES40 CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x06", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "RC2 CBC 40", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_EXPORT_WITH_RC4_40_MD5": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x03", + "protocol_version": "TLS EXPORT", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "RC4 40", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_RSA_PSK_3DES_EDE_CBC_SHA1", + "openssl_name": "RSA-PSK-3DES-EDE-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x93", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_RSA_PSK_AES_128_CBC_SHA1", + "openssl_name": "RSA-PSK-AES128-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x94", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_RSA_PSK_AES_128_CBC_SHA256", + "openssl_name": "RSA-PSK-AES128-CBC-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB6", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_RSA_PSK_AES_128_GCM_SHA256", + "openssl_name": "RSA-PSK-AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xAC", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_RSA_PSK_AES_256_CBC_SHA1", + "openssl_name": "RSA-PSK-AES256-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x95", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384": { + "gnutls_name": "TLS_RSA_PSK_AES_256_CBC_SHA384", + "openssl_name": "RSA-PSK-AES256-CBC-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB7", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_RSA_PSK_AES_256_GCM_SHA384", + "openssl_name": "RSA-PSK-AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xAD", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x68", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x6E", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x69", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x6F", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_RSA_PSK_CAMELLIA_128_CBC_SHA256", + "openssl_name": "RSA-PSK-CAMELLIA128-SHA256", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x98", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_RSA_PSK_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x92", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": { + "gnutls_name": "TLS_RSA_PSK_CAMELLIA_256_CBC_SHA384", + "openssl_name": "RSA-PSK-CAMELLIA256-SHA384", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x99", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_RSA_PSK_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x93", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": { + "gnutls_name": "TLS_RSA_PSK_CHACHA20_POLY1305", + "openssl_name": "RSA-PSK-CHACHA20-POLY1305", + "hex_byte_1": "0xCC", + "hex_byte_2": "0xAE", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "CHACHA20 POLY1305", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_NULL_SHA": { + "gnutls_name": "TLS_RSA_PSK_NULL_SHA1", + "openssl_name": "RSA-PSK-NULL-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x2E", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_NULL_SHA256": { + "gnutls_name": "TLS_RSA_PSK_NULL_SHA256", + "openssl_name": "RSA-PSK-NULL-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB8", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_NULL_SHA384": { + "gnutls_name": "TLS_RSA_PSK_NULL_SHA384", + "openssl_name": "RSA-PSK-NULL-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0xB9", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_PSK_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_RSA_PSK_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x92", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "PSK", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_RSA_3DES_EDE_CBC_SHA1", + "openssl_name": "DES-CBC3-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x0A", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_RSA_AES_128_CBC_SHA1", + "openssl_name": "AES128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x2F", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_128_CBC_SHA256": { + "gnutls_name": "TLS_RSA_AES_128_CBC_SHA256", + "openssl_name": "AES128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x3C", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_128_CCM": { + "gnutls_name": "TLS_RSA_AES_128_CCM", + "openssl_name": "AES128-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x9C", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_128_CCM_8": { + "gnutls_name": "TLS_RSA_AES_128_CCM_8", + "openssl_name": "AES128-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA0", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_128_GCM_SHA256": { + "gnutls_name": "TLS_RSA_AES_128_GCM_SHA256", + "openssl_name": "AES128-GCM-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x9C", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_RSA_AES_256_CBC_SHA1", + "openssl_name": "AES256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x35", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_256_CBC_SHA256": { + "gnutls_name": "TLS_RSA_AES_256_CBC_SHA256", + "openssl_name": "AES256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x3D", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_256_CCM": { + "gnutls_name": "TLS_RSA_AES_256_CCM", + "openssl_name": "AES256-CCM", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x9D", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_256_CCM_8": { + "gnutls_name": "TLS_RSA_AES_256_CCM_8", + "openssl_name": "AES256-CCM8", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xA1", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 CCM 8", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_AES_256_GCM_SHA384": { + "gnutls_name": "TLS_RSA_AES_256_GCM_SHA384", + "openssl_name": "AES256-GCM-SHA384", + "hex_byte_1": "0x00", + "hex_byte_2": "0x9D", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "AES 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_ARIA_128_CBC_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x3C", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_ARIA_128_GCM_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x50", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_ARIA_256_CBC_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x3D", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 CBC", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_ARIA_256_GCM_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x51", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "ARIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA": { + "gnutls_name": "TLS_RSA_CAMELLIA_128_CBC_SHA1", + "openssl_name": "CAMELLIA128-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x41", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256": { + "gnutls_name": "TLS_RSA_CAMELLIA_128_CBC_SHA256", + "openssl_name": "CAMELLIA128-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xBA", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256": { + "gnutls_name": "TLS_RSA_CAMELLIA_128_GCM_SHA256", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x7A", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 128 GCM", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA": { + "gnutls_name": "TLS_RSA_CAMELLIA_256_CBC_SHA1", + "openssl_name": "CAMELLIA256-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x84", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256": { + "gnutls_name": "TLS_RSA_CAMELLIA_256_CBC_SHA256", + "openssl_name": "CAMELLIA256-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC0", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 CBC", + "hash_algorithm": "SHA256", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384": { + "gnutls_name": "TLS_RSA_CAMELLIA_256_GCM_SHA384", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x7B", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "CAMELLIA 256 GCM", + "hash_algorithm": "SHA384", + "security": "weak", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_DES_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x09", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "DES CBC", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_IDEA_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "IDEA-CBC-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x07", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "IDEA CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_NULL_MD5": { + "gnutls_name": "TLS_RSA_NULL_MD5", + "openssl_name": "NULL-MD5", + "hex_byte_1": "0x00", + "hex_byte_2": "0x01", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "NULL", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_NULL_SHA": { + "gnutls_name": "TLS_RSA_NULL_SHA1", + "openssl_name": "NULL-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x02", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_NULL_SHA256": { + "gnutls_name": "TLS_RSA_NULL_SHA256", + "openssl_name": "NULL-SHA256", + "hex_byte_1": "0x00", + "hex_byte_2": "0x3B", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_RC4_128_MD5": { + "gnutls_name": "TLS_RSA_ARCFOUR_128_MD5", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x04", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "RC4 128", + "hash_algorithm": "MD5", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_RC4_128_SHA": { + "gnutls_name": "TLS_RSA_ARCFOUR_128_SHA1", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0x05", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "RC4 128", + "hash_algorithm": "SHA", + "security": "insecure", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_RSA_WITH_SEED_CBC_SHA": { + "gnutls_name": "", + "openssl_name": "SEED-SHA", + "hex_byte_1": "0x00", + "hex_byte_2": "0x96", + "protocol_version": "TLS", + "kex_algorithm": "RSA", + "auth_algorithm": "RSA", + "enc_algorithm": "SEED CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SHA256_SHA256": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xB4", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "SHA256", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA256", + "security": "insecure", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_SHA384_SHA384": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0xC0", + "hex_byte_2": "0xB5", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "SHA384", + "enc_algorithm": "NULL", + "hash_algorithm": "SHA384", + "security": "insecure", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_SM4_CCM_SM3": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC7", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "SM4 CCM", + "hash_algorithm": "SM3", + "security": "insecure", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_SM4_GCM_SM3": { + "gnutls_name": "", + "openssl_name": "", + "hex_byte_1": "0x00", + "hex_byte_2": "0xC6", + "protocol_version": "TLS", + "kex_algorithm": "-", + "auth_algorithm": "-", + "enc_algorithm": "SM4 GCM", + "hash_algorithm": "SM3", + "security": "insecure", + "tls_version": [ + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_DSS_3DES_EDE_CBC_SHA1", + "openssl_name": "SRP-DSS-3DES-EDE-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x1C", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA DSS", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_DSS_AES_128_CBC_SHA1", + "openssl_name": "SRP-DSS-AES-128-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x1F", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA DSS", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_DSS_AES_256_CBC_SHA1", + "openssl_name": "SRP-DSS-AES-256-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x22", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA DSS", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_RSA_3DES_EDE_CBC_SHA1", + "openssl_name": "SRP-RSA-3DES-EDE-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x1B", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA RSA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_RSA_AES_128_CBC_SHA1", + "openssl_name": "SRP-RSA-AES-128-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x1E", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA RSA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_RSA_AES_256_CBC_SHA1", + "openssl_name": "SRP-RSA-AES-256-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x21", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA RSA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_3DES_EDE_CBC_SHA1", + "openssl_name": "SRP-3DES-EDE-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x1A", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA", + "enc_algorithm": "3DES EDE CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_WITH_AES_128_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_AES_128_CBC_SHA1", + "openssl_name": "SRP-AES-128-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x1D", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA", + "enc_algorithm": "AES 128 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + }, + { + "TLS_SRP_SHA_WITH_AES_256_CBC_SHA": { + "gnutls_name": "TLS_SRP_SHA_AES_256_CBC_SHA1", + "openssl_name": "SRP-AES-256-CBC-SHA", + "hex_byte_1": "0xC0", + "hex_byte_2": "0x20", + "protocol_version": "TLS", + "kex_algorithm": "SRP", + "auth_algorithm": "SHA", + "enc_algorithm": "AES 256 CBC", + "hash_algorithm": "SHA", + "security": "weak", + "tls_version": [ + "TLS1.0", + "TLS1.1", + "TLS1.2", + "TLS1.3" + ] + } + } + ] +} diff --git a/mapper/creatCipherSuiteClass.py b/mapper/creatCipherSuiteClass.py index a5ae8611d..92366d48b 100644 --- a/mapper/creatCipherSuiteClass.py +++ b/mapper/creatCipherSuiteClass.py @@ -6,6 +6,39 @@ def optional(value): else: return "\"" + value + "\"" +# Hash names that can end a TLS 1.3 suite name, longest first so a shorter suffix never wins. +TLS13_HASH_ALGORITHMS = ( + "ASCONHASH256", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHA256", + "SHA384", + "SHA512", + "SM3", +) + +# The ciphersuite.info API splits some TLS 1.3 names across kex/auth and leaves enc/hash empty. +# TLS 1.3 has no negotiable kex/auth, so recover enc/hash from the name instead. Matched against +# the known hashes above rather than the last "_" token, which would mis-split SHA3_256. +def resolve_unparsed_tls13_fields(name, struct): + if struct["enc_algorithm"] or struct["hash_algorithm"]: + return + if "TLS1.3" not in struct.get("tls_version", []) or not name.startswith("TLS_"): + return + + body = name[len("TLS_"):] + for hash_algorithm in TLS13_HASH_ALGORITHMS: + suffix = "_" + hash_algorithm + if body.endswith(suffix) and len(body) > len(suffix): + struct["kex_algorithm"] = "-" + struct["auth_algorithm"] = "-" + struct["enc_algorithm"] = body[: -len(suffix)].replace("_", " ") + struct["hash_algorithm"] = hash_algorithm + return + + print(f"WARNING: cannot normalize TLS 1.3 cipher suite '{name}'; enc/hash left unset") + with open("./ciphersuites.json", "r") as stream: cipherList = list() @@ -15,6 +48,7 @@ def optional(value): name, *_ = cipherSuite.keys() struct, *_ = cipherSuite.values() struct["name"] = name + resolve_unparsed_tls13_fields(name, struct) cipherList.append(struct) diff --git a/mapper/download-cipher-suites.sh b/mapper/download-cipher-suites.sh index 0ca71747c..8671ab394 100755 --- a/mapper/download-cipher-suites.sh +++ b/mapper/download-cipher-suites.sh @@ -19,8 +19,8 @@ set -eu url=https://ciphersuite.info/api/cs/ target=ciphersuites.json tmp=$(mktemp) -# Do not leave the temp file behind if we exit early. -trap 'rm -f "$tmp"' EXIT +# Do not leave the temp file(s) behind if we exit early. +trap 'rm -f "$tmp" "$tmp.pretty"' EXIT # --fail makes curl exit non-zero on a 4xx/5xx instead of writing the error # page to disk. We download to a temp file so a bad response can never @@ -30,6 +30,10 @@ if ! curl --fail --silent --show-error --location --max-time 60 "$url" -o "$tmp" exit 1 fi +# Pretty-print so the committed diff stays reviewable. +python3 -m json.tool --indent 2 "$tmp" "$tmp.pretty" +mv "$tmp.pretty" "$tmp" + # The API has to give us parseable JSON with a plausible number of entries. # A sharp drop means the API changed or returned a partial body, and we would # rather stop than commit a shrunken cipher-suite list. diff --git a/mapper/pom.xml b/mapper/pom.xml index 114e26b90..653deb713 100644 --- a/mapper/pom.xml +++ b/mapper/pom.xml @@ -12,8 +12,6 @@ mapper - 17 - 17 UTF-8 diff --git a/mapper/src/main/java/com/ibm/mapper/ITranslator.java b/mapper/src/main/java/com/ibm/mapper/ITranslator.java index dab705728..4bca11c49 100644 --- a/mapper/src/main/java/com/ibm/mapper/ITranslator.java +++ b/mapper/src/main/java/com/ibm/mapper/ITranslator.java @@ -23,12 +23,15 @@ import com.ibm.engine.model.IValue; import com.ibm.engine.model.context.DetectionContext; import com.ibm.engine.rule.IBundle; +import com.ibm.mapper.model.IAlgorithm; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.NodeOrigin; import com.ibm.mapper.model.collections.AbstractAssetCollection; +import com.ibm.mapper.model.collections.IAssetCollection; import com.ibm.mapper.utils.DetectionLocation; import java.util.ArrayList; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -61,7 +64,10 @@ public abstract class ITranslator { @Nonnull public List translate(@Nonnull DetectionStore rootDetectionStore) { final Traverser traverser = - new Traverser<>(rootDetectionStore, this::translateStore); + new Traverser<>( + rootDetectionStore, + this::translateStore, + argumentsConfigureTheOperatedObject()); return traverser.translate(); } @@ -109,50 +115,143 @@ protected abstract Optional translate( @Nullable protected abstract DetectionLocation getDetectionContextFrom( @Nonnull T location, @Nonnull final IBundle bundle, @Nonnull String filePath); + /** + * Whether an argument of a call that has no node of its own configures the object the call + * operates on. In C, a function operates on the object it is given, e.g. {@code + * EVP_KDF_derive(ctx, out, 32, params)} sets the key length and the parameters of the key + * derivation function of {@code ctx}. By default, such an argument adds to the first node of + * the call, as the arguments of a method add to what the method sets up. + */ + protected boolean argumentsConfigureTheOperatedObject() { + return false; + } + /* * private traverser */ static class Traverser { @Nonnull final DetectionStore rootDetectionStore; - @Nonnull final List newRoots = new ArrayList<>(); + @Nonnull final List alternatives = new ArrayList<>(); + + /** The nodes merged into an equal node they were appended to, see {@link #append}. */ + @Nonnull final Map mergedNodes = new IdentityHashMap<>(); + @Nonnull final Function, Map>> translator; + final boolean argumentsConfigureTheOperatedObject; public Traverser( @Nonnull DetectionStore rootDetectionStore, - @Nonnull - Function, Map>> - translator) { + @Nonnull Function, Map>> translator, + boolean argumentsConfigureTheOperatedObject) { this.rootDetectionStore = rootDetectionStore; this.translator = translator; + this.argumentsConfigureTheOperatedObject = argumentsConfigureTheOperatedObject; } @Nonnull public List translate() { final Map> rootNodes = translator.apply(rootDetectionStore); - travers(rootDetectionStore, rootNodes); + final List>> uses; + if (rootNodes.isEmpty()) { + uses = traversUses(rootDetectionStore); + } else { + travers(rootDetectionStore, rootNodes, null); + uses = List.of(rootNodes); + } final List translatedRootNodes = - new ArrayList<>(rootNodes.values().stream().flatMap(List::stream).toList()); - translatedRootNodes.addAll(newRoots); + new ArrayList<>( + uses.stream() + .flatMap(use -> use.values().stream()) + .flatMap(List::stream) + .toList()); + translatedRootNodes.addAll(alternativeRoots(translatedRootNodes)); return translatedRootNodes; } + /** + * Appends the nodes of the children of a store to the nodes of the store ({@code + * parentNodes}), which were appended to {@code enclosingNodes}, or are roots when that is + * null. + */ private void travers( @Nonnull DetectionStore store, - @Nonnull Map> parentNodes) { + @Nonnull Map> parentNodes, + @Nullable Map> enclosingNodes) { store.getChildrenForMethod() - .forEach(child -> translateAndAppend(-1, child, parentNodes)); + .forEach(child -> translateAndAppend(-1, child, parentNodes, enclosingNodes)); + store.childrenForEachParameter( + (id, children) -> { + for (DetectionStore child : children) { + translateAndAppend(id, child, parentNodes, enclosingNodes); + } + }); + } + + /** + * The uses of an object created by a call without a node of its own, e.g. the creation of a + * context by a C function, each with the nodes describing it. The arguments of the call + * that created the object describe it, and the calls made on it add to that description. An + * object created without describing arguments, e.g. by {@code EVP_CIPHER_CTX_new()}, gets + * its algorithm from a call made on it: a call that brings another algorithm once the + * object has one, e.g. the context initialized again with another cipher, starts another + * use of the object, which the calls after it describe. + */ + @Nonnull + private List>> traversUses( + @Nonnull DetectionStore store) { + final Map> describedByArguments = new HashMap<>(); store.childrenForEachParameter( (id, children) -> { for (DetectionStore child : children) { - translateAndAppend(id, child, parentNodes); + translateAndAppend(id, child, describedByArguments, null); } }); + final boolean usedAsCreated = !describedByArguments.isEmpty(); + final List>> uses = new ArrayList<>(); + Map> use = describedByArguments; + for (DetectionStore child : store.getChildrenForMethod()) { + if (!usedAsCreated && hasAlgorithm(use) && bringsAlgorithm(child)) { + uses.add(use); + use = new HashMap<>(); + } + translateAndAppend(-1, child, use, null); + } + uses.add(use); + return uses; + } + + /** + * Whether the nodes of a call made on an object, and of the calls below it, hold an + * algorithm. + */ + private boolean bringsAlgorithm(@Nonnull DetectionStore call) { + // translated on its own, so that its values do not count as other values of this tree + final Map> nodes = new HashMap<>(); + new Traverser<>(call, translator, argumentsConfigureTheOperatedObject) + .translateAndAppend(-1, call, nodes, null); + return hasAlgorithm(nodes); + } + + private static boolean hasAlgorithm(@Nonnull Map> nodes) { + return nodes.values().stream().flatMap(List::stream).anyMatch(Traverser::hasAlgorithm); + } + + private static boolean hasAlgorithm(@Nonnull INode node) { + if (node instanceof IAlgorithm) { + return true; + } + if (node instanceof IAssetCollection collection + && collection.getCollection().stream().anyMatch(Traverser::hasAlgorithm)) { + return true; + } + return node.getChildren().values().stream().anyMatch(Traverser::hasAlgorithm); } private void translateAndAppend( int id, @Nonnull DetectionStore child, - @Nonnull Map> mapOfParentNodes) { + @Nonnull Map> mapOfParentNodes, + @Nullable Map> enclosingNodes) { Map> nodes = translator.apply(child); // collect nodes and add to parent @@ -169,22 +268,64 @@ private void translateAndAppend( mapOfParentNodes.put( -1, newNodesCollection); // add node as main } else { - mapOfParentNodes.values().stream() - .findFirst() - .ifPresent( - parentNodes -> - this.append( - parentNodes, - newNodesCollection)); + this.append( + nodesConfiguredBy(mapOfParentNodes, enclosingNodes), + newNodesCollection); } }); } + // next iteration if (nodes.isEmpty()) { - nodes = mapOfParentNodes; + travers(child, mapOfParentNodes, enclosingNodes); + } else { + travers(child, withMergedNodes(nodes), mapOfParentNodes); } - // next iteration - travers(child, nodes); + } + + /** + * The nodes, each merged node replaced by the node it was merged into, so that what is + * found below a node adds to the node that holds its value. + */ + @Nonnull + private Map> withMergedNodes( + @Nonnull Map> nodes) { + final Map> result = new HashMap<>(); + nodes.forEach( + (id, list) -> + result.put( + id, + new ArrayList<>( + list.stream() + .map( + node -> + mergedNodes.getOrDefault( + node, node)) + .toList()))); + return result; + } + + /** + * The nodes that an argument of a call configures when the call has no node for that + * argument: the first nodes of the call, or, where arguments configure the operated object + * (see {@link ITranslator#argumentsConfigureTheOperatedObject()}), the operation of the + * call, else what the nodes of the call describe, e.g. the key derivation function whose + * key length and parameters {@code EVP_KDF_derive(ctx, out, 32, params)} sets. + */ + @Nonnull + private List nodesConfiguredBy( + @Nonnull Map> callNodes, + @Nullable Map> enclosingNodes) { + if (!argumentsConfigureTheOperatedObject) { + return callNodes.values().iterator().next(); + } + if (callNodes.containsKey(-1)) { + return callNodes.get(-1); + } + if (enclosingNodes != null && !enclosingNodes.isEmpty()) { + return enclosingNodes.getOrDefault(-1, enclosingNodes.values().iterator().next()); + } + return callNodes.values().iterator().next(); } private void append( @@ -236,7 +377,17 @@ private void append( parentNode.put(mergedCollectionNode); } else if (existingNode.is(childNode.getKind()) - && !existingNode.asString().equals(childNode.asString())) { + && existingNode.asString().equals(childNode.asString())) { + // the same value again: what is found with it and below it + // adds to it, e.g. the salt length set for an RSA-PSS + // signature + if (existingNode != childNode) { + this.append( + List.of(existingNode), + List.copyOf(childNode.getChildren().values())); + mergedNodes.putIfAbsent(childNode, existingNode); + } + } else if (existingNode.is(childNode.getKind())) { // Handle based on origin: // - DETECTED values override DEFAULT/ENRICHED values // - Only create new roots when both are DETECTED with different @@ -253,10 +404,9 @@ private void append( // Keep existing DETECTED value, ignore default/enriched // child } else { - // Both are DETECTED with different values: create new roots - final INode newParent = parentNode.deepCopy(); - newParent.put(childNode); - newRoots.add(newParent); + // Both are DETECTED with different values: another tree + // with this value, built once the tree is complete + alternatives.add(new Alternative(parentNode, childNode)); } } } else { @@ -265,5 +415,110 @@ private void append( }); } } + + /** + * A tree for each other value found for a node that already has a value of that kind, e.g. + * the second key length of a key set up twice: a copy of the complete tree of the node, in + * which the other value takes the place of the value of the node. The trees are built once + * the traversal is complete, so that they hold everything found in the tree and below the + * value, and they share no node with it. A node that is in no tree gets a copy of itself + * with the other value. + */ + @Nonnull + private List alternativeRoots(@Nonnull List roots) { + final List searched = new ArrayList<>(roots); + final List alternativeRoots = new ArrayList<>(); + for (Alternative alternative : alternatives) { + final INode alternativeRoot = + searched.stream() + .map( + root -> + pathTo(root, alternative.node()) + .map( + path -> + copyWith( + root, + path, + alternative + .value()))) + .flatMap(Optional::stream) + .findFirst() + .orElseGet( + () -> + copyWith( + alternative.node(), + List.of(), + alternative.value())); + alternativeRoots.add(alternativeRoot); + searched.add(alternativeRoot); + } + return alternativeRoots; + } + + /** A copy of the root with a copy of the value put in the node at the end of the path. */ + @Nonnull + private static INode copyWith( + @Nonnull INode root, @Nonnull List path, @Nonnull INode value) { + final INode copy = root.deepCopy(); + INode node = copy; + for (Step step : path) { + node = step.from(node); + } + node.put(value.deepCopy()); + return copy; + } + + /** The steps from the node to the target, when the target is the node or below it. */ + @Nonnull + private static Optional> pathTo(@Nonnull INode node, @Nonnull INode target) { + if (node == target) { + return Optional.of(new ArrayList<>()); + } + for (Map.Entry, INode> child : node.getChildren().entrySet()) { + final Optional> below = pathTo(child.getValue(), target); + if (below.isPresent()) { + below.get().add(0, Step.child(child.getKey())); + return below; + } + } + if (node instanceof IAssetCollection collection) { + final List items = collection.getCollection(); + for (int i = 0; i < items.size(); i++) { + final Optional> below = pathTo(items.get(i), target); + if (below.isPresent()) { + below.get().add(0, Step.item(i)); + return below; + } + } + } + return Optional.empty(); + } + + /** Another value of the kind of a value the node already has. */ + private record Alternative(@Nonnull INode node, @Nonnull INode value) {} + + /** + * A step from a node to its child of a kind, or to the item at an index of its collection. + */ + private record Step(@Nullable Class kind, int item) { + + @Nonnull + static Step child(@Nonnull Class kind) { + return new Step(kind, -1); + } + + @Nonnull + static Step item(int item) { + return new Step(null, item); + } + + @Nonnull + INode from(@Nonnull INode node) { + if (kind != null) { + return node.getChildren().get(kind); + } + return ((IAssetCollection) node).getCollection().get(item); + } + } } } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/gocrypto/GoCryptoTLSVersionMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/gocrypto/GoCryptoTLSVersionMapper.java index 15ac1b343..4fb859399 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/gocrypto/GoCryptoTLSVersionMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/gocrypto/GoCryptoTLSVersionMapper.java @@ -50,7 +50,7 @@ public Optional parse( } return switch (str.trim()) { - case "VersionSSL30" -> Optional.of(new Version("SSLv3", detectionLocation)); + case "VersionSSL30" -> Optional.of(new Version("3.0", detectionLocation)); case "VersionTLS10" -> Optional.of(new Version("1.0", detectionLocation)); case "VersionTLS11" -> Optional.of(new Version("1.1", detectionLocation)); case "VersionTLS12" -> Optional.of(new Version("1.2", detectionLocation)); diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCipherMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCipherMapper.java new file mode 100644 index 000000000..88f275057 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCipherMapper.java @@ -0,0 +1,775 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.Aria; +import com.ibm.mapper.model.algorithms.Blowfish; +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.DESX; +import com.ibm.mapper.model.algorithms.DESede; +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.RC5; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SEED; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.SM4; +import com.ibm.mapper.model.algorithms.cast.CAST128; +import com.ibm.mapper.model.mode.CBC; +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.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.OAEP; +import com.ibm.mapper.model.padding.PKCS1; +import com.ibm.mapper.model.padding.Raw; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL cipher names (the names accepted by {@code EVP_CIPHER_fetch} and {@code + * EVP_get_cipherbyname}, and the names the cipher detection rules give the {@code EVP_*} cipher + * functions and the legacy cipher functions) to the model. + */ +public class OpenSslCipherMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + final String name = str.toUpperCase().trim(); + return switch (name) { + + // AES (Advanced Encryption Standard) + + // AES-128 + case "AES-128-CBC" -> + Optional.of(new AES(128, new CBC(detectionLocation), detectionLocation)); + case "AES-128-ECB" -> + Optional.of(new AES(128, new ECB(detectionLocation), detectionLocation)); + case "AES-128-GCM" -> + Optional.of(new AES(128, new GCM(detectionLocation), detectionLocation)); + case "AES-128-CTR" -> + Optional.of(new AES(128, new CTR(detectionLocation), detectionLocation)); + case "AES-128-CCM" -> + Optional.of(new AES(128, new CCM(detectionLocation), detectionLocation)); + case "AES-128-CFB" -> + Optional.of(new AES(128, new CFB(detectionLocation), detectionLocation)); + case "AES-128-CFB1" -> + Optional.of(new AES(128, new CFB(1, detectionLocation), detectionLocation)); + case "AES-128-CFB8" -> + Optional.of(new AES(128, new CFB(8, detectionLocation), detectionLocation)); + case "AES-128-CFB128" -> + Optional.of(new AES(128, new CFB(128, detectionLocation), detectionLocation)); + case "AES-128-OFB" -> + Optional.of(new AES(128, new OFB(detectionLocation), detectionLocation)); + case "AES-128-XTS" -> + Optional.of(new AES(128, new XTS(detectionLocation), detectionLocation)); + case "AES-128-OCB" -> + Optional.of(new AES(128, new OCB(detectionLocation), detectionLocation)); + case "AES-128-WRAP" -> + Optional.of(new AES(128, new KW(detectionLocation), detectionLocation)); + case "AES-128-WRAP-PAD" -> + Optional.of(new AES(128, new KWP(detectionLocation), detectionLocation)); + + // AES-192 + case "AES-192-CBC" -> + Optional.of(new AES(192, new CBC(detectionLocation), detectionLocation)); + case "AES-192-ECB" -> + Optional.of(new AES(192, new ECB(detectionLocation), detectionLocation)); + case "AES-192-GCM" -> + Optional.of(new AES(192, new GCM(detectionLocation), detectionLocation)); + case "AES-192-CFB" -> + Optional.of(new AES(192, new CFB(detectionLocation), detectionLocation)); + case "AES-192-CFB1" -> + Optional.of(new AES(192, new CFB(1, detectionLocation), detectionLocation)); + case "AES-192-CFB8" -> + Optional.of(new AES(192, new CFB(8, detectionLocation), detectionLocation)); + case "AES-192-CFB128" -> + Optional.of(new AES(192, new CFB(128, detectionLocation), detectionLocation)); + case "AES-192-OFB" -> + Optional.of(new AES(192, new OFB(detectionLocation), detectionLocation)); + case "AES-192-OCB" -> + Optional.of(new AES(192, new OCB(detectionLocation), detectionLocation)); + case "AES-192-WRAP" -> + Optional.of(new AES(192, new KW(detectionLocation), detectionLocation)); + case "AES-192-WRAP-PAD" -> + Optional.of(new AES(192, new KWP(detectionLocation), detectionLocation)); + case "AES-192-CTR" -> + Optional.of(new AES(192, new CTR(detectionLocation), detectionLocation)); + case "AES-192-CCM" -> + Optional.of(new AES(192, new CCM(detectionLocation), detectionLocation)); + + // AES-256 + case "AES-256-CBC" -> + Optional.of(new AES(256, new CBC(detectionLocation), detectionLocation)); + case "AES-256-ECB" -> + Optional.of(new AES(256, new ECB(detectionLocation), detectionLocation)); + case "AES-256-GCM" -> + Optional.of(new AES(256, new GCM(detectionLocation), detectionLocation)); + case "AES-256-CTR" -> + Optional.of(new AES(256, new CTR(detectionLocation), detectionLocation)); + case "AES-256-CCM" -> + Optional.of(new AES(256, new CCM(detectionLocation), detectionLocation)); + case "AES-256-CFB" -> + Optional.of(new AES(256, new CFB(detectionLocation), detectionLocation)); + case "AES-256-CFB1" -> + Optional.of(new AES(256, new CFB(1, detectionLocation), detectionLocation)); + case "AES-256-CFB8" -> + Optional.of(new AES(256, new CFB(8, detectionLocation), detectionLocation)); + case "AES-256-CFB128" -> + Optional.of(new AES(256, new CFB(128, detectionLocation), detectionLocation)); + case "AES-256-OFB" -> + Optional.of(new AES(256, new OFB(detectionLocation), detectionLocation)); + case "AES-256-XTS" -> + Optional.of(new AES(256, new XTS(detectionLocation), detectionLocation)); + case "AES-256-OCB" -> + Optional.of(new AES(256, new OCB(detectionLocation), detectionLocation)); + case "AES-256-WRAP" -> + Optional.of(new AES(256, new KW(detectionLocation), detectionLocation)); + case "AES-256-WRAP-PAD" -> + Optional.of(new AES(256, new KWP(detectionLocation), detectionLocation)); + + // Provider-only AES modes (no EVP_* convenience functions) + case "AES-128-SIV" -> + Optional.of(new AES(128, new SIV(detectionLocation), detectionLocation)); + case "AES-192-SIV" -> + Optional.of(new AES(192, new SIV(detectionLocation), detectionLocation)); + case "AES-256-SIV" -> + Optional.of(new AES(256, new SIV(detectionLocation), detectionLocation)); + case "AES-128-GCM-SIV" -> + Optional.of(new AES(128, new GCMSIV(detectionLocation), detectionLocation)); + case "AES-192-GCM-SIV" -> + Optional.of(new AES(192, new GCMSIV(detectionLocation), detectionLocation)); + case "AES-256-GCM-SIV" -> + Optional.of(new AES(256, new GCMSIV(detectionLocation), detectionLocation)); + case "AES-128-CBC-CTS" -> + Optional.of( + new AES( + 128, + new Mode("CBC-CTS", detectionLocation), + detectionLocation)); + case "AES-192-CBC-CTS" -> + Optional.of( + new AES( + 192, + new Mode("CBC-CTS", detectionLocation), + detectionLocation)); + case "AES-256-CBC-CTS" -> + Optional.of( + new AES( + 256, + new Mode("CBC-CTS", detectionLocation), + detectionLocation)); + case "AES-128-WRAP-INV" -> + Optional.of( + new AES( + 128, + new Mode("WRAP-INV", detectionLocation), + detectionLocation)); + case "AES-192-WRAP-INV" -> + Optional.of( + new AES( + 192, + new Mode("WRAP-INV", detectionLocation), + detectionLocation)); + case "AES-256-WRAP-INV" -> + Optional.of( + new AES( + 256, + new Mode("WRAP-INV", detectionLocation), + detectionLocation)); + case "AES-128-WRAP-PAD-INV" -> + Optional.of( + new AES( + 128, + new Mode("WRAP-PAD-INV", detectionLocation), + detectionLocation)); + case "AES-192-WRAP-PAD-INV" -> + Optional.of( + new AES( + 192, + new Mode("WRAP-PAD-INV", detectionLocation), + detectionLocation)); + case "AES-256-WRAP-PAD-INV" -> + Optional.of( + new AES( + 256, + new Mode("WRAP-PAD-INV", detectionLocation), + detectionLocation)); + case "AES-128-CBC-HMAC-SHA1" -> + Optional.of( + new AES( + 128, + new Mode("CBC-HMAC-SHA1", detectionLocation), + detectionLocation)); + case "AES-128-CBC-HMAC-SHA256" -> + Optional.of( + new AES( + 128, + new Mode("CBC-HMAC-SHA256", detectionLocation), + detectionLocation)); + case "AES-192-CBC-HMAC-SHA1" -> + Optional.of( + new AES( + 192, + new Mode("CBC-HMAC-SHA1", detectionLocation), + detectionLocation)); + case "AES-192-CBC-HMAC-SHA256" -> + Optional.of( + new AES( + 192, + new Mode("CBC-HMAC-SHA256", detectionLocation), + detectionLocation)); + case "AES-256-CBC-HMAC-SHA1" -> + Optional.of( + new AES( + 256, + new Mode("CBC-HMAC-SHA1", detectionLocation), + detectionLocation)); + case "AES-256-CBC-HMAC-SHA256" -> + Optional.of( + new AES( + 256, + new Mode("CBC-HMAC-SHA256", detectionLocation), + detectionLocation)); + + // Provider-only AES ETM (Encrypt-then-MAC) modes + case "AES-128-CBC-HMAC-SHA1-ETM" -> + Optional.of( + new AES( + 128, + new Mode("CBC-HMAC-SHA1-ETM", detectionLocation), + detectionLocation)); + case "AES-192-CBC-HMAC-SHA1-ETM" -> + Optional.of( + new AES( + 192, + new Mode("CBC-HMAC-SHA1-ETM", detectionLocation), + detectionLocation)); + case "AES-256-CBC-HMAC-SHA1-ETM" -> + Optional.of( + new AES( + 256, + new Mode("CBC-HMAC-SHA1-ETM", detectionLocation), + detectionLocation)); + case "AES-128-CBC-HMAC-SHA256-ETM" -> + Optional.of( + new AES( + 128, + new Mode("CBC-HMAC-SHA256-ETM", detectionLocation), + detectionLocation)); + case "AES-192-CBC-HMAC-SHA256-ETM" -> + Optional.of( + new AES( + 192, + new Mode("CBC-HMAC-SHA256-ETM", detectionLocation), + detectionLocation)); + case "AES-256-CBC-HMAC-SHA256-ETM" -> + Optional.of( + new AES( + 256, + new Mode("CBC-HMAC-SHA256-ETM", detectionLocation), + detectionLocation)); + case "AES-128-CBC-HMAC-SHA512-ETM" -> + Optional.of( + new AES( + 128, + new Mode("CBC-HMAC-SHA512-ETM", detectionLocation), + detectionLocation)); + case "AES-192-CBC-HMAC-SHA512-ETM" -> + Optional.of( + new AES( + 192, + new Mode("CBC-HMAC-SHA512-ETM", detectionLocation), + detectionLocation)); + case "AES-256-CBC-HMAC-SHA512-ETM" -> + Optional.of( + new AES( + 256, + new Mode("CBC-HMAC-SHA512-ETM", detectionLocation), + detectionLocation)); + + // Camellia + + // Camellia-128 + case "CAMELLIA-128-ECB" -> + Optional.of(new Camellia(128, new ECB(detectionLocation), detectionLocation)); + case "CAMELLIA-128-CBC" -> + Optional.of(new Camellia(128, new CBC(detectionLocation), detectionLocation)); + case "CAMELLIA-128-CFB" -> + Optional.of(new Camellia(128, new CFB(detectionLocation), detectionLocation)); + case "CAMELLIA-128-CFB1" -> + Optional.of( + new Camellia(128, new CFB(1, detectionLocation), detectionLocation)); + case "CAMELLIA-128-CFB8" -> + Optional.of( + new Camellia(128, new CFB(8, detectionLocation), detectionLocation)); + case "CAMELLIA-128-CFB128" -> + Optional.of( + new Camellia(128, new CFB(128, detectionLocation), detectionLocation)); + case "CAMELLIA-128-OFB" -> + Optional.of(new Camellia(128, new OFB(detectionLocation), detectionLocation)); + case "CAMELLIA-128-CTR" -> + Optional.of(new Camellia(128, new CTR(detectionLocation), detectionLocation)); + case "CAMELLIA-128-GCM" -> + Optional.of(new Camellia(128, new GCM(detectionLocation), detectionLocation)); + case "CAMELLIA-128-CCM" -> + Optional.of(new Camellia(128, new CCM(detectionLocation), detectionLocation)); + + // Camellia-192 + case "CAMELLIA-192-ECB" -> + Optional.of(new Camellia(192, new ECB(detectionLocation), detectionLocation)); + case "CAMELLIA-192-CBC" -> + Optional.of(new Camellia(192, new CBC(detectionLocation), detectionLocation)); + case "CAMELLIA-192-CFB" -> + Optional.of(new Camellia(192, new CFB(detectionLocation), detectionLocation)); + case "CAMELLIA-192-CFB1" -> + Optional.of( + new Camellia(192, new CFB(1, detectionLocation), detectionLocation)); + case "CAMELLIA-192-CFB8" -> + Optional.of( + new Camellia(192, new CFB(8, detectionLocation), detectionLocation)); + case "CAMELLIA-192-CFB128" -> + Optional.of( + new Camellia(192, new CFB(128, detectionLocation), detectionLocation)); + case "CAMELLIA-192-OFB" -> + Optional.of(new Camellia(192, new OFB(detectionLocation), detectionLocation)); + case "CAMELLIA-192-CTR" -> + Optional.of(new Camellia(192, new CTR(detectionLocation), detectionLocation)); + case "CAMELLIA-192-GCM" -> + Optional.of(new Camellia(192, new GCM(detectionLocation), detectionLocation)); + case "CAMELLIA-192-CCM" -> + Optional.of(new Camellia(192, new CCM(detectionLocation), detectionLocation)); + + // Camellia-256 + case "CAMELLIA-256-ECB" -> + Optional.of(new Camellia(256, new ECB(detectionLocation), detectionLocation)); + case "CAMELLIA-256-CBC" -> + Optional.of(new Camellia(256, new CBC(detectionLocation), detectionLocation)); + case "CAMELLIA-256-CFB" -> + Optional.of(new Camellia(256, new CFB(detectionLocation), detectionLocation)); + case "CAMELLIA-256-CFB1" -> + Optional.of( + new Camellia(256, new CFB(1, detectionLocation), detectionLocation)); + case "CAMELLIA-256-CFB8" -> + Optional.of( + new Camellia(256, new CFB(8, detectionLocation), detectionLocation)); + case "CAMELLIA-256-CFB128" -> + Optional.of( + new Camellia(256, new CFB(128, detectionLocation), detectionLocation)); + case "CAMELLIA-256-OFB" -> + Optional.of(new Camellia(256, new OFB(detectionLocation), detectionLocation)); + case "CAMELLIA-256-CTR" -> + Optional.of(new Camellia(256, new CTR(detectionLocation), detectionLocation)); + case "CAMELLIA-256-GCM" -> + Optional.of(new Camellia(256, new GCM(detectionLocation), detectionLocation)); + case "CAMELLIA-256-CCM" -> + Optional.of(new Camellia(256, new CCM(detectionLocation), detectionLocation)); + + // Provider-only Camellia modes + case "CAMELLIA-128-CBC-CTS" -> + Optional.of( + new Camellia( + 128, + new Mode("CBC-CTS", detectionLocation), + detectionLocation)); + case "CAMELLIA-192-CBC-CTS" -> + Optional.of( + new Camellia( + 192, + new Mode("CBC-CTS", detectionLocation), + detectionLocation)); + case "CAMELLIA-256-CBC-CTS" -> + Optional.of( + new Camellia( + 256, + new Mode("CBC-CTS", detectionLocation), + detectionLocation)); + + // ARIA + + // ARIA-128 + case "ARIA-128-ECB" -> + Optional.of(new Aria(128, new ECB(detectionLocation), detectionLocation)); + case "ARIA-128-CBC" -> + Optional.of(new Aria(128, new CBC(detectionLocation), detectionLocation)); + case "ARIA-128-CFB" -> + Optional.of(new Aria(128, new CFB(detectionLocation), detectionLocation)); + case "ARIA-128-CFB1" -> + Optional.of(new Aria(128, new CFB(1, detectionLocation), detectionLocation)); + case "ARIA-128-CFB8" -> + Optional.of(new Aria(128, new CFB(8, detectionLocation), detectionLocation)); + case "ARIA-128-CFB128" -> + Optional.of(new Aria(128, new CFB(128, detectionLocation), detectionLocation)); + case "ARIA-128-OFB" -> + Optional.of(new Aria(128, new OFB(detectionLocation), detectionLocation)); + case "ARIA-128-CTR" -> + Optional.of(new Aria(128, new CTR(detectionLocation), detectionLocation)); + case "ARIA-128-GCM" -> + Optional.of(new Aria(128, new GCM(detectionLocation), detectionLocation)); + case "ARIA-128-CCM" -> + Optional.of(new Aria(128, new CCM(detectionLocation), detectionLocation)); + + // ARIA-192 + case "ARIA-192-ECB" -> + Optional.of(new Aria(192, new ECB(detectionLocation), detectionLocation)); + case "ARIA-192-CBC" -> + Optional.of(new Aria(192, new CBC(detectionLocation), detectionLocation)); + case "ARIA-192-CFB" -> + Optional.of(new Aria(192, new CFB(detectionLocation), detectionLocation)); + case "ARIA-192-CFB1" -> + Optional.of(new Aria(192, new CFB(1, detectionLocation), detectionLocation)); + case "ARIA-192-CFB8" -> + Optional.of(new Aria(192, new CFB(8, detectionLocation), detectionLocation)); + case "ARIA-192-CFB128" -> + Optional.of(new Aria(192, new CFB(128, detectionLocation), detectionLocation)); + case "ARIA-192-OFB" -> + Optional.of(new Aria(192, new OFB(detectionLocation), detectionLocation)); + case "ARIA-192-CTR" -> + Optional.of(new Aria(192, new CTR(detectionLocation), detectionLocation)); + case "ARIA-192-GCM" -> + Optional.of(new Aria(192, new GCM(detectionLocation), detectionLocation)); + case "ARIA-192-CCM" -> + Optional.of(new Aria(192, new CCM(detectionLocation), detectionLocation)); + + // ARIA-256 + case "ARIA-256-ECB" -> + Optional.of(new Aria(256, new ECB(detectionLocation), detectionLocation)); + case "ARIA-256-CBC" -> + Optional.of(new Aria(256, new CBC(detectionLocation), detectionLocation)); + case "ARIA-256-CFB" -> + Optional.of(new Aria(256, new CFB(detectionLocation), detectionLocation)); + case "ARIA-256-CFB1" -> + Optional.of(new Aria(256, new CFB(1, detectionLocation), detectionLocation)); + case "ARIA-256-CFB8" -> + Optional.of(new Aria(256, new CFB(8, detectionLocation), detectionLocation)); + case "ARIA-256-CFB128" -> + Optional.of(new Aria(256, new CFB(128, detectionLocation), detectionLocation)); + case "ARIA-256-OFB" -> + Optional.of(new Aria(256, new OFB(detectionLocation), detectionLocation)); + case "ARIA-256-CTR" -> + Optional.of(new Aria(256, new CTR(detectionLocation), detectionLocation)); + case "ARIA-256-GCM" -> + Optional.of(new Aria(256, new GCM(detectionLocation), detectionLocation)); + case "ARIA-256-CCM" -> + Optional.of(new Aria(256, new CCM(detectionLocation), detectionLocation)); + + // SM4 (Chinese National Standard) + + case "SM4-ECB" -> Optional.of(new SM4(new ECB(detectionLocation), detectionLocation)); + case "SM4-CBC" -> Optional.of(new SM4(new CBC(detectionLocation), detectionLocation)); + case "SM4-CFB" -> Optional.of(new SM4(new CFB(detectionLocation), detectionLocation)); + case "SM4-CFB128" -> + Optional.of(new SM4(new CFB(128, detectionLocation), detectionLocation)); + case "SM4-OFB" -> Optional.of(new SM4(new OFB(detectionLocation), detectionLocation)); + case "SM4-CTR" -> Optional.of(new SM4(new CTR(detectionLocation), detectionLocation)); + case "SM4-GCM" -> Optional.of(new SM4(new GCM(detectionLocation), detectionLocation)); + case "SM4-CCM" -> Optional.of(new SM4(new CCM(detectionLocation), detectionLocation)); + case "SM4-XTS" -> Optional.of(new SM4(new XTS(detectionLocation), detectionLocation)); + + // DES / 3DES + + case "DES-CBC" -> Optional.of(new DES(new CBC(detectionLocation), detectionLocation)); + case "DES-ECB" -> Optional.of(new DES(new ECB(detectionLocation), detectionLocation)); + case "DES-CFB" -> Optional.of(new DES(new CFB(detectionLocation), detectionLocation)); + case "DES-CFB1" -> + Optional.of(new DES(new CFB(1, detectionLocation), detectionLocation)); + case "DES-CFB8" -> + Optional.of(new DES(new CFB(8, detectionLocation), detectionLocation)); + case "DES-CFB64" -> + Optional.of(new DES(new CFB(64, detectionLocation), detectionLocation)); + case "DES-OFB" -> Optional.of(new DES(new OFB(detectionLocation), detectionLocation)); + + // DESX (DES with pre/post XOR whitening) + case "DESX-CBC" -> Optional.of(new DESX(new CBC(detectionLocation), detectionLocation)); + + // DESede (2-key Triple-DES, 112-bit effective key strength) + case "DESEDE" -> Optional.of(new DESede(112, detectionLocation)); + case "DESEDE-ECB" -> + Optional.of(new DESede(112, new ECB(detectionLocation), detectionLocation)); + case "DESEDE-CBC" -> + Optional.of(new DESede(112, new CBC(detectionLocation), detectionLocation)); + case "DESEDE-CFB64" -> + Optional.of(new DESede(112, new CFB(64, detectionLocation), detectionLocation)); + case "DESEDE-OFB" -> + Optional.of(new DESede(112, new OFB(detectionLocation), detectionLocation)); + + // DESede3 (3-key Triple-DES, 168-bit effective key strength) + case "DESEDE3" -> Optional.of(new DESede(168, detectionLocation)); + case "DESEDE3-ECB" -> + Optional.of(new DESede(168, new ECB(detectionLocation), detectionLocation)); + case "DES-EDE3-WRAP" -> + Optional.of(new DESede(168, new KW(detectionLocation), detectionLocation)); + case "DESEDE3-CBC" -> + Optional.of(new DESede(168, new CBC(detectionLocation), detectionLocation)); + case "DESEDE3-CFB1" -> + Optional.of(new DESede(168, new CFB(1, detectionLocation), detectionLocation)); + case "DESEDE3-CFB8" -> + Optional.of(new DESede(168, new CFB(8, detectionLocation), detectionLocation)); + case "DESEDE3-CFB64" -> + Optional.of(new DESede(168, new CFB(64, detectionLocation), detectionLocation)); + case "DESEDE3-OFB" -> + Optional.of(new DESede(168, new OFB(detectionLocation), detectionLocation)); + + // Blowfish + + case "BLOWFISH-ECB" -> + Optional.of(new Blowfish(128, new ECB(detectionLocation), detectionLocation)); + case "BLOWFISH-CBC" -> + Optional.of(new Blowfish(128, new CBC(detectionLocation), detectionLocation)); + case "BLOWFISH-CFB" -> + Optional.of(new Blowfish(128, new CFB(detectionLocation), detectionLocation)); + case "BLOWFISH-CFB64" -> + Optional.of( + new Blowfish(128, new CFB(64, detectionLocation), detectionLocation)); + case "BLOWFISH-OFB" -> + Optional.of(new Blowfish(128, new OFB(detectionLocation), detectionLocation)); + + // CAST5 (CAST-128) + + case "CAST5-ECB" -> + Optional.of(new CAST128(128, new ECB(detectionLocation), detectionLocation)); + case "CAST5-CBC" -> + Optional.of(new CAST128(128, new CBC(detectionLocation), detectionLocation)); + case "CAST5-CFB" -> + Optional.of(new CAST128(128, new CFB(detectionLocation), detectionLocation)); + case "CAST5-CFB64" -> + Optional.of( + new CAST128(128, new CFB(64, detectionLocation), detectionLocation)); + case "CAST5-OFB" -> + Optional.of(new CAST128(128, new OFB(detectionLocation), detectionLocation)); + + // RC2 + + case "RC2-ECB" -> + Optional.of(new RC2(128, new ECB(detectionLocation), detectionLocation)); + case "RC2-CBC" -> + Optional.of(new RC2(128, new CBC(detectionLocation), detectionLocation)); + case "RC2-CFB" -> + Optional.of(new RC2(128, new CFB(detectionLocation), detectionLocation)); + case "RC2-CFB64" -> + Optional.of(new RC2(128, new CFB(64, detectionLocation), detectionLocation)); + case "RC2-OFB" -> + Optional.of(new RC2(128, new OFB(detectionLocation), detectionLocation)); + case "RC2-40-CBC" -> + Optional.of(new RC2(40, new CBC(detectionLocation), detectionLocation)); + case "RC2-64-CBC" -> + Optional.of(new RC2(64, new CBC(detectionLocation), detectionLocation)); + + // RC4 (Stream Cipher) + + case "RC4" -> Optional.of(new RC4(detectionLocation)); + case "RC4-40" -> Optional.of(new RC4(40, detectionLocation)); + case "RC4-HMAC-MD5" -> Optional.of(new RC4(detectionLocation)); + + // RC5 + + case "RC5-ECB" -> + Optional.of(new RC5(128, new ECB(detectionLocation), detectionLocation)); + case "RC5-CBC" -> + Optional.of(new RC5(128, new CBC(detectionLocation), detectionLocation)); + case "RC5-CFB" -> + Optional.of(new RC5(128, new CFB(detectionLocation), detectionLocation)); + case "RC5-CFB64" -> + Optional.of(new RC5(128, new CFB(64, detectionLocation), detectionLocation)); + case "RC5-OFB" -> + Optional.of(new RC5(128, new OFB(detectionLocation), detectionLocation)); + + // IDEA + + case "IDEA-ECB" -> Optional.of(new IDEA(new ECB(detectionLocation), detectionLocation)); + case "IDEA-CBC" -> Optional.of(new IDEA(new CBC(detectionLocation), detectionLocation)); + case "IDEA-CFB" -> Optional.of(new IDEA(new CFB(detectionLocation), detectionLocation)); + case "IDEA-CFB64" -> + Optional.of(new IDEA(new CFB(64, detectionLocation), detectionLocation)); + case "IDEA-OFB" -> Optional.of(new IDEA(new OFB(detectionLocation), detectionLocation)); + + // SEED (Korean National Standard) + + case "SEED-ECB" -> Optional.of(new SEED(new ECB(detectionLocation), detectionLocation)); + case "SEED-CBC" -> Optional.of(new SEED(new CBC(detectionLocation), detectionLocation)); + case "SEED-CFB" -> Optional.of(new SEED(new CFB(detectionLocation), detectionLocation)); + case "SEED-CFB128" -> + Optional.of(new SEED(new CFB(128, detectionLocation), detectionLocation)); + case "SEED-OFB" -> Optional.of(new SEED(new OFB(detectionLocation), detectionLocation)); + + // Legacy AES functions (AES_set_encrypt_key, AES_cbc_encrypt, ...): the key size is + // set by the key setup call + case "AES" -> Optional.of(new AES(detectionLocation)); + case "AES-ECB", + "AES-CBC", + "AES-CFB1", + "AES-CFB8", + "AES-CFB128", + "AES-OFB", + "AES-IGE", + "AES-BI-IGE" -> + Optional.of( + new AES(mode(name.substring(4), detectionLocation), detectionLocation)); + // AES_wrap_key / AES_unwrap_key: the AES key wrap of RFC 3394 + case "AES-WRAP" -> Optional.of(new AES(new KW(detectionLocation), detectionLocation)); + + // Legacy three-key Triple DES functions (DES_ede3_cbc_encrypt, ...) + case "3DES-CBC", "3DES-ECB", "3DES-CFB", "3DES-OFB" -> + Optional.of( + new DESede( + 168, + new Mode(name.substring(5), detectionLocation), + detectionLocation)); + // DES_xcbc_encrypt is DESX in CBC mode, as EVP_desx_cbc + case "DES-XCBC" -> Optional.of(new DESX(new CBC(detectionLocation), detectionLocation)); + + // Legacy (pre-EVP) key setup: the cipher is known, its mode is not + case "DES" -> Optional.of(new DES(detectionLocation)); + case "BLOWFISH" -> Optional.of(new Blowfish(detectionLocation)); + case "CAST5" -> Optional.of(new CAST128(detectionLocation)); + case "IDEA" -> Optional.of(new IDEA(detectionLocation)); + case "RC2" -> Optional.of(new RC2(detectionLocation)); + case "RC5" -> Optional.of(new RC5(detectionLocation)); + case "SEED" -> Optional.of(new SEED(detectionLocation)); + case "CAMELLIA" -> Optional.of(new Camellia(detectionLocation)); + + // Legacy Camellia functions: the key size is set by Camellia_set_key + case "CAMELLIA-CBC" -> Optional.of(camellia("CBC", detectionLocation)); + case "CAMELLIA-CFB1" -> Optional.of(camellia("CFB1", detectionLocation)); + case "CAMELLIA-CFB8" -> Optional.of(camellia("CFB8", detectionLocation)); + case "CAMELLIA-CFB128" -> Optional.of(camellia("CFB128", detectionLocation)); + case "CAMELLIA-CTR" -> Optional.of(camellia("CTR", detectionLocation)); + case "CAMELLIA-ECB" -> Optional.of(camellia("ECB", detectionLocation)); + case "CAMELLIA-OFB" -> Optional.of(camellia("OFB", detectionLocation)); + + // Legacy RSA encryption and its paddings + case "RSA-ENCRYPT", "RSA-DECRYPT" -> Optional.of(new RSA(detectionLocation)); + case "RSA-NO-PADDING" -> { + final RSA rsa = new RSA(detectionLocation); + rsa.put(new Raw(detectionLocation)); + yield Optional.of(rsa); + } + case "RSA-OAEP", "RSA-OAEP-MGF1" -> + Optional.of(rsaWithPadding(new OAEP(detectionLocation), detectionLocation)); + case "RSA-PKCS1-TYPE2" -> + Optional.of(rsaWithPadding(new PKCS1(detectionLocation), detectionLocation)); + // PKCS#1 type 1 and X9.31 paddings are used for RSA signatures + case "RSA-PKCS1" -> Optional.of(OpenSslRsaSignatureSchemes.pkcs1v15(detectionLocation)); + case "RSA-X931" -> Optional.of(OpenSslRsaSignatureSchemes.x931(detectionLocation)); + // RSA_PKCS1_PSS_PADDING selects RSA-PSS signatures + case "RSA-PSS" -> Optional.of(new RSAssaPSS(detectionLocation)); + + // ChaCha20 + + case "CHACHA20" -> Optional.of(new ChaCha20(detectionLocation)); + case "CHACHA20-POLY1305" -> Optional.of(new ChaCha20Poly1305(detectionLocation)); + + // SM2 Public Key Encryption + + // Asymmetric ciphers fetched by EVP_ASYM_CIPHER_fetch + case "RSA" -> Optional.of(new RSA(detectionLocation)); + case "SM2", "SM2-PKE" -> + Optional.of(new SM2(PublicKeyEncryption.class, new SM2(detectionLocation))); + + default -> Optional.empty(); + }; + } + + /** + * Maps the label of a legacy (pre-EVP) cipher function, e.g. {@code BLOWFISH-CBC} for {@code + * BF_cbc_encrypt}. The label names the cipher and its mode but not the key length, which the + * key setup function of the key sets ({@code BF_set_key}). Unlike the {@code EVP_*} function of + * the same name ({@code EVP_bf_cbc}), whose key length is 128 bits by default, a cipher with a + * variable key length gets no key length from the label. + */ + @Nonnull + public Optional parseLegacy( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + return parse(str, detectionLocation) + .map( + cipher -> { + if (cipher instanceof Blowfish + || cipher instanceof CAST128 + || cipher instanceof RC2 + || cipher instanceof RC5) { + cipher.removeChildOfType(KeyLength.class); + } + return cipher; + }); + } + + @Nonnull + private static Camellia camellia( + @Nonnull String mode, @Nonnull DetectionLocation detectionLocation) { + Camellia camellia = new Camellia(detectionLocation); + camellia.put(mode(mode, detectionLocation)); + return camellia; + } + + /** + * The mode of operation with the given OpenSSL name, e.g. {@code CFB8}. A mode the model has a + * class for is built from that class, which gives it its CBOM mode and its OID. + */ + @Nonnull + private static Mode mode(@Nonnull String name, @Nonnull DetectionLocation detectionLocation) { + return switch (name) { + case "CBC" -> new CBC(detectionLocation); + case "CCM" -> new CCM(detectionLocation); + case "CTR" -> new CTR(detectionLocation); + case "ECB" -> new ECB(detectionLocation); + case "GCM" -> new GCM(detectionLocation); + case "OFB" -> new OFB(detectionLocation); + case "CFB" -> new CFB(detectionLocation); + default -> + name.startsWith("CFB") + ? new CFB(Integer.parseInt(name.substring(3)), detectionLocation) + : new Mode(name, detectionLocation); + }; + } + + @Nonnull + private static RSA rsaWithPadding( + @Nonnull INode padding, @Nonnull DetectionLocation detectionLocation) { + RSA rsa = new RSA(detectionLocation); + rsa.put(padding); + return rsa; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCipherStringMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCipherStringMapper.java new file mode 100644 index 000000000..929d650bf --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCipherStringMapper.java @@ -0,0 +1,76 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.mapper.ssl.CipherSuiteMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Maps an OpenSSL cipher string to the TLS cipher suites it names. */ +public class OpenSslCipherStringMapper implements IMapper { + + /** + * Maps an OpenSSL cipher string (e.g. {@code "ECDHE-RSA-AES256-GCM-SHA384:!aNULL"}), as passed + * to {@code SSL_CTX_set_cipher_list} or {@code SSL_CTX_set_ciphersuites}, into one node per + * cipher suite it names. Entries are separated by colons, commas or spaces. Entries that do not + * name a single suite are skipped: exclusions and reordering ({@code !x}, {@code -x}, {@code + * +x}), directives ({@code @SECLEVEL=2}) and keywords that select a group of suites ({@code + * HIGH}, {@code aNULL}, {@code DEFAULT}). The result is a {@link CipherSuiteCollection} of the + * suites: the cipher suites of the TLS protocol they are configured on. + */ + @Nonnull + @Override + public Optional parse( + @Nullable final String cipherString, @Nonnull DetectionLocation detectionLocation) { + if (cipherString == null) { + return Optional.empty(); + } + final CipherSuiteMapper cipherSuiteMapper = new CipherSuiteMapper(); + final List suites = new ArrayList<>(); + for (String rawEntry : cipherString.split("[:, ]")) { + final String entry = rawEntry.trim(); + if (entry.isEmpty() || "!-+@".indexOf(entry.charAt(0)) >= 0) { + continue; + } + // suite names contain a hyphen (OpenSSL names) or start with "TLS_" (standard names); + // keywords such as HIGH or aNULL do neither + if (CipherSuiteMapper.findCipherSuite(entry).isPresent() + || entry.contains("-") + || entry.startsWith("TLS_")) { + cipherSuiteMapper + .parse(entry, detectionLocation) + .filter(com.ibm.mapper.model.CipherSuite.class::isInstance) + .map(com.ibm.mapper.model.CipherSuite.class::cast) + .ifPresent(suites::add); + } + } + if (suites.isEmpty()) { + return Optional.empty(); + } + return Optional.of(new CipherSuiteCollection(suites)); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCurveMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCurveMapper.java new file mode 100644 index 000000000..9897984db --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslCurveMapper.java @@ -0,0 +1,192 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.EllipticCurve; +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.utils.DetectionLocation; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL names of a named elliptic curve to the corresponding elliptic curve model + * classes. A curve has an SEC 2 short name (e.g. {@code secp256r1}), for some curves an X9.62 name + * (e.g. {@code prime256v1}) and a NIST name (e.g. {@code P-256}, {@code K-283}, {@code B-283}); + * OpenSSL accepts all of them as the group of an EC key (obj_mac.h, crypto/evp/ec_support.c). Every + * other named curve of OpenSSL, e.g. {@code prime239v1}, is mapped by its short name. + */ +public final class OpenSslCurveMapper implements IMapper { + + /** + * The short names of the named curves of OpenSSL 3 ({@code openssl ecparam -list_curves}, + * obj_mac.h). A curve without a model of its own is mapped to an elliptic curve with its short + * name. + */ + public static final List NAMED_CURVES = + List.of( + "prime192v1", + "prime192v2", + "prime192v3", + "prime239v1", + "prime239v2", + "prime239v3", + "prime256v1", + "c2pnb163v1", + "c2pnb163v2", + "c2pnb163v3", + "c2pnb176v1", + "c2tnb191v1", + "c2tnb191v2", + "c2tnb191v3", + "c2pnb208w1", + "c2tnb239v1", + "c2tnb239v2", + "c2tnb239v3", + "c2pnb272w1", + "c2pnb304w1", + "c2tnb359v1", + "c2pnb368w1", + "c2tnb431r1", + "secp112r1", + "secp112r2", + "secp128r1", + "secp128r2", + "secp160k1", + "secp160r1", + "secp160r2", + "secp192k1", + "secp224k1", + "secp224r1", + "secp256k1", + "secp384r1", + "secp521r1", + "sect113r1", + "sect113r2", + "sect131r1", + "sect131r2", + "sect163k1", + "sect163r1", + "sect163r2", + "sect193r1", + "sect193r2", + "sect233k1", + "sect233r1", + "sect239k1", + "sect283k1", + "sect283r1", + "sect409k1", + "sect409r1", + "sect571k1", + "sect571r1", + "wap-wsg-idm-ecid-wtls1", + "wap-wsg-idm-ecid-wtls3", + "wap-wsg-idm-ecid-wtls4", + "wap-wsg-idm-ecid-wtls5", + "wap-wsg-idm-ecid-wtls6", + "wap-wsg-idm-ecid-wtls7", + "wap-wsg-idm-ecid-wtls8", + "wap-wsg-idm-ecid-wtls9", + "wap-wsg-idm-ecid-wtls10", + "wap-wsg-idm-ecid-wtls11", + "wap-wsg-idm-ecid-wtls12", + "Oakley-EC2N-3", + "Oakley-EC2N-4", + "brainpoolP160r1", + "brainpoolP160t1", + "brainpoolP192r1", + "brainpoolP192t1", + "brainpoolP224r1", + "brainpoolP224t1", + "brainpoolP256r1", + "brainpoolP256t1", + "brainpoolP320r1", + "brainpoolP320t1", + "brainpoolP384r1", + "brainpoolP384t1", + "brainpoolP512r1", + "brainpoolP512t1", + "SM2"); + + private static final Map SHORT_NAMES = + NAMED_CURVES.stream() + .collect( + Collectors.toUnmodifiableMap( + name -> name.toUpperCase(Locale.ROOT), name -> name)); + + @Nonnull + @Override + public Optional parse( + @Nullable String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + + return switch (str.toUpperCase().trim()) { + case "P-192", "SECP192R1", "PRIME192V1" -> + Optional.of(new Secp192r1(detectionLocation)); + case "P-224", "SECP224R1" -> Optional.of(new Secp224r1(detectionLocation)); + case "P-256", "SECP256R1", "PRIME256V1" -> + Optional.of(new Secp256r1(detectionLocation)); + case "P-384", "SECP384R1" -> Optional.of(new Secp384r1(detectionLocation)); + case "P-521", "SECP521R1" -> Optional.of(new Secp521r1(detectionLocation)); + case "SECP256K1" -> Optional.of(new Secp256k1(detectionLocation)); + case "BRAINPOOLP256R1" -> Optional.of(new Brainpoolp256r1(detectionLocation)); + case "BRAINPOOLP384R1" -> Optional.of(new Brainpoolp384r1(detectionLocation)); + case "BRAINPOOLP512R1" -> Optional.of(new Brainpoolp512r1(detectionLocation)); + case "K-163", "SECT163K1" -> Optional.of(new Sect163k1(detectionLocation)); + case "B-163", "SECT163R2" -> Optional.of(new Sect163r2(detectionLocation)); + case "K-233", "SECT233K1" -> Optional.of(new Sect233k1(detectionLocation)); + case "B-233", "SECT233R1" -> Optional.of(new Sect233r1(detectionLocation)); + case "K-283", "SECT283K1" -> Optional.of(new Sect283k1(detectionLocation)); + case "B-283", "SECT283R1" -> Optional.of(new Sect283r1(detectionLocation)); + case "K-409", "SECT409K1" -> Optional.of(new Sect409k1(detectionLocation)); + case "B-409", "SECT409R1" -> Optional.of(new Sect409r1(detectionLocation)); + case "K-571", "SECT571K1" -> Optional.of(new Sect571k1(detectionLocation)); + case "B-571", "SECT571R1" -> Optional.of(new Sect571r1(detectionLocation)); + default -> + Optional.ofNullable(SHORT_NAMES.get(str.toUpperCase(Locale.ROOT).trim())) + .map(name -> new EllipticCurve(name, detectionLocation)); + }; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKdfMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKdfMapper.java new file mode 100644 index 000000000..ff1a64b85 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKdfMapper.java @@ -0,0 +1,202 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.Cipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.ANSIX942; +import com.ibm.mapper.model.algorithms.ANSIX963; +import com.ibm.mapper.model.algorithms.Argon2; +import com.ibm.mapper.model.algorithms.ConcatenationKDF; +import com.ibm.mapper.model.algorithms.DES; +import com.ibm.mapper.model.algorithms.DESede; +import com.ibm.mapper.model.algorithms.HKDF; +import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.model.algorithms.HMACDRBGKDF; +import com.ibm.mapper.model.algorithms.KDFCounter; +import com.ibm.mapper.model.algorithms.KRB5KDF; +import com.ibm.mapper.model.algorithms.MD2; +import com.ibm.mapper.model.algorithms.MD5; +import com.ibm.mapper.model.algorithms.PBES1; +import com.ibm.mapper.model.algorithms.PBES2; +import com.ibm.mapper.model.algorithms.PBKDF1; +import com.ibm.mapper.model.algorithms.PBKDF2; +import com.ibm.mapper.model.algorithms.PKCS12KDF; +import com.ibm.mapper.model.algorithms.PKCS12PBE; +import com.ibm.mapper.model.algorithms.PVKKDF; +import com.ibm.mapper.model.algorithms.RC2; +import com.ibm.mapper.model.algorithms.RC4; +import com.ibm.mapper.model.algorithms.SHA; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.SSHKDF; +import com.ibm.mapper.model.algorithms.Scrypt; +import com.ibm.mapper.model.algorithms.TLSPRF; +import com.ibm.mapper.model.mode.CBC; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL key derivation names (the names accepted by {@code EVP_KDF_fetch}, the KDF key + * types of {@code EVP_PKEY_CTX_new_id}, and the names the KDF and password-based encryption + * detection rules give the PKCS#5 and PKCS#12 functions) to the model. + */ +public class OpenSslKdfMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + return switch (str.toUpperCase().trim()) { + // Names accepted by EVP_KDF_fetch. The digest of a KDF is set through the + // "digest" OSSL_PARAM of its context and is attached to the node as a child. + case "PBKDF2", "1.2.840.113549.1.5.12" -> Optional.of(new PBKDF2(detectionLocation)); + case "PBKDF1" -> Optional.of(new PBKDF1(detectionLocation)); + case "HKDF" -> Optional.of(new HKDF(detectionLocation)); + case "HKDF-SHA256" -> Optional.of(new HKDF(new SHA2(256, detectionLocation))); + case "HKDF-SHA384" -> Optional.of(new HKDF(new SHA2(384, detectionLocation))); + case "HKDF-SHA512" -> Optional.of(new HKDF(new SHA2(512, detectionLocation))); + // the TLS 1.3 key schedule is built on HKDF (RFC 8446, section 7.1) + case "TLS13-KDF" -> Optional.of(new HKDF(detectionLocation)); + case "TLS1-PRF" -> Optional.of(new TLSPRF(detectionLocation)); + case "SSKDF" -> Optional.of(new ConcatenationKDF(detectionLocation)); + case "X963KDF", "X963-KDF" -> Optional.of(new ANSIX963(detectionLocation)); + case "X942KDF-ASN1", "X942KDF" -> Optional.of(new ANSIX942("ASN1", detectionLocation)); + case "X942KDF-CONCAT" -> Optional.of(new ANSIX942("CONCAT", detectionLocation)); + // SP 800-108 KBKDF runs in counter mode unless the "mode" parameter selects + // feedback mode + case "KBKDF" -> Optional.of(new KDFCounter(detectionLocation)); + case "SSHKDF" -> Optional.of(new SSHKDF(detectionLocation)); + case "SCRYPT", "ID-SCRYPT", "1.3.6.1.4.1.11591.4.11" -> + Optional.of(new Scrypt(detectionLocation)); + case "KRB5KDF" -> Optional.of(new KRB5KDF(detectionLocation)); + case "ARGON2D" -> Optional.of(new Argon2(Argon2.Variant.D, detectionLocation)); + case "ARGON2I" -> Optional.of(new Argon2(Argon2.Variant.I, detectionLocation)); + case "ARGON2ID" -> Optional.of(new Argon2(Argon2.Variant.ID, detectionLocation)); + case "PKCS12KDF" -> Optional.of(new PKCS12KDF(detectionLocation)); + case "PVKKDF" -> Optional.of(new PVKKDF(detectionLocation)); + case "HMAC-DRBG-KDF" -> Optional.of(new HMACDRBGKDF(detectionLocation)); + + // PKCS12_PBE_keyivgen and PKCS5_PBE_keyivgen: the cipher and the digest come from + // their arguments + case "PKCS12-PBE" -> Optional.of(new PKCS12PBE(detectionLocation)); + case "PBES1" -> Optional.of(new PBES1(detectionLocation)); + // PKCS8_encrypt and EVP_PBE_CipherInit: PBES2 with the cipher given with it + case "PBES2" -> Optional.of(new PBES2(detectionLocation)); + + // PKCS#5 v1.5 schemes (PBES1), selected by their NID + case "PBE-MD2-DES" -> + Optional.of(pbes1(new MD2(detectionLocation), des(detectionLocation))); + case "PBE-MD5-DES" -> + Optional.of(pbes1(new MD5(detectionLocation), des(detectionLocation))); + case "PBE-SHA1-DES" -> + Optional.of(pbes1(new SHA(detectionLocation), des(detectionLocation))); + case "PBE-MD2-RC2-64" -> + Optional.of(pbes1(new MD2(detectionLocation), rc2(detectionLocation))); + case "PBE-MD5-RC2-64" -> + Optional.of(pbes1(new MD5(detectionLocation), rc2(detectionLocation))); + case "PBE-SHA1-RC2-64" -> + Optional.of(pbes1(new SHA(detectionLocation), rc2(detectionLocation))); + + // Encryption selected by the key and certificate NIDs of PKCS12_create + case "PBE-SHA1-RC4-128" -> + Optional.of( + new PKCS12PBE( + new SHA(detectionLocation), new RC4(128, detectionLocation))); + case "PBE-SHA1-RC4-40" -> + Optional.of( + new PKCS12PBE( + new SHA(detectionLocation), new RC4(40, detectionLocation))); + case "PBE-SHA1-3DES" -> + Optional.of( + new PKCS12PBE( + new SHA(detectionLocation), + new DESede( + 168, new CBC(detectionLocation), detectionLocation))); + case "PBE-SHA1-2DES" -> + Optional.of( + new PKCS12PBE( + new SHA(detectionLocation), + new DESede( + 112, new CBC(detectionLocation), detectionLocation))); + case "PBE-SHA1-RC2-128" -> + Optional.of( + new PKCS12PBE( + new SHA(detectionLocation), + new RC2(128, new CBC(detectionLocation), detectionLocation))); + case "PBE-SHA1-RC2-40" -> + Optional.of( + new PKCS12PBE( + new SHA(detectionLocation), + new RC2(40, new CBC(detectionLocation), detectionLocation))); + // PBES2 with PBKDF2 and HMAC-SHA256, the default of PKCS12_create + case "PBES2-AES-128-CBC" -> Optional.of(pbes2WithAesCbc(128, detectionLocation)); + case "PBES2-AES-192-CBC" -> Optional.of(pbes2WithAesCbc(192, detectionLocation)); + case "PBES2-AES-256-CBC" -> Optional.of(pbes2WithAesCbc(256, detectionLocation)); + case "PBES2-DES-EDE3-CBC" -> + Optional.of( + new PBES2( + new HMAC(new SHA2(256, detectionLocation)), + new DESede( + 168, new CBC(detectionLocation), detectionLocation))); + + // an ECDH or DH derivation whose KDF type is set to none uses no KDF + case "NONE" -> Optional.empty(); + + // PKCS5_PBKDF2_HMAC: the digest comes from its md argument + case "PBKDF2-HMAC" -> Optional.of(new PBKDF2(detectionLocation)); + case "PBKDF2-HMAC-SHA1" -> Optional.of(new PBKDF2(new SHA(detectionLocation))); + + default -> Optional.empty(); + }; + } + + @Nonnull + private static PBES1 pbes1(@Nonnull MessageDigest digest, @Nonnull Cipher cipher) { + return new PBES1(digest, cipher); + } + + /** The DES-CBC of the PKCS#5 v1.5 schemes. */ + @Nonnull + private static DES des(@Nonnull DetectionLocation detectionLocation) { + return new DES(56, new CBC(detectionLocation), detectionLocation); + } + + /** The 64-bit RC2-CBC of the PKCS#5 v1.5 schemes. */ + @Nonnull + private static RC2 rc2(@Nonnull DetectionLocation detectionLocation) { + return new RC2(64, new CBC(detectionLocation), detectionLocation); + } + + @Nonnull + private static PBES2 pbes2WithAesCbc( + int keyLength, @Nonnull DetectionLocation detectionLocation) { + return new PBES2( + new HMAC(new SHA2(256, detectionLocation)), + new AES(keyLength, new CBC(detectionLocation), detectionLocation)); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKemMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKemMapper.java new file mode 100644 index 000000000..1332458e6 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKemMapper.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.mapper.mapper.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.ChaCha20Poly1305; +import com.ibm.mapper.model.algorithms.DHKEM; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.HKDF; +import com.ibm.mapper.model.algorithms.HPKE; +import com.ibm.mapper.model.algorithms.RSASVE; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.mode.GCM; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL key encapsulation names (the names accepted by {@code EVP_KEM_fetch}) and the + * HPKE suites {@code ",,"} to the model. The other KEM names are key agreement + * names, mapped by {@link OpenSslKeyAgreementMapper}. + */ +public class OpenSslKemMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + return switch (str.toUpperCase().trim()) { + // RSA secret value encapsulation (RSASVE, NIST SP 800-56B) + case "RSA" -> Optional.of(new RSASVE(detectionLocation)); + // DHKEM (RFC 9180) over the curve of the key + case "EC" -> Optional.of(new DHKEM(new ECDH(detectionLocation), detectionLocation)); + case "X25519" -> + Optional.of(new DHKEM(new X25519(detectionLocation), detectionLocation)); + case "X448" -> Optional.of(new DHKEM(new X448(detectionLocation), detectionLocation)); + default -> new OpenSslKeyAgreementMapper().parse(str, detectionLocation); + }; + } + + /** + * An HPKE suite {@code ",,"}, e.g. {@code "X25519,HKDF-SHA256,AES-128-GCM"}. + * The export-only AEAD has no cipher. + */ + @Nonnull + public Optional parseHpkeSuite( + @Nullable final String suite, @Nonnull DetectionLocation detectionLocation) { + if (suite == null) { + return Optional.empty(); + } + final String[] parts = suite.toUpperCase().trim().split(","); + if (parts.length != 3) { + return Optional.empty(); + } + final Optional kem = + switch (parts[0]) { + case "P-256" -> Optional.of(dhkemOverCurve("secp256r1", detectionLocation)); + case "P-384" -> Optional.of(dhkemOverCurve("secp384r1", detectionLocation)); + case "P-521" -> Optional.of(dhkemOverCurve("secp521r1", detectionLocation)); + case "X25519" -> + Optional.of( + new DHKEM(new X25519(detectionLocation), detectionLocation)); + case "X448" -> + Optional.of(new DHKEM(new X448(detectionLocation), detectionLocation)); + default -> Optional.empty(); + }; + final Optional kdf = + switch (parts[1]) { + case "HKDF-SHA256" -> Optional.of(new HKDF(new SHA2(256, detectionLocation))); + case "HKDF-SHA384" -> Optional.of(new HKDF(new SHA2(384, detectionLocation))); + case "HKDF-SHA512" -> Optional.of(new HKDF(new SHA2(512, detectionLocation))); + default -> Optional.empty(); + }; + final Optional aead = + switch (parts[2]) { + case "AES-128-GCM" -> + Optional.of( + new AES(128, new GCM(detectionLocation), detectionLocation)); + case "AES-256-GCM" -> + Optional.of( + new AES(256, new GCM(detectionLocation), detectionLocation)); + case "CHACHA20-POLY1305" -> + Optional.of(new ChaCha20Poly1305(detectionLocation)); + default -> Optional.empty(); + }; + if (kem.isEmpty() || kdf.isEmpty()) { + return Optional.empty(); + } + final HPKE hpke = new HPKE(detectionLocation); + hpke.put(kem.get()); + hpke.put(kdf.get()); + aead.ifPresent(hpke::put); + return Optional.of(hpke); + } + + @Nonnull + private static DHKEM dhkemOverCurve( + @Nonnull String curve, @Nonnull DetectionLocation detectionLocation) { + return new DHKEM( + new ECDH(new OpenSslCurveMapper().parse(curve, detectionLocation).orElseThrow()), + detectionLocation); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKeyAgreementMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKeyAgreementMapper.java new file mode 100644 index 000000000..4303593fd --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKeyAgreementMapper.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.mapper.mapper.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.algorithms.DH; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.SecP256r1MLKEM768; +import com.ibm.mapper.model.algorithms.SecP384r1MLKEM1024; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X25519MLKEM768; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.algorithms.X448MLKEM1024; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL key agreement names (the key types of {@code EVP_PKEY_CTX_new_id} / {@code + * EVP_PKEY_CTX_new_from_name} used for {@code EVP_PKEY_derive}, the ffdhe named groups, and the + * names the key agreement detection rules give the legacy {@code DH_*} / {@code ECDH_*} functions) + * to the model. + */ +public class OpenSslKeyAgreementMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + return switch (str.toUpperCase().trim()) { + case "DH" -> Optional.of(new DH(KeyAgreement.class, detectionLocation)); + // the ffdhe named groups (RFC 7919) + case "DH-2048" -> Optional.of(finiteFieldDh(2048, detectionLocation)); + case "DH-3072" -> Optional.of(finiteFieldDh(3072, detectionLocation)); + case "DH-4096" -> Optional.of(finiteFieldDh(4096, detectionLocation)); + case "ECDH" -> Optional.of(new ECDH(detectionLocation)); + case "ECDH-P256" -> Optional.of(ecdh("secp256r1", detectionLocation)); + case "ECDH-P384" -> Optional.of(ecdh("secp384r1", detectionLocation)); + case "ECDH-P521" -> Optional.of(ecdh("secp521r1", detectionLocation)); + case "ECDH-SECP256K1" -> Optional.of(ecdh("secp256k1", detectionLocation)); + case "X25519" -> Optional.of(new X25519(detectionLocation)); + case "X448" -> Optional.of(new X448(detectionLocation)); + case "ML-KEM-512" -> Optional.of(new MLKEM(512, detectionLocation)); + case "ML-KEM-768" -> Optional.of(new MLKEM(768, detectionLocation)); + case "ML-KEM-1024" -> Optional.of(new MLKEM(1024, detectionLocation)); + case "X25519MLKEM768" -> Optional.of(new X25519MLKEM768(detectionLocation)); + case "X448MLKEM1024" -> Optional.of(new X448MLKEM1024(detectionLocation)); + case "SECP256R1MLKEM768" -> Optional.of(new SecP256r1MLKEM768(detectionLocation)); + case "SECP384R1MLKEM1024" -> Optional.of(new SecP384r1MLKEM1024(detectionLocation)); + // SM2 key exchange + case "SM2" -> Optional.of(new SM2(KeyAgreement.class, new SM2(detectionLocation))); + default -> Optional.empty(); + }; + } + + @Nonnull + private static DH finiteFieldDh(int keyLength, @Nonnull DetectionLocation detectionLocation) { + final DH dh = new DH(KeyAgreement.class, detectionLocation); + dh.put(new KeyLength(keyLength, detectionLocation)); + return dh; + } + + @Nonnull + private static ECDH ecdh(@Nonnull String curve, @Nonnull DetectionLocation detectionLocation) { + return new ECDH(new OpenSslCurveMapper().parse(curve, detectionLocation).orElseThrow()); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKeyMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKeyMapper.java new file mode 100644 index 000000000..c4d9a1688 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslKeyMapper.java @@ -0,0 +1,204 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +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.MLDSA; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SPHINCSPlus; +import com.ibm.mapper.model.algorithms.SecP256r1MLKEM768; +import com.ibm.mapper.model.algorithms.SecP384r1MLKEM1024; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X25519MLKEM768; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.algorithms.X448MLKEM1024; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL key names to the model: the key types of {@code EVP_PKEY_CTX_new_id}, {@code + * EVP_PKEY_CTX_new_from_name} and {@code EVP_PKEY_Q_keygen}, the named curves of an EC key, the DH + * groups, and the names the key detection rules give a key with its size (e.g. {@code RSA-3072}, + * {@code DSA-2048}, {@code EC-P256}). + */ +public class OpenSslKeyMapper implements IMapper { + + /** The prefix of the name of an EC key on a named curve, e.g. {@code EC-P256}. */ + private static final String EC_CURVE_PREFIX = "EC-"; + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + final String algorithmName = str.toUpperCase().trim(); + + // EC key on a named curve (e.g. EC_KEY_new_by_curve_name, EVP_PKEY_CTX_set_group_name), + // usable for ECDSA and ECDH + final Optional curve = parseCurve(algorithmName, detectionLocation); + if (curve.isPresent()) { + return curve.map(EllipticCurveAlgorithm::new); + } + + // RSA key length (EVP_PKEY_CTX_set_rsa_keygen_bits) — any bit-length the code sets, + // not just a fixed whitelist + if (algorithmName.startsWith("RSA-") && !algorithmName.equals("RSA-PSS")) { + Integer bits = parseBits(algorithmName, "RSA-"); + if (bits != null) { + return Optional.of(new RSA(bits, detectionLocation)); + } + } + + // DSA parameter length (EVP_PKEY_CTX_set_dsa_paramgen_bits, DSA_generate_parameters) + if (algorithmName.startsWith("DSA-")) { + Integer bits = parseBits(algorithmName, "DSA-"); + if (bits != null) { + final DSA dsa = new DSA(detectionLocation); + dsa.put(new KeyLength(bits, detectionLocation)); + return Optional.of(dsa); + } + } + + return switch (algorithmName) { + // RSA + case "RSA" -> Optional.of(new RSA(detectionLocation)); + // a key restricted to RSA-PSS signatures + case "RSA-PSS" -> Optional.of(new RSAssaPSS(detectionLocation)); + + // DSA + case "DSA" -> Optional.of(new DSA(detectionLocation)); + + // EC + case "EC" -> Optional.of(new EllipticCurveAlgorithm(detectionLocation)); + + // DH + case "DH" -> Optional.of(new DH(detectionLocation)); + case "DH-2048" -> Optional.of(finiteFieldDh(2048, detectionLocation)); + case "DH-3072" -> Optional.of(finiteFieldDh(3072, detectionLocation)); + case "DH-4096" -> Optional.of(finiteFieldDh(4096, detectionLocation)); + // RFC 5114 groups (DH_get_1024_160 etc.): prime size and subgroup size + case "DH-1024-160" -> Optional.of(finiteFieldDh(1024, detectionLocation)); + case "DH-2048-224", "DH-2048-256" -> + Optional.of(finiteFieldDh(2048, detectionLocation)); + + // EdDSA + case "ED25519" -> Optional.of(new Ed25519(detectionLocation)); + case "ED448" -> Optional.of(new Ed448(detectionLocation)); + + // X25519/X448 + case "X25519" -> Optional.of(new X25519(detectionLocation)); + case "X448" -> Optional.of(new X448(detectionLocation)); + + // ML-KEM (Post-Quantum) + case "ML-KEM-512" -> Optional.of(new MLKEM(512, detectionLocation)); + case "ML-KEM-768" -> Optional.of(new MLKEM(768, detectionLocation)); + case "ML-KEM-1024" -> Optional.of(new MLKEM(1024, detectionLocation)); + + // ML-DSA (Post-Quantum) + case "ML-DSA-44" -> Optional.of(new MLDSA(44, detectionLocation)); + case "ML-DSA-65" -> Optional.of(new MLDSA(65, detectionLocation)); + case "ML-DSA-87" -> Optional.of(new MLDSA(87, detectionLocation)); + + // SLH-DSA (Post-Quantum) + case "SLH-DSA-SHA2-128F" -> + Optional.of(new SPHINCSPlus("SHA2-128F", detectionLocation)); + case "SLH-DSA-SHA2-128S" -> + Optional.of(new SPHINCSPlus("SHA2-128S", detectionLocation)); + case "SLH-DSA-SHAKE-128F" -> + Optional.of(new SPHINCSPlus("SHAKE-128F", detectionLocation)); + case "SLH-DSA-SHAKE-128S" -> + Optional.of(new SPHINCSPlus("SHAKE-128S", detectionLocation)); + case "SLH-DSA-SHA2-192F" -> + Optional.of(new SPHINCSPlus("SHA2-192F", detectionLocation)); + case "SLH-DSA-SHA2-192S" -> + Optional.of(new SPHINCSPlus("SHA2-192S", detectionLocation)); + case "SLH-DSA-SHAKE-192F" -> + Optional.of(new SPHINCSPlus("SHAKE-192F", detectionLocation)); + case "SLH-DSA-SHAKE-192S" -> + Optional.of(new SPHINCSPlus("SHAKE-192S", detectionLocation)); + case "SLH-DSA-SHA2-256F" -> + Optional.of(new SPHINCSPlus("SHA2-256F", detectionLocation)); + case "SLH-DSA-SHA2-256S" -> + Optional.of(new SPHINCSPlus("SHA2-256S", detectionLocation)); + case "SLH-DSA-SHAKE-256F" -> + Optional.of(new SPHINCSPlus("SHAKE-256F", detectionLocation)); + case "SLH-DSA-SHAKE-256S" -> + Optional.of(new SPHINCSPlus("SHAKE-256S", detectionLocation)); + + // Hybrid Post-Quantum KEMs (PQC + Classical) + case "X25519MLKEM768" -> Optional.of(new X25519MLKEM768(detectionLocation)); + case "X448MLKEM1024" -> Optional.of(new X448MLKEM1024(detectionLocation)); + case "SECP256R1MLKEM768" -> Optional.of(new SecP256r1MLKEM768(detectionLocation)); + case "SECP384R1MLKEM1024" -> Optional.of(new SecP384r1MLKEM1024(detectionLocation)); + + // SM2 + case "SM2" -> Optional.of(new com.ibm.mapper.model.algorithms.SM2(detectionLocation)); + + default -> Optional.empty(); + }; + } + + /** A named curve, e.g. {@code EC-P256} (as given to {@code EVP_PKEY_CTX_set_group_name}). */ + @Nonnull + public Optional parseCurve( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + final String curveName = str.toUpperCase().trim(); + if (!curveName.startsWith(EC_CURVE_PREFIX)) { + return Optional.empty(); + } + return new OpenSslCurveMapper() + .parse(curveName.substring(EC_CURVE_PREFIX.length()), detectionLocation) + .map(curve -> curve); + } + + @Nullable private static Integer parseBits(@Nonnull String algorithmName, @Nonnull String prefix) { + try { + int bits = Integer.parseInt(algorithmName.substring(prefix.length())); + return bits > 0 ? bits : null; + } catch (NumberFormatException e) { + return null; + } + } + + @Nonnull + private static DH finiteFieldDh(int primeBits, @Nonnull DetectionLocation detectionLocation) { + DH dh = new DH(PublicKeyEncryption.class, detectionLocation); + dh.put(new KeyLength(primeBits, detectionLocation)); + return dh; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslMacMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslMacMapper.java new file mode 100644 index 000000000..caa84ef9b --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslMacMapper.java @@ -0,0 +1,135 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.mapper.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.Aria; +import com.ibm.mapper.model.algorithms.CMAC; +import com.ibm.mapper.model.algorithms.Camellia; +import com.ibm.mapper.model.algorithms.DESede; +import com.ibm.mapper.model.algorithms.GMAC; +import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.model.algorithms.KMAC; +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.SM4; +import com.ibm.mapper.model.algorithms.SipHash; +import com.ibm.mapper.model.algorithms.blake.BLAKE2b; +import com.ibm.mapper.model.algorithms.blake.BLAKE2s; +import com.ibm.mapper.utils.DetectionLocation; +import com.ibm.mapper.utils.Utils; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL MAC names (the names accepted by {@code EVP_MAC_fetch} and the names the MAC + * detection rules give the legacy MAC functions) to the model. + */ +public class OpenSslMacMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + return switch (str.toUpperCase().trim()) { + // Names accepted by EVP_MAC_fetch. The digest of an HMAC and the cipher of a CMAC + // or GMAC are set through OSSL_PARAMs of the context and attached as children. + case "HMAC" -> Optional.of(new HMAC(detectionLocation)); + case "CMAC" -> Optional.of(new CMAC(detectionLocation)); + case "GMAC" -> Optional.of(new GMAC(detectionLocation)); + case "KMAC-128", "KMAC128" -> Optional.of(new KMAC(128, detectionLocation)); + case "KMAC-256", "KMAC256" -> Optional.of(new KMAC(256, detectionLocation)); + case "SIPHASH" -> Optional.of(new SipHash(detectionLocation)); + + // HMAC variants + case "HMAC-MD5" -> Optional.of(new HMAC(new MD5(detectionLocation))); + case "HMAC-SHA1" -> Optional.of(new HMAC(new SHA(detectionLocation))); + case "HMAC-SHA224" -> Optional.of(new HMAC(new SHA2(224, detectionLocation))); + case "HMAC-SHA256" -> Optional.of(new HMAC(new SHA2(256, detectionLocation))); + case "HMAC-SHA384" -> Optional.of(new HMAC(new SHA2(384, detectionLocation))); + case "HMAC-SHA512" -> Optional.of(new HMAC(new SHA2(512, detectionLocation))); + // SHA-512/224 and SHA-512/256 are truncated variants of SHA-512 + case "HMAC-SHA512/224" -> Optional.of(new HMAC(new SHA2(224, detectionLocation))); + case "HMAC-SHA512/256" -> Optional.of(new HMAC(new SHA2(256, detectionLocation))); + case "HMAC-SHA3-224" -> Optional.of(new HMAC(new SHA3(224, detectionLocation))); + case "HMAC-SHA3-256" -> Optional.of(new HMAC(new SHA3(256, detectionLocation))); + case "HMAC-SHA3-384" -> Optional.of(new HMAC(new SHA3(384, detectionLocation))); + case "HMAC-SHA3-512" -> Optional.of(new HMAC(new SHA3(512, detectionLocation))); + case "HMAC-RIPEMD160" -> Optional.of(new HMAC(new RIPEMD(160, detectionLocation))); + case "HMAC-BLAKE2B" -> + Optional.of(new HMAC(new BLAKE2b(512, false, detectionLocation))); + case "HMAC-BLAKE2S" -> + Optional.of(new HMAC(new BLAKE2s(256, false, detectionLocation))); + case "HMAC-SM3" -> Optional.of(new HMAC(new SM3(detectionLocation))); + + // CMAC variants + case "CMAC-AES-128" -> Optional.of(new CMAC(new AES(128, detectionLocation))); + case "CMAC-AES-192" -> Optional.of(new CMAC(new AES(192, detectionLocation))); + case "CMAC-AES-256" -> Optional.of(new CMAC(new AES(256, detectionLocation))); + case "CMAC-3DES" -> Optional.of(new CMAC(new DESede(168, detectionLocation))); + case "CMAC-CAMELLIA-128" -> Optional.of(new CMAC(new Camellia(128, detectionLocation))); + case "CMAC-CAMELLIA-192" -> Optional.of(new CMAC(new Camellia(192, detectionLocation))); + case "CMAC-CAMELLIA-256" -> Optional.of(new CMAC(new Camellia(256, detectionLocation))); + case "CMAC-ARIA-128" -> Optional.of(new CMAC(new Aria(128, detectionLocation))); + case "CMAC-ARIA-192" -> Optional.of(new CMAC(new Aria(192, detectionLocation))); + case "CMAC-ARIA-256" -> Optional.of(new CMAC(new Aria(256, detectionLocation))); + case "CMAC-SM4" -> Optional.of(new CMAC(new SM4(detectionLocation))); + + // GMAC variants: the cipher as a Mac in GMAC mode (Galois MAC, the + // authentication-only + // use of GCM) + case "GMAC-AES-128" -> Optional.of(gmac(new AES(128, detectionLocation))); + case "GMAC-AES-192" -> Optional.of(gmac(new AES(192, detectionLocation))); + case "GMAC-AES-256" -> Optional.of(gmac(new AES(256, detectionLocation))); + + // Poly1305 + case "POLY1305" -> + Optional.of(new Poly1305(Mac.class, new Poly1305(detectionLocation))); + + // BLAKE2 MAC + case "BLAKE2BMAC" -> + Optional.of(new BLAKE2b(Mac.class, new BLAKE2b(512, false, detectionLocation))); + case "BLAKE2SMAC" -> + Optional.of(new BLAKE2s(Mac.class, new BLAKE2s(256, false, detectionLocation))); + + default -> Optional.empty(); + }; + } + + @Nonnull + private static Algorithm gmac(@Nonnull AES aes) { + return Utils.cipherWithMode( + new AES(Mac.class, aes), + new com.ibm.mapper.model.mode.GMAC(aes.getDetectionContext())); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslMessageDigestMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslMessageDigestMapper.java new file mode 100644 index 000000000..1e8672985 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslMessageDigestMapper.java @@ -0,0 +1,74 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.mapper.jca.JcaMessageDigestMapper; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.algorithms.MD4; +import com.ibm.mapper.model.algorithms.MD5SHA1; +import com.ibm.mapper.model.algorithms.MDC2; +import com.ibm.mapper.model.algorithms.RIPEMD; +import com.ibm.mapper.model.algorithms.SM3; +import com.ibm.mapper.model.algorithms.Whirlpool; +import com.ibm.mapper.model.algorithms.blake.BLAKE2b; +import com.ibm.mapper.model.algorithms.blake.BLAKE2s; +import com.ibm.mapper.model.algorithms.shake.SHAKE; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL digest names (the names accepted by {@code EVP_MD_fetch} and {@code + * EVP_get_digestbyname}, and the names the digest detection rules give the {@code EVP_*} digest + * functions and the legacy digest functions) to the model. The names shared with the JCA are mapped + * by {@link JcaMessageDigestMapper}. + */ +public class OpenSslMessageDigestMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + final Optional jcaDigest = + new JcaMessageDigestMapper().parse(str, detectionLocation); + if (jcaDigest.isPresent()) { + return jcaDigest; + } + return switch (str.toUpperCase().trim()) { + case "MD4" -> Optional.of(new MD4(detectionLocation)); + case "MDC2" -> Optional.of(new MDC2(detectionLocation)); + case "MD5-SHA1" -> Optional.of(new MD5SHA1(detectionLocation)); + case "SHAKE128" -> Optional.of(new SHAKE(128, detectionLocation)); + case "SHAKE256" -> Optional.of(new SHAKE(256, detectionLocation)); + case "RIPEMD160" -> Optional.of(new RIPEMD(160, detectionLocation)); + case "WHIRLPOOL" -> Optional.of(new Whirlpool(detectionLocation)); + case "BLAKE2B-512" -> Optional.of(new BLAKE2b(512, false, detectionLocation)); + case "BLAKE2S-256" -> Optional.of(new BLAKE2s(256, false, detectionLocation)); + case "SM3" -> Optional.of(new SM3(detectionLocation)); + // EVP_md_null: no digest + default -> Optional.empty(); + }; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslPRNGMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslPRNGMapper.java new file mode 100644 index 000000000..42e675fca --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslPRNGMapper.java @@ -0,0 +1,92 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.mapper.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.model.algorithms.CTRDRBG; +import com.ibm.mapper.model.algorithms.HMACDRBG; +import com.ibm.mapper.model.algorithms.HashDRBG; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL random generator names (the names accepted by {@code EVP_RAND_fetch} and {@code + * RAND_set_DRBG_type}, and the names the random detection rules give the {@code RAND_*} functions) + * to the model. + */ +public class OpenSslPRNGMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + return switch (str.toUpperCase().trim()) { + // Basic RAND operations + case "RAND" -> + Optional.of( + new Algorithm( + "RAND", PseudorandomNumberGenerator.class, detectionLocation)); + case "RAND-PSEUDO" -> + Optional.of( + new Algorithm( + "RAND-PSEUDO", + PseudorandomNumberGenerator.class, + detectionLocation)); + + // DRBG names accepted by EVP_RAND_fetch and RAND_set_DRBG_type (NIST SP 800-90A). + // The cipher or digest of the DRBG is attached to the node as a child. + case "CTR-DRBG" -> Optional.of(new CTRDRBG(detectionLocation)); + case "HASH-DRBG" -> Optional.of(new HashDRBG(detectionLocation)); + case "HMAC-DRBG" -> Optional.of(new HMACDRBG(detectionLocation)); + + case "SEED-SRC" -> + Optional.of( + new Algorithm( + "SEED-SRC", + PseudorandomNumberGenerator.class, + detectionLocation)); + case "JITTER" -> + Optional.of( + new Algorithm( + "JITTER", + PseudorandomNumberGenerator.class, + detectionLocation)); + case "TEST-RAND" -> + Optional.of( + new Algorithm( + "TEST-RAND", + PseudorandomNumberGenerator.class, + detectionLocation)); + + // Entropy seeding operations (not distinct algorithms — yield empty) + case "RAND-SEED", "RAND-ADD", "RAND-POLL" -> Optional.empty(); + + default -> Optional.empty(); + }; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslRsaSignatureSchemes.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslRsaSignatureSchemes.java new file mode 100644 index 000000000..69321744f --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslRsaSignatureSchemes.java @@ -0,0 +1,62 @@ +/* + * 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.openssl; + +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.ANSIX931; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.padding.PKCS1; +import com.ibm.mapper.model.padding.Raw; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * The RSA signature schemes selected by an OpenSSL RSA padding, shared by the cipher and signature + * mappers: the padding of RSA_private_encrypt or EVP_PKEY_CTX_set_rsa_padding names a signature + * scheme when it is PKCS#1 v1.5 type 1 or ANSI X9.31. + */ +final class OpenSslRsaSignatureSchemes { + + private OpenSslRsaSignatureSchemes() { + // private + } + + /** RSA signatures with PKCS#1 v1.5 (type 1) padding. */ + @Nonnull + static RSA pkcs1v15(@Nonnull DetectionLocation detectionLocation) { + final RSA rsa = new RSA(Signature.class, new RSA(detectionLocation)); + rsa.put(new PKCS1(detectionLocation)); + return rsa; + } + + /** RSA signatures with ANSI X9.31 padding. */ + @Nonnull + static ANSIX931 x931(@Nonnull DetectionLocation detectionLocation) { + return new ANSIX931(detectionLocation); + } + + /** RSA signatures without padding. */ + @Nonnull + static RSA withoutPadding(@Nonnull DetectionLocation detectionLocation) { + final RSA rsa = new RSA(Signature.class, new RSA(detectionLocation)); + rsa.put(new Raw(detectionLocation)); + return rsa; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslSignatureAlgorithmMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslSignatureAlgorithmMapper.java new file mode 100644 index 000000000..2ae69eadd --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslSignatureAlgorithmMapper.java @@ -0,0 +1,120 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.DSA; +import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.Ed25519; +import com.ibm.mapper.model.algorithms.Ed448; +import com.ibm.mapper.model.algorithms.MLDSA; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.SPHINCSPlus; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps the OpenSSL signature names to the model: the names accepted by {@code EVP_SIGNATURE_fetch}, + * the RSA signature schemes selected by an RSA padding ({@code RSA-PKCS1}, {@code RSA-X931}, {@code + * RSA-NO-PADDING}), and the names the signature detection rules give a scheme with its digest, + * {@code -} (e.g. {@code RSA-SHA256}, {@code RSA-PSS-SHA384}, {@code + * ECDSA-SHA3-256}, {@code RSA-MD5-SHA1}). The digest is mapped by {@link + * OpenSslMessageDigestMapper}. + */ +public class OpenSslSignatureAlgorithmMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable final String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + final String name = str.toUpperCase().trim(); + return switch (name) { + case "RSA-PKCS1" -> Optional.of(OpenSslRsaSignatureSchemes.pkcs1v15(detectionLocation)); + case "RSA-X931" -> Optional.of(OpenSslRsaSignatureSchemes.x931(detectionLocation)); + case "RSA-NO-PADDING" -> + Optional.of(OpenSslRsaSignatureSchemes.withoutPadding(detectionLocation)); + case "RSA" -> Optional.of(new RSA(Signature.class, detectionLocation)); + case "RSA-PSS" -> Optional.of(new RSAssaPSS(detectionLocation)); + case "DSA" -> Optional.of(new DSA(detectionLocation)); + case "ECDSA" -> Optional.of(new ECDSA(detectionLocation)); + case "ED25519" -> Optional.of(new Ed25519(detectionLocation)); + case "ED448" -> Optional.of(new Ed448(detectionLocation)); + case "SM2" -> Optional.of(new SM2(detectionLocation)); + case "ML-DSA-44" -> Optional.of(new MLDSA(44, detectionLocation)); + case "ML-DSA-65" -> Optional.of(new MLDSA(65, detectionLocation)); + case "ML-DSA-87" -> Optional.of(new MLDSA(87, detectionLocation)); + default -> withParameter(name, detectionLocation); + }; + } + + /** A scheme followed by its digest, e.g. {@code RSA-SHA256}, or an SLH-DSA parameter set. */ + @Nonnull + private static Optional withParameter( + @Nonnull String name, @Nonnull DetectionLocation detectionLocation) { + if (name.startsWith("SLH-DSA-")) { + return Optional.of( + new SPHINCSPlus(name.substring("SLH-DSA-".length()), detectionLocation)); + } + // RSA-PSS before RSA, as both are prefixes of an RSA-PSS name + if (name.startsWith("RSA-PSS-")) { + final RSAssaPSS rsaPss = new RSAssaPSS(detectionLocation); + digest(name, "RSA-PSS-", detectionLocation).ifPresent(rsaPss::put); + return Optional.of(rsaPss); + } + if (name.startsWith("RSA-")) { + final RSA rsa = new RSA(Signature.class, detectionLocation); + digest(name, "RSA-", detectionLocation).ifPresent(rsa::put); + return Optional.of(rsa); + } + if (name.startsWith("DSA-")) { + return Optional.of( + digest(name, "DSA-", detectionLocation) + .map(DSA::new) + .orElseGet(() -> new DSA(detectionLocation))); + } + if (name.startsWith("ECDSA-")) { + final ECDSA ecdsa = new ECDSA(detectionLocation); + digest(name, "ECDSA-", detectionLocation).ifPresent(ecdsa::put); + return Optional.of(ecdsa); + } + return Optional.empty(); + } + + /** The digest named after the scheme prefix. */ + @Nonnull + private static Optional digest( + @Nonnull String name, + @Nonnull String schemePrefix, + @Nonnull DetectionLocation detectionLocation) { + return new OpenSslMessageDigestMapper() + .parse(name.substring(schemePrefix.length()), detectionLocation) + .map(MessageDigest.class::cast); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslSrtpProfileMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslSrtpProfileMapper.java new file mode 100644 index 000000000..c571f4165 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/openssl/OpenSslSrtpProfileMapper.java @@ -0,0 +1,166 @@ +/* + * 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.openssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Identifier; +import com.ibm.mapper.model.Protocol; +import com.ibm.mapper.model.TagLength; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.Aria; +import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.model.algorithms.SHA; +import com.ibm.mapper.model.collections.AssetCollection; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.collections.IdentifierCollection; +import com.ibm.mapper.model.mode.CTR; +import com.ibm.mapper.model.mode.GCM; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Maps the SRTP protection profiles given to {@code SSL_CTX_set_tlsext_use_srtp} to the model. */ +public class OpenSslSrtpProfileMapper implements IMapper { + + /** + * Maps an SRTP protection profile list (e.g. {@code + * "SRTP_AES128_CM_SHA1_80:SRTP_AEAD_AES_128_GCM"}), as passed to {@code + * SSL_CTX_set_tlsext_use_srtp}, into an SRTP protocol node holding one entry per profile with + * its algorithms and its two-byte identifier (RFC 5764, RFC 7714, RFC 8269, RFC 8723). Names + * OpenSSL does not accept are skipped. + */ + @Nonnull + @Override + public Optional parse( + @Nullable final String profiles, @Nonnull DetectionLocation detectionLocation) { + if (profiles == null) { + return Optional.empty(); + } + final List entries = new ArrayList<>(); + for (String rawName : profiles.split(":")) { + final String name = rawName.trim(); + srtpProfileAlgorithms(name, detectionLocation) + .ifPresent( + algorithms -> + entries.add( + new com.ibm.mapper.model.CipherSuite( + name, + new AssetCollection(algorithms), + new IdentifierCollection( + List.of( + new Identifier( + "0x00", + detectionLocation), + new Identifier( + SRTP_PROFILE_IDS.get( + name), + detectionLocation))), + detectionLocation))); + } + if (entries.isEmpty()) { + return Optional.empty(); + } + final Protocol srtp = new Protocol("SRTP", detectionLocation); + srtp.put(new CipherSuiteCollection(entries)); + return Optional.of(srtp); + } + + /** SRTP protection profile names accepted by OpenSSL → the second byte of their identifier. */ + private static final Map SRTP_PROFILE_IDS = + Map.ofEntries( + Map.entry("SRTP_AES128_CM_SHA1_80", "0x01"), + Map.entry("SRTP_AES128_CM_SHA1_32", "0x02"), + Map.entry("SRTP_AEAD_AES_128_GCM", "0x07"), + Map.entry("SRTP_AEAD_AES_256_GCM", "0x08"), + Map.entry("SRTP_DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM", "0x09"), + Map.entry("SRTP_DOUBLE_AEAD_AES_256_GCM_AEAD_AES_256_GCM", "0x0A"), + Map.entry("SRTP_ARIA_128_CTR_HMAC_SHA1_80", "0x0B"), + Map.entry("SRTP_ARIA_128_CTR_HMAC_SHA1_32", "0x0C"), + Map.entry("SRTP_ARIA_256_CTR_HMAC_SHA1_80", "0x0D"), + Map.entry("SRTP_ARIA_256_CTR_HMAC_SHA1_32", "0x0E"), + Map.entry("SRTP_AEAD_ARIA_128_GCM", "0x0F"), + Map.entry("SRTP_AEAD_ARIA_256_GCM", "0x10")); + + /** + * The algorithms of an SRTP protection profile: a counter mode cipher with an HMAC-SHA1 + * authentication tag of 80 or 32 bits, or an AEAD cipher. + */ + @Nonnull + private static Optional> srtpProfileAlgorithms( + @Nonnull String name, @Nonnull DetectionLocation detectionLocation) { + return switch (name) { + case "SRTP_AES128_CM_SHA1_80" -> + Optional.of( + List.of( + new AES(128, new CTR(detectionLocation), detectionLocation), + hmacSha1(80, detectionLocation))); + case "SRTP_AES128_CM_SHA1_32" -> + Optional.of( + List.of( + new AES(128, new CTR(detectionLocation), detectionLocation), + hmacSha1(32, detectionLocation))); + case "SRTP_AEAD_AES_128_GCM", "SRTP_DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM" -> + Optional.of( + List.of(new AES(128, new GCM(detectionLocation), detectionLocation))); + case "SRTP_AEAD_AES_256_GCM", "SRTP_DOUBLE_AEAD_AES_256_GCM_AEAD_AES_256_GCM" -> + Optional.of( + List.of(new AES(256, new GCM(detectionLocation), detectionLocation))); + case "SRTP_ARIA_128_CTR_HMAC_SHA1_80" -> + Optional.of( + List.of( + new Aria(128, new CTR(detectionLocation), detectionLocation), + hmacSha1(80, detectionLocation))); + case "SRTP_ARIA_128_CTR_HMAC_SHA1_32" -> + Optional.of( + List.of( + new Aria(128, new CTR(detectionLocation), detectionLocation), + hmacSha1(32, detectionLocation))); + case "SRTP_ARIA_256_CTR_HMAC_SHA1_80" -> + Optional.of( + List.of( + new Aria(256, new CTR(detectionLocation), detectionLocation), + hmacSha1(80, detectionLocation))); + case "SRTP_ARIA_256_CTR_HMAC_SHA1_32" -> + Optional.of( + List.of( + new Aria(256, new CTR(detectionLocation), detectionLocation), + hmacSha1(32, detectionLocation))); + case "SRTP_AEAD_ARIA_128_GCM" -> + Optional.of( + List.of(new Aria(128, new GCM(detectionLocation), detectionLocation))); + case "SRTP_AEAD_ARIA_256_GCM" -> + Optional.of( + List.of(new Aria(256, new GCM(detectionLocation), detectionLocation))); + default -> Optional.empty(); + }; + } + + @Nonnull + private static HMAC hmacSha1(int tagLength, @Nonnull DetectionLocation detectionLocation) { + final HMAC hmac = new HMAC(new SHA(detectionLocation)); + hmac.put(new TagLength(tagLength, detectionLocation)); + return hmac; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapper.java index 31fb5620c..d4b6f79f0 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapper.java @@ -139,7 +139,8 @@ public static Optional findCipherSuite(@Nonnull final String id return Optional.of(suite); } } - return Optional.empty(); + return OpenSslCipherSuiteNames.ianaName(identifier) + .map(JsonCipherSuites.CIPHER_SUITES::get); }); } } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapper.java index 55a2fda60..f4cffd578 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapper.java @@ -32,6 +32,7 @@ import com.ibm.mapper.model.algorithms.SM4; import com.ibm.mapper.model.algorithms.Skipjack; import com.ibm.mapper.model.algorithms.TripleDES; +import com.ibm.mapper.model.algorithms.ascon.Ascon128; import com.ibm.mapper.model.algorithms.gost.GOST28147; import com.ibm.mapper.model.algorithms.gost.GOSTR34122015; import com.ibm.mapper.model.mode.CBC; @@ -112,6 +113,7 @@ public Optional parse( case "IDEA CBC" -> Optional.of(new IDEA(new CBC(detectionLocation), detectionLocation)); case "SM4 CCM" -> Optional.of(new SM4(new CCM(detectionLocation), detectionLocation)); case "SM4 GCM" -> Optional.of(new SM4(new GCM(detectionLocation), detectionLocation)); + case "ASCONAEAD128" -> Optional.of(new Ascon128(detectionLocation)); default -> Optional.empty(); }; } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapper.java index aaf8d5025..5e3291816 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapper.java @@ -25,6 +25,7 @@ import com.ibm.mapper.model.algorithms.SHA; import com.ibm.mapper.model.algorithms.SHA2; import com.ibm.mapper.model.algorithms.SM3; +import com.ibm.mapper.model.algorithms.ascon.AsconHash; import com.ibm.mapper.model.algorithms.gost.GOSTR341112; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; @@ -48,6 +49,7 @@ public Optional parse( case "SHA384" -> Optional.of(new SHA2(384, detectionLocation)); case "SM3" -> Optional.of(new SM3(detectionLocation)); case "GOSTR341112" -> Optional.of(new GOSTR341112(detectionLocation)); + case "ASCONHASH256" -> Optional.of(new AsconHash(detectionLocation)); default -> Optional.empty(); }; } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslCipherSuiteNames.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslCipherSuiteNames.java new file mode 100644 index 000000000..2176cac32 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslCipherSuiteNames.java @@ -0,0 +1,190 @@ +/* + * 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.ssl; + +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * The OpenSSL names of the TLS cipher suites for which the cipher suite data ({@code + * ciphersuites.json}, from ciphersuite.info) gives no OpenSSL name, by the IANA name of the suite. + * These are suites that OpenSSL 1.0.2 and 1.1.1 define, or that OpenSSL 3.0 defines but leaves out + * of the build by default, e.g. the RC4, DES, export, Kerberos, static DH and static ECDH suites. + * The names and their codes are those of {@code SSLn_TXT_*} / {@code TLS1_TXT_*} and {@code + * SSLn_CK_*} / {@code TLS1_CK_*} in OpenSSL's {@code ssl3.h} and {@code tls1.h}; both spellings of + * the ephemeral DH suites ({@code EDH-} and {@code DHE-}) are accepted, as OpenSSL does. The + * Fortezza name {@code FZA-RC4-SHA} of code 0x001E, which IANA assigns to {@code + * TLS_KRB5_WITH_DES_CBC_SHA}, is left out. + */ +final class OpenSslCipherSuiteNames { + + private static final Map IANA_NAMES = + Map.ofEntries( + Map.entry("EXP-RC4-MD5", "TLS_RSA_EXPORT_WITH_RC4_40_MD5"), + Map.entry("RC4-MD5", "TLS_RSA_WITH_RC4_128_MD5"), + Map.entry("RC4-SHA", "TLS_RSA_WITH_RC4_128_SHA"), + Map.entry("EXP-RC2-CBC-MD5", "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5"), + Map.entry("EXP-DES-CBC-SHA", "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("DES-CBC-SHA", "TLS_RSA_WITH_DES_CBC_SHA"), + Map.entry("EXP-DH-DSS-DES-CBC-SHA", "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("DH-DSS-DES-CBC-SHA", "TLS_DH_DSS_WITH_DES_CBC_SHA"), + Map.entry("DH-DSS-DES-CBC3-SHA", "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA"), + Map.entry("EXP-DH-RSA-DES-CBC-SHA", "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("DH-RSA-DES-CBC-SHA", "TLS_DH_RSA_WITH_DES_CBC_SHA"), + Map.entry("DH-RSA-DES-CBC3-SHA", "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA"), + Map.entry("EXP-DHE-DSS-DES-CBC-SHA", "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("EXP-EDH-DSS-DES-CBC-SHA", "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("DHE-DSS-DES-CBC-SHA", "TLS_DHE_DSS_WITH_DES_CBC_SHA"), + Map.entry("EDH-DSS-DES-CBC-SHA", "TLS_DHE_DSS_WITH_DES_CBC_SHA"), + Map.entry("EXP-DHE-RSA-DES-CBC-SHA", "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("EXP-EDH-RSA-DES-CBC-SHA", "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("DHE-RSA-DES-CBC-SHA", "TLS_DHE_RSA_WITH_DES_CBC_SHA"), + Map.entry("EDH-RSA-DES-CBC-SHA", "TLS_DHE_RSA_WITH_DES_CBC_SHA"), + Map.entry("EXP-ADH-RC4-MD5", "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5"), + Map.entry("ADH-RC4-MD5", "TLS_DH_anon_WITH_RC4_128_MD5"), + Map.entry("EXP-ADH-DES-CBC-SHA", "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA"), + Map.entry("ADH-DES-CBC-SHA", "TLS_DH_anon_WITH_DES_CBC_SHA"), + Map.entry("KRB5-DES-CBC-SHA", "TLS_KRB5_WITH_DES_CBC_SHA"), + Map.entry("KRB5-DES-CBC3-SHA", "TLS_KRB5_WITH_3DES_EDE_CBC_SHA"), + Map.entry("KRB5-RC4-SHA", "TLS_KRB5_WITH_RC4_128_SHA"), + Map.entry("KRB5-IDEA-CBC-SHA", "TLS_KRB5_WITH_IDEA_CBC_SHA"), + Map.entry("KRB5-DES-CBC-MD5", "TLS_KRB5_WITH_DES_CBC_MD5"), + Map.entry("KRB5-DES-CBC3-MD5", "TLS_KRB5_WITH_3DES_EDE_CBC_MD5"), + Map.entry("KRB5-RC4-MD5", "TLS_KRB5_WITH_RC4_128_MD5"), + Map.entry("KRB5-IDEA-CBC-MD5", "TLS_KRB5_WITH_IDEA_CBC_MD5"), + Map.entry("EXP-KRB5-DES-CBC-SHA", "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA"), + Map.entry("EXP-KRB5-RC2-CBC-SHA", "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA"), + Map.entry("EXP-KRB5-RC4-SHA", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA"), + Map.entry("EXP-KRB5-DES-CBC-MD5", "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5"), + Map.entry("EXP-KRB5-RC2-CBC-MD5", "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5"), + Map.entry("EXP-KRB5-RC4-MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_MD5"), + Map.entry("DH-DSS-AES128-SHA", "TLS_DH_DSS_WITH_AES_128_CBC_SHA"), + Map.entry("DH-RSA-AES128-SHA", "TLS_DH_RSA_WITH_AES_128_CBC_SHA"), + Map.entry("DH-DSS-AES256-SHA", "TLS_DH_DSS_WITH_AES_256_CBC_SHA"), + Map.entry("DH-RSA-AES256-SHA", "TLS_DH_RSA_WITH_AES_256_CBC_SHA"), + Map.entry("DH-DSS-AES128-SHA256", "TLS_DH_DSS_WITH_AES_128_CBC_SHA256"), + Map.entry("DH-RSA-AES128-SHA256", "TLS_DH_RSA_WITH_AES_128_CBC_SHA256"), + Map.entry("DH-DSS-CAMELLIA128-SHA", "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA"), + Map.entry("DH-RSA-CAMELLIA128-SHA", "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA"), + Map.entry("DH-DSS-AES256-SHA256", "TLS_DH_DSS_WITH_AES_256_CBC_SHA256"), + Map.entry("DH-RSA-AES256-SHA256", "TLS_DH_RSA_WITH_AES_256_CBC_SHA256"), + Map.entry("DH-DSS-CAMELLIA256-SHA", "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA"), + Map.entry("DH-RSA-CAMELLIA256-SHA", "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA"), + Map.entry("PSK-RC4-SHA", "TLS_PSK_WITH_RC4_128_SHA"), + Map.entry("DHE-PSK-RC4-SHA", "TLS_DHE_PSK_WITH_RC4_128_SHA"), + Map.entry("RSA-PSK-RC4-SHA", "TLS_RSA_PSK_WITH_RC4_128_SHA"), + Map.entry("DH-DSS-SEED-SHA", "TLS_DH_DSS_WITH_SEED_CBC_SHA"), + Map.entry("DH-RSA-SEED-SHA", "TLS_DH_RSA_WITH_SEED_CBC_SHA"), + Map.entry("DH-RSA-AES128-GCM-SHA256", "TLS_DH_RSA_WITH_AES_128_GCM_SHA256"), + Map.entry("DH-RSA-AES256-GCM-SHA384", "TLS_DH_RSA_WITH_AES_256_GCM_SHA384"), + Map.entry("DH-DSS-AES128-GCM-SHA256", "TLS_DH_DSS_WITH_AES_128_GCM_SHA256"), + Map.entry("DH-DSS-AES256-GCM-SHA384", "TLS_DH_DSS_WITH_AES_256_GCM_SHA384"), + Map.entry( + "DH-DSS-CAMELLIA128-SHA256", "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256"), + Map.entry( + "DH-RSA-CAMELLIA128-SHA256", "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256"), + Map.entry( + "DH-DSS-CAMELLIA256-SHA256", "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256"), + Map.entry( + "DH-RSA-CAMELLIA256-SHA256", "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256"), + Map.entry("ECDH-ECDSA-NULL-SHA", "TLS_ECDH_ECDSA_WITH_NULL_SHA"), + Map.entry("ECDH-ECDSA-RC4-SHA", "TLS_ECDH_ECDSA_WITH_RC4_128_SHA"), + Map.entry("ECDH-ECDSA-DES-CBC3-SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"), + Map.entry("ECDH-ECDSA-AES128-SHA", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA"), + Map.entry("ECDH-ECDSA-AES256-SHA", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA"), + Map.entry("ECDHE-ECDSA-RC4-SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"), + Map.entry("ECDH-RSA-NULL-SHA", "TLS_ECDH_RSA_WITH_NULL_SHA"), + Map.entry("ECDH-RSA-RC4-SHA", "TLS_ECDH_RSA_WITH_RC4_128_SHA"), + Map.entry("ECDH-RSA-DES-CBC3-SHA", "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA"), + Map.entry("ECDH-RSA-AES128-SHA", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"), + Map.entry("ECDH-RSA-AES256-SHA", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA"), + Map.entry("ECDHE-RSA-RC4-SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA"), + Map.entry("AECDH-RC4-SHA", "TLS_ECDH_anon_WITH_RC4_128_SHA"), + Map.entry("ECDH-ECDSA-AES128-SHA256", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"), + Map.entry("ECDH-ECDSA-AES256-SHA384", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384"), + Map.entry("ECDH-RSA-AES128-SHA256", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256"), + Map.entry("ECDH-RSA-AES256-SHA384", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384"), + Map.entry( + "ECDH-ECDSA-AES128-GCM-SHA256", + "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"), + Map.entry( + "ECDH-ECDSA-AES256-GCM-SHA384", + "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384"), + Map.entry("ECDH-RSA-AES128-GCM-SHA256", "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256"), + Map.entry("ECDH-RSA-AES256-GCM-SHA384", "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384"), + Map.entry("ECDHE-PSK-RC4-SHA", "TLS_ECDHE_PSK_WITH_RC4_128_SHA"), + Map.entry("ARIA128-GCM-SHA256", "TLS_RSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry("ARIA256-GCM-SHA384", "TLS_RSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry("DHE-RSA-ARIA128-GCM-SHA256", "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry("DHE-RSA-ARIA256-GCM-SHA384", "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry("DH-RSA-ARIA128-GCM-SHA256", "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry("DH-RSA-ARIA256-GCM-SHA384", "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry("DHE-DSS-ARIA128-GCM-SHA256", "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256"), + Map.entry("DHE-DSS-ARIA256-GCM-SHA384", "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384"), + Map.entry("DH-DSS-ARIA128-GCM-SHA256", "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256"), + Map.entry("DH-DSS-ARIA256-GCM-SHA384", "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384"), + Map.entry("ADH-ARIA128-GCM-SHA256", "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256"), + Map.entry("ADH-ARIA256-GCM-SHA384", "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384"), + Map.entry( + "ECDHE-ECDSA-ARIA128-GCM-SHA256", + "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry( + "ECDHE-ECDSA-ARIA256-GCM-SHA384", + "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry( + "ECDH-ECDSA-ARIA128-GCM-SHA256", + "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry( + "ECDH-ECDSA-ARIA256-GCM-SHA384", + "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry("ECDHE-ARIA128-GCM-SHA256", "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry("ECDHE-ARIA256-GCM-SHA384", "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry("ECDH-ARIA128-GCM-SHA256", "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256"), + Map.entry("ECDH-ARIA256-GCM-SHA384", "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384"), + Map.entry("PSK-ARIA128-GCM-SHA256", "TLS_PSK_WITH_ARIA_128_GCM_SHA256"), + Map.entry("PSK-ARIA256-GCM-SHA384", "TLS_PSK_WITH_ARIA_256_GCM_SHA384"), + Map.entry("DHE-PSK-ARIA128-GCM-SHA256", "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256"), + Map.entry("DHE-PSK-ARIA256-GCM-SHA384", "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384"), + Map.entry("RSA-PSK-ARIA128-GCM-SHA256", "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256"), + Map.entry("RSA-PSK-ARIA256-GCM-SHA384", "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384"), + Map.entry( + "ECDH-ECDSA-CAMELLIA128-SHA256", + "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256"), + Map.entry( + "ECDH-ECDSA-CAMELLIA256-SHA384", + "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384"), + Map.entry( + "ECDH-RSA-CAMELLIA128-SHA256", + "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256"), + Map.entry( + "ECDH-RSA-CAMELLIA256-SHA384", + "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384")); + + private OpenSslCipherSuiteNames() { + // private + } + + /** The IANA name of the cipher suite with the given OpenSSL name, when it is listed here. */ + @Nonnull + static Optional ianaName(@Nonnull String opensslName) { + return Optional.ofNullable(IANA_NAMES.get(opensslName)); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslGroupMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslGroupMapper.java new file mode 100644 index 000000000..12d62b2a3 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslGroupMapper.java @@ -0,0 +1,66 @@ +/* + * 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.ssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.mapper.openssl.OpenSslCurveMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.SecP256r1MLKEM768; +import com.ibm.mapper.model.algorithms.SecP384r1MLKEM1024; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X25519MLKEM768; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps OpenSSL TLS group names (the {@code SSL_(CTX_)set1_groups_list} argument) to model classes. + * Names follow OpenSSL's spelling (e.g. {@code MLKEM768}, {@code X25519MLKEM768}, {@code + * secp256r1}) matched case-insensitively. Unrecognized names return {@link Optional#empty()}; the + * caller emits them as a raw asset so nothing is dropped. + */ +public final class OpenSslGroupMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + + return switch (str.trim().toUpperCase()) { + case "MLKEM512" -> Optional.of(new MLKEM(512, detectionLocation)); + case "MLKEM768" -> Optional.of(new MLKEM(768, detectionLocation)); + case "MLKEM1024" -> Optional.of(new MLKEM(1024, detectionLocation)); + case "X25519" -> Optional.of(new X25519(detectionLocation)); + case "X448" -> Optional.of(new X448(detectionLocation)); + case "X25519MLKEM768" -> Optional.of(new X25519MLKEM768(detectionLocation)); + case "SECP256R1MLKEM768" -> Optional.of(new SecP256r1MLKEM768(detectionLocation)); + case "SECP384R1MLKEM1024" -> Optional.of(new SecP384r1MLKEM1024(detectionLocation)); + // a named elliptic curve group, e.g. secp256r1 or P-256 + default -> new OpenSslCurveMapper().parse(str, detectionLocation).map(ECDH::new); + }; + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslSignatureMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslSignatureMapper.java new file mode 100644 index 000000000..c03b8bcc0 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/OpenSslSignatureMapper.java @@ -0,0 +1,121 @@ +/* + * 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.ssl; + +import com.ibm.mapper.mapper.IMapper; +import com.ibm.mapper.mapper.openssl.OpenSslCurveMapper; +import com.ibm.mapper.mapper.openssl.OpenSslMessageDigestMapper; +import com.ibm.mapper.mapper.openssl.OpenSslSignatureAlgorithmMapper; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Maps OpenSSL TLS signature-algorithm names (the {@code SSL_(CTX_)set1_sigalgs_list} argument) to + * the signature scheme with its digest and, for ECDSA, its curve. Handles the legacy {@code + * ALG+HASH} form (e.g. {@code ECDSA+SHA256}, {@code RSA-PSS+SHA256}), the TLS 1.2/1.3 wire-format + * names (e.g. {@code rsa_pss_rsae_sha256}, {@code ecdsa_secp256r1_sha256}, {@code + * rsa_pkcs1_sha256}), and the scheme names without a digest (e.g. {@code ed25519}, {@code mldsa65}, + * {@code SLH-DSA-SHA2-256s}). In TLS, an RSA signature that is not RSA-PSS uses PKCS#1 v1.5 + * padding. Unrecognized names return {@link Optional#empty()}; the caller emits them as a raw asset + * so nothing is dropped. + */ +public final class OpenSslSignatureMapper implements IMapper { + + @Nonnull + @Override + public Optional parse( + @Nullable String str, @Nonnull DetectionLocation detectionLocation) { + if (str == null) { + return Optional.empty(); + } + final String name = str.trim().toUpperCase(); + final int plus = name.indexOf('+'); + if (plus >= 0) { + return scheme(name.substring(0, plus), name.substring(plus + 1), detectionLocation); + } + if (name.startsWith("RSA_PSS_RSAE_") || name.startsWith("RSA_PSS_PSS_")) { + return scheme("RSA-PSS", afterLastUnderscore(name), detectionLocation); + } + if (name.startsWith("RSA_PKCS1_")) { + return scheme("RSA", afterLastUnderscore(name), detectionLocation); + } + if (name.startsWith("ECDSA_")) { + return ecdsa(str.trim().substring("ECDSA_".length()), detectionLocation); + } + if (name.startsWith("MLDSA")) { + return signature("ML-DSA-" + name.substring("MLDSA".length()), detectionLocation); + } + return signature(name, detectionLocation); + } + + /** A scheme with its digest: in TLS, an RSA scheme other than RSA-PSS is PKCS#1 v1.5. */ + @Nonnull + private static Optional scheme( + @Nonnull String scheme, + @Nonnull String digest, + @Nonnull DetectionLocation detectionLocation) { + final String algorithm = "RSA".equals(scheme.trim()) ? "RSA-PKCS1" : scheme.trim(); + final Optional signature = signature(algorithm, detectionLocation); + signature.ifPresent( + node -> + new OpenSslMessageDigestMapper() + .parse(digest.trim(), detectionLocation) + .filter(MessageDigest.class::isInstance) + .ifPresent(node::put)); + return signature; + } + + /** + * An ECDSA wire-format name after its {@code ecdsa_} prefix: the curve, when named, and the + * digest, e.g. {@code secp256r1_sha256}, {@code brainpoolP256r1tls13_sha256} or {@code sha1}. + */ + @Nonnull + private static Optional ecdsa( + @Nonnull String curveAndDigest, @Nonnull DetectionLocation detectionLocation) { + final int underscore = curveAndDigest.lastIndexOf('_'); + final Optional ecdsa = + scheme("ECDSA", curveAndDigest.substring(underscore + 1), detectionLocation); + if (underscore > 0) { + final String curve = + curveAndDigest.substring(0, underscore).replaceFirst("(?i)tls13$", ""); + ecdsa.ifPresent( + node -> + new OpenSslCurveMapper() + .parse(curve, detectionLocation) + .ifPresent(node::put)); + } + return ecdsa; + } + + @Nonnull + private static Optional signature( + @Nonnull String name, @Nonnull DetectionLocation detectionLocation) { + return new OpenSslSignatureAlgorithmMapper().parse(name, detectionLocation); + } + + @Nonnull + private static String afterLastUnderscore(@Nonnull String name) { + return name.substring(name.lastIndexOf('_') + 1); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/SSLVersionMapper.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/SSLVersionMapper.java index 2baf9a8c4..607f58992 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/SSLVersionMapper.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/SSLVersionMapper.java @@ -37,12 +37,12 @@ public Optional parse( return Optional.empty(); } - Pattern pattern = Pattern.compile("tlsv(\\d+(\\.\\d+)?)"); + Pattern pattern = Pattern.compile("^(?:tls|ssl|dtls)v(\\d+(\\.\\d+)?)"); Matcher matcher = pattern.matcher(str.toLowerCase()); if (matcher.find()) { String number = matcher.group(1); - if (number.equals("1")) { - number = "1.0"; + if (matcher.group(2) == null) { + number = number + ".0"; } return Optional.of(new Version(number, detectionLocation)); } diff --git a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/json/JsonCipherSuites.java b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/json/JsonCipherSuites.java index b94d3d1a3..8a1ab60f3 100644 --- a/mapper/src/main/java/com/ibm/mapper/mapper/ssl/json/JsonCipherSuites.java +++ b/mapper/src/main/java/com/ibm/mapper/mapper/ssl/json/JsonCipherSuites.java @@ -41,6 +41,17 @@ private JsonCipherSuites() { null, "AES 128 CCM 8", "SHA256")), + Map.entry( + "TLS_AES_128_CCM_ASCONHASH256", + new JsonCipherSuite( + "TLS_AES_128_CCM_ASCONHASH256", + null, + null, + new String[] {"0x00", "0x71"}, + null, + null, + "AES 128 CCM", + "ASCONHASH256")), Map.entry( "TLS_AES_128_CCM_SHA256", new JsonCipherSuite( @@ -52,6 +63,17 @@ private JsonCipherSuites() { null, "AES 128 CCM", "SHA256")), + Map.entry( + "TLS_AES_128_GCM_ASCONHASH256", + new JsonCipherSuite( + "TLS_AES_128_GCM_ASCONHASH256", + null, + null, + new String[] {"0x00", "0x70"}, + null, + null, + "AES 128 GCM", + "ASCONHASH256")), Map.entry( "TLS_AES_128_GCM_SHA256", new JsonCipherSuite( @@ -74,6 +96,17 @@ private JsonCipherSuites() { null, "AES 256 GCM", "SHA384")), + Map.entry( + "TLS_ASCONAEAD128_SHA256", + new JsonCipherSuite( + "TLS_ASCONAEAD128_SHA256", + null, + null, + new String[] {"0x00", "0x6F"}, + null, + null, + "ASCONAEAD128", + "SHA256")), Map.entry( "TLS_CHACHA20_POLY1305_SHA256", new JsonCipherSuite( 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 4585e602d..0101a7818 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Algorithm.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Algorithm.java @@ -61,6 +61,15 @@ public Algorithm( this.origin = origin; } + /** A copy of the given algorithm without its children. */ + protected Algorithm(@Nonnull Algorithm algorithm) { + this.children = new HashMap<>(); + this.kind = algorithm.kind; + this.detectionLocation = algorithm.detectionLocation; + this.name = algorithm.name; + this.origin = algorithm.origin; + } + @Override public boolean equals(Object object) { if (this == object) return true; @@ -141,18 +150,33 @@ public void removeChildOfType(@Nonnull Class nodeType) { @Nonnull @Override public INode deepCopy() { - try { - // Subclasses keep their mutable state in children. Cloning preserves the concrete - // algorithm type and metadata; the child tree must then be copied separately. - Algorithm copy = (Algorithm) super.clone(); - copy.children = new HashMap<>(); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; - } catch (CloneNotSupportedException exception) { - throw new AssertionError(exception); + final Algorithm copy = copy(); + for (INode child : this.children.values()) { + copy.children.put(child.getKind(), child.deepCopy()); } + return copy; + } + + /** + * A copy of this algorithm without its children. An algorithm class returns an instance of its + * own class, which gives the composed name of the algorithm, how the enrichers complete it and + * how the output reports it. + */ + @Nonnull + protected Algorithm copy() { + return new Algorithm(this); + } + + /** + * This algorithm as another kind, e.g. a block cipher used as a MAC: a node of the class of + * this algorithm with its name, detection location and origin, that shares its children. It + * takes the place of this algorithm. An algorithm class returns an instance of its own class, + * which gives the composed name of the algorithm (e.g. {@code AES-128-GMAC}), how the enrichers + * complete it and how the output reports it. + */ + @Nonnull + public Algorithm asKind(@Nonnull Class kind) { + return new Algorithm(this, kind); } public boolean is(@Nonnull final Class type) { diff --git a/mapper/src/main/java/com/ibm/mapper/model/BlockSize.java b/mapper/src/main/java/com/ibm/mapper/model/BlockSize.java index 11f92f11f..21d01cbaa 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/BlockSize.java +++ b/mapper/src/main/java/com/ibm/mapper/model/BlockSize.java @@ -40,7 +40,7 @@ public BlockSize( } private BlockSize(@Nonnull BlockSize blockSize) { - super(blockSize.type, blockSize.detectionLocation, blockSize.children, blockSize.origin); + super(blockSize); this.value = blockSize.value; } @@ -70,12 +70,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - BlockSize copy = new BlockSize(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected BlockSize copy() { + return new BlockSize(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/ClassicalBitSecurityLevel.java b/mapper/src/main/java/com/ibm/mapper/model/ClassicalBitSecurityLevel.java index 5e643a7d0..73d3c0426 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/ClassicalBitSecurityLevel.java +++ b/mapper/src/main/java/com/ibm/mapper/model/ClassicalBitSecurityLevel.java @@ -35,10 +35,7 @@ public ClassicalBitSecurityLevel( private ClassicalBitSecurityLevel( @Nonnull ClassicalBitSecurityLevel classicalBitSecurityLevel) { - super( - classicalBitSecurityLevel.type, - classicalBitSecurityLevel.detectionLocation, - classicalBitSecurityLevel.children); + super(classicalBitSecurityLevel); this.bitSecurityLevel = classicalBitSecurityLevel.bitSecurityLevel; } @@ -55,12 +52,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - ClassicalBitSecurityLevel copy = new ClassicalBitSecurityLevel(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected ClassicalBitSecurityLevel copy() { + return new ClassicalBitSecurityLevel(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java b/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java index c5ac177c9..42667af47 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java +++ b/mapper/src/main/java/com/ibm/mapper/model/DigestSize.java @@ -32,7 +32,7 @@ public DigestSize(@Nonnull Integer value, @Nonnull DetectionLocation detectionLo } private DigestSize(@Nonnull DigestSize digestSize) { - super(digestSize.type, digestSize.detectionLocation, digestSize.children); + super(digestSize); this.value = digestSize.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - DigestSize copy = new DigestSize(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected DigestSize copy() { + return new DigestSize(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/EllipticCurve.java b/mapper/src/main/java/com/ibm/mapper/model/EllipticCurve.java index 18dd60861..5f979ab63 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/EllipticCurve.java +++ b/mapper/src/main/java/com/ibm/mapper/model/EllipticCurve.java @@ -31,8 +31,9 @@ public EllipticCurve(@Nonnull String value, @Nonnull DetectionLocation detection this.value = value; } - private EllipticCurve(@Nonnull EllipticCurve ellipticCurve) { - super(ellipticCurve.type, ellipticCurve.detectionLocation, ellipticCurve.children); + /** A copy of the given curve without its children. */ + protected EllipticCurve(@Nonnull EllipticCurve ellipticCurve) { + super(ellipticCurve); this.value = ellipticCurve.value; } @@ -47,14 +48,14 @@ public String asString() { return value; } + /** + * A curve class returns an instance of its own class, which the enrichers recognise the curve + * by. + */ @Nonnull @Override - public INode deepCopy() { - EllipticCurve copy = new EllipticCurve(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected EllipticCurve copy() { + return new EllipticCurve(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/EllipticCurveAlgorithm.java b/mapper/src/main/java/com/ibm/mapper/model/EllipticCurveAlgorithm.java index d2632a6f9..725acd1f8 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/EllipticCurveAlgorithm.java +++ b/mapper/src/main/java/com/ibm/mapper/model/EllipticCurveAlgorithm.java @@ -25,14 +25,28 @@ public class EllipticCurveAlgorithm extends Algorithm implements PublicKeyEncryption, Signature, KeyAgreement { + private static final String NAME = "EC"; + + /** The name, followed by the curve when the curve is known, e.g. {@code EC-secp256r1}. */ + @Nonnull + @Override + public String asString() { + if (!this.name.equals(NAME)) { + return super.asString(); + } + return this.hasChildOfType(EllipticCurve.class) + .map(curve -> NAME + "-" + curve.asString()) + .orElse(NAME); + } + public EllipticCurveAlgorithm(@Nonnull EllipticCurve curve) { - super("EC-" + curve.asString(), PublicKeyEncryption.class, curve.detectionLocation); + super(NAME + "-" + curve.asString(), PublicKeyEncryption.class, curve.detectionLocation); this.put(curve); this.put(new Oid("1.2.840.10045.2.1", curve.detectionLocation)); } public EllipticCurveAlgorithm(@Nonnull DetectionLocation detectionLocation) { - super("EC", PublicKeyEncryption.class, detectionLocation); + super(NAME, PublicKeyEncryption.class, detectionLocation); this.put(new Oid("1.2.840.10045.2.1", detectionLocation)); } @@ -41,4 +55,20 @@ public EllipticCurveAlgorithm( @Nonnull EllipticCurveAlgorithm ellipticCurveAlgorithm) { super(ellipticCurveAlgorithm, asKind); } + + private EllipticCurveAlgorithm(@Nonnull EllipticCurveAlgorithm algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected EllipticCurveAlgorithm copy() { + return new EllipticCurveAlgorithm(this); + } + + @Nonnull + @Override + public EllipticCurveAlgorithm asKind(@Nonnull Class kind) { + return new EllipticCurveAlgorithm(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/Identifier.java b/mapper/src/main/java/com/ibm/mapper/model/Identifier.java index 43f89d653..afee0eb48 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Identifier.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Identifier.java @@ -32,7 +32,7 @@ public Identifier(@Nonnull String value, @Nonnull DetectionLocation detectionLoc } private Identifier(@Nonnull Identifier identifier) { - super(identifier.type, identifier.detectionLocation, identifier.children); + super(identifier); this.value = identifier.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - Identifier copy = new Identifier(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Identifier copy() { + return new Identifier(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/InitializationVectorLength.java b/mapper/src/main/java/com/ibm/mapper/model/InitializationVectorLength.java index 280bc6f6f..c1bec5960 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/InitializationVectorLength.java +++ b/mapper/src/main/java/com/ibm/mapper/model/InitializationVectorLength.java @@ -34,10 +34,7 @@ public InitializationVectorLength( private InitializationVectorLength( @Nonnull InitializationVectorLength initializationVectorLength) { - super( - initializationVectorLength.type, - initializationVectorLength.detectionLocation, - initializationVectorLength.children); + super(initializationVectorLength); this.value = initializationVectorLength.value; } @@ -54,12 +51,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - InitializationVectorLength copy = new InitializationVectorLength(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected InitializationVectorLength copy() { + return new InitializationVectorLength(this); } @Override 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 ca82e11c9..ba54df8e4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Key.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Key.java @@ -58,7 +58,8 @@ protected Key(@Nonnull IAlgorithm algorithm, @Nonnull final Class this.kind = asKind; } - private Key(@Nonnull Key key) { + /** A copy of the given key without its children. */ + protected Key(@Nonnull Key key) { this.children = new HashMap<>(); this.kind = key.kind; this.detectionLocation = key.detectionLocation; @@ -129,13 +130,22 @@ public void removeChildOfType(@Nonnull Class nodeType) { @Nonnull @Override public INode deepCopy() { - Key copy = new Key(this); + final Key copy = copy(); for (INode child : this.children.values()) { copy.children.put(child.getKind(), child.deepCopy()); } return copy; } + /** + * A copy of this key without its children. A key class returns an instance of its own class, + * which the output reports the key by. + */ + @Nonnull + protected Key copy() { + return new Key(this); + } + @Override public boolean equals(Object object) { if (this == object) return true; diff --git a/mapper/src/main/java/com/ibm/mapper/model/KeyLength.java b/mapper/src/main/java/com/ibm/mapper/model/KeyLength.java index 6be9bbc31..ca72e32ce 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/KeyLength.java +++ b/mapper/src/main/java/com/ibm/mapper/model/KeyLength.java @@ -41,7 +41,7 @@ public KeyLength( } private KeyLength(@Nonnull KeyLength keyLength) { - super(keyLength.type, keyLength.detectionLocation, keyLength.children, keyLength.origin); + super(keyLength); this.value = keyLength.value; } @@ -71,12 +71,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - KeyLength copy = new KeyLength(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected KeyLength copy() { + return new KeyLength(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/Mode.java b/mapper/src/main/java/com/ibm/mapper/model/Mode.java index 3b87bb75e..150412cb3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Mode.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Mode.java @@ -41,8 +41,9 @@ public Mode( this.put(blockSize); } - private Mode(@Nonnull Mode mode) { - super(mode.type, mode.detectionLocation, mode.children); + /** A copy of the given mode without its children. */ + protected Mode(@Nonnull Mode mode) { + super(mode); this.name = mode.name; } @@ -71,14 +72,14 @@ public String asString() { return name; } + /** + * A mode class returns an instance of its own class, which the enrichers and the output + * recognise the mode by. + */ @Nonnull @Override - public INode deepCopy() { - Mode copy = new Mode(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Mode copy() { + return new Mode(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/NonceLength.java b/mapper/src/main/java/com/ibm/mapper/model/NonceLength.java index 01932fe06..5ca22d9fd 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/NonceLength.java +++ b/mapper/src/main/java/com/ibm/mapper/model/NonceLength.java @@ -32,7 +32,7 @@ public NonceLength(@Nonnull Integer value, @Nonnull DetectionLocation detectionL } private NonceLength(@Nonnull NonceLength nonceLength) { - super(nonceLength.type, nonceLength.detectionLocation, nonceLength.children); + super(nonceLength); this.value = nonceLength.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - NonceLength copy = new NonceLength(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected NonceLength copy() { + return new NonceLength(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/NumberOfIterations.java b/mapper/src/main/java/com/ibm/mapper/model/NumberOfIterations.java index 140fbf936..ed8932ea3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/NumberOfIterations.java +++ b/mapper/src/main/java/com/ibm/mapper/model/NumberOfIterations.java @@ -33,10 +33,7 @@ public NumberOfIterations( } public NumberOfIterations(@Nonnull NumberOfIterations numberOfIterations) { - super( - numberOfIterations.type, - numberOfIterations.detectionLocation, - numberOfIterations.children); + super(numberOfIterations); this.value = numberOfIterations.value; } @@ -53,12 +50,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - NumberOfIterations copy = new NumberOfIterations(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected NumberOfIterations copy() { + return new NumberOfIterations(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/Oid.java b/mapper/src/main/java/com/ibm/mapper/model/Oid.java index 220c1a47f..abd513cbe 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Oid.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Oid.java @@ -33,7 +33,7 @@ public Oid(@Nonnull String value, @Nonnull DetectionLocation detectionLocation) } private Oid(@Nonnull Oid oid) { - super(Oid.class, oid.detectionLocation, oid.children); + super(oid); this.value = oid.value; } @@ -50,12 +50,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - Oid copy = new Oid(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Oid copy() { + return new Oid(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/OutputFormat.java b/mapper/src/main/java/com/ibm/mapper/model/OutputFormat.java index 950a78cb4..3e8141516 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/OutputFormat.java +++ b/mapper/src/main/java/com/ibm/mapper/model/OutputFormat.java @@ -33,7 +33,7 @@ public OutputFormat(@Nonnull String value, @Nonnull DetectionLocation detectionL } private OutputFormat(@Nonnull OutputFormat outputFormat) { - super(outputFormat.type, outputFormat.detectionLocation, outputFormat.children); + super(outputFormat); this.value = outputFormat.value; } @@ -50,12 +50,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - OutputFormat copy = new OutputFormat(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected OutputFormat copy() { + return new OutputFormat(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/Padding.java b/mapper/src/main/java/com/ibm/mapper/model/Padding.java index 8bbb74731..c909f0072 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Padding.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Padding.java @@ -20,7 +20,6 @@ package com.ibm.mapper.model; import com.ibm.mapper.utils.DetectionLocation; -import java.util.HashMap; import java.util.Objects; import javax.annotation.Nonnull; @@ -28,7 +27,7 @@ public class Padding extends Property { @Nonnull private final String name; public Padding(@Nonnull String name, @Nonnull DetectionLocation detectionLocation) { - super(Padding.class, detectionLocation, new HashMap<>()); + super(Padding.class, detectionLocation); this.name = name; } @@ -36,12 +35,13 @@ protected Padding( @Nonnull String name, @Nonnull DetectionLocation detectionLocation, @Nonnull Class kind) { - super(kind, detectionLocation, new HashMap<>()); + super(kind, detectionLocation); this.name = name; } - private Padding(@Nonnull Padding padding) { - super(padding.type, padding.detectionLocation, padding.children); + /** A copy of the given padding without its children. */ + protected Padding(@Nonnull Padding padding) { + super(padding); this.name = padding.name; } @@ -61,14 +61,14 @@ public String asString() { return name; } + /** + * A padding class returns an instance of its own class, which the output recognises the padding + * by. + */ @Nonnull @Override - public INode deepCopy() { - Padding copy = new Padding(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Padding copy() { + return new Padding(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/ParameterSetIdentifier.java b/mapper/src/main/java/com/ibm/mapper/model/ParameterSetIdentifier.java index 40b0c9cfa..f2e94d2cf 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/ParameterSetIdentifier.java +++ b/mapper/src/main/java/com/ibm/mapper/model/ParameterSetIdentifier.java @@ -33,10 +33,7 @@ public ParameterSetIdentifier( } private ParameterSetIdentifier(@Nonnull ParameterSetIdentifier parameterSetIdentifier) { - super( - parameterSetIdentifier.type, - parameterSetIdentifier.detectionLocation, - parameterSetIdentifier.children); + super(parameterSetIdentifier); this.value = parameterSetIdentifier.value; } @@ -53,12 +50,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - ParameterSetIdentifier copy = new ParameterSetIdentifier(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected ParameterSetIdentifier copy() { + return new ParameterSetIdentifier(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/PasswordLength.java b/mapper/src/main/java/com/ibm/mapper/model/PasswordLength.java index 61541b894..7042e9caf 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/PasswordLength.java +++ b/mapper/src/main/java/com/ibm/mapper/model/PasswordLength.java @@ -32,7 +32,7 @@ public PasswordLength(@Nonnull Integer value, @Nonnull DetectionLocation detecti } private PasswordLength(@Nonnull PasswordLength passwordLength) { - super(passwordLength.type, passwordLength.detectionLocation, passwordLength.children); + super(passwordLength); this.value = passwordLength.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - PasswordLength copy = new PasswordLength(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected PasswordLength copy() { + return new PasswordLength(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java b/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java index 3fb96f585..2d4202e80 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java +++ b/mapper/src/main/java/com/ibm/mapper/model/PrivateKey.java @@ -33,4 +33,14 @@ public PrivateKey(@Nonnull Signature signature) { public PrivateKey(@Nonnull Key key) { super(key, key.detectionLocation, PrivateKey.class); } + + private PrivateKey(@Nonnull PrivateKey key) { + super(key); + } + + @Nonnull + @Override + protected PrivateKey copy() { + return new PrivateKey(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/Property.java b/mapper/src/main/java/com/ibm/mapper/model/Property.java index e10add1f2..d2e774193 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Property.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Property.java @@ -48,24 +48,28 @@ protected Property( this.origin = origin; } - protected Property( - @Nonnull Class type, - @Nonnull DetectionLocation detectionLocation, - @Nonnull Map, INode> children) { - this(type, detectionLocation, children, NodeOrigin.DETECTED); + /** A copy of the given property without its children. */ + protected Property(@Nonnull Property property) { + this(property.type, property.detectionLocation, property.origin); } - protected Property( - @Nonnull Class type, - @Nonnull DetectionLocation detectionLocation, - @Nonnull Map, INode> children, - @Nonnull NodeOrigin origin) { - this.type = type; - this.children = children; - this.detectionLocation = detectionLocation; - this.origin = origin; + @Nonnull + @Override + public INode deepCopy() { + final Property copy = copy(); + for (INode child : this.children.values()) { + copy.children.put(child.getKind(), child.deepCopy()); + } + return copy; } + /** + * A copy of this property without its children, of the class of this property: the class gives + * the name of the property, how the enrichers complete it and how the output reports it. + */ + @Nonnull + protected abstract Property copy(); + @Override public void put(@Nonnull INode child) { this.children.put(child.getKind(), child); diff --git a/mapper/src/main/java/com/ibm/mapper/model/Protocol.java b/mapper/src/main/java/com/ibm/mapper/model/Protocol.java index ed4d8d1f5..fa6422915 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Protocol.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Protocol.java @@ -45,7 +45,8 @@ public Protocol(@Nonnull String type, @Nonnull DetectionLocation detectionLocati this.kind = Protocol.class; } - private Protocol(@Nonnull Protocol protocol) { + /** A copy of the given protocol without its children. */ + protected Protocol(@Nonnull Protocol protocol) { this.children = new HashMap<>(); this.kind = protocol.kind; this.detectionLocation = protocol.detectionLocation; @@ -119,13 +120,22 @@ public void removeChildOfType(@Nonnull Class nodeType) { @Nonnull @Override public INode deepCopy() { - Protocol copy = new Protocol(this); + final Protocol copy = copy(); for (INode child : this.children.values()) { copy.children.put(child.getKind(), child.deepCopy()); } return copy; } + /** + * A copy of this protocol without its children. A protocol class returns an instance of its own + * class, which the output reports the protocol by. + */ + @Nonnull + protected Protocol copy() { + return new Protocol(this); + } + public boolean is(@Nonnull final Class type) { return this.getKind().equals(type); } diff --git a/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java b/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java index d080d7fee..02624d931 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java +++ b/mapper/src/main/java/com/ibm/mapper/model/PublicKey.java @@ -33,4 +33,14 @@ public PublicKey(@Nonnull Signature signature) { public PublicKey(@Nonnull Key key) { super(key, key.detectionLocation, PublicKey.class); } + + private PublicKey(@Nonnull PublicKey key) { + super(key); + } + + @Nonnull + @Override + protected PublicKey copy() { + return new PublicKey(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/SaltLength.java b/mapper/src/main/java/com/ibm/mapper/model/SaltLength.java index d2b10c65d..a2361ec3f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/SaltLength.java +++ b/mapper/src/main/java/com/ibm/mapper/model/SaltLength.java @@ -32,7 +32,7 @@ public SaltLength(@Nonnull Integer value, @Nonnull DetectionLocation detectionLo } private SaltLength(@Nonnull SaltLength saltLength) { - super(saltLength.type, saltLength.detectionLocation, saltLength.children); + super(saltLength); this.value = saltLength.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - SaltLength copy = new SaltLength(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected SaltLength copy() { + return new SaltLength(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/SecretKey.java b/mapper/src/main/java/com/ibm/mapper/model/SecretKey.java index 94a4aa31a..5b965c353 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/SecretKey.java +++ b/mapper/src/main/java/com/ibm/mapper/model/SecretKey.java @@ -29,4 +29,14 @@ public SecretKey(@Nonnull Key key) { public SecretKey(@Nonnull Algorithm algorithm) { super(algorithm, SecretKey.class); } + + private SecretKey(@Nonnull SecretKey key) { + super(key); + } + + @Nonnull + @Override + protected SecretKey copy() { + return new SecretKey(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/Seed.java b/mapper/src/main/java/com/ibm/mapper/model/Seed.java index 5698445f5..daa6994f9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Seed.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Seed.java @@ -32,7 +32,7 @@ public Seed(@Nonnull Integer value, @Nonnull DetectionLocation detectionLocation } private Seed(@Nonnull Seed seed) { - super(seed.type, seed.detectionLocation, seed.children); + super(seed); this.value = seed.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - Seed copy = new Seed(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Seed copy() { + return new Seed(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/TagLength.java b/mapper/src/main/java/com/ibm/mapper/model/TagLength.java index 905d27692..d549488c0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/TagLength.java +++ b/mapper/src/main/java/com/ibm/mapper/model/TagLength.java @@ -32,7 +32,7 @@ public TagLength(@Nonnull Integer value, @Nonnull DetectionLocation detectionLoc } private TagLength(@Nonnull TagLength tagLength) { - super(tagLength.type, tagLength.detectionLocation, tagLength.children); + super(tagLength); this.value = tagLength.value; } @@ -49,12 +49,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - TagLength copy = new TagLength(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected TagLength copy() { + return new TagLength(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/Unknown.java b/mapper/src/main/java/com/ibm/mapper/model/Unknown.java index 6adfdcf35..47f5c40be 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Unknown.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Unknown.java @@ -29,7 +29,7 @@ public Unknown(@Nonnull DetectionLocation detectionLocation) { } private Unknown(@Nonnull Unknown unknown) { - super(unknown.type, unknown.detectionLocation, unknown.children); + super(unknown); } @Override @@ -59,12 +59,8 @@ public String toString() { @Nonnull @Override - public INode deepCopy() { - Unknown copy = new Unknown(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Unknown copy() { + return new Unknown(this); } @Nonnull diff --git a/mapper/src/main/java/com/ibm/mapper/model/Version.java b/mapper/src/main/java/com/ibm/mapper/model/Version.java index bb4a4aa7d..c3b58bace 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/Version.java +++ b/mapper/src/main/java/com/ibm/mapper/model/Version.java @@ -31,7 +31,7 @@ public Version(@Nonnull String value, @Nonnull DetectionLocation detectionLocati } private Version(@Nonnull Version version) { - super(version.type, version.detectionLocation, version.children); + super(version); this.value = version.value; } @@ -48,12 +48,8 @@ public String asString() { @Nonnull @Override - public INode deepCopy() { - Version copy = new Version(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Version copy() { + return new Version(this); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/AES.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/AES.java index d8bf7b243..0f377c34d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/AES.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/AES.java @@ -56,8 +56,19 @@ public final class AES extends Algorithm implements BlockCipher, AuthenticatedEncryption, KeyWrap, Mac { private static final String NAME = "AES"; + /** + * {@code AES[-keyLength][-mode][-padding]}; as a MAC {@code AES[-keyLength][-GMAC]}, the MAC + * names of the CycloneDX registry: the mode of a MAC built on another mode (e.g. a CBC-MAC) is + * a child of the MAC, not part of its name. + */ @Override public @Nonnull String asString() { + if (is(Mac.class)) { + return composeName( + true, + getMode().filter(com.ibm.mapper.model.mode.GMAC.class::isInstance).isPresent(), + false); + } return composeName(true, true, true); } @@ -102,4 +113,20 @@ public AES( super(NAME, asKind, detectionLocation); this.put(BlockSize.ofDefault(128, detectionLocation)); } + + private AES(@Nonnull AES algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected AES copy() { + return new AES(this); + } + + @Nonnull + @Override + public AES asKind(@Nonnull Class kind) { + return new AES(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX931.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX931.java index 621a0ce07..2e391ba12 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX931.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX931.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public final class ANSIX931 extends Algorithm implements Signature { public ANSIX931(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private ANSIX931(@Nonnull ANSIX931 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ANSIX931 copy() { + return new ANSIX931(this); + } + + public ANSIX931(@Nonnull final Class asKind, @Nonnull ANSIX931 ansix931) { + super(ansix931, asKind); + } + + @Nonnull + @Override + public ANSIX931 asKind(@Nonnull Class kind) { + return new ANSIX931(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX942.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX942.java index 6a8492b3c..fcf18ed57 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX942.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX942.java @@ -20,8 +20,13 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; import javax.annotation.Nonnull; /** @@ -46,7 +51,48 @@ public final class ANSIX942 extends Algorithm implements KeyDerivationFunction { private static final String NAME = "ANSI-KDF-X9.42"; + @Override + public @Nonnull String asString() { + final StringBuilder stringBuilder = new StringBuilder(this.name); + final Optional digest = this.hasChildOfType(MessageDigest.class); + digest.ifPresent(node -> stringBuilder.append("-").append(node.asString())); + final Optional parameterSetIdentifier = + this.hasChildOfType(ParameterSetIdentifier.class); + parameterSetIdentifier.ifPresent(node -> stringBuilder.append("-").append(node.asString())); + return stringBuilder.toString(); + } + public ANSIX942(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyDerivationFunction.class, detectionLocation); } + + public ANSIX942(@Nonnull MessageDigest messageDigest) { + super(NAME, KeyDerivationFunction.class, messageDigest.getDetectionContext()); + this.put(messageDigest); + } + + public ANSIX942(@Nonnull String mode, @Nonnull DetectionLocation detectionLocation) { + this(detectionLocation); + this.put(new ParameterSetIdentifier(mode, detectionLocation)); + } + + private ANSIX942(@Nonnull ANSIX942 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ANSIX942 copy() { + return new ANSIX942(this); + } + + public ANSIX942(@Nonnull final Class asKind, @Nonnull ANSIX942 ansix942) { + super(ansix942, asKind); + } + + @Nonnull + @Override + public ANSIX942 asKind(@Nonnull Class kind) { + return new ANSIX942(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX963.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX963.java index 819d3cb14..f1ced56d5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX963.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ANSIX963.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -55,4 +56,24 @@ public ANSIX963(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + private ANSIX963(@Nonnull ANSIX963 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ANSIX963 copy() { + return new ANSIX963(this); + } + + public ANSIX963(@Nonnull final Class asKind, @Nonnull ANSIX963 ansix963) { + super(ansix963, asKind); + } + + @Nonnull + @Override + public ANSIX963 asKind(@Nonnull Class kind) { + return new ANSIX963(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Argon2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Argon2.java new file mode 100644 index 000000000..0804f46e9 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Argon2.java @@ -0,0 +1,88 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

Argon2

+ * + *

Memory-hard password hashing and key derivation function, in its data-dependent (Argon2d), + * data-independent (Argon2i) or hybrid (Argon2id) variant. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc9106 + *
  • https://cyclonedx.org/schema/cryptography-defs.json (family: Argon2) + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • Argon2d, Argon2i, Argon2id + *
+ */ +public final class Argon2 extends Algorithm implements PasswordBasedKeyDerivationFunction { + + private static final String NAME = "Argon2"; + + public enum Variant { + D("d"), + I("i"), + ID("id"); + + @Nonnull private final String suffix; + + Variant(@Nonnull String suffix) { + this.suffix = suffix; + } + } + + public Argon2(@Nonnull Variant variant, @Nonnull DetectionLocation detectionLocation) { + super(NAME + variant.suffix, PasswordBasedKeyDerivationFunction.class, detectionLocation); + } + + private Argon2(@Nonnull Argon2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Argon2 copy() { + return new Argon2(this); + } + + public Argon2(@Nonnull final Class asKind, @Nonnull Argon2 argon2) { + super(argon2, asKind); + } + + @Nonnull + @Override + public Argon2 asKind(@Nonnull Class kind) { + return new Argon2(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Aria.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Aria.java index 87dc2e3b4..7df7f30b0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Aria.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Aria.java @@ -85,4 +85,20 @@ public Aria( public Aria(@Nonnull final Class asKind, @Nonnull Aria aria) { super(aria, asKind); } + + private Aria(@Nonnull Aria algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Aria copy() { + return new Aria(this); + } + + @Nonnull + @Override + public Aria asKind(@Nonnull Class kind) { + return new Aria(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/BIKE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/BIKE.java index 16895ad35..aeac4e711 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/BIKE.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/BIKE.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public final class BIKE extends Algorithm implements KeyEncapsulationMechanism { public BIKE(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private BIKE(@Nonnull BIKE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected BIKE copy() { + return new BIKE(this); + } + + public BIKE(@Nonnull final Class asKind, @Nonnull BIKE bike) { + super(bike, asKind); + } + + @Nonnull + @Override + public BIKE asKind(@Nonnull Class kind) { + return new BIKE(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Blowfish.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Blowfish.java index 4bbf36933..660c21395 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Blowfish.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Blowfish.java @@ -86,4 +86,20 @@ public Blowfish( public Blowfish(@Nonnull final Class asKind, @Nonnull Blowfish blowfish) { super(blowfish, asKind); } + + private Blowfish(@Nonnull Blowfish algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Blowfish copy() { + return new Blowfish(this); + } + + @Nonnull + @Override + public Blowfish asKind(@Nonnull Class kind) { + return new Blowfish(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/CMAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/CMAC.java index 115a87fc8..ac2770275 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/CMAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/CMAC.java @@ -23,6 +23,7 @@ import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.Cipher; import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -63,4 +64,24 @@ public CMAC(@Nonnull Cipher cipher) { .map(node -> this.name + "-" + ((IAlgorithm) node).getName()) .orElse(this.name); } + + private CMAC(@Nonnull CMAC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected CMAC copy() { + return new CMAC(this); + } + + public CMAC(@Nonnull final Class asKind, @Nonnull CMAC cmac) { + super(cmac, asKind); + } + + @Nonnull + @Override + public CMAC asKind(@Nonnull Class kind) { + return new CMAC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/CTRDRBG.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/CTRDRBG.java new file mode 100644 index 000000000..adc0e2da4 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/CTRDRBG.java @@ -0,0 +1,93 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Deterministic random bit generator built on a block cipher in counter mode, which is a child + * of this node. + * + *

Specification

+ * + *
    + *
  • See 10.2 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf + *
  • https://cyclonedx.org/schema/cryptography-defs.json (pattern: + * CTR_DRBG[-{cipherAlgorithm}][-{keyLength}]) + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • NIST SP 800-90A + *
+ */ +public final class CTRDRBG extends Algorithm implements PseudorandomNumberGenerator { + + private static final String NAME = "CTR_DRBG"; + + public CTRDRBG(@Nonnull DetectionLocation detectionLocation) { + super(NAME, PseudorandomNumberGenerator.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(BlockCipher.class) + .map( + cipher -> { + String withCipher = this.name + "-" + ((IAlgorithm) cipher).getName(); + return cipher.hasChildOfType(KeyLength.class) + .map(keyLength -> withCipher + "-" + keyLength.asString()) + .orElse(withCipher); + }) + .orElse(this.name); + } + + private CTRDRBG(@Nonnull CTRDRBG algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected CTRDRBG copy() { + return new CTRDRBG(this); + } + + public CTRDRBG(@Nonnull final Class asKind, @Nonnull CTRDRBG ctrdrbg) { + super(ctrdrbg, asKind); + } + + @Nonnull + @Override + public CTRDRBG asKind(@Nonnull Class kind) { + return new CTRDRBG(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Camellia.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Camellia.java index 269a34cb4..3579cc7f5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Camellia.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Camellia.java @@ -70,4 +70,20 @@ public Camellia( public Camellia(@Nonnull final Class asKind, @Nonnull Camellia camellia) { super(camellia, asKind); } + + private Camellia(@Nonnull Camellia algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Camellia copy() { + return new Camellia(this); + } + + @Nonnull + @Override + public Camellia asKind(@Nonnull Class kind) { + return new Camellia(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20.java index e9de3c37d..ed06f6ab0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20.java @@ -79,4 +79,20 @@ public ChaCha20( public ChaCha20(@Nonnull final Class asKind, @Nonnull ChaCha20 chaCha20) { super(chaCha20, asKind); } + + private ChaCha20(@Nonnull ChaCha20 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ChaCha20 copy() { + return new ChaCha20(this); + } + + @Nonnull + @Override + public ChaCha20 asKind(@Nonnull Class kind) { + return new ChaCha20(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20Poly1305.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20Poly1305.java index 80f911f54..0e2dd5ffa 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20Poly1305.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ChaCha20Poly1305.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,26 @@ public ChaCha20Poly1305(@Nonnull DetectionLocation detectionLocation) { super(new ChaCha20(detectionLocation), AuthenticatedEncryption.class); this.put(new Poly1305(detectionLocation)); } + + private ChaCha20Poly1305(@Nonnull ChaCha20Poly1305 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ChaCha20Poly1305 copy() { + return new ChaCha20Poly1305(this); + } + + public ChaCha20Poly1305( + @Nonnull final Class asKind, + @Nonnull ChaCha20Poly1305 chaCha20Poly1305) { + super(chaCha20Poly1305, asKind); + } + + @Nonnull + @Override + public ChaCha20Poly1305 asKind(@Nonnull Class kind) { + return new ChaCha20Poly1305(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ClassicMcEliece.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ClassicMcEliece.java index c3e4f6b30..e05692285 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ClassicMcEliece.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ClassicMcEliece.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,26 @@ public final class ClassicMcEliece extends Algorithm implements KeyEncapsulation public ClassicMcEliece(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private ClassicMcEliece(@Nonnull ClassicMcEliece algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ClassicMcEliece copy() { + return new ClassicMcEliece(this); + } + + public ClassicMcEliece( + @Nonnull final Class asKind, + @Nonnull ClassicMcEliece classicMcEliece) { + super(classicMcEliece, asKind); + } + + @Nonnull + @Override + public ClassicMcEliece asKind(@Nonnull Class kind) { + return new ClassicMcEliece(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ConcatenationKDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ConcatenationKDF.java index cad5a67b6..614e3bc47 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ConcatenationKDF.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ConcatenationKDF.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -56,4 +57,26 @@ public ConcatenationKDF(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + private ConcatenationKDF(@Nonnull ConcatenationKDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ConcatenationKDF copy() { + return new ConcatenationKDF(this); + } + + public ConcatenationKDF( + @Nonnull final Class asKind, + @Nonnull ConcatenationKDF concatenationKDF) { + super(concatenationKDF, asKind); + } + + @Nonnull + @Override + public ConcatenationKDF asKind(@Nonnull Class kind) { + return new ConcatenationKDF(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DES.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DES.java index be933c3bc..1242680ed 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DES.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DES.java @@ -102,4 +102,20 @@ public DES( public DES(@Nonnull final Class asKind, @Nonnull DES des) { super(des, asKind); } + + private DES(@Nonnull DES algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DES copy() { + return new DES(this); + } + + @Nonnull + @Override + public DES asKind(@Nonnull Class kind) { + return new DES(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESX.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESX.java new file mode 100644 index 000000000..faf942653 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESX.java @@ -0,0 +1,90 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

DES with key whitening: the block is XORed with a second key before and a third key after the + * DES encryption. The key is a 56-bit DES key and two 64-bit whitening keys (184 bits). + * + *

Specification

+ * + *
    + *
  • https://en.wikipedia.org/wiki/DES-X + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • DES-X, DESX-CBC (OpenSSL {@code EVP_desx_cbc}, {@code DES_xcbc_encrypt}) + *
+ */ +public final class DESX extends Algorithm implements BlockCipher { + + private static final String NAME = "DESX"; + + @Override + public @Nonnull String asString() { + return composeName(true, true, false); + } + + public DESX(@Nonnull DetectionLocation detectionLocation) { + super(NAME, BlockCipher.class, detectionLocation); + this.put(KeyLength.ofDefault(184, detectionLocation)); + this.put(BlockSize.ofDefault(64, detectionLocation)); + } + + public DESX(@Nonnull Mode mode, @Nonnull DetectionLocation detectionLocation) { + this(detectionLocation); + this.put(mode); + } + + private DESX(@Nonnull DESX algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DESX copy() { + return new DESX(this); + } + + public DESX(@Nonnull final Class asKind, @Nonnull DESX desx) { + super(desx, asKind); + } + + @Nonnull + @Override + public DESX asKind(@Nonnull Class kind) { + return new DESX(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESede.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESede.java index 764912389..f1baefb09 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESede.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DESede.java @@ -95,4 +95,20 @@ public DESede( public DESede(@Nonnull final Class asKind, @Nonnull DESede desEde) { super(desEde, asKind); } + + private DESede(@Nonnull DESede algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DESede copy() { + return new DESede(this); + } + + @Nonnull + @Override + public DESede asKind(@Nonnull Class kind) { + return new DESede(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DH.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DH.java index 52eab1622..fdb4f7c9f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DH.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DH.java @@ -73,4 +73,24 @@ public DH( super(NAME, asKind, detectionLocation); this.put(new Oid("1.2.840.113549.1.3.1", detectionLocation)); } + + private DH(@Nonnull DH algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DH copy() { + return new DH(this); + } + + public DH(@Nonnull final Class asKind, @Nonnull DH dh) { + super(dh, asKind); + } + + @Nonnull + @Override + public DH asKind(@Nonnull Class kind) { + return new DH(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DHKEM.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DHKEM.java new file mode 100644 index 000000000..a022ab05f --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DHKEM.java @@ -0,0 +1,81 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Key encapsulation built on a Diffie-Hellman key agreement (ECDH over a NIST curve, X25519 or + * X448), which is a child of this node. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc9180#section-4.1 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • DHKEM(P-256, HKDF-SHA256), DHKEM(X25519, HKDF-SHA256), ... (RFC 9180) + *
+ */ +public final class DHKEM extends Algorithm implements KeyEncapsulationMechanism { + + private static final String NAME = "DHKEM"; + + public DHKEM(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyEncapsulationMechanism.class, detectionLocation); + } + + public DHKEM(@Nonnull INode diffieHellman, @Nonnull DetectionLocation detectionLocation) { + this(detectionLocation); + this.put(diffieHellman); + } + + private DHKEM(@Nonnull DHKEM algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DHKEM copy() { + return new DHKEM(this); + } + + public DHKEM(@Nonnull final Class asKind, @Nonnull DHKEM dhkem) { + super(dhkem, asKind); + } + + @Nonnull + @Override + public DHKEM asKind(@Nonnull Class kind) { + return new DHKEM(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSA.java index 33f506d56..88fcf506a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSA.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.Oid; @@ -49,4 +50,24 @@ public DSA(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + protected DSA(@Nonnull DSA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DSA copy() { + return new DSA(this); + } + + public DSA(@Nonnull final Class asKind, @Nonnull DSA dsa) { + super(dsa, asKind); + } + + @Nonnull + @Override + public DSA asKind(@Nonnull Class kind) { + return new DSA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSS.java index e0468ee94..ad90fe264 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSS.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -35,4 +36,24 @@ public final class DSS extends DSA { public DSS(@Nonnull DetectionLocation detectionLocation) { super(detectionLocation); } + + private DSS(@Nonnull DSS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DSS copy() { + return new DSS(this); + } + + public DSS(@Nonnull final Class asKind, @Nonnull DSS dss) { + super(asKind, dss); + } + + @Nonnull + @Override + public DSS asKind(@Nonnull Class kind) { + return new DSS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSTU4145.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSTU4145.java index 3250b6f46..a5e76115c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSTU4145.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/DSTU4145.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,24 @@ public final class DSTU4145 extends Algorithm implements Signature { public DSTU4145(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private DSTU4145(@Nonnull DSTU4145 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DSTU4145 copy() { + return new DSTU4145(this); + } + + public DSTU4145(@Nonnull final Class asKind, @Nonnull DSTU4145 dstu4145) { + super(dstu4145, asKind); + } + + @Nonnull + @Override + public DSTU4145 asKind(@Nonnull Class kind) { + return new DSTU4145(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Dilithium.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Dilithium.java index 73ff8770e..90d0239bb 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Dilithium.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Dilithium.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -64,4 +65,25 @@ public Dilithium(int parameterSetIdentifier, @Nonnull DetectionLocation detectio new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), detectionLocation)); } + + private Dilithium(@Nonnull Dilithium algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Dilithium copy() { + return new Dilithium(this); + } + + public Dilithium( + @Nonnull final Class asKind, @Nonnull Dilithium dilithium) { + super(dilithium, asKind); + } + + @Nonnull + @Override + public Dilithium asKind(@Nonnull Class kind) { + return new Dilithium(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECCPWD.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECCPWD.java index 0d5eaf15c..b3762ea3e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECCPWD.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECCPWD.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,24 @@ public final class ECCPWD extends Algorithm implements KeyAgreement { public ECCPWD(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyAgreement.class, detectionLocation); } + + private ECCPWD(@Nonnull ECCPWD algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECCPWD copy() { + return new ECCPWD(this); + } + + public ECCPWD(@Nonnull final Class asKind, @Nonnull ECCPWD eccpwd) { + super(eccpwd, asKind); + } + + @Nonnull + @Override + public ECCPWD asKind(@Nonnull Class kind) { + return new ECCPWD(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDH.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDH.java index 5294ae51a..b018d50a3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDH.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDH.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.model.Oid; import com.ibm.mapper.utils.DetectionLocation; @@ -56,4 +57,24 @@ public ECDH(@Nonnull EllipticCurve ellipticCurve) { this(ellipticCurve.getDetectionContext()); this.put(ellipticCurve); } + + private ECDH(@Nonnull ECDH algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECDH copy() { + return new ECDH(this); + } + + public ECDH(@Nonnull final Class asKind, @Nonnull ECDH ecdh) { + super(ecdh, asKind); + } + + @Nonnull + @Override + public ECDH asKind(@Nonnull Class kind) { + return new ECDH(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDSA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDSA.java index e234421b8..5d98f17d5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDSA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECDSA.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -68,4 +69,28 @@ public ECDSA( this(detectionLocation); this.put(ellipticCurve); } + + public ECDSA(@Nonnull String curveName, @Nonnull DetectionLocation detectionLocation) { + this(new EllipticCurve(curveName, detectionLocation), detectionLocation); + } + + private ECDSA(@Nonnull ECDSA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECDSA copy() { + return new ECDSA(this); + } + + public ECDSA(@Nonnull final Class asKind, @Nonnull ECDSA ecdsa) { + super(ecdsa, asKind); + } + + @Nonnull + @Override + public ECDSA asKind(@Nonnull Class kind) { + return new ECDSA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECMQV.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECMQV.java index 6e84a30bd..fd49c62cd 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECMQV.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECMQV.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -56,4 +57,24 @@ public ECMQV( this(detectionLocation); this.put(ellipticCurve); } + + private ECMQV(@Nonnull ECMQV algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECMQV copy() { + return new ECMQV(this); + } + + public ECMQV(@Nonnull final Class asKind, @Nonnull ECMQV ecmqv) { + super(asKind, ecmqv); + } + + @Nonnull + @Override + public ECMQV asKind(@Nonnull Class kind) { + return new ECMQV(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECNR.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECNR.java index 8bbc061ff..4c97f3566 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECNR.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ECNR.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -58,4 +59,24 @@ public ECNR( this(detectionLocation); this.put(ellipticCurve); } + + private ECNR(@Nonnull ECNR algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECNR copy() { + return new ECNR(this); + } + + public ECNR(@Nonnull final Class asKind, @Nonnull ECNR ecnr) { + super(ecnr, asKind); + } + + @Nonnull + @Override + public ECNR asKind(@Nonnull Class kind) { + return new ECNR(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed25519.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed25519.java index 89f5d3266..6c9ac8314 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed25519.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed25519.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Signature; import com.ibm.mapper.model.curves.Edwards25519; @@ -52,4 +53,24 @@ public Ed25519(@Nonnull DetectionLocation detectionLocation) { this.put(new SHA2(512, detectionLocation)); this.put(new Oid("1.3.101.112", detectionLocation)); } + + private Ed25519(@Nonnull Ed25519 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Ed25519 copy() { + return new Ed25519(this); + } + + public Ed25519(@Nonnull final Class asKind, @Nonnull Ed25519 ed25519) { + super(asKind, ed25519); + } + + @Nonnull + @Override + public Ed25519 asKind(@Nonnull Class kind) { + return new Ed25519(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed448.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed448.java index 556c7e452..e1a805913 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed448.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Ed448.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Signature; import com.ibm.mapper.model.algorithms.shake.SHAKE; @@ -53,4 +54,24 @@ public Ed448(@Nonnull DetectionLocation detectionLocation) { this.put(new SHAKE(256, detectionLocation)); this.put(new Oid("1.3.101.113", detectionLocation)); } + + private Ed448(@Nonnull Ed448 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Ed448 copy() { + return new Ed448(this); + } + + public Ed448(@Nonnull final Class asKind, @Nonnull Ed448 ed448) { + super(asKind, ed448); + } + + @Nonnull + @Override + public Ed448 asKind(@Nonnull Class kind) { + return new Ed448(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/EdDSA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/EdDSA.java index 96ae109a4..95c759b63 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/EdDSA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/EdDSA.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,4 +35,24 @@ protected EdDSA(@Nonnull String name, @Nonnull DetectionLocation detectionLocati public EdDSA(@Nonnull DetectionLocation detectionLocation) { this(NAME, detectionLocation); } + + protected EdDSA(@Nonnull EdDSA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected EdDSA copy() { + return new EdDSA(this); + } + + public EdDSA(@Nonnull final Class asKind, @Nonnull EdDSA edDSA) { + super(edDSA, asKind); + } + + @Nonnull + @Override + public EdDSA asKind(@Nonnull Class kind) { + return new EdDSA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ElGamal.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ElGamal.java index 43c2c824d..80c14c292 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ElGamal.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ElGamal.java @@ -67,4 +67,20 @@ public ElGamal(int keyLength, @Nonnull DetectionLocation detectionLocation) { public ElGamal(@Nonnull final Class asKind, @Nonnull ElGamal elGamal) { super(elGamal, asKind); } + + private ElGamal(@Nonnull ElGamal algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ElGamal copy() { + return new ElGamal(this); + } + + @Nonnull + @Override + public ElGamal asKind(@Nonnull Class kind) { + return new ElGamal(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Falcon.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Falcon.java index f7d04e40a..9c50fc285 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Falcon.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Falcon.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -64,4 +65,24 @@ public Falcon(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLo new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), detectionLocation)); } + + private Falcon(@Nonnull Falcon algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Falcon copy() { + return new Falcon(this); + } + + public Falcon(@Nonnull final Class asKind, @Nonnull Falcon falcon) { + super(falcon, asKind); + } + + @Nonnull + @Override + public Falcon asKind(@Nonnull Class kind) { + return new Falcon(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Fernet.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Fernet.java index 483174ff8..f8040715e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Fernet.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Fernet.java @@ -62,4 +62,20 @@ public Fernet(@Nonnull DetectionLocation detectionLocation) { public Fernet(@Nonnull final Class asKind, @Nonnull Fernet fernet) { super(fernet, asKind); } + + private Fernet(@Nonnull Fernet algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Fernet copy() { + return new Fernet(this); + } + + @Nonnull + @Override + public Fernet asKind(@Nonnull Class kind) { + return new Fernet(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/FrodoKEM.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/FrodoKEM.java index 5e0a37637..f1412ca5b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/FrodoKEM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/FrodoKEM.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public final class FrodoKEM extends Algorithm implements KeyEncapsulationMechani public FrodoKEM(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private FrodoKEM(@Nonnull FrodoKEM algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected FrodoKEM copy() { + return new FrodoKEM(this); + } + + public FrodoKEM(@Nonnull final Class asKind, @Nonnull FrodoKEM frodoKEM) { + super(frodoKEM, asKind); + } + + @Nonnull + @Override + public FrodoKEM asKind(@Nonnull Class kind) { + return new FrodoKEM(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMAC.java new file mode 100644 index 000000000..e2de664ca --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMAC.java @@ -0,0 +1,78 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Galois message authentication code: the authentication-only use of the GCM mode of a block + * cipher. A GMAC whose cipher is known is reported as that cipher in {@link + * com.ibm.mapper.model.mode.GMAC} mode (e.g. {@code AES-128-GMAC}); this node stands for a GMAC + * whose cipher is not known. + * + *

Specification

+ * + *
    + *
  • https://doi.org/10.6028/NIST.SP.800-38D + *
  • https://cyclonedx.org/schema/cryptography-defs.json (pattern: AES[-(128|192|256)][-GMAC]) + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • NIST SP 800-38D + *
+ */ +public final class GMAC extends Algorithm implements Mac { + + public static final String NAME = "GMAC"; + + public GMAC(@Nonnull DetectionLocation detectionLocation) { + super(NAME, Mac.class, detectionLocation); + } + + private GMAC(@Nonnull GMAC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GMAC copy() { + return new GMAC(this); + } + + public GMAC(@Nonnull final Class asKind, @Nonnull GMAC gmac) { + super(gmac, asKind); + } + + @Nonnull + @Override + public GMAC asKind(@Nonnull Class kind) { + return new GMAC(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMSS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMSS.java index 1ca20a969..795c32d41 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMSS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/GMSS.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public class GMSS extends Algorithm implements Signature { public GMSS(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private GMSS(@Nonnull GMSS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GMSS copy() { + return new GMSS(this); + } + + public GMSS(@Nonnull final Class asKind, @Nonnull GMSS gmss) { + super(gmss, asKind); + } + + @Nonnull + @Override + public GMSS asKind(@Nonnull Class kind) { + return new GMSS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/GeMSS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/GeMSS.java index 4eea079ea..05811c20f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/GeMSS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/GeMSS.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -65,4 +66,24 @@ public GeMSS(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLoc new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), detectionLocation)); } + + private GeMSS(@Nonnull GeMSS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GeMSS copy() { + return new GeMSS(this); + } + + public GeMSS(@Nonnull final Class asKind, @Nonnull GeMSS geMSS) { + super(geMSS, asKind); + } + + @Nonnull + @Override + public GeMSS asKind(@Nonnull Class kind) { + return new GeMSS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HC.java index 52ac786ca..71350fff4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HC.java @@ -67,4 +67,20 @@ public HC(int keyLength, @Nonnull DetectionLocation detectionLocation) { public HC(@Nonnull final Class asKind, @Nonnull HC hc) { super(hc, asKind); } + + private HC(@Nonnull HC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HC copy() { + return new HC(this); + } + + @Nonnull + @Override + public HC asKind(@Nonnull Class kind) { + return new HC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HKDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HKDF.java index cc2f3212e..89b0c3286 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HKDF.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HKDF.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -64,4 +65,24 @@ public HKDF(@Nonnull MessageDigest messageDigest) { .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) .orElse(this.name); } + + private HKDF(@Nonnull HKDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HKDF copy() { + return new HKDF(this); + } + + public HKDF(@Nonnull final Class asKind, @Nonnull HKDF hkdf) { + super(hkdf, asKind); + } + + @Nonnull + @Override + public HKDF asKind(@Nonnull Class kind) { + return new HKDF(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMAC.java index 62eb17c29..4e2c45dd9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMAC.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -62,4 +63,24 @@ public HMAC(@Nonnull MessageDigest messageDigest) { .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) .orElse(this.name); } + + private HMAC(@Nonnull HMAC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HMAC copy() { + return new HMAC(this); + } + + public HMAC(@Nonnull final Class asKind, @Nonnull HMAC hmac) { + super(hmac, asKind); + } + + @Nonnull + @Override + public HMAC asKind(@Nonnull Class kind) { + return new HMAC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMACDRBG.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMACDRBG.java new file mode 100644 index 000000000..f21d3d14b --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMACDRBG.java @@ -0,0 +1,84 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Deterministic random bit generator built on a HMAC, which is a child of this node. + * + *

Specification

+ * + *
    + *
  • See 10.1.2 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf + *
  • https://cyclonedx.org/schema/cryptography-defs.json (pattern: HMAC_DRBG[-{hashAlgorithm}]) + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • NIST SP 800-90A + *
+ */ +public final class HMACDRBG extends Algorithm implements PseudorandomNumberGenerator { + + private static final String NAME = "HMAC_DRBG"; + + public HMACDRBG(@Nonnull DetectionLocation detectionLocation) { + super(NAME, PseudorandomNumberGenerator.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(algorithm -> this.name + "-" + ((IAlgorithm) algorithm).getName()) + .orElse(this.name); + } + + private HMACDRBG(@Nonnull HMACDRBG algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HMACDRBG copy() { + return new HMACDRBG(this); + } + + public HMACDRBG(@Nonnull final Class asKind, @Nonnull HMACDRBG hmacdrbg) { + super(hmacdrbg, asKind); + } + + @Nonnull + @Override + public HMACDRBG asKind(@Nonnull Class kind) { + return new HMACDRBG(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMACDRBGKDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMACDRBGKDF.java new file mode 100644 index 000000000..32319cba5 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HMACDRBGKDF.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.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

A key derivation function built on the HMAC DRBG, used for deterministic nonces. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc6979#section-3.2 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • RFC 6979, NIST SP 800-90A HMAC_DRBG + *
+ */ +public final class HMACDRBGKDF extends Algorithm implements KeyDerivationFunction { + + private static final String NAME = "HMAC-DRBG-KDF"; + + public HMACDRBGKDF(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyDerivationFunction.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) + .orElse(this.name); + } + + private HMACDRBGKDF(@Nonnull HMACDRBGKDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HMACDRBGKDF copy() { + return new HMACDRBGKDF(this); + } + + public HMACDRBGKDF( + @Nonnull final Class asKind, @Nonnull HMACDRBGKDF hmacdrbgkdf) { + super(hmacdrbgkdf, asKind); + } + + @Nonnull + @Override + public HMACDRBGKDF asKind(@Nonnull Class kind) { + return new HMACDRBGKDF(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HPKE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HPKE.java new file mode 100644 index 000000000..3582f14f1 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HPKE.java @@ -0,0 +1,75 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Hybrid public key encryption, made of a key encapsulation mechanism, a key derivation function + * and an AEAD cipher, which are children of this node. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc9180 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • Hybrid Public Key Encryption + *
+ */ +public final class HPKE extends Algorithm implements PublicKeyEncryption { + + private static final String NAME = "HPKE"; + + public HPKE(@Nonnull DetectionLocation detectionLocation) { + super(NAME, PublicKeyEncryption.class, detectionLocation); + } + + private HPKE(@Nonnull HPKE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HPKE copy() { + return new HPKE(this); + } + + public HPKE(@Nonnull final Class asKind, @Nonnull HPKE hpke) { + super(hpke, asKind); + } + + @Nonnull + @Override + public HPKE asKind(@Nonnull Class kind) { + return new HPKE(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HQC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HQC.java index 1b4b27f92..2afa8b310 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HQC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HQC.java @@ -69,4 +69,24 @@ public HQC( @Nonnull DetectionLocation detectionLocation) { super(NAME, asKind, detectionLocation); } + + private HQC(@Nonnull HQC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HQC copy() { + return new HQC(this); + } + + public HQC(@Nonnull final Class asKind, @Nonnull HQC hqc) { + super(hqc, asKind); + } + + @Nonnull + @Override + public HQC asKind(@Nonnull Class kind) { + return new HQC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HSS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HSS.java index ed8dd9ef4..8056f45f9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HSS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HSS.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public class HSS extends Algorithm implements Signature { public HSS(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private HSS(@Nonnull HSS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HSS copy() { + return new HSS(this); + } + + public HSS(@Nonnull final Class asKind, @Nonnull HSS hss) { + super(hss, asKind); + } + + @Nonnull + @Override + public HSS asKind(@Nonnull Class kind) { + return new HSS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HarakaV2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HarakaV2.java index eee111167..3c22f852e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HarakaV2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HarakaV2.java @@ -80,4 +80,20 @@ public HarakaV2(int blockSize, @Nonnull DetectionLocation detectionLocation) { public HarakaV2(@Nonnull final Class asKind, @Nonnull HarakaV2 haraka) { super(haraka, asKind); } + + private HarakaV2(@Nonnull HarakaV2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HarakaV2 copy() { + return new HarakaV2(this); + } + + @Nonnull + @Override + public HarakaV2 asKind(@Nonnull Class kind) { + return new HarakaV2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/HashDRBG.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HashDRBG.java new file mode 100644 index 000000000..ccc64e126 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/HashDRBG.java @@ -0,0 +1,84 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Deterministic random bit generator built on a hash function, which is a child of this node. + * + *

Specification

+ * + *
    + *
  • See 10.1.1 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf + *
  • https://cyclonedx.org/schema/cryptography-defs.json (pattern: Hash_DRBG[-{hashAlgorithm}]) + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • NIST SP 800-90A + *
+ */ +public final class HashDRBG extends Algorithm implements PseudorandomNumberGenerator { + + private static final String NAME = "Hash_DRBG"; + + public HashDRBG(@Nonnull DetectionLocation detectionLocation) { + super(NAME, PseudorandomNumberGenerator.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(algorithm -> this.name + "-" + ((IAlgorithm) algorithm).getName()) + .orElse(this.name); + } + + private HashDRBG(@Nonnull HashDRBG algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected HashDRBG copy() { + return new HashDRBG(this); + } + + public HashDRBG(@Nonnull final Class asKind, @Nonnull HashDRBG hashDRBG) { + super(hashDRBG, asKind); + } + + @Nonnull + @Override + public HashDRBG asKind(@Nonnull Class kind) { + return new HashDRBG(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/IDEA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/IDEA.java index 3a2f861ea..2956e54e0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/IDEA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/IDEA.java @@ -65,4 +65,20 @@ public IDEA(@Nonnull Mode mode, @Nonnull DetectionLocation detectionLocation) { public IDEA(@Nonnull final Class asKind, @Nonnull IDEA idea) { super(idea, asKind); } + + private IDEA(@Nonnull IDEA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected IDEA copy() { + return new IDEA(this); + } + + @Nonnull + @Override + public IDEA asKind(@Nonnull Class kind) { + return new IDEA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISAAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISAAC.java index 364a16681..b9599aded 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISAAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISAAC.java @@ -55,4 +55,20 @@ public ISAAC(@Nonnull DetectionLocation detectionLocation) { public ISAAC(@Nonnull final Class asKind, @Nonnull ISAAC isaac) { super(isaac, asKind); } + + private ISAAC(@Nonnull ISAAC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ISAAC copy() { + return new ISAAC(this); + } + + @Nonnull + @Override + public ISAAC asKind(@Nonnull Class kind) { + return new ISAAC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISO9796.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISO9796.java index cebd00514..2b8d6a94e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISO9796.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ISO9796.java @@ -53,4 +53,20 @@ public ISO9796( public ISO9796(@Nonnull final Class asKind, @Nonnull ISO9796 iso) { super(iso, asKind); } + + private ISO9796(@Nonnull ISO9796 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ISO9796 copy() { + return new ISO9796(this); + } + + @Nonnull + @Override + public ISO9796 asKind(@Nonnull Class kind) { + return new ISO9796(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF1.java index 3b0b52a25..587ed3366 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF1.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -56,4 +57,24 @@ public KDF1(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + private KDF1(@Nonnull KDF1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KDF1 copy() { + return new KDF1(this); + } + + public KDF1(@Nonnull final Class asKind, @Nonnull KDF1 kdf1) { + super(kdf1, asKind); + } + + @Nonnull + @Override + public KDF1 asKind(@Nonnull Class kind) { + return new KDF1(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF2.java index 690d24e6f..4a7d9bc0f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDF2.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -58,4 +59,24 @@ public KDF2(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + private KDF2(@Nonnull KDF2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KDF2 copy() { + return new KDF2(this); + } + + public KDF2(@Nonnull final Class asKind, @Nonnull KDF2 kdf2) { + super(kdf2, asKind); + } + + @Nonnull + @Override + public KDF2 asKind(@Nonnull Class kind) { + return new KDF2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFCounter.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFCounter.java index 8fa0a3d35..99592bd50 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFCounter.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFCounter.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.Mac; import com.ibm.mapper.utils.DetectionLocation; @@ -56,4 +57,25 @@ public KDFCounter(@Nonnull Mac mac) { this(mac.getDetectionContext()); this.put(mac); } + + private KDFCounter(@Nonnull KDFCounter algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KDFCounter copy() { + return new KDFCounter(this); + } + + public KDFCounter( + @Nonnull final Class asKind, @Nonnull KDFCounter kdfCounter) { + super(kdfCounter, asKind); + } + + @Nonnull + @Override + public KDFCounter asKind(@Nonnull Class kind) { + return new KDFCounter(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFDoublePipeline.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFDoublePipeline.java index 6c85c4419..abef7eddc 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFDoublePipeline.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFDoublePipeline.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.Mac; import com.ibm.mapper.utils.DetectionLocation; @@ -55,4 +56,26 @@ public KDFDoublePipeline(@Nonnull Mac mac) { this(mac.getDetectionContext()); this.put(mac); } + + private KDFDoublePipeline(@Nonnull KDFDoublePipeline algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KDFDoublePipeline copy() { + return new KDFDoublePipeline(this); + } + + public KDFDoublePipeline( + @Nonnull final Class asKind, + @Nonnull KDFDoublePipeline kdfDoublePipeline) { + super(kdfDoublePipeline, asKind); + } + + @Nonnull + @Override + public KDFDoublePipeline asKind(@Nonnull Class kind) { + return new KDFDoublePipeline(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFFeedback.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFFeedback.java index 33a4ac169..4277509db 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFFeedback.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFFeedback.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.Mac; import com.ibm.mapper.utils.DetectionLocation; @@ -56,4 +57,25 @@ public KDFFeedback(@Nonnull Mac mac) { this(mac.getDetectionContext()); this.put(mac); } + + private KDFFeedback(@Nonnull KDFFeedback algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KDFFeedback copy() { + return new KDFFeedback(this); + } + + public KDFFeedback( + @Nonnull final Class asKind, @Nonnull KDFFeedback kdfFeedback) { + super(kdfFeedback, asKind); + } + + @Nonnull + @Override + public KDFFeedback asKind(@Nonnull Class kind) { + return new KDFFeedback(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFSession.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFSession.java index e6fd61bfe..483d2f429 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFSession.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KDFSession.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; @@ -56,4 +57,25 @@ public KDFSession(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + private KDFSession(@Nonnull KDFSession algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KDFSession copy() { + return new KDFSession(this); + } + + public KDFSession( + @Nonnull final Class asKind, @Nonnull KDFSession kdfSession) { + super(kdfSession, asKind); + } + + @Nonnull + @Override + public KDFSession asKind(@Nonnull Class kind) { + return new KDFSession(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java index 373d538e3..b8d94c11e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KMAC.java @@ -84,4 +84,20 @@ public KMAC(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLoca public KMAC(@Nonnull final Class asKind, @Nonnull KMAC kmac) { super(kmac, asKind); } + + private KMAC(@Nonnull KMAC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KMAC copy() { + return new KMAC(this); + } + + @Nonnull + @Override + public KMAC asKind(@Nonnull Class kind) { + return new KMAC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KRB5KDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KRB5KDF.java new file mode 100644 index 000000000..fde22c546 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KRB5KDF.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.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

The Kerberos 5 key derivation function (DK), deriving keys from a base key with a block + * cipher. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc3961#section-5.1 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • RFC 3961 DK + *
+ */ +public final class KRB5KDF extends Algorithm implements KeyDerivationFunction { + + private static final String NAME = "KRB5KDF"; + + public KRB5KDF(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyDerivationFunction.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) + .orElse(this.name); + } + + private KRB5KDF(@Nonnull KRB5KDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KRB5KDF copy() { + return new KRB5KDF(this); + } + + public KRB5KDF(@Nonnull final Class asKind, @Nonnull KRB5KDF krb5kdf) { + super(krb5kdf, asKind); + } + + @Nonnull + @Override + public KRB5KDF asKind(@Nonnull Class kind) { + return new KRB5KDF(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kalyna.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kalyna.java index 2d1281952..dacd9ce0a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kalyna.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kalyna.java @@ -84,4 +84,20 @@ public Kalyna(int blockSize, int keyLength, @Nonnull DetectionLocation detection public Kalyna(@Nonnull final Class asKind, @Nonnull Kalyna kalyna) { super(kalyna, asKind); } + + private Kalyna(@Nonnull Kalyna algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Kalyna copy() { + return new Kalyna(this); + } + + @Nonnull + @Override + public Kalyna asKind(@Nonnull Class kind) { + return new Kalyna(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KangarooTwelve.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KangarooTwelve.java index bef5a9a60..26aa44626 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/KangarooTwelve.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/KangarooTwelve.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.NumberOfIterations; import com.ibm.mapper.utils.DetectionLocation; @@ -58,4 +59,26 @@ public KangarooTwelve(int digestSize, @Nonnull DetectionLocation detectionLocati this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + private KangarooTwelve(@Nonnull KangarooTwelve algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected KangarooTwelve copy() { + return new KangarooTwelve(this); + } + + public KangarooTwelve( + @Nonnull final Class asKind, + @Nonnull KangarooTwelve kangarooTwelve) { + super(kangarooTwelve, asKind); + } + + @Nonnull + @Override + public KangarooTwelve asKind(@Nonnull Class kind) { + return new KangarooTwelve(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Keccak.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Keccak.java index 4d916dfa6..1d7c6d4c0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Keccak.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Keccak.java @@ -55,4 +55,20 @@ public Keccak(@Nonnull DetectionLocation detectionLocation) { public Keccak(@Nonnull final Class asKind, @Nonnull Keccak keccak) { super(keccak, asKind); } + + private Keccak(@Nonnull Keccak algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Keccak copy() { + return new Keccak(this); + } + + @Nonnull + @Override + public Keccak asKind(@Nonnull Class kind) { + return new Keccak(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kerberos.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kerberos.java index 18174f02d..b5a2f5f92 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kerberos.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kerberos.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -52,4 +53,24 @@ public Kerberos(@Nonnull DetectionLocation detectionLocation) { public Kerberos(int version, @Nonnull DetectionLocation detectionLocation) { super(NAME + version, KeyAgreement.class, detectionLocation); } + + private Kerberos(@Nonnull Kerberos algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Kerberos copy() { + return new Kerberos(this); + } + + public Kerberos(@Nonnull final Class asKind, @Nonnull Kerberos kerberos) { + super(kerberos, asKind); + } + + @Nonnull + @Override + public Kerberos asKind(@Nonnull Class kind) { + return new Kerberos(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kupyna.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kupyna.java index 1c7718936..389a71d1d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kupyna.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Kupyna.java @@ -89,4 +89,20 @@ public Kupyna(int digestSize, @Nonnull DetectionLocation detectionLocation) { public Kupyna(@Nonnull final Class asKind, @Nonnull Kupyna kupyna) { super(kupyna, asKind); } + + private Kupyna(@Nonnull Kupyna algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Kupyna copy() { + return new Kupyna(this); + } + + @Nonnull + @Override + public Kupyna asKind(@Nonnull Class kind) { + return new Kupyna(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/LEA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/LEA.java index e304ed738..6c9dcf4a0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/LEA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/LEA.java @@ -81,4 +81,20 @@ public LEA( public LEA(@Nonnull final Class asKind, @Nonnull LEA lea) { super(lea, asKind); } + + private LEA(@Nonnull LEA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected LEA copy() { + return new LEA(this); + } + + @Nonnull + @Override + public LEA asKind(@Nonnull Class kind) { + return new LEA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/LMS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/LMS.java index c4086a691..2b91097a2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/LMS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/LMS.java @@ -69,4 +69,24 @@ public LMS( @Nonnull DetectionLocation detectionLocation) { super(NAME, asKind, detectionLocation); } + + private LMS(@Nonnull LMS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected LMS copy() { + return new LMS(this); + } + + public LMS(@Nonnull final Class asKind, @Nonnull LMS lms) { + super(lms, asKind); + } + + @Nonnull + @Override + public LMS asKind(@Nonnull Class kind) { + return new LMS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD2.java index 6028b8433..6b43f816b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD2.java @@ -59,4 +59,24 @@ public MD2( this.put(BlockSize.ofDefault(128, detectionLocation)); this.put(new DigestSize(128, detectionLocation)); } + + private MD2(@Nonnull MD2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MD2 copy() { + return new MD2(this); + } + + public MD2(@Nonnull final Class asKind, @Nonnull MD2 md2) { + super(md2, asKind); + } + + @Nonnull + @Override + public MD2 asKind(@Nonnull Class kind) { + return new MD2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD4.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD4.java index a132ccdc4..9e23c5ca5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD4.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD4.java @@ -59,4 +59,24 @@ public MD4( this.put(BlockSize.ofDefault(512, detectionLocation)); this.put(new DigestSize(128, detectionLocation)); } + + private MD4(@Nonnull MD4 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MD4 copy() { + return new MD4(this); + } + + public MD4(@Nonnull final Class asKind, @Nonnull MD4 md4) { + super(md4, asKind); + } + + @Nonnull + @Override + public MD4 asKind(@Nonnull Class kind) { + return new MD4(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5.java index e33b7c182..799117e1b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5.java @@ -59,4 +59,24 @@ public MD5( this.put(BlockSize.ofDefault(512, detectionLocation)); this.put(new DigestSize(128, detectionLocation)); } + + private MD5(@Nonnull MD5 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MD5 copy() { + return new MD5(this); + } + + public MD5(@Nonnull final Class asKind, @Nonnull MD5 md5) { + super(md5, asKind); + } + + @Nonnull + @Override + public MD5 asKind(@Nonnull Class kind) { + return new MD5(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5SHA1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5SHA1.java new file mode 100644 index 000000000..7ff37e61b --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MD5SHA1.java @@ -0,0 +1,77 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

The concatenation of an MD5 and a SHA-1 digest of the same data (128 + 160 = 288 bits), used + * by the TLS 1.0 and 1.1 handshake and PRF. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc4346#section-5 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • OpenSSL {@code EVP_md5_sha1} + *
+ */ +public final class MD5SHA1 extends Algorithm implements MessageDigest { + + private static final String NAME = "MD5-SHA1"; + + public MD5SHA1(@Nonnull DetectionLocation detectionLocation) { + super(NAME, MessageDigest.class, detectionLocation); + this.put(new DigestSize(288, detectionLocation)); + } + + private MD5SHA1(@Nonnull MD5SHA1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MD5SHA1 copy() { + return new MD5SHA1(this); + } + + public MD5SHA1(@Nonnull final Class asKind, @Nonnull MD5SHA1 md5sha1) { + super(md5sha1, asKind); + } + + @Nonnull + @Override + public MD5SHA1 asKind(@Nonnull Class kind) { + return new MD5SHA1(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MDC2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MDC2.java new file mode 100644 index 000000000..ba131a27b --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MDC2.java @@ -0,0 +1,79 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Modification Detection Code 2: a 128-bit hash function built on the DES block cipher. + * + *

Specification

+ * + *
    + *
  • ISO/IEC 10118-2 + *
  • https://en.wikipedia.org/wiki/MDC-2 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • MDC-2, Meyer-Schilling hash + *
+ */ +public final class MDC2 extends Algorithm implements MessageDigest { + + private static final String NAME = "MDC2"; + + public MDC2(@Nonnull DetectionLocation detectionLocation) { + super(NAME, MessageDigest.class, detectionLocation); + this.put(BlockSize.ofDefault(64, detectionLocation)); + this.put(new DigestSize(128, detectionLocation)); + } + + private MDC2(@Nonnull MDC2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MDC2 copy() { + return new MDC2(this); + } + + public MDC2(@Nonnull final Class asKind, @Nonnull MDC2 mdc2) { + super(mdc2, asKind); + } + + @Nonnull + @Override + public MDC2 asKind(@Nonnull Class kind) { + return new MDC2(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MGF1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MGF1.java index 0b9967c41..07457b96e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MGF1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MGF1.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MaskGenerationFunction; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.Oid; @@ -37,4 +38,24 @@ public MGF1(@Nonnull MessageDigest messageDigest) { this(messageDigest.getDetectionContext()); this.put(messageDigest); } + + private MGF1(@Nonnull MGF1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MGF1 copy() { + return new MGF1(this); + } + + public MGF1(@Nonnull final Class asKind, @Nonnull MGF1 mgf1) { + super(mgf1, asKind); + } + + @Nonnull + @Override + public MGF1 asKind(@Nonnull Class kind) { + return new MGF1(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLDSA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLDSA.java index ba0fc3bfa..0cdb29a88 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLDSA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLDSA.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; @@ -88,4 +89,24 @@ public MLDSA(int parameterSetIdentifier, @Nonnull MessageDigest preHash) { new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), preHash.getDetectionContext())); } + + private MLDSA(@Nonnull MLDSA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MLDSA copy() { + return new MLDSA(this); + } + + public MLDSA(@Nonnull final Class asKind, @Nonnull MLDSA mldsa) { + super(mldsa, asKind); + } + + @Nonnull + @Override + public MLDSA asKind(@Nonnull Class kind) { + return new MLDSA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLKEM.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLKEM.java index 7d9385867..3a45102ef 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLKEM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MLKEM.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.utils.DetectionLocation; @@ -73,4 +74,24 @@ public MLKEM(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLoc new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), detectionLocation)); } + + private MLKEM(@Nonnull MLKEM algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MLKEM copy() { + return new MLKEM(this); + } + + public MLKEM(@Nonnull final Class asKind, @Nonnull MLKEM mlkem) { + super(mlkem, asKind); + } + + @Nonnull + @Override + public MLKEM asKind(@Nonnull Class kind) { + return new MLKEM(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MQV.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MQV.java index adf9d83c0..64c7764b5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MQV.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MQV.java @@ -58,4 +58,24 @@ protected MQV( @Nonnull DetectionLocation detectionLocation) { super(name, asKind, detectionLocation); } + + protected MQV(@Nonnull MQV algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MQV copy() { + return new MQV(this); + } + + public MQV(@Nonnull final Class asKind, @Nonnull MQV mqv) { + super(mqv, asKind); + } + + @Nonnull + @Override + public MQV asKind(@Nonnull Class kind) { + return new MQV(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MarsupilamiFourteen.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MarsupilamiFourteen.java index 28ff4f9f4..34bb3e631 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/MarsupilamiFourteen.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/MarsupilamiFourteen.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.NumberOfIterations; import com.ibm.mapper.utils.DetectionLocation; @@ -58,4 +59,26 @@ public MarsupilamiFourteen(int digestSize, @Nonnull DetectionLocation detectionL this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + private MarsupilamiFourteen(@Nonnull MarsupilamiFourteen algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected MarsupilamiFourteen copy() { + return new MarsupilamiFourteen(this); + } + + public MarsupilamiFourteen( + @Nonnull final Class asKind, + @Nonnull MarsupilamiFourteen marsupilamiFourteen) { + super(marsupilamiFourteen, asKind); + } + + @Nonnull + @Override + public MarsupilamiFourteen asKind(@Nonnull Class kind) { + return new MarsupilamiFourteen(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/NOEKEON.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/NOEKEON.java index 00015b0b3..f2e743cda 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/NOEKEON.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/NOEKEON.java @@ -74,4 +74,20 @@ public NOEKEON( public NOEKEON(@Nonnull final Class asKind, @Nonnull NOEKEON noekeon) { super(noekeon, asKind); } + + private NOEKEON(@Nonnull NOEKEON algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected NOEKEON copy() { + return new NOEKEON(this); + } + + @Nonnull + @Override + public NOEKEON asKind(@Nonnull Class kind) { + return new NOEKEON(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/NaccacheStern.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/NaccacheStern.java index f67531d3f..f50173784 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/NaccacheStern.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/NaccacheStern.java @@ -63,4 +63,20 @@ public NaccacheStern( @Nonnull NaccacheStern naccacheStern) { super(naccacheStern, asKind); } + + private NaccacheStern(@Nonnull NaccacheStern algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected NaccacheStern copy() { + return new NaccacheStern(this); + } + + @Nonnull + @Override + public NaccacheStern asKind(@Nonnull Class kind) { + return new NaccacheStern(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES1.java index 451841e05..9636e877b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES1.java @@ -23,6 +23,7 @@ import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.Cipher; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.PasswordBasedEncryption; @@ -99,4 +100,24 @@ public PBES1(@Nonnull Mac mac) { this(mac.getDetectionContext()); this.put(mac); } + + private PBES1(@Nonnull PBES1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PBES1 copy() { + return new PBES1(this); + } + + public PBES1(@Nonnull final Class asKind, @Nonnull PBES1 pbes1) { + super(pbes1, asKind); + } + + @Nonnull + @Override + public PBES1 asKind(@Nonnull Class kind) { + return new PBES1(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES2.java index 004f3cb7e..416176360 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBES2.java @@ -23,6 +23,7 @@ import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.Cipher; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.Oid; @@ -94,4 +95,24 @@ public PBES2(@Nonnull Mac mac) { this(mac.getDetectionContext()); this.put(mac); } + + private PBES2(@Nonnull PBES2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PBES2 copy() { + return new PBES2(this); + } + + public PBES2(@Nonnull final Class asKind, @Nonnull PBES2 pbes2) { + super(pbes2, asKind); + } + + @Nonnull + @Override + public PBES2 asKind(@Nonnull Class kind) { + return new PBES2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF1.java index 30197b473..f0fea199e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF1.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; @@ -79,4 +80,24 @@ public PBKDF1(@Nonnull MessageDigest messageDigest) { public PBKDF1(@Nonnull DetectionLocation detectionLocation) { super(NAME, PasswordBasedKeyDerivationFunction.class, detectionLocation); } + + private PBKDF1(@Nonnull PBKDF1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PBKDF1 copy() { + return new PBKDF1(this); + } + + public PBKDF1(@Nonnull final Class asKind, @Nonnull PBKDF1 pbkdf1) { + super(pbkdf1, asKind); + } + + @Nonnull + @Override + public PBKDF1 asKind(@Nonnull Class kind) { + return new PBKDF1(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF2.java index a5ca07e74..84605ae6f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PBKDF2.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; @@ -79,4 +80,24 @@ public PBKDF2(@Nonnull MessageDigest messageDigest) { public PBKDF2(@Nonnull DetectionLocation detectionLocation) { super(NAME, PasswordBasedKeyDerivationFunction.class, detectionLocation); } + + private PBKDF2(@Nonnull PBKDF2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PBKDF2 copy() { + return new PBKDF2(this); + } + + public PBKDF2(@Nonnull final Class asKind, @Nonnull PBKDF2 pbkdf2) { + super(pbkdf2, asKind); + } + + @Nonnull + @Override + public PBKDF2 asKind(@Nonnull Class kind) { + return new PBKDF2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12KDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12KDF.java new file mode 100644 index 000000000..da6bc4e41 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12KDF.java @@ -0,0 +1,85 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

The PKCS#12 key derivation function, which derives keys, IVs and MAC keys from a password and + * a salt with a digest. + * + *

Specification

+ * + *
    + *
  • https://www.rfc-editor.org/rfc/rfc7292#appendix-B + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • PKCS#12 v1.1 appendix B + *
+ */ +public final class PKCS12KDF extends Algorithm implements PasswordBasedKeyDerivationFunction { + + private static final String NAME = "PKCS12KDF"; + + public PKCS12KDF(@Nonnull DetectionLocation detectionLocation) { + super(NAME, PasswordBasedKeyDerivationFunction.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) + .orElse(this.name); + } + + private PKCS12KDF(@Nonnull PKCS12KDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PKCS12KDF copy() { + return new PKCS12KDF(this); + } + + public PKCS12KDF( + @Nonnull final Class asKind, @Nonnull PKCS12KDF pkcs12kdf) { + super(pkcs12kdf, asKind); + } + + @Nonnull + @Override + public PKCS12KDF asKind(@Nonnull Class kind) { + return new PKCS12KDF(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12PBE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12PBE.java index 79b0b678a..c0c826dc2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12PBE.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PKCS12PBE.java @@ -20,11 +20,16 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.Cipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.PasswordBasedEncryption; +import com.ibm.mapper.model.StreamCipher; import com.ibm.mapper.utils.DetectionLocation; +import java.util.Optional; import javax.annotation.Nonnull; public final class PKCS12PBE extends Algorithm implements PasswordBasedEncryption { @@ -32,6 +37,28 @@ public final class PKCS12PBE extends Algorithm implements PasswordBasedEncryptio private static final String NAME = "PKCS12"; // id-PKCS12PBE + /** The name, followed by the cipher and the MAC or digest when known. */ + @Nonnull + @Override + public String asString() { + final Optional cipher = + this.hasChildOfType(BlockCipher.class) + .or(() -> this.hasChildOfType(StreamCipher.class)); + final StringBuilder stringBuilder = new StringBuilder(this.name); + cipher.ifPresent( + c -> { + stringBuilder.append("-").append(c.asString()); + this.hasChildOfType(Mac.class) + .or(() -> this.hasChildOfType(MessageDigest.class)) + .ifPresent( + macOrDigest -> + stringBuilder + .append("-") + .append(macOrDigest.asString())); + }); + return stringBuilder.toString(); + } + public PKCS12PBE(@Nonnull DetectionLocation detectionLocation) { super(NAME, PasswordBasedEncryption.class, detectionLocation); } @@ -52,4 +79,25 @@ public PKCS12PBE(@Nonnull Mac mac) { this(mac.getDetectionContext()); this.put(mac); } + + private PKCS12PBE(@Nonnull PKCS12PBE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PKCS12PBE copy() { + return new PKCS12PBE(this); + } + + public PKCS12PBE( + @Nonnull final Class asKind, @Nonnull PKCS12PBE pkcs12pbe) { + super(pkcs12pbe, asKind); + } + + @Nonnull + @Override + public PKCS12PBE asKind(@Nonnull Class kind) { + return new PKCS12PBE(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PSK.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PSK.java index 3c7cb6229..84df87eae 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PSK.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PSK.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,24 @@ public final class PSK extends Algorithm implements KeyAgreement { public PSK(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyAgreement.class, detectionLocation); } + + private PSK(@Nonnull PSK algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PSK copy() { + return new PSK(this); + } + + public PSK(@Nonnull final Class asKind, @Nonnull PSK psk) { + super(psk, asKind); + } + + @Nonnull + @Override + public PSK asKind(@Nonnull Class kind) { + return new PSK(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/PVKKDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PVKKDF.java new file mode 100644 index 000000000..be10f174b --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/PVKKDF.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.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

The key derivation of the Microsoft PVK private key format, deriving an RC4 key from a + * password with a digest. + * + *

Specification

+ * + *
    + *
  • https://learn.microsoft.com/en-us/windows/win32/seccrypto/pvk-file-format + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • Microsoft PVK + *
+ */ +public final class PVKKDF extends Algorithm implements PasswordBasedKeyDerivationFunction { + + private static final String NAME = "PVKKDF"; + + public PVKKDF(@Nonnull DetectionLocation detectionLocation) { + super(NAME, PasswordBasedKeyDerivationFunction.class, detectionLocation); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) + .orElse(this.name); + } + + private PVKKDF(@Nonnull PVKKDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PVKKDF copy() { + return new PVKKDF(this); + } + + public PVKKDF(@Nonnull final Class asKind, @Nonnull PVKKDF pvkkdf) { + super(pvkkdf, asKind); + } + + @Nonnull + @Override + public PVKKDF asKind(@Nonnull Class kind) { + return new PVKKDF(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ParallelHash.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ParallelHash.java index 69c574ee3..accb84d5d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ParallelHash.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ParallelHash.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -55,4 +56,25 @@ public ParallelHash(int digestSize, @Nonnull DetectionLocation detectionLocation this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + private ParallelHash(@Nonnull ParallelHash algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ParallelHash copy() { + return new ParallelHash(this); + } + + public ParallelHash( + @Nonnull final Class asKind, @Nonnull ParallelHash parallelHash) { + super(parallelHash, asKind); + } + + @Nonnull + @Override + public ParallelHash asKind(@Nonnull Class kind) { + return new ParallelHash(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Picnic.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Picnic.java index e313bec5e..43041eda0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Picnic.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Picnic.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -63,4 +64,24 @@ public Picnic(String parameterSetIdentifier, @Nonnull DetectionLocation detectio this(detectionLocation); this.put(new ParameterSetIdentifier(parameterSetIdentifier, detectionLocation)); } + + private Picnic(@Nonnull Picnic algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Picnic copy() { + return new Picnic(this); + } + + public Picnic(@Nonnull final Class asKind, @Nonnull Picnic picnic) { + super(picnic, asKind); + } + + @Nonnull + @Override + public Picnic asKind(@Nonnull Class kind) { + return new Picnic(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Poly1305.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Poly1305.java index 2c050a56b..2e819138f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Poly1305.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Poly1305.java @@ -53,4 +53,20 @@ public Poly1305(@Nonnull DetectionLocation detectionLocation) { public Poly1305(@Nonnull final Class asKind, @Nonnull Poly1305 poly1305) { super(poly1305, asKind); } + + private Poly1305(@Nonnull Poly1305 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Poly1305 copy() { + return new Poly1305(this); + } + + @Nonnull + @Override + public Poly1305 asKind(@Nonnull Class kind) { + return new Poly1305(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/QTESLA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/QTESLA.java index 9f16d5ae2..13eedc088 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/QTESLA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/QTESLA.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public class QTESLA extends Algorithm implements Signature { public QTESLA(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private QTESLA(@Nonnull QTESLA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected QTESLA copy() { + return new QTESLA(this); + } + + public QTESLA(@Nonnull final Class asKind, @Nonnull QTESLA qtesla) { + super(qtesla, asKind); + } + + @Nonnull + @Override + public QTESLA asKind(@Nonnull Class kind) { + return new QTESLA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC2.java index 363cf51ec..e055e11f4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC2.java @@ -85,4 +85,20 @@ public RC2( public RC2(@Nonnull final Class asKind, @Nonnull RC2 rc2) { super(rc2, asKind); } + + private RC2(@Nonnull RC2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RC2 copy() { + return new RC2(this); + } + + @Nonnull + @Override + public RC2 asKind(@Nonnull Class kind) { + return new RC2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC4.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC4.java index 67f0a4122..2219c1dc0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC4.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC4.java @@ -85,4 +85,20 @@ public RC4( public RC4(@Nonnull final Class asKind, @Nonnull RC4 rc4) { super(rc4, asKind); } + + private RC4(@Nonnull RC4 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RC4 copy() { + return new RC4(this); + } + + @Nonnull + @Override + public RC4 asKind(@Nonnull Class kind) { + return new RC4(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC5.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC5.java index e7e5c9e49..69d32bb9a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC5.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC5.java @@ -87,4 +87,20 @@ public RC5( public RC5(@Nonnull final Class asKind, @Nonnull RC5 rc5) { super(rc5, asKind); } + + private RC5(@Nonnull RC5 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RC5 copy() { + return new RC5(this); + } + + @Nonnull + @Override + public RC5 asKind(@Nonnull Class kind) { + return new RC5(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC6.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC6.java index 4907c7d12..d897cf448 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC6.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RC6.java @@ -88,4 +88,20 @@ public RC6( public RC6(@Nonnull final Class asKind, @Nonnull RC6 rc6) { super(rc6, asKind); } + + private RC6(@Nonnull RC6 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RC6 copy() { + return new RC6(this); + } + + @Nonnull + @Override + public RC6 asKind(@Nonnull Class kind) { + return new RC6(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RFC3211Wrap.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RFC3211Wrap.java index 3dd6167c6..4c7b907a5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RFC3211Wrap.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RFC3211Wrap.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyWrap; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,25 @@ public class RFC3211Wrap extends Algorithm implements KeyWrap { public RFC3211Wrap(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyWrap.class, detectionLocation); } + + private RFC3211Wrap(@Nonnull RFC3211Wrap algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RFC3211Wrap copy() { + return new RFC3211Wrap(this); + } + + public RFC3211Wrap( + @Nonnull final Class asKind, @Nonnull RFC3211Wrap rfc3211Wrap) { + super(rfc3211Wrap, asKind); + } + + @Nonnull + @Override + public RFC3211Wrap asKind(@Nonnull Class kind) { + return new RFC3211Wrap(kind, this); + } } 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..6a90d3552 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,24 @@ public RIPEMD(int digestSize, @Nonnull DetectionLocation detectionLocation) { this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + private RIPEMD(@Nonnull RIPEMD algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RIPEMD copy() { + return new RIPEMD(this); + } + + public RIPEMD(@Nonnull final Class asKind, @Nonnull RIPEMD ripemd) { + super(ripemd, asKind); + } + + @Nonnull + @Override + public RIPEMD asKind(@Nonnull Class kind) { + return new RIPEMD(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSA.java index 5087fa386..584d8c452 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSA.java @@ -96,4 +96,20 @@ public RSA( public RSA(@Nonnull final Class asKind, @Nonnull RSA rsa) { super(rsa, asKind); } + + private RSA(@Nonnull final RSA rsa) { + super(rsa); + } + + @Nonnull + @Override + protected RSA copy() { + return new RSA(this); + } + + @Nonnull + @Override + public RSA asKind(@Nonnull Class kind) { + return new RSA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAKEM.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAKEM.java index 78bc08039..c7f55544a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAKEM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAKEM.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public final class RSAKEM extends Algorithm implements KeyEncapsulationMechanism public RSAKEM(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private RSAKEM(@Nonnull RSAKEM algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RSAKEM copy() { + return new RSAKEM(this); + } + + public RSAKEM(@Nonnull final Class asKind, @Nonnull RSAKEM rsakem) { + super(rsakem, asKind); + } + + @Nonnull + @Override + public RSAKEM asKind(@Nonnull Class kind) { + return new RSAKEM(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSASVE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSASVE.java new file mode 100644 index 000000000..3d841e881 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSASVE.java @@ -0,0 +1,75 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

RSA secret-value encapsulation: a random secret value is encrypted with the RSA public key and + * used directly as the shared secret. Unlike {@link RSAKEM}, no key derivation function is applied. + * + *

Specification

+ * + *
    + *
  • See 7.2.1 of https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • NIST SP 800-56B + *
+ */ +public final class RSASVE extends Algorithm implements KeyEncapsulationMechanism { + + private static final String NAME = "RSASVE"; + + public RSASVE(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyEncapsulationMechanism.class, detectionLocation); + } + + private RSASVE(@Nonnull RSASVE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RSASVE copy() { + return new RSASVE(this); + } + + public RSASVE(@Nonnull final Class asKind, @Nonnull RSASVE rsasve) { + super(rsasve, asKind); + } + + @Nonnull + @Override + public RSASVE asKind(@Nonnull Class kind) { + return new RSASVE(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAssaPSS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAssaPSS.java index a2f9244b5..101584bb3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAssaPSS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/RSAssaPSS.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.ProbabilisticSignatureScheme; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,25 @@ public final class RSAssaPSS extends Algorithm implements ProbabilisticSignature public RSAssaPSS(@Nonnull DetectionLocation detectionLocation) { super("RSA-PSS", ProbabilisticSignatureScheme.class, detectionLocation); } + + private RSAssaPSS(@Nonnull RSAssaPSS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected RSAssaPSS copy() { + return new RSAssaPSS(this); + } + + public RSAssaPSS( + @Nonnull final Class asKind, @Nonnull RSAssaPSS rsAssaPSS) { + super(rsAssaPSS, asKind); + } + + @Nonnull + @Override + public RSAssaPSS asKind(@Nonnull Class kind) { + return new RSAssaPSS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Rainbow.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Rainbow.java index cafc92325..ba720d862 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Rainbow.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Rainbow.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public class Rainbow extends Algorithm implements Signature { public Rainbow(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private Rainbow(@Nonnull Rainbow algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Rainbow copy() { + return new Rainbow(this); + } + + public Rainbow(@Nonnull final Class asKind, @Nonnull Rainbow rainbow) { + super(rainbow, asKind); + } + + @Nonnull + @Override + public Rainbow asKind(@Nonnull Class kind) { + return new Rainbow(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SABER.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SABER.java index f68208519..a07f281dd 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SABER.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SABER.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public final class SABER extends Algorithm implements KeyEncapsulationMechanism public SABER(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private SABER(@Nonnull SABER algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SABER copy() { + return new SABER(this); + } + + public SABER(@Nonnull final Class asKind, @Nonnull SABER saber) { + super(saber, asKind); + } + + @Nonnull + @Override + public SABER asKind(@Nonnull Class kind) { + return new SABER(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SEED.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SEED.java index 024969141..b09cff9c2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SEED.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SEED.java @@ -80,4 +80,20 @@ public SEED( public SEED(@Nonnull final Class asKind, @Nonnull SEED seed) { super(seed, asKind); } + + private SEED(@Nonnull SEED algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SEED copy() { + return new SEED(this); + } + + @Nonnull + @Override + public SEED asKind(@Nonnull Class kind) { + return new SEED(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA.java index 3a37e9eda..949f9ef9c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA.java @@ -60,4 +60,20 @@ public SHA( public SHA(@Nonnull final Class asKind, @Nonnull SHA sha) { super(sha, asKind); } + + private SHA(@Nonnull SHA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SHA copy() { + return new SHA(this); + } + + @Nonnull + @Override + public SHA asKind(@Nonnull Class kind) { + return new SHA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java index 9be9b27c8..a19bc3df2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA2.java @@ -81,4 +81,20 @@ private static String buildPreHashName(int digestSize, @Nonnull MessageDigest pr .map(size -> NAME + size.asString() + "/" + digestSize) .orElse(NAME + digestSize); } + + private SHA2(@Nonnull SHA2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SHA2 copy() { + return new SHA2(this); + } + + @Nonnull + @Override + public SHA2 asKind(@Nonnull Class kind) { + return new SHA2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA3.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA3.java index 74ca1a729..41014da5f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA3.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SHA3.java @@ -77,4 +77,20 @@ public SHA3( public SHA3(@Nonnull final Class asKind, @Nonnull SHA3 sha3) { super(sha3, asKind); } + + private SHA3(@Nonnull SHA3 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SHA3 copy() { + return new SHA3(this); + } + + @Nonnull + @Override + public SHA3 asKind(@Nonnull Class kind) { + return new SHA3(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM2.java index ac1d74fbb..cc7ca73b1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM2.java @@ -56,4 +56,20 @@ public SM2(@Nonnull DetectionLocation detectionLocation) { public SM2(@Nonnull final Class asKind, @Nonnull SM2 sm2) { super(sm2, asKind); } + + private SM2(@Nonnull SM2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SM2 copy() { + return new SM2(this); + } + + @Nonnull + @Override + public SM2 asKind(@Nonnull Class kind) { + return new SM2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM3.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM3.java index 38979221c..6567a4f90 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM3.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM3.java @@ -55,4 +55,20 @@ public SM3(@Nonnull DetectionLocation detectionLocation) { public SM3(@Nonnull final Class asKind, @Nonnull SM3 sm3) { super(sm3, asKind); } + + private SM3(@Nonnull SM3 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SM3 copy() { + return new SM3(this); + } + + @Nonnull + @Override + public SM3 asKind(@Nonnull Class kind) { + return new SM3(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM4.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM4.java index bea5aea21..4606519a1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM4.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SM4.java @@ -66,4 +66,20 @@ public SM4(@Nonnull Mode mode, @Nonnull DetectionLocation detectionLocation) { public SM4(@Nonnull final Class asKind, @Nonnull SM4 sm4) { super(sm4, asKind); } + + private SM4(@Nonnull SM4 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SM4 copy() { + return new SM4(this); + } + + @Nonnull + @Override + public SM4 asKind(@Nonnull Class kind) { + return new SM4(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCS.java index e0fa1487a..6d3258666 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCS.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; @@ -64,4 +65,24 @@ public SPHINCS(int parameterSetIdentifier, @Nonnull DetectionLocation detectionL new ParameterSetIdentifier( String.valueOf(parameterSetIdentifier), detectionLocation)); } + + private SPHINCS(@Nonnull SPHINCS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SPHINCS copy() { + return new SPHINCS(this); + } + + public SPHINCS(@Nonnull final Class asKind, @Nonnull SPHINCS sphincs) { + super(sphincs, asKind); + } + + @Nonnull + @Override + public SPHINCS asKind(@Nonnull Class kind) { + return new SPHINCS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCSPlus.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCSPlus.java index dc8a01e76..dce9b3aa4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCSPlus.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SPHINCSPlus.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Signature; @@ -84,4 +85,25 @@ public SPHINCSPlus( this(detectionLocation); this.put(new ParameterSetIdentifier(parameterSetIdentifier, detectionLocation)); } + + private SPHINCSPlus(@Nonnull SPHINCSPlus algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SPHINCSPlus copy() { + return new SPHINCSPlus(this); + } + + public SPHINCSPlus( + @Nonnull final Class asKind, @Nonnull SPHINCSPlus sphincsPlus) { + super(sphincsPlus, asKind); + } + + @Nonnull + @Override + public SPHINCSPlus asKind(@Nonnull Class kind) { + return new SPHINCSPlus(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SRP.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SRP.java index e25adeb9a..42fefc9b1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SRP.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SRP.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,24 @@ public final class SRP extends Algorithm implements KeyAgreement { public SRP(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyAgreement.class, detectionLocation); } + + private SRP(@Nonnull SRP algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SRP copy() { + return new SRP(this); + } + + public SRP(@Nonnull final Class asKind, @Nonnull SRP srp) { + super(srp, asKind); + } + + @Nonnull + @Override + public SRP asKind(@Nonnull Class kind) { + return new SRP(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SSHKDF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SSHKDF.java new file mode 100644 index 000000000..57c32512d --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SSHKDF.java @@ -0,0 +1,89 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

The SSH Key Derivation Function used to derive session keys and IVs from the SSH transport + * layer shared secret and exchange hash. + * + *

Specification

+ * + *
    + *
  • https://datatracker.ietf.org/doc/html/rfc4253#section-7.2 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • RFC 4253 + *
+ */ +public final class SSHKDF extends Algorithm implements KeyDerivationFunction { + + private static final String NAME = "SSHKDF"; + + public SSHKDF(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyDerivationFunction.class, detectionLocation); + } + + public SSHKDF(@Nonnull MessageDigest messageDigest) { + this(messageDigest.getDetectionContext()); + this.put(messageDigest); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) + .orElse(this.name); + } + + private SSHKDF(@Nonnull SSHKDF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SSHKDF copy() { + return new SSHKDF(this); + } + + public SSHKDF(@Nonnull final Class asKind, @Nonnull SSHKDF sshkdf) { + super(sshkdf, asKind); + } + + @Nonnull + @Override + public SSHKDF asKind(@Nonnull Class kind) { + return new SSHKDF(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Salsa20.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Salsa20.java index fcfa8f937..c5b559391 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Salsa20.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Salsa20.java @@ -60,4 +60,20 @@ public Salsa20(int keyLength, @Nonnull DetectionLocation detectionLocation) { public Salsa20(@Nonnull final Class asKind, @Nonnull Salsa20 salsa20) { super(salsa20, asKind); } + + private Salsa20(@Nonnull Salsa20 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Salsa20 copy() { + return new Salsa20(this); + } + + @Nonnull + @Override + public Salsa20 asKind(@Nonnull Class kind) { + return new Salsa20(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Scrypt.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Scrypt.java index 5993962a0..898eab201 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Scrypt.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Scrypt.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,24 @@ public final class Scrypt extends Algorithm implements PasswordBasedKeyDerivatio public Scrypt(@Nonnull DetectionLocation detectionLocation) { super(NAME, PasswordBasedKeyDerivationFunction.class, detectionLocation); } + + private Scrypt(@Nonnull Scrypt algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Scrypt copy() { + return new Scrypt(this); + } + + public Scrypt(@Nonnull final Class asKind, @Nonnull Scrypt scrypt) { + super(scrypt, asKind); + } + + @Nonnull + @Override + public Scrypt asKind(@Nonnull Class kind) { + return new Scrypt(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SecP256r1MLKEM768.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SecP256r1MLKEM768.java new file mode 100644 index 000000000..fb2a7a44d --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SecP256r1MLKEM768.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Hybrid post-quantum key encapsulation mechanism combining ECDH P-256 (secp256r1, NIST P-256) + * with ML-KEM-768 (post-quantum KEM), for TLS 1.3 key exchange. + * + *

Specification

+ * + *
    + *
  • https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/ + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • TLS Named Group 0x11EB + *
  • Not to be confused with the earlier, distinct draft-Kyber group SecP256r1Kyber768Draft00 + * (TLS Named Group 0x639A), which predates ML-KEM standardization and uses round-3 Kyber + * rather than the final FIPS 203 ML-KEM. + *
+ */ +public final class SecP256r1MLKEM768 extends Algorithm implements KeyEncapsulationMechanism { + + private static final String NAME = "SecP256r1MLKEM768"; + + public SecP256r1MLKEM768(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyEncapsulationMechanism.class, detectionLocation); + } + + private SecP256r1MLKEM768(@Nonnull SecP256r1MLKEM768 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SecP256r1MLKEM768 copy() { + return new SecP256r1MLKEM768(this); + } + + public SecP256r1MLKEM768( + @Nonnull final Class asKind, + @Nonnull SecP256r1MLKEM768 secP256r1MLKEM768) { + super(secP256r1MLKEM768, asKind); + } + + @Nonnull + @Override + public SecP256r1MLKEM768 asKind(@Nonnull Class kind) { + return new SecP256r1MLKEM768(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SecP384r1MLKEM1024.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SecP384r1MLKEM1024.java new file mode 100644 index 000000000..15ba7e337 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SecP384r1MLKEM1024.java @@ -0,0 +1,77 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Hybrid post-quantum key encapsulation mechanism combining ECDH P-384 (secp384r1, NIST P-384) + * with ML-KEM-1024 (post-quantum KEM), for high-security TLS 1.3 key exchange. + * + *

Specification

+ * + *
    + *
  • https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/ + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • TLS Named Group 0x11ED + *
+ */ +public final class SecP384r1MLKEM1024 extends Algorithm implements KeyEncapsulationMechanism { + + private static final String NAME = "SecP384r1MLKEM1024"; + + public SecP384r1MLKEM1024(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyEncapsulationMechanism.class, detectionLocation); + } + + private SecP384r1MLKEM1024(@Nonnull SecP384r1MLKEM1024 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SecP384r1MLKEM1024 copy() { + return new SecP384r1MLKEM1024(this); + } + + public SecP384r1MLKEM1024( + @Nonnull final Class asKind, + @Nonnull SecP384r1MLKEM1024 secP384r1MLKEM1024) { + super(secP384r1MLKEM1024, asKind); + } + + @Nonnull + @Override + public SecP384r1MLKEM1024 asKind(@Nonnull Class kind) { + return new SecP384r1MLKEM1024(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Serpent.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Serpent.java index 48fa406cb..fd23da3d1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Serpent.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Serpent.java @@ -91,4 +91,20 @@ public Serpent( public Serpent(@Nonnull final Class asKind, @Nonnull Serpent serpent) { super(serpent, asKind); } + + private Serpent(@Nonnull Serpent algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Serpent copy() { + return new Serpent(this); + } + + @Nonnull + @Override + public Serpent asKind(@Nonnull Class kind) { + return new Serpent(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SipHash.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SipHash.java index b44701e6e..db1264a89 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/SipHash.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/SipHash.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.Mac; import com.ibm.mapper.utils.DetectionLocation; @@ -57,4 +58,24 @@ public SipHash(int outputSize, @Nonnull DetectionLocation detectionLocation) { public SipHash(@Nonnull DetectionLocation detectionLocation) { this(64, detectionLocation); } + + private SipHash(@Nonnull SipHash algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SipHash copy() { + return new SipHash(this); + } + + public SipHash(@Nonnull final Class asKind, @Nonnull SipHash sipHash) { + super(sipHash, asKind); + } + + @Nonnull + @Override + public SipHash asKind(@Nonnull Class kind) { + return new SipHash(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skein.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skein.java index 678528e3b..ec1ab0926 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skein.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skein.java @@ -73,4 +73,20 @@ public Skein(int blockSize, int digestSize, @Nonnull DetectionLocation detection public Skein(@Nonnull final Class asKind, @Nonnull Skein skein) { super(skein, asKind); } + + private Skein(@Nonnull Skein algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Skein copy() { + return new Skein(this); + } + + @Nonnull + @Override + public Skein asKind(@Nonnull Class kind) { + return new Skein(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skipjack.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skipjack.java index 74dcace5c..d480b2740 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skipjack.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Skipjack.java @@ -74,4 +74,20 @@ public Skipjack( public Skipjack(@Nonnull final Class asKind, @Nonnull Skipjack skipjack) { super(skipjack, asKind); } + + private Skipjack(@Nonnull Skipjack algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Skipjack copy() { + return new Skipjack(this); + } + + @Nonnull + @Override + public Skipjack asKind(@Nonnull Class kind) { + return new Skipjack(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TLSPRF.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TLSPRF.java new file mode 100644 index 000000000..c1c22ac3e --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TLSPRF.java @@ -0,0 +1,91 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

The TLS 1.0/1.1/1.2 Pseudo-Random Function used to derive the master secret and key material + * from a shared secret. + * + *

Specification

+ * + *
    + *
  • https://datatracker.ietf.org/doc/html/rfc5246#section-5 + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • RFC 2246 (TLS 1.0) + *
  • RFC 4346 (TLS 1.1) + *
  • RFC 5246 (TLS 1.2) + *
+ */ +public final class TLSPRF extends Algorithm implements KeyDerivationFunction { + + private static final String NAME = "TLS-PRF"; + + public TLSPRF(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyDerivationFunction.class, detectionLocation); + } + + public TLSPRF(@Nonnull MessageDigest messageDigest) { + this(messageDigest.getDetectionContext()); + this.put(messageDigest); + } + + @Override + public @Nonnull String asString() { + return this.hasChildOfType(MessageDigest.class) + .map(digest -> this.name + "-" + ((IAlgorithm) digest).getName()) + .orElse(this.name); + } + + private TLSPRF(@Nonnull TLSPRF algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected TLSPRF copy() { + return new TLSPRF(this); + } + + public TLSPRF(@Nonnull final Class asKind, @Nonnull TLSPRF tlsprf) { + super(tlsprf, asKind); + } + + @Nonnull + @Override + public TLSPRF asKind(@Nonnull Class kind) { + return new TLSPRF(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Threefish.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Threefish.java index 03bed2483..9c302338d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Threefish.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Threefish.java @@ -82,4 +82,20 @@ public Threefish( @Nonnull final Class asKind, @Nonnull Threefish threefish) { super(threefish, asKind); } + + private Threefish(@Nonnull Threefish algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Threefish copy() { + return new Threefish(this); + } + + @Nonnull + @Override + public Threefish asKind(@Nonnull Class kind) { + return new Threefish(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Tiger.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Tiger.java index e132d72df..5f92e28aa 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Tiger.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Tiger.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.NumberOfIterations; import com.ibm.mapper.utils.DetectionLocation; @@ -60,4 +61,24 @@ public Tiger(int digestSize, @Nonnull DetectionLocation detectionLocation) { this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + private Tiger(@Nonnull Tiger algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Tiger copy() { + return new Tiger(this); + } + + public Tiger(@Nonnull final Class asKind, @Nonnull Tiger tiger) { + super(tiger, asKind); + } + + @Nonnull + @Override + public Tiger asKind(@Nonnull Class kind) { + return new Tiger(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TripleDES.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TripleDES.java index 10c22be92..80a03f227 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TripleDES.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TripleDES.java @@ -101,4 +101,20 @@ public TripleDES( @Nonnull final Class asKind, @Nonnull TripleDES tripleDES) { super(tripleDES, asKind); } + + private TripleDES(@Nonnull TripleDES algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected TripleDES copy() { + return new TripleDES(this); + } + + @Nonnull + @Override + public TripleDES asKind(@Nonnull Class kind) { + return new TripleDES(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java index 9a765a455..56d4624d7 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/TupleHash.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -55,4 +56,25 @@ public TupleHash(int digestSize, @Nonnull DetectionLocation detectionLocation) { this(detectionLocation); this.put(new DigestSize(digestSize, detectionLocation)); } + + private TupleHash(@Nonnull TupleHash algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected TupleHash copy() { + return new TupleHash(this); + } + + public TupleHash( + @Nonnull final Class asKind, @Nonnull TupleHash tupleHash) { + super(tupleHash, asKind); + } + + @Nonnull + @Override + public TupleHash asKind(@Nonnull Class kind) { + return new TupleHash(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Twofish.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Twofish.java index 4b5448427..6a6e1748a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Twofish.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Twofish.java @@ -89,4 +89,20 @@ public Twofish( public Twofish(@Nonnull final Class asKind, @Nonnull Twofish twofish) { super(twofish, asKind); } + + private Twofish(@Nonnull Twofish algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Twofish copy() { + return new Twofish(this); + } + + @Nonnull + @Override + public Twofish asKind(@Nonnull Class kind) { + return new Twofish(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Whirlpool.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Whirlpool.java index c902209ef..10580f319 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Whirlpool.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Whirlpool.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.NumberOfIterations; import com.ibm.mapper.utils.DetectionLocation; @@ -55,4 +56,25 @@ public Whirlpool(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(512, detectionLocation)); this.put(new NumberOfIterations(10, detectionLocation)); } + + private Whirlpool(@Nonnull Whirlpool algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Whirlpool copy() { + return new Whirlpool(this); + } + + public Whirlpool( + @Nonnull final Class asKind, @Nonnull Whirlpool whirlpool) { + super(whirlpool, asKind); + } + + @Nonnull + @Override + public Whirlpool asKind(@Nonnull Class kind) { + return new Whirlpool(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519.java index 0c07fc80d..812762b29 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.curves.Curve25519; @@ -52,4 +53,24 @@ public X25519(@Nonnull DetectionLocation detectionLocation) { this.put(new Curve25519(detectionLocation)); this.put(new Oid("1.3.101.110", detectionLocation)); } + + private X25519(@Nonnull X25519 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected X25519 copy() { + return new X25519(this); + } + + public X25519(@Nonnull final Class asKind, @Nonnull X25519 x25519) { + super(x25519, asKind); + } + + @Nonnull + @Override + public X25519 asKind(@Nonnull Class kind) { + return new X25519(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519MLKEM768.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519MLKEM768.java new file mode 100644 index 000000000..e8601dfb1 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X25519MLKEM768.java @@ -0,0 +1,80 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Hybrid post-quantum key encapsulation mechanism combining X25519 (Curve25519 elliptic curve + * Diffie-Hellman) with ML-KEM-768 (post-quantum KEM), for TLS 1.3 key exchange. + * + *

Specification

+ * + *
    + *
  • https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/ + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • TLS Named Group 0x11EC + *
  • Not to be confused with the earlier, distinct draft-Kyber group X25519Kyber768Draft00 (TLS + * Named Group 0x6399), which predates ML-KEM standardization and uses round-3 Kyber rather + * than the final FIPS 203 ML-KEM. + *
+ */ +public final class X25519MLKEM768 extends Algorithm implements KeyEncapsulationMechanism { + + private static final String NAME = "X25519MLKEM768"; + + public X25519MLKEM768(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyEncapsulationMechanism.class, detectionLocation); + } + + private X25519MLKEM768(@Nonnull X25519MLKEM768 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected X25519MLKEM768 copy() { + return new X25519MLKEM768(this); + } + + public X25519MLKEM768( + @Nonnull final Class asKind, + @Nonnull X25519MLKEM768 x25519mlkem768) { + super(x25519mlkem768, asKind); + } + + @Nonnull + @Override + public X25519MLKEM768 asKind(@Nonnull Class kind) { + return new X25519MLKEM768(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448.java index 8ec2b2a40..7afa78515 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.curves.Curve448; @@ -52,4 +53,24 @@ public X448(@Nonnull DetectionLocation detectionLocation) { this.put(new Curve448(detectionLocation)); this.put(new Oid("1.3.101.111", detectionLocation)); } + + private X448(@Nonnull X448 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected X448 copy() { + return new X448(this); + } + + public X448(@Nonnull final Class asKind, @Nonnull X448 x448) { + super(x448, asKind); + } + + @Nonnull + @Override + public X448 asKind(@Nonnull Class kind) { + return new X448(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448MLKEM1024.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448MLKEM1024.java new file mode 100644 index 000000000..d1e948614 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/X448MLKEM1024.java @@ -0,0 +1,77 @@ +/* + * 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.model.algorithms; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** + * + * + *

{@value #NAME}

+ * + *

Hybrid post-quantum key encapsulation mechanism combining X448 (Curve448 elliptic curve + * Diffie-Hellman) with ML-KEM-1024 (post-quantum KEM), for high-security TLS 1.3 key exchange. + * + *

Specification

+ * + *
    + *
  • https://datatracker.ietf.org/doc/draft-kwiatkowski-tls-ecdhe-mlkem/ + *
+ * + *

Other Names and Related Standards

+ * + *
    + *
  • TLS Named Group 0x11EE + *
+ */ +public final class X448MLKEM1024 extends Algorithm implements KeyEncapsulationMechanism { + + private static final String NAME = "X448MLKEM1024"; + + public X448MLKEM1024(@Nonnull DetectionLocation detectionLocation) { + super(NAME, KeyEncapsulationMechanism.class, detectionLocation); + } + + private X448MLKEM1024(@Nonnull X448MLKEM1024 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected X448MLKEM1024 copy() { + return new X448MLKEM1024(this); + } + + public X448MLKEM1024( + @Nonnull final Class asKind, + @Nonnull X448MLKEM1024 x448mlkem1024) { + super(x448mlkem1024, asKind); + } + + @Nonnull + @Override + public X448MLKEM1024 asKind(@Nonnull Class kind) { + return new X448MLKEM1024(kind, this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/XDH.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/XDH.java index c865e872b..64427375c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/XDH.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/XDH.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyAgreement; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,24 @@ public final class XDH extends Algorithm implements KeyAgreement { public XDH(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyAgreement.class, detectionLocation); } + + private XDH(@Nonnull XDH algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected XDH copy() { + return new XDH(this); + } + + public XDH(@Nonnull final Class asKind, @Nonnull XDH xdh) { + super(xdh, asKind); + } + + @Nonnull + @Override + public XDH asKind(@Nonnull Class kind) { + return new XDH(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSS.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSS.java index e2fa3976f..8887e5b93 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSS.java @@ -59,4 +59,24 @@ public XMSS( @Nonnull DetectionLocation detectionLocation) { super(NAME, asKind, detectionLocation); } + + private XMSS(@Nonnull XMSS algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected XMSS copy() { + return new XMSS(this); + } + + public XMSS(@Nonnull final Class asKind, @Nonnull XMSS xmss) { + super(xmss, asKind); + } + + @Nonnull + @Override + public XMSS asKind(@Nonnull Class kind) { + return new XMSS(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSSMT.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSSMT.java index 3e938ee56..295e621c3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSSMT.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/XMSSMT.java @@ -58,4 +58,24 @@ public XMSSMT( @Nonnull DetectionLocation detectionLocation) { super(NAME, asKind, detectionLocation); } + + private XMSSMT(@Nonnull XMSSMT algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected XMSSMT copy() { + return new XMSSMT(this); + } + + public XMSSMT(@Nonnull final Class asKind, @Nonnull XMSSMT xmssmt) { + super(xmssmt, asKind); + } + + @Nonnull + @Override + public XMSSMT asKind(@Nonnull Class kind) { + return new XMSSMT(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Xoodyak.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Xoodyak.java index 48485d19c..cbb1d3153 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/Xoodyak.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/Xoodyak.java @@ -61,4 +61,24 @@ public Xoodyak( @Nonnull DetectionLocation detectionLocation) { super(NAME, asKind, detectionLocation); } + + private Xoodyak(@Nonnull Xoodyak algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Xoodyak copy() { + return new Xoodyak(this); + } + + public Xoodyak(@Nonnull final Class asKind, @Nonnull Xoodyak xoodyak) { + super(xoodyak, asKind); + } + + @Nonnull + @Override + public Xoodyak asKind(@Nonnull Class kind) { + return new Xoodyak(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ZUC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ZUC.java index aa9e539ea..270f6c3e6 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ZUC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ZUC.java @@ -78,4 +78,20 @@ public ZUC(int securityLevel, @Nonnull DetectionLocation detectionLocation) { public ZUC(@Nonnull final Class asKind, @Nonnull ZUC zuc) { super(zuc, asKind); } + + private ZUC(@Nonnull ZUC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ZUC copy() { + return new ZUC(this); + } + + @Nonnull + @Override + public ZUC asKind(@Nonnull Class kind) { + return new ZUC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon.java index a26e9d243..12e3b8030 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon.java @@ -64,4 +64,24 @@ protected Ascon( @Nonnull DetectionLocation detectionLocation) { super(name, asKind, detectionLocation); } + + protected Ascon(@Nonnull Ascon algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Ascon copy() { + return new Ascon(this); + } + + public Ascon(@Nonnull final Class asKind, @Nonnull Ascon ascon) { + super(ascon, asKind); + } + + @Nonnull + @Override + public Ascon asKind(@Nonnull Class kind) { + return new Ascon(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128.java index 530a16168..01c01d019 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.AuthenticatedEncryption; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NonceLength; import com.ibm.mapper.model.TagLength; @@ -55,4 +56,24 @@ public Ascon128(@Nonnull DetectionLocation detectionLocation) { this.put(new TagLength(128, detectionLocation)); this.put(BlockSize.ofDefault(64, detectionLocation)); } + + private Ascon128(@Nonnull Ascon128 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Ascon128 copy() { + return new Ascon128(this); + } + + public Ascon128(@Nonnull final Class asKind, @Nonnull Ascon128 ascon128) { + super(asKind, ascon128); + } + + @Nonnull + @Override + public Ascon128 asKind(@Nonnull Class kind) { + return new Ascon128(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128a.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128a.java index a4dc3b660..9ac0900a9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128a.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon128a.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.AuthenticatedEncryption; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NonceLength; import com.ibm.mapper.model.TagLength; @@ -55,4 +56,25 @@ public Ascon128a(@Nonnull DetectionLocation detectionLocation) { this.put(new TagLength(128, detectionLocation)); this.put(BlockSize.ofDefault(128, detectionLocation)); } + + private Ascon128a(@Nonnull Ascon128a algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Ascon128a copy() { + return new Ascon128a(this); + } + + public Ascon128a( + @Nonnull final Class asKind, @Nonnull Ascon128a ascon128a) { + super(asKind, ascon128a); + } + + @Nonnull + @Override + public Ascon128a asKind(@Nonnull Class kind) { + return new Ascon128a(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon80pq.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon80pq.java index 494538440..495ef0efc 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon80pq.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/Ascon80pq.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.AuthenticatedEncryption; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NonceLength; import com.ibm.mapper.model.TagLength; @@ -55,4 +56,25 @@ public Ascon80pq(@Nonnull DetectionLocation detectionLocation) { this.put(new TagLength(128, detectionLocation)); this.put(BlockSize.ofDefault(64, detectionLocation)); } + + private Ascon80pq(@Nonnull Ascon80pq algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Ascon80pq copy() { + return new Ascon80pq(this); + } + + public Ascon80pq( + @Nonnull final Class asKind, @Nonnull Ascon80pq ascon80pq) { + super(asKind, ascon80pq); + } + + @Nonnull + @Override + public Ascon80pq asKind(@Nonnull Class kind) { + return new Ascon80pq(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconHash.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconHash.java index 8d93f3222..cb3cd3a9f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconHash.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconHash.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -51,4 +52,25 @@ public AsconHash(@Nonnull DetectionLocation detectionLocation) { this.put(new DigestSize(256, detectionLocation)); this.put(BlockSize.ofDefault(64, detectionLocation)); } + + private AsconHash(@Nonnull AsconHash algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected AsconHash copy() { + return new AsconHash(this); + } + + public AsconHash( + @Nonnull final Class asKind, @Nonnull AsconHash asconHash) { + super(asKind, asconHash); + } + + @Nonnull + @Override + public AsconHash asKind(@Nonnull Class kind) { + return new AsconHash(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconXof.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconXof.java index 2ce770465..890d0a2f9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconXof.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ascon/AsconXof.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,24 @@ public AsconXof(@Nonnull DetectionLocation detectionLocation) { super(NAME, ExtendableOutputFunction.class, detectionLocation); this.put(BlockSize.ofDefault(64, detectionLocation)); } + + private AsconXof(@Nonnull AsconXof algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected AsconXof copy() { + return new AsconXof(this); + } + + public AsconXof(@Nonnull final Class asKind, @Nonnull AsconXof asconXof) { + super(asKind, asconXof); + } + + @Nonnull + @Override + public AsconXof asKind(@Nonnull Class kind) { + return new AsconXof(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE.java index ed75e3ad9..551368348 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE.java @@ -55,4 +55,20 @@ public BLAKE(int digestSize, @Nonnull DetectionLocation detectionLocation) { public BLAKE(@Nonnull final Class asKind, @Nonnull BLAKE blake) { super(blake, asKind); } + + private BLAKE(@Nonnull BLAKE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected BLAKE copy() { + return new BLAKE(this); + } + + @Nonnull + @Override + public BLAKE asKind(@Nonnull Class kind) { + return new BLAKE(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2X.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2X.java index ee1eb97f1..1b2a3c4f2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2X.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2X.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.ExtendableOutputFunction; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -51,4 +52,24 @@ public BLAKE2X(@Nonnull MessageDigest blake2, @Nonnull DetectionLocation detecti super(NAME, ExtendableOutputFunction.class, detectionLocation); this.put(blake2); } + + private BLAKE2X(@Nonnull BLAKE2X algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected BLAKE2X copy() { + return new BLAKE2X(this); + } + + public BLAKE2X(@Nonnull final Class asKind, @Nonnull BLAKE2X blake2x) { + super(blake2x, asKind); + } + + @Nonnull + @Override + public BLAKE2X asKind(@Nonnull Class kind) { + return new BLAKE2X(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2b.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2b.java index 81dafd89d..e588f8db5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2b.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2b.java @@ -74,4 +74,20 @@ public BLAKE2b( public BLAKE2b(@Nonnull final Class asKind, @Nonnull BLAKE2b blake) { super(blake, asKind); } + + private BLAKE2b(@Nonnull BLAKE2b algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected BLAKE2b copy() { + return new BLAKE2b(this); + } + + @Nonnull + @Override + public BLAKE2b asKind(@Nonnull Class kind) { + return new BLAKE2b(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2s.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2s.java index 0dd23323f..9bb0e878d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2s.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE2s.java @@ -74,4 +74,20 @@ public BLAKE2s( public BLAKE2s(@Nonnull final Class asKind, @Nonnull BLAKE2s blake) { super(blake, asKind); } + + private BLAKE2s(@Nonnull BLAKE2s algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected BLAKE2s copy() { + return new BLAKE2s(this); + } + + @Nonnull + @Override + public BLAKE2s asKind(@Nonnull Class kind) { + return new BLAKE2s(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE3.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE3.java index 7f0030821..e1ce7e8c9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE3.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/blake/BLAKE3.java @@ -62,4 +62,20 @@ public BLAKE3(@Nonnull DetectionLocation detectionLocation) { public BLAKE3(@Nonnull final Class asKind, @Nonnull BLAKE3 blake) { super(blake, asKind); } + + private BLAKE3(@Nonnull BLAKE3 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected BLAKE3 copy() { + return new BLAKE3(this); + } + + @Nonnull + @Override + public BLAKE3 asKind(@Nonnull Class kind) { + return new BLAKE3(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST128.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST128.java index 430e47b5f..559e0ef0c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST128.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST128.java @@ -90,4 +90,20 @@ public CAST128( public CAST128(@Nonnull final Class asKind, @Nonnull CAST128 cast128) { super(cast128, asKind); } + + private CAST128(@Nonnull CAST128 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected CAST128 copy() { + return new CAST128(this); + } + + @Nonnull + @Override + public CAST128 asKind(@Nonnull Class kind) { + return new CAST128(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST256.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST256.java index abe9222fb..0fb50bef8 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST256.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/cast/CAST256.java @@ -90,4 +90,20 @@ public CAST256( public CAST256(@Nonnull final Class asKind, @Nonnull CAST256 cast256) { super(cast256, asKind); } + + private CAST256(@Nonnull CAST256 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected CAST256 copy() { + return new CAST256(this); + } + + @Nonnull + @Override + public CAST256 asKind(@Nonnull Class kind) { + return new CAST256(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Delirium.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Delirium.java index 7556ceca0..4983a184c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Delirium.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Delirium.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.elephant; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public Delirium(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(200, detectionLocation)); this.put(new TagLength(128, detectionLocation)); } + + private Delirium(@Nonnull Delirium algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Delirium copy() { + return new Delirium(this); + } + + public Delirium(@Nonnull final Class asKind, @Nonnull Delirium delirium) { + super(asKind, delirium); + } + + @Nonnull + @Override + public Delirium asKind(@Nonnull Class kind) { + return new Delirium(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Dumbo.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Dumbo.java index 3626a62d9..e10c44282 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Dumbo.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Dumbo.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.elephant; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public Dumbo(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(160, detectionLocation)); this.put(new TagLength(64, detectionLocation)); } + + private Dumbo(@Nonnull Dumbo algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Dumbo copy() { + return new Dumbo(this); + } + + public Dumbo(@Nonnull final Class asKind, @Nonnull Dumbo dumbo) { + super(asKind, dumbo); + } + + @Nonnull + @Override + public Dumbo asKind(@Nonnull Class kind) { + return new Dumbo(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Elephant.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Elephant.java index bd00caaad..64ce6bfcd 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Elephant.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Elephant.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NonceLength; import com.ibm.mapper.utils.DetectionLocation; @@ -57,4 +58,24 @@ protected Elephant(@Nonnull String name, @Nonnull DetectionLocation detectionLoc this.put(KeyLength.ofDefault(128, detectionLocation)); this.put(new NonceLength(96, detectionLocation)); } + + protected Elephant(@Nonnull Elephant algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Elephant copy() { + return new Elephant(this); + } + + public Elephant(@Nonnull final Class asKind, @Nonnull Elephant elephant) { + super(elephant, asKind); + } + + @Nonnull + @Override + public Elephant asKind(@Nonnull Class kind) { + return new Elephant(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Jumbo.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Jumbo.java index 364f469af..2d823dcb9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Jumbo.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/elephant/Jumbo.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.elephant; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.TagLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public Jumbo(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(176, detectionLocation)); this.put(new TagLength(64, detectionLocation)); } + + private Jumbo(@Nonnull Jumbo algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Jumbo copy() { + return new Jumbo(this); + } + + public Jumbo(@Nonnull final Class asKind, @Nonnull Jumbo jumbo) { + super(asKind, jumbo); + } + + @Nonnull + @Override + public Jumbo asKind(@Nonnull Class kind) { + return new Jumbo(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/CryptoPro.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/CryptoPro.java index 8e8de850a..6fa1f98ef 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/CryptoPro.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/CryptoPro.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.gost; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyWrap; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,25 @@ public class CryptoPro extends Algorithm implements KeyWrap { public CryptoPro(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyWrap.class, detectionLocation); } + + private CryptoPro(@Nonnull CryptoPro algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected CryptoPro copy() { + return new CryptoPro(this); + } + + public CryptoPro( + @Nonnull final Class asKind, @Nonnull CryptoPro cryptoPro) { + super(cryptoPro, asKind); + } + + @Nonnull + @Override + public CryptoPro asKind(@Nonnull Class kind) { + return new CryptoPro(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST28147.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST28147.java index 688a6c6e2..ebdb013c0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST28147.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST28147.java @@ -72,4 +72,20 @@ public GOST28147( @Nonnull final Class asKind, @Nonnull GOST28147 gost28147) { super(gost28147, asKind); } + + private GOST28147(@Nonnull GOST28147 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GOST28147 copy() { + return new GOST28147(this); + } + + @Nonnull + @Override + public GOST28147 asKind(@Nonnull Class kind) { + return new GOST28147(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST341194.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST341194.java index 3fc496228..c4db7c089 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST341194.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOST341194.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -60,7 +61,23 @@ public GOST341194(@Nonnull DetectionLocation detectionLocation) { } public GOST341194( - @Nonnull final Class asKind, @Nonnull GOST341194 gostr341194) { + @Nonnull final Class asKind, @Nonnull GOST341194 gostr341194) { super(gostr341194, asKind); } + + private GOST341194(@Nonnull GOST341194 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GOST341194 copy() { + return new GOST341194(this); + } + + @Nonnull + @Override + public GOST341194 asKind(@Nonnull Class kind) { + return new GOST341194(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341012.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341012.java index f190aa909..2df31e106 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341012.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341012.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.gost; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,25 @@ public final class GOSTR341012 extends Algorithm implements Signature { public GOSTR341012(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private GOSTR341012(@Nonnull GOSTR341012 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GOSTR341012 copy() { + return new GOSTR341012(this); + } + + public GOSTR341012( + @Nonnull final Class asKind, @Nonnull GOSTR341012 gostr341012) { + super(gostr341012, asKind); + } + + @Nonnull + @Override + public GOSTR341012 asKind(@Nonnull Class kind) { + return new GOSTR341012(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341094.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341094.java index 282643b85..2956f113c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341094.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341094.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.gost; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -48,4 +49,25 @@ public final class GOSTR341094 extends Algorithm implements Signature { public GOSTR341094(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private GOSTR341094(@Nonnull GOSTR341094 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GOSTR341094 copy() { + return new GOSTR341094(this); + } + + public GOSTR341094( + @Nonnull final Class asKind, @Nonnull GOSTR341094 gostr341094) { + super(gostr341094, asKind); + } + + @Nonnull + @Override + public GOSTR341094 asKind(@Nonnull Class kind) { + return new GOSTR341094(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341112.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341112.java index 243993363..737851532 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341112.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR341112.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -59,8 +60,23 @@ public GOSTR341112(int digestSize, @Nonnull DetectionLocation detectionLocation) } public GOSTR341112( - @Nonnull final Class asKind, - @Nonnull GOSTR341112 gostr341112) { + @Nonnull final Class asKind, @Nonnull GOSTR341112 gostr341112) { super(gostr341112, asKind); } + + private GOSTR341112(@Nonnull GOSTR341112 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GOSTR341112 copy() { + return new GOSTR341112(this); + } + + @Nonnull + @Override + public GOSTR341112 asKind(@Nonnull Class kind) { + return new GOSTR341112(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR34122015.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR34122015.java index bdf928bdc..0bbea8285 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR34122015.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/gost/GOSTR34122015.java @@ -66,4 +66,20 @@ public GOSTR34122015( @Nonnull GOSTR34122015 gostr34122015) { super(gostr34122015, asKind); } + + private GOSTR34122015(@Nonnull GOSTR34122015 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected GOSTR34122015 copy() { + return new GOSTR34122015(this); + } + + @Nonnull + @Override + public GOSTR34122015 asKind(@Nonnull Class kind) { + return new GOSTR34122015(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain.java index 5fd24bddb..1732af5de 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain.java @@ -38,4 +38,24 @@ protected Grain( @Nonnull DetectionLocation detectionLocation) { super(name, asKind, detectionLocation); } + + protected Grain(@Nonnull Grain algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grain copy() { + return new Grain(this); + } + + public Grain(@Nonnull final Class asKind, @Nonnull Grain grain) { + super(grain, asKind); + } + + @Nonnull + @Override + public Grain asKind(@Nonnull Class kind) { + return new Grain(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128.java index e7ca3f637..8fd4dc548 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms.grain; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.StreamCipher; @@ -33,4 +34,24 @@ public Grain128(@Nonnull DetectionLocation detectionLocation) { this.put(KeyLength.ofDefault(128, detectionLocation)); this.put(new InitializationVectorLength(96, detectionLocation)); } + + private Grain128(@Nonnull Grain128 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grain128 copy() { + return new Grain128(this); + } + + public Grain128(@Nonnull final Class asKind, @Nonnull Grain128 grain128) { + super(asKind, grain128); + } + + @Nonnull + @Override + public Grain128 asKind(@Nonnull Class kind) { + return new Grain128(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEAD.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEAD.java index d3a767c26..6aac3de56 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEAD.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEAD.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.grain; import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.utils.DetectionLocation; @@ -52,4 +53,25 @@ public Grain128AEAD(@Nonnull DetectionLocation detectionLocation) { this.put(KeyLength.ofDefault(128, detectionLocation)); this.put(new InitializationVectorLength(96, detectionLocation)); } + + private Grain128AEAD(@Nonnull Grain128AEAD algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grain128AEAD copy() { + return new Grain128AEAD(this); + } + + public Grain128AEAD( + @Nonnull final Class asKind, @Nonnull Grain128AEAD grain128AEAD) { + super(asKind, grain128AEAD); + } + + @Nonnull + @Override + public Grain128AEAD asKind(@Nonnull Class kind) { + return new Grain128AEAD(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEADv2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEADv2.java index bf82b585d..f9530f607 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEADv2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128AEADv2.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.grain; import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.utils.DetectionLocation; @@ -52,4 +53,26 @@ public Grain128AEADv2(@Nonnull DetectionLocation detectionLocation) { this.put(KeyLength.ofDefault(128, detectionLocation)); this.put(new InitializationVectorLength(96, detectionLocation)); } + + private Grain128AEADv2(@Nonnull Grain128AEADv2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grain128AEADv2 copy() { + return new Grain128AEADv2(this); + } + + public Grain128AEADv2( + @Nonnull final Class asKind, + @Nonnull Grain128AEADv2 grain128AEADv2) { + super(asKind, grain128AEADv2); + } + + @Nonnull + @Override + public Grain128AEADv2 asKind(@Nonnull Class kind) { + return new Grain128AEADv2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128a.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128a.java index 6d249c52e..d0e8c2795 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128a.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grain128a.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms.grain; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.StreamCipher; @@ -33,4 +34,25 @@ public Grain128a(@Nonnull DetectionLocation detectionLocation) { this.put(KeyLength.ofDefault(128, detectionLocation)); this.put(new InitializationVectorLength(96, detectionLocation)); } + + private Grain128a(@Nonnull Grain128a algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grain128a copy() { + return new Grain128a(this); + } + + public Grain128a( + @Nonnull final Class asKind, @Nonnull Grain128a grain128a) { + super(asKind, grain128a); + } + + @Nonnull + @Override + public Grain128a asKind(@Nonnull Class kind) { + return new Grain128a(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv0.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv0.java index a67b88ef8..ef521c5da 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv0.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv0.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms.grain; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.StreamCipher; @@ -52,4 +53,24 @@ public Grainv0(@Nonnull DetectionLocation detectionLocation) { this.put(KeyLength.ofDefault(80, detectionLocation)); this.put(new InitializationVectorLength(64, detectionLocation)); } + + private Grainv0(@Nonnull Grainv0 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grainv0 copy() { + return new Grainv0(this); + } + + public Grainv0(@Nonnull final Class asKind, @Nonnull Grainv0 grainv0) { + super(asKind, grainv0); + } + + @Nonnull + @Override + public Grainv0 asKind(@Nonnull Class kind) { + return new Grainv0(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv1.java index d46f5b5a4..d7018c420 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/grain/Grainv1.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms.grain; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.StreamCipher; @@ -33,4 +34,24 @@ public Grainv1(@Nonnull DetectionLocation detectionLocation) { this.put(KeyLength.ofDefault(80, detectionLocation)); this.put(new InitializationVectorLength(64, detectionLocation)); } + + private Grainv1(@Nonnull Grainv1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Grainv1 copy() { + return new Grainv1(this); + } + + public Grainv1(@Nonnull final Class asKind, @Nonnull Grainv1 grainv1) { + super(asKind, grainv1); + } + + @Nonnull + @Override + public Grainv1 asKind(@Nonnull Class kind) { + return new Grainv1(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/DLIES.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/DLIES.java index 8d3a9aab4..55ce92de2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/DLIES.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/DLIES.java @@ -19,6 +19,7 @@ */ package com.ibm.mapper.model.algorithms.ies; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.PublicKeyEncryption; import com.ibm.mapper.model.algorithms.DH; import com.ibm.mapper.utils.DetectionLocation; @@ -50,4 +51,24 @@ public DLIES(@Nonnull DetectionLocation detectionLocation) { super(NAME, PublicKeyEncryption.class, detectionLocation); this.put(new DH(detectionLocation)); } + + private DLIES(@Nonnull DLIES algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected DLIES copy() { + return new DLIES(this); + } + + public DLIES(@Nonnull final Class asKind, @Nonnull DLIES dlies) { + super(asKind, dlies); + } + + @Nonnull + @Override + public DLIES asKind(@Nonnull Class kind) { + return new DLIES(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIES.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIES.java index 15e0906bf..b154e499d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIES.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIES.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyDerivationFunction; import com.ibm.mapper.model.Mac; import com.ibm.mapper.model.PublicKeyEncryption; @@ -75,4 +76,24 @@ public ECIES(@Nonnull DetectionLocation detectionLocation) { super(NAME, PublicKeyEncryption.class, detectionLocation); this.put(new DH(detectionLocation)); } + + private ECIES(@Nonnull ECIES algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECIES copy() { + return new ECIES(this); + } + + public ECIES(@Nonnull final Class asKind, @Nonnull ECIES ecies) { + super(asKind, ecies); + } + + @Nonnull + @Override + public ECIES asKind(@Nonnull Class kind) { + return new ECIES(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIESKEM.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIESKEM.java index a68c0c08e..da2df7b66 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIESKEM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/ECIESKEM.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.ies; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public class ECIESKEM extends Algorithm implements KeyEncapsulationMechanism { public ECIESKEM(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private ECIESKEM(@Nonnull ECIESKEM algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected ECIESKEM copy() { + return new ECIESKEM(this); + } + + public ECIESKEM(@Nonnull final Class asKind, @Nonnull ECIESKEM ecieskem) { + super(ecieskem, asKind); + } + + @Nonnull + @Override + public ECIESKEM asKind(@Nonnull Class kind) { + return new ECIESKEM(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/IES.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/IES.java index e4421ad83..afcb88fee 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/IES.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ies/IES.java @@ -62,4 +62,20 @@ protected IES( public IES(@Nonnull final Class asKind, @Nonnull IES ies) { super(ies, asKind); } + + protected IES(@Nonnull IES algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected IES copy() { + return new IES(this); + } + + @Nonnull + @Override + public IES asKind(@Nonnull Class kind) { + return new IES(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/Isap.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/Isap.java index 2d166934d..018219c89 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/Isap.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/Isap.java @@ -64,4 +64,20 @@ protected Isap(@Nonnull String name, @Nonnull DetectionLocation detectionLocatio this.put(new NonceLength(128, detectionLocation)); this.put(new TagLength(128, detectionLocation)); } + + protected Isap(@Nonnull Isap algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Isap copy() { + return new Isap(this); + } + + @Nonnull + @Override + public Isap asKind(@Nonnull Class kind) { + return new Isap(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128.java index 3e2603c0e..6c1411a32 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.isap; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -32,4 +33,24 @@ public IsapA128(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(64, detectionLocation)); this.put(new InitializationVectorLength(192, detectionLocation)); } + + private IsapA128(@Nonnull IsapA128 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected IsapA128 copy() { + return new IsapA128(this); + } + + public IsapA128(@Nonnull final Class asKind, @Nonnull IsapA128 isapA128) { + super(asKind, isapA128); + } + + @Nonnull + @Override + public IsapA128 asKind(@Nonnull Class kind) { + return new IsapA128(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128a.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128a.java index 8f3c4a6db..7ddaa6c10 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128a.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapA128a.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.isap; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -32,4 +33,25 @@ public IsapA128a(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(64, detectionLocation)); this.put(new InitializationVectorLength(192, detectionLocation)); } + + private IsapA128a(@Nonnull IsapA128a algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected IsapA128a copy() { + return new IsapA128a(this); + } + + public IsapA128a( + @Nonnull final Class asKind, @Nonnull IsapA128a isapA128a) { + super(asKind, isapA128a); + } + + @Nonnull + @Override + public IsapA128a asKind(@Nonnull Class kind) { + return new IsapA128a(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128.java index 334f541fc..8426dcf25 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.isap; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -32,4 +33,24 @@ public IsapK128(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(144, detectionLocation)); this.put(new InitializationVectorLength(272, detectionLocation)); } + + private IsapK128(@Nonnull IsapK128 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected IsapK128 copy() { + return new IsapK128(this); + } + + public IsapK128(@Nonnull final Class asKind, @Nonnull IsapK128 isapK128) { + super(asKind, isapK128); + } + + @Nonnull + @Override + public IsapK128 asKind(@Nonnull Class kind) { + return new IsapK128(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128a.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128a.java index 6fd0e5123..f501db515 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128a.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/isap/IsapK128a.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.isap; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.InitializationVectorLength; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -32,4 +33,25 @@ public IsapK128a(@Nonnull DetectionLocation detectionLocation) { this.put(BlockSize.ofDefault(144, detectionLocation)); this.put(new InitializationVectorLength(272, detectionLocation)); } + + private IsapK128a(@Nonnull IsapK128a algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected IsapK128a copy() { + return new IsapK128a(this); + } + + public IsapK128a( + @Nonnull final Class asKind, @Nonnull IsapK128a isapK128a) { + super(asKind, isapK128a); + } + + @Nonnull + @Override + public IsapK128a asKind(@Nonnull Class kind) { + return new IsapK128a(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/kyber/Kyber.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/kyber/Kyber.java index 46f757f74..57b8ff02a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/kyber/Kyber.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/kyber/Kyber.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.model.ParameterSetIdentifier; import com.ibm.mapper.model.Version; @@ -94,4 +95,24 @@ public Kyber( this(parameterSetIdentifier, detectionLocation); this.put(new Version(submissionVersionNumber, detectionLocation)); } + + private Kyber(@Nonnull Kyber algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Kyber copy() { + return new Kyber(this); + } + + public Kyber(@Nonnull final Class asKind, @Nonnull Kyber kyber) { + super(kyber, asKind); + } + + @Nonnull + @Override + public Kyber asKind(@Nonnull Class kind) { + return new Kyber(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRU.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRU.java index 065449b24..80b62ef71 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRU.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRU.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.ntru; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public class NTRU extends Algorithm implements KeyEncapsulationMechanism { public NTRU(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private NTRU(@Nonnull NTRU algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected NTRU copy() { + return new NTRU(this); + } + + public NTRU(@Nonnull final Class asKind, @Nonnull NTRU ntru) { + super(ntru, asKind); + } + + @Nonnull + @Override + public NTRU asKind(@Nonnull Class kind) { + return new NTRU(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUEncrypt.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUEncrypt.java index 9a3686285..67d630ed2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUEncrypt.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUEncrypt.java @@ -61,4 +61,20 @@ public NTRUEncrypt( @Nonnull final Class asKind, @Nonnull NTRUEncrypt ntruEncrypt) { super(ntruEncrypt, asKind); } + + private NTRUEncrypt(@Nonnull NTRUEncrypt algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected NTRUEncrypt copy() { + return new NTRUEncrypt(this); + } + + @Nonnull + @Override + public NTRUEncrypt asKind(@Nonnull Class kind) { + return new NTRUEncrypt(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRULPrime.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRULPrime.java index b508e1c22..ece687e15 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRULPrime.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRULPrime.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.ntru; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,25 @@ public class NTRULPrime extends Algorithm implements KeyEncapsulationMechanism { public NTRULPrime(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private NTRULPrime(@Nonnull NTRULPrime algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected NTRULPrime copy() { + return new NTRULPrime(this); + } + + public NTRULPrime( + @Nonnull final Class asKind, @Nonnull NTRULPrime ntrulPrime) { + super(ntrulPrime, asKind); + } + + @Nonnull + @Override + public NTRULPrime asKind(@Nonnull Class kind) { + return new NTRULPrime(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUSign.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUSign.java index 795060acb..94fda1364 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUSign.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/NTRUSign.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.ntru; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Signature; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -30,4 +31,24 @@ public class NTRUSign extends Algorithm implements Signature { public NTRUSign(@Nonnull DetectionLocation detectionLocation) { super(NAME, Signature.class, detectionLocation); } + + private NTRUSign(@Nonnull NTRUSign algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected NTRUSign copy() { + return new NTRUSign(this); + } + + public NTRUSign(@Nonnull final Class asKind, @Nonnull NTRUSign ntruSign) { + super(ntruSign, asKind); + } + + @Nonnull + @Override + public NTRUSign asKind(@Nonnull Class kind) { + return new NTRUSign(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/StreamlinedNTRUPrime.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/StreamlinedNTRUPrime.java index 410f40272..d9af23db1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/StreamlinedNTRUPrime.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/ntru/StreamlinedNTRUPrime.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.ntru; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyEncapsulationMechanism; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -49,4 +50,26 @@ public class StreamlinedNTRUPrime extends Algorithm implements KeyEncapsulationM public StreamlinedNTRUPrime(@Nonnull DetectionLocation detectionLocation) { super(NAME, KeyEncapsulationMechanism.class, detectionLocation); } + + private StreamlinedNTRUPrime(@Nonnull StreamlinedNTRUPrime algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected StreamlinedNTRUPrime copy() { + return new StreamlinedNTRUPrime(this); + } + + public StreamlinedNTRUPrime( + @Nonnull final Class asKind, + @Nonnull StreamlinedNTRUPrime streamlinedNTRUPrime) { + super(streamlinedNTRUPrime, asKind); + } + + @Nonnull + @Override + public StreamlinedNTRUPrime asKind(@Nonnull Class kind) { + return new StreamlinedNTRUPrime(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleAEAD.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleAEAD.java index 747f47072..60b794103 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleAEAD.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleAEAD.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.AuthenticatedEncryption; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NonceLength; import com.ibm.mapper.model.TagLength; @@ -61,4 +62,26 @@ public PhotonBeetleAEAD(int rate, @Nonnull DetectionLocation detectionLocation) this(detectionLocation); this.put(new BlockSize(rate, detectionLocation)); } + + private PhotonBeetleAEAD(@Nonnull PhotonBeetleAEAD algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PhotonBeetleAEAD copy() { + return new PhotonBeetleAEAD(this); + } + + public PhotonBeetleAEAD( + @Nonnull final Class asKind, + @Nonnull PhotonBeetleAEAD photonBeetleAEAD) { + super(photonBeetleAEAD, asKind); + } + + @Nonnull + @Override + public PhotonBeetleAEAD asKind(@Nonnull Class kind) { + return new PhotonBeetleAEAD(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleHash.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleHash.java index 3378887fb..00f9e280b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleHash.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/photonbeetle/PhotonBeetleHash.java @@ -22,6 +22,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -57,4 +58,26 @@ public PhotonBeetleHash(int blockSize, @Nonnull DetectionLocation detectionLocat this(detectionLocation); this.put(new BlockSize(blockSize, detectionLocation)); } + + private PhotonBeetleHash(@Nonnull PhotonBeetleHash algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected PhotonBeetleHash copy() { + return new PhotonBeetleHash(this); + } + + public PhotonBeetleHash( + @Nonnull final Class asKind, + @Nonnull PhotonBeetleHash photonBeetleHash) { + super(photonBeetleHash, asKind); + } + + @Nonnull + @Override + public PhotonBeetleHash asKind(@Nonnull Class kind) { + return new PhotonBeetleHash(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL1.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL1.java index ea238e182..47daf87cc 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL1.java @@ -84,4 +84,24 @@ public SHACAL1( public SHACAL1(@Nonnull final Class asKind, @Nonnull SHACAL2 shacal1) { super(shacal1, asKind); } + + private SHACAL1(@Nonnull SHACAL1 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SHACAL1 copy() { + return new SHACAL1(this); + } + + public SHACAL1(@Nonnull final Class asKind, @Nonnull SHACAL1 shacal1) { + super(shacal1, asKind); + } + + @Nonnull + @Override + public SHACAL1 asKind(@Nonnull Class kind) { + return new SHACAL1(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL2.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL2.java index 03ee34ff1..899c1a446 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shacal/SHACAL2.java @@ -83,4 +83,20 @@ public SHACAL2( public SHACAL2(@Nonnull final Class asKind, @Nonnull SHACAL2 shacal2) { super(shacal2, asKind); } + + private SHACAL2(@Nonnull SHACAL2 algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SHACAL2 copy() { + return new SHACAL2(this); + } + + @Nonnull + @Override + public SHACAL2 asKind(@Nonnull Class kind) { + return new SHACAL2(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/CSHAKE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/CSHAKE.java index d33a8c5a9..53cc55eeb 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/CSHAKE.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/CSHAKE.java @@ -76,4 +76,20 @@ public CSHAKE(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLo public CSHAKE(@Nonnull final Class asKind, @Nonnull CSHAKE cSHAKE) { super(cSHAKE, asKind); } + + private CSHAKE(@Nonnull CSHAKE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected CSHAKE copy() { + return new CSHAKE(this); + } + + @Nonnull + @Override + public CSHAKE asKind(@Nonnull Class kind) { + return new CSHAKE(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/SHAKE.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/SHAKE.java index 6b32eec33..56ee6a08d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/SHAKE.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/shake/SHAKE.java @@ -79,4 +79,20 @@ public SHAKE(int parameterSetIdentifier, @Nonnull DetectionLocation detectionLoc public SHAKE(@Nonnull final Class asKind, @Nonnull SHAKE shake) { super(shake, asKind); } + + private SHAKE(@Nonnull SHAKE algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected SHAKE copy() { + return new SHAKE(this); + } + + @Nonnull + @Override + public SHAKE asKind(@Nonnull Class kind) { + return new SHAKE(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Esch.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Esch.java index c4e8fa876..5469f76b9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Esch.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Esch.java @@ -24,6 +24,7 @@ import com.ibm.mapper.model.ClassicalBitSecurityLevel; 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; @@ -81,4 +82,24 @@ public Esch(int digestSize, @Nonnull DetectionLocation detectionLocation) { this.put(new ClassicalBitSecurityLevel(192, detectionLocation)); } } + + private Esch(@Nonnull Esch algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Esch copy() { + return new Esch(this); + } + + public Esch(@Nonnull final Class asKind, @Nonnull Esch esch) { + super(esch, asKind); + } + + @Nonnull + @Override + public Esch asKind(@Nonnull Class kind) { + return new Esch(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Schwaemm.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Schwaemm.java index 13a457591..16b878e7c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Schwaemm.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/Schwaemm.java @@ -23,6 +23,7 @@ import com.ibm.mapper.model.AuthenticatedEncryption; import com.ibm.mapper.model.BlockCipher; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.NonceLength; import com.ibm.mapper.model.TagLength; @@ -89,4 +90,24 @@ public Schwaemm(int rate, int capacity, @Nonnull DetectionLocation detectionLoca this.put(new TagLength(capacity, detectionLocation)); this.put(new NonceLength(rate, detectionLocation)); } + + private Schwaemm(@Nonnull Schwaemm algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected Schwaemm copy() { + return new Schwaemm(this); + } + + public Schwaemm(@Nonnull final Class asKind, @Nonnull Schwaemm schwaemm) { + super(schwaemm, asKind); + } + + @Nonnull + @Override + public Schwaemm asKind(@Nonnull Class kind) { + return new Schwaemm(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/XOEsch.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/XOEsch.java index 39c6edd52..029c4d644 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/XOEsch.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/sparkle/XOEsch.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -52,4 +53,24 @@ public XOEsch(@Nonnull DetectionLocation detectionLocation) { super(NAME, MessageDigest.class, detectionLocation); this.put(BlockSize.ofDefault(128, detectionLocation)); } + + private XOEsch(@Nonnull XOEsch algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected XOEsch copy() { + return new XOEsch(this); + } + + public XOEsch(@Nonnull final Class asKind, @Nonnull XOEsch xoEsch) { + super(xoEsch, asKind); + } + + @Nonnull + @Override + public XOEsch asKind(@Nonnull Class kind) { + return new XOEsch(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/TEA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/TEA.java index 446842d23..c9c424e9d 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/TEA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/TEA.java @@ -75,4 +75,20 @@ public TEA( public TEA(@Nonnull final Class asKind, @Nonnull TEA tea) { super(tea, asKind); } + + private TEA(@Nonnull TEA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected TEA copy() { + return new TEA(this); + } + + @Nonnull + @Override + public TEA asKind(@Nonnull Class kind) { + return new TEA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/XTEA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/XTEA.java index 0306a8288..d02746ae4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/XTEA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/tea/XTEA.java @@ -76,4 +76,20 @@ public XTEA( public XTEA(@Nonnull final Class asKind, @Nonnull XTEA xtea) { super(xtea, asKind); } + + private XTEA(@Nonnull XTEA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected XTEA copy() { + return new XTEA(this); + } + + @Nonnull + @Override + public XTEA asKind(@Nonnull Class kind) { + return new XTEA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPC.java index e28055e0d..3e7cb3b99 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPC.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.vmpc; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.StreamCipher; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public class VMPC extends Algorithm implements StreamCipher { public VMPC(@Nonnull DetectionLocation detectionLocation) { super(NAME, StreamCipher.class, detectionLocation); } + + private VMPC(@Nonnull VMPC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected VMPC copy() { + return new VMPC(this); + } + + public VMPC(@Nonnull final Class asKind, @Nonnull VMPC vmpc) { + super(vmpc, asKind); + } + + @Nonnull + @Override + public VMPC asKind(@Nonnull Class kind) { + return new VMPC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCKSA.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCKSA.java index 6cfc0c4fe..30b7dfc54 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCKSA.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCKSA.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.vmpc; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.StreamCipher; import com.ibm.mapper.model.Version; import com.ibm.mapper.utils.DetectionLocation; @@ -64,4 +65,24 @@ public VMPCKSA(int version, @Nonnull DetectionLocation detectionLocation) { this(detectionLocation); this.put(new Version(String.valueOf(version), detectionLocation)); } + + private VMPCKSA(@Nonnull VMPCKSA algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected VMPCKSA copy() { + return new VMPCKSA(this); + } + + public VMPCKSA(@Nonnull final Class asKind, @Nonnull VMPCKSA vmpcksa) { + super(vmpcksa, asKind); + } + + @Nonnull + @Override + public VMPCKSA asKind(@Nonnull Class kind) { + return new VMPCKSA(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCMAC.java b/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCMAC.java index 7a5c4200d..e93dd05f1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCMAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/algorithms/vmpc/VMPCMAC.java @@ -20,6 +20,7 @@ package com.ibm.mapper.model.algorithms.vmpc; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Mac; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -50,4 +51,24 @@ public VMPCMAC(@Nonnull DetectionLocation detectionLocation) { super(NAME, Mac.class, detectionLocation); this.put(new VMPC(detectionLocation)); } + + private VMPCMAC(@Nonnull VMPCMAC algorithm) { + super(algorithm); + } + + @Nonnull + @Override + protected VMPCMAC copy() { + return new VMPCMAC(this); + } + + public VMPCMAC(@Nonnull final Class asKind, @Nonnull VMPCMAC vmpcmac) { + super(vmpcmac, asKind); + } + + @Nonnull + @Override + public VMPCMAC asKind(@Nonnull Class kind) { + return new VMPCMAC(kind, this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/collections/AbstractAssetCollection.java b/mapper/src/main/java/com/ibm/mapper/model/collections/AbstractAssetCollection.java index e8dad8fdc..c05b24c1a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/collections/AbstractAssetCollection.java +++ b/mapper/src/main/java/com/ibm/mapper/model/collections/AbstractAssetCollection.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.NodeOrigin; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -112,6 +113,35 @@ public boolean isMergeable() { return false; } + /** + * A copy of this collection, with a copy of each of its items and children: the items belong to + * the collection, e.g. the algorithms of an {@link AssetCollection} are enriched in place. + */ + @Nonnull + @Override + public INode deepCopy() { + final List items = new ArrayList<>(this.collection.size()); + for (K item : this.collection) { + items.add(copyOf(item)); + } + final AbstractAssetCollection copy = createMerged(items); + for (INode child : this.children.values()) { + copy.children.put(child.getKind(), child.deepCopy()); + } + return copy; + } + + /** + * A collection of the class of this collection that holds the given items, without the children + * of this collection: the items of merged collections, enriched items or copied items. + */ @Nonnull public abstract AbstractAssetCollection createMerged(@Nonnull List mergedCollection); + + /** A copy of the item, which is of the class of the item, as every node is copied. */ + @SuppressWarnings("unchecked") + @Nonnull + private K copyOf(@Nonnull K item) { + return (K) item.deepCopy(); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/collections/AssetCollection.java b/mapper/src/main/java/com/ibm/mapper/model/collections/AssetCollection.java index 115d7cbf5..c0a84fc6a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/collections/AssetCollection.java +++ b/mapper/src/main/java/com/ibm/mapper/model/collections/AssetCollection.java @@ -29,20 +29,6 @@ public AssetCollection(@Nonnull List collection) { super(collection, AssetCollection.class); } - private AssetCollection(@Nonnull AssetCollection assetCollection) { - super(assetCollection.collection, assetCollection.kind); - } - - @Nonnull - @Override - public INode deepCopy() { - AssetCollection copy = new AssetCollection(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; - } - @Nonnull @Override public AssetCollection createMerged(@Nonnull List mergedCollection) { diff --git a/mapper/src/main/java/com/ibm/mapper/model/collections/CipherSuiteCollection.java b/mapper/src/main/java/com/ibm/mapper/model/collections/CipherSuiteCollection.java index c802e0270..8fb6cf247 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/collections/CipherSuiteCollection.java +++ b/mapper/src/main/java/com/ibm/mapper/model/collections/CipherSuiteCollection.java @@ -20,7 +20,6 @@ package com.ibm.mapper.model.collections; import com.ibm.mapper.model.CipherSuite; -import com.ibm.mapper.model.INode; import java.util.List; import javax.annotation.Nonnull; @@ -30,20 +29,6 @@ public CipherSuiteCollection(@Nonnull List collection) { super(collection, CipherSuiteCollection.class); } - private CipherSuiteCollection(@Nonnull CipherSuiteCollection cipherSuiteCollection) { - super(cipherSuiteCollection.collection, cipherSuiteCollection.kind); - } - - @Nonnull - @Override - public INode deepCopy() { - CipherSuiteCollection copy = new CipherSuiteCollection(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; - } - @Override public boolean isMergeable() { return true; diff --git a/mapper/src/main/java/com/ibm/mapper/model/collections/IdentifierCollection.java b/mapper/src/main/java/com/ibm/mapper/model/collections/IdentifierCollection.java index 9865113ad..d9c1046ab 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/collections/IdentifierCollection.java +++ b/mapper/src/main/java/com/ibm/mapper/model/collections/IdentifierCollection.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.collections; -import com.ibm.mapper.model.INode; import com.ibm.mapper.model.Identifier; import java.util.List; import javax.annotation.Nonnull; @@ -30,20 +29,6 @@ public IdentifierCollection(@Nonnull List collection) { super(collection, IdentifierCollection.class); } - private IdentifierCollection(@Nonnull IdentifierCollection identifierCollection) { - super(identifierCollection.collection, identifierCollection.kind); - } - - @Nonnull - @Override - public INode deepCopy() { - IdentifierCollection copy = new IdentifierCollection(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; - } - @Nonnull @Override public IdentifierCollection createMerged(@Nonnull List mergedCollection) { diff --git a/mapper/src/main/java/com/ibm/mapper/model/collections/MergeableCollection.java b/mapper/src/main/java/com/ibm/mapper/model/collections/MergeableCollection.java index 993849ef1..ca66ef9ee 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/collections/MergeableCollection.java +++ b/mapper/src/main/java/com/ibm/mapper/model/collections/MergeableCollection.java @@ -31,27 +31,12 @@ *

This differs from the default behavior, in which the root nodes are duplicated to create * multiple trees, each containing one instance of the various {@code MergeableCollection}. */ -// TODO: handle this in the output layer public class MergeableCollection extends AbstractAssetCollection { public MergeableCollection(@Nonnull List collection) { super(collection, MergeableCollection.class); } - private MergeableCollection(@Nonnull MergeableCollection mergeableCollection) { - super(mergeableCollection.collection, mergeableCollection.kind); - } - - @Nonnull - @Override - public INode deepCopy() { - MergeableCollection copy = new MergeableCollection(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; - } - @Override public boolean isMergeable() { return true; diff --git a/mapper/src/main/java/com/ibm/mapper/model/collections/ProtocolVersionSettings.java b/mapper/src/main/java/com/ibm/mapper/model/collections/ProtocolVersionSettings.java new file mode 100644 index 000000000..937100893 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/collections/ProtocolVersionSettings.java @@ -0,0 +1,87 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model.collections; + +import com.ibm.mapper.model.INode; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Protocol versions set on a protocol, each a protocol node of its version: the versions {@link + * Disabled disabled} on it, e.g. by the {@code SSL_OP_NO_TLSv1} option of an OpenSSL context, and + * its {@link Minimum minimum} and {@link Maximum maximum} version. The settings made on the same + * protocol add up: the settings of a kind appended to a protocol are merged into one, and the + * settings of all kinds together bound the range of versions the protocol uses (see {@code + * ProtocolVersionReorganizer}). + */ +public abstract sealed class ProtocolVersionSettings extends AbstractAssetCollection + permits ProtocolVersionSettings.Disabled, + ProtocolVersionSettings.Minimum, + ProtocolVersionSettings.Maximum { + + protected ProtocolVersionSettings( + @Nonnull List versions, @Nonnull Class kind) { + super(versions, kind); + } + + @Override + public boolean isMergeable() { + return true; + } + + /** The versions disabled on the protocol. */ + public static final class Disabled extends ProtocolVersionSettings { + public Disabled(@Nonnull List versions) { + super(versions, Disabled.class); + } + + @Nonnull + @Override + public Disabled createMerged(@Nonnull List mergedCollection) { + return new Disabled(mergedCollection); + } + } + + /** The minimum versions set on the protocol. */ + public static final class Minimum extends ProtocolVersionSettings { + public Minimum(@Nonnull List versions) { + super(versions, Minimum.class); + } + + @Nonnull + @Override + public Minimum createMerged(@Nonnull List mergedCollection) { + return new Minimum(mergedCollection); + } + } + + /** The maximum versions set on the protocol. */ + public static final class Maximum extends ProtocolVersionSettings { + public Maximum(@Nonnull List versions) { + super(versions, Maximum.class); + } + + @Nonnull + @Override + public Maximum createMerged(@Nonnull List mergedCollection) { + return new Maximum(mergedCollection); + } + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp256r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp256r1.java index 5489396a4..1b405b010 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp256r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp256r1.java @@ -27,4 +27,14 @@ public final class Brainpoolp256r1 extends EllipticCurve { public Brainpoolp256r1(@Nonnull DetectionLocation detectionLocation) { super("Brainpoolp256r1", detectionLocation); } + + private Brainpoolp256r1(@Nonnull Brainpoolp256r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Brainpoolp256r1 copy() { + return new Brainpoolp256r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp384r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp384r1.java index 1cc4f1e46..d32286185 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp384r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp384r1.java @@ -27,4 +27,14 @@ public final class Brainpoolp384r1 extends EllipticCurve { public Brainpoolp384r1(@Nonnull DetectionLocation detectionLocation) { super("Brainpoolp384r1", detectionLocation); } + + private Brainpoolp384r1(@Nonnull Brainpoolp384r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Brainpoolp384r1 copy() { + return new Brainpoolp384r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp512r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp512r1.java index bedd0c648..b7cdaa9c4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp512r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Brainpoolp512r1.java @@ -27,4 +27,14 @@ public final class Brainpoolp512r1 extends EllipticCurve { public Brainpoolp512r1(@Nonnull DetectionLocation detectionLocation) { super("Brainpoolp512r1", detectionLocation); } + + private Brainpoolp512r1(@Nonnull Brainpoolp512r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Brainpoolp512r1 copy() { + return new Brainpoolp512r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Curve25519.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Curve25519.java index 7676a842e..61515e0aa 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Curve25519.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Curve25519.java @@ -27,4 +27,14 @@ public final class Curve25519 extends EllipticCurve { public Curve25519(@Nonnull DetectionLocation detectionLocation) { super("Curve25519", detectionLocation); } + + private Curve25519(@Nonnull Curve25519 curve) { + super(curve); + } + + @Nonnull + @Override + protected Curve25519 copy() { + return new Curve25519(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Curve448.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Curve448.java index 894b4b0bf..2a40d4a09 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Curve448.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Curve448.java @@ -27,4 +27,14 @@ public final class Curve448 extends EllipticCurve { public Curve448(@Nonnull DetectionLocation detectionLocation) { super("Curve448", detectionLocation); } + + private Curve448(@Nonnull Curve448 curve) { + super(curve); + } + + @Nonnull + @Override + protected Curve448 copy() { + return new Curve448(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards25519.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards25519.java index a705da19e..4440d30d8 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards25519.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards25519.java @@ -28,4 +28,14 @@ public final class Edwards25519 extends EllipticCurve { public Edwards25519(@Nonnull DetectionLocation detectionLocation) { super("Edwards25519", detectionLocation); } + + private Edwards25519(@Nonnull Edwards25519 curve) { + super(curve); + } + + @Nonnull + @Override + protected Edwards25519 copy() { + return new Edwards25519(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards448.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards448.java index b06786985..e4aaca7f3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards448.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Edwards448.java @@ -28,4 +28,14 @@ public final class Edwards448 extends EllipticCurve { public Edwards448(@Nonnull DetectionLocation detectionLocation) { super("Edwards448", detectionLocation); } + + private Edwards448(@Nonnull Edwards448 curve) { + super(curve); + } + + @Nonnull + @Override + protected Edwards448 copy() { + return new Edwards448(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp192r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp192r1.java index 5e7007fbb..bb47c8b6e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp192r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp192r1.java @@ -27,4 +27,14 @@ public final class Secp192r1 extends EllipticCurve { public Secp192r1(@Nonnull DetectionLocation detectionLocation) { super("secp192r1", detectionLocation); } + + private Secp192r1(@Nonnull Secp192r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Secp192r1 copy() { + return new Secp192r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp224r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp224r1.java index 043cf3662..28d6655d3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp224r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp224r1.java @@ -27,4 +27,14 @@ public final class Secp224r1 extends EllipticCurve { public Secp224r1(@Nonnull DetectionLocation detectionLocation) { super("secp224r1", detectionLocation); } + + private Secp224r1(@Nonnull Secp224r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Secp224r1 copy() { + return new Secp224r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256k1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256k1.java index fc039a7bc..5c65fae95 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256k1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256k1.java @@ -27,4 +27,14 @@ public final class Secp256k1 extends EllipticCurve { public Secp256k1(@Nonnull DetectionLocation detectionLocation) { super("secp256k1", detectionLocation); } + + private Secp256k1(@Nonnull Secp256k1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Secp256k1 copy() { + return new Secp256k1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256r1.java index 97ed9aa9b..e67fc8038 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp256r1.java @@ -27,4 +27,14 @@ public final class Secp256r1 extends EllipticCurve { public Secp256r1(@Nonnull DetectionLocation detectionLocation) { super("secp256r1", detectionLocation); } + + private Secp256r1(@Nonnull Secp256r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Secp256r1 copy() { + return new Secp256r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp384r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp384r1.java index 05d016376..62fa04a0c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp384r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp384r1.java @@ -27,4 +27,14 @@ public final class Secp384r1 extends EllipticCurve { public Secp384r1(@Nonnull DetectionLocation detectionLocation) { super("secp384r1", detectionLocation); } + + private Secp384r1(@Nonnull Secp384r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Secp384r1 copy() { + return new Secp384r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp521r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp521r1.java index ebef84a2a..0f108833c 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Secp521r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Secp521r1.java @@ -27,4 +27,14 @@ public final class Secp521r1 extends EllipticCurve { public Secp521r1(@Nonnull DetectionLocation detectionLocation) { super("secp521r1", detectionLocation); } + + private Secp521r1(@Nonnull Secp521r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Secp521r1 copy() { + return new Secp521r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163k1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163k1.java index 9f2ffceb0..02c6e2d35 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163k1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163k1.java @@ -27,4 +27,14 @@ public final class Sect163k1 extends EllipticCurve { public Sect163k1(@Nonnull DetectionLocation detectionLocation) { super("sect163k1", detectionLocation); } + + private Sect163k1(@Nonnull Sect163k1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect163k1 copy() { + return new Sect163k1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163r2.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163r2.java index 1f1bf0fb0..1435e2733 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163r2.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect163r2.java @@ -27,4 +27,14 @@ public final class Sect163r2 extends EllipticCurve { public Sect163r2(@Nonnull DetectionLocation detectionLocation) { super("sect163r2", detectionLocation); } + + private Sect163r2(@Nonnull Sect163r2 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect163r2 copy() { + return new Sect163r2(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233k1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233k1.java index 9f87d67fb..3fce9de9a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233k1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233k1.java @@ -27,4 +27,14 @@ public final class Sect233k1 extends EllipticCurve { public Sect233k1(@Nonnull DetectionLocation detectionLocation) { super("sect233k1", detectionLocation); } + + private Sect233k1(@Nonnull Sect233k1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect233k1 copy() { + return new Sect233k1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233r1.java index ac5d597a4..6b2af4ee1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect233r1.java @@ -27,4 +27,14 @@ public final class Sect233r1 extends EllipticCurve { public Sect233r1(@Nonnull DetectionLocation detectionLocation) { super("sect233r1", detectionLocation); } + + private Sect233r1(@Nonnull Sect233r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect233r1 copy() { + return new Sect233r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283k1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283k1.java index fc3f526c4..ad56bf8f1 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283k1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283k1.java @@ -27,4 +27,14 @@ public final class Sect283k1 extends EllipticCurve { public Sect283k1(@Nonnull DetectionLocation detectionLocation) { super("sect283k1", detectionLocation); } + + private Sect283k1(@Nonnull Sect283k1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect283k1 copy() { + return new Sect283k1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283r1.java index 616cf23f2..df505d473 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect283r1.java @@ -27,4 +27,14 @@ public final class Sect283r1 extends EllipticCurve { public Sect283r1(@Nonnull DetectionLocation detectionLocation) { super("sect283r1", detectionLocation); } + + private Sect283r1(@Nonnull Sect283r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect283r1 copy() { + return new Sect283r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409k1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409k1.java index daa6cb6f9..b853e79f2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409k1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409k1.java @@ -27,4 +27,14 @@ public final class Sect409k1 extends EllipticCurve { public Sect409k1(@Nonnull DetectionLocation detectionLocation) { super("sect409k1", detectionLocation); } + + private Sect409k1(@Nonnull Sect409k1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect409k1 copy() { + return new Sect409k1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409r1.java index 81510bff9..d34a571e2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect409r1.java @@ -27,4 +27,14 @@ public final class Sect409r1 extends EllipticCurve { public Sect409r1(@Nonnull DetectionLocation detectionLocation) { super("sect409r1", detectionLocation); } + + private Sect409r1(@Nonnull Sect409r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect409r1 copy() { + return new Sect409r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571k1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571k1.java index 5932e7f8e..18610c471 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571k1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571k1.java @@ -27,4 +27,14 @@ public final class Sect571k1 extends EllipticCurve { public Sect571k1(@Nonnull DetectionLocation detectionLocation) { super("sect571k1", detectionLocation); } + + private Sect571k1(@Nonnull Sect571k1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect571k1 copy() { + return new Sect571k1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571r1.java b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571r1.java index 023893474..45df869d9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571r1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/curves/Sect571r1.java @@ -27,4 +27,14 @@ public final class Sect571r1 extends EllipticCurve { public Sect571r1(@Nonnull DetectionLocation detectionLocation) { super("sect571r1", detectionLocation); } + + private Sect571r1(@Nonnull Sect571r1 curve) { + super(curve); + } + + @Nonnull + @Override + protected Sect571r1 copy() { + return new Sect571r1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Decapsulate.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Decapsulate.java index 09b501ffb..acdd83388 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Decapsulate.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Decapsulate.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Decapsulate(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Decapsulate copy = new Decapsulate(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Decapsulate copy() { + return new Decapsulate(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Decrypt.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Decrypt.java index 495b4045a..1107646b6 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Decrypt.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Decrypt.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Decrypt(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Decrypt copy = new Decrypt(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Decrypt copy() { + return new Decrypt(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Digest.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Digest.java index 9446447f3..158693632 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Digest.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Digest.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Digest(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Digest copy = new Digest(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Digest copy() { + return new Digest(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Encapsulate.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Encapsulate.java index d4f39fad8..c84c6aee6 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Encapsulate.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Encapsulate.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Encapsulate(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Encapsulate copy = new Encapsulate(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Encapsulate copy() { + return new Encapsulate(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Encrypt.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Encrypt.java index c2ad2de7d..44e0b667b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Encrypt.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Encrypt.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Encrypt(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Encrypt copy = new Encrypt(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Encrypt copy() { + return new Encrypt(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Functionality.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Functionality.java index 3a0c74f96..66a352db7 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Functionality.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Functionality.java @@ -34,7 +34,7 @@ protected Functionality( } protected Functionality(@Nonnull Functionality functionality) { - super(functionality.type, functionality.detectionLocation, functionality.children); + super(functionality); } @Nonnull diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Generate.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Generate.java index 98939cacd..523fb25ae 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Generate.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Generate.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Generate(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Generate copy = new Generate(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Generate copy() { + return new Generate(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyDerivation.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyDerivation.java index a2652a731..a57430602 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyDerivation.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyDerivation.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private KeyDerivation(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - KeyDerivation copy = new KeyDerivation(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected KeyDerivation copy() { + return new KeyDerivation(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyGeneration.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyGeneration.java index 8b39a9278..1c0951696 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyGeneration.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/KeyGeneration.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; import javax.annotation.Nonnull; @@ -62,11 +61,7 @@ public Optional getSpecification() { @Nonnull @Override - public INode deepCopy() { - KeyGeneration copy = new KeyGeneration(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected KeyGeneration copy() { + return new KeyGeneration(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Sign.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Sign.java index 794c924b1..7a5e2c512 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Sign.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Sign.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Sign(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Sign copy = new Sign(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Sign copy() { + return new Sign(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Tag.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Tag.java index 3feb9f65e..ec84c2b52 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Tag.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Tag.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Tag(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Tag copy = new Tag(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Tag copy() { + return new Tag(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/functionality/Verify.java b/mapper/src/main/java/com/ibm/mapper/model/functionality/Verify.java index 98c21957b..986b9db23 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/functionality/Verify.java +++ b/mapper/src/main/java/com/ibm/mapper/model/functionality/Verify.java @@ -19,7 +19,6 @@ */ package com.ibm.mapper.model.functionality; -import com.ibm.mapper.model.INode; import com.ibm.mapper.utils.DetectionLocation; import javax.annotation.Nonnull; @@ -34,11 +33,7 @@ private Verify(@Nonnull Functionality functionality) { @Nonnull @Override - public INode deepCopy() { - Verify copy = new Verify(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected Verify copy() { + return new Verify(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/CBC.java b/mapper/src/main/java/com/ibm/mapper/model/mode/CBC.java index c885f6e99..8bb0ae153 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/CBC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/CBC.java @@ -28,4 +28,14 @@ public final class CBC extends Mode { public CBC(@Nonnull DetectionLocation detectionLocation) { super("CBC", detectionLocation); } + + private CBC(@Nonnull CBC mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new CBC(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/CCM.java b/mapper/src/main/java/com/ibm/mapper/model/mode/CCM.java index 41b2d4792..712f89b36 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/CCM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/CCM.java @@ -46,4 +46,14 @@ public String asString() { tagLength.ifPresent(node -> builtName.append(node.asString())); return builtName.toString(); } + + private CCM(@Nonnull CCM mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new CCM(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/CFB.java b/mapper/src/main/java/com/ibm/mapper/model/mode/CFB.java index 6d8d364d6..2b6c6888b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/CFB.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/CFB.java @@ -44,4 +44,14 @@ public CFB(@Nonnull DetectionLocation detectionLocation) { public CFB(int s, @Nonnull DetectionLocation detectionLocation) { super("CFB" + s, detectionLocation); } + + private CFB(@Nonnull CFB mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new CFB(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/CNT.java b/mapper/src/main/java/com/ibm/mapper/model/mode/CNT.java index 7eb42fe05..891dd731a 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/CNT.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/CNT.java @@ -28,4 +28,14 @@ public final class CNT extends Mode { public CNT(@Nonnull DetectionLocation detectionLocation) { super("CNT", detectionLocation); } + + private CNT(@Nonnull CNT mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new CNT(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/CTR.java b/mapper/src/main/java/com/ibm/mapper/model/mode/CTR.java index 6285b364d..f37e0c9d8 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/CTR.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/CTR.java @@ -31,4 +31,14 @@ public final class CTR extends Mode { public CTR(@Nonnull DetectionLocation detectionLocation) { super(NAME, detectionLocation); } + + private CTR(@Nonnull CTR mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new CTR(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/CTS.java b/mapper/src/main/java/com/ibm/mapper/model/mode/CTS.java index 5d3013aa9..e56fb4176 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/CTS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/CTS.java @@ -28,4 +28,14 @@ public final class CTS extends Mode { public CTS(@Nonnull DetectionLocation detectionLocation) { super("CTS", detectionLocation); } + + private CTS(@Nonnull CTS mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new CTS(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/EAX.java b/mapper/src/main/java/com/ibm/mapper/model/mode/EAX.java index fdd155478..038e52d47 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/EAX.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/EAX.java @@ -28,4 +28,14 @@ public final class EAX extends Mode { public EAX(@Nonnull DetectionLocation detectionLocation) { super("EAX", detectionLocation); } + + private EAX(@Nonnull EAX mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new EAX(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/ECB.java b/mapper/src/main/java/com/ibm/mapper/model/mode/ECB.java index 9bd0c5116..01f73b8fb 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/ECB.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/ECB.java @@ -28,4 +28,14 @@ public final class ECB extends Mode { public ECB(@Nonnull DetectionLocation detectionLocation) { super("ECB", detectionLocation); } + + private ECB(@Nonnull ECB mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new ECB(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/GCM.java b/mapper/src/main/java/com/ibm/mapper/model/mode/GCM.java index 1502b183c..4549fdfd3 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/GCM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/GCM.java @@ -28,4 +28,14 @@ public final class GCM extends Mode { public GCM(@Nonnull DetectionLocation detectionLocation) { super("GCM", detectionLocation); } + + private GCM(@Nonnull GCM mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new GCM(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/GCMSIV.java b/mapper/src/main/java/com/ibm/mapper/model/mode/GCMSIV.java index d8b0bec6d..e6bc93c17 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/GCMSIV.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/GCMSIV.java @@ -28,4 +28,14 @@ public final class GCMSIV extends Mode { public GCMSIV(@Nonnull DetectionLocation detectionLocation) { super("GCM-SIV", detectionLocation); } + + private GCMSIV(@Nonnull GCMSIV mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new GCMSIV(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/GMAC.java b/mapper/src/main/java/com/ibm/mapper/model/mode/GMAC.java index 54f13abde..5413fb365 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/GMAC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/GMAC.java @@ -29,4 +29,14 @@ public final class GMAC extends Mode { public GMAC(@Nonnull DetectionLocation detectionLocation) { super("GMAC", detectionLocation); } + + private GMAC(@Nonnull GMAC mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new GMAC(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/KW.java b/mapper/src/main/java/com/ibm/mapper/model/mode/KW.java index 7d75c6ea9..238968d7e 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/KW.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/KW.java @@ -29,4 +29,14 @@ public final class KW extends Mode { public KW(@Nonnull DetectionLocation detectionLocation) { super("WRAP", detectionLocation); } + + private KW(@Nonnull KW mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new KW(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/KWP.java b/mapper/src/main/java/com/ibm/mapper/model/mode/KWP.java index 38af3f2b1..abe72f7be 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/KWP.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/KWP.java @@ -28,4 +28,14 @@ public final class KWP extends Mode { public KWP(@Nonnull DetectionLocation detectionLocation) { super("WRAP-PAD", detectionLocation); } + + private KWP(@Nonnull KWP mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new KWP(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/MGM.java b/mapper/src/main/java/com/ibm/mapper/model/mode/MGM.java index 8ae2c4b2c..0e5e997ec 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/MGM.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/MGM.java @@ -28,4 +28,14 @@ public final class MGM extends Mode { public MGM(@Nonnull DetectionLocation detectionLocation) { super("MGM", detectionLocation); } + + private MGM(@Nonnull MGM mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new MGM(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/OCB.java b/mapper/src/main/java/com/ibm/mapper/model/mode/OCB.java index 25c7d964f..b85b60d6b 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/OCB.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/OCB.java @@ -34,4 +34,14 @@ public OCB(int blockSize, @Nonnull DetectionLocation detectionLocation) { super("OCB", detectionLocation); this.put(new BlockSize(blockSize, detectionLocation)); } + + private OCB(@Nonnull OCB mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new OCB(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/OFB.java b/mapper/src/main/java/com/ibm/mapper/model/mode/OFB.java index a456203e1..024139a19 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/OFB.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/OFB.java @@ -37,4 +37,14 @@ public OFB(@Nonnull DetectionLocation detectionLocation) { public OFB(int s, @Nonnull DetectionLocation detectionLocation) { super("OFB" + s, detectionLocation); } + + private OFB(@Nonnull OFB mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new OFB(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/PCBC.java b/mapper/src/main/java/com/ibm/mapper/model/mode/PCBC.java index fb5570ab6..a9a27e0f7 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/PCBC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/PCBC.java @@ -28,4 +28,14 @@ public final class PCBC extends Mode { public PCBC(@Nonnull DetectionLocation detectionLocation) { super("PCBC", detectionLocation); } + + private PCBC(@Nonnull PCBC mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new PCBC(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/SIV.java b/mapper/src/main/java/com/ibm/mapper/model/mode/SIV.java index 8dada4851..5fc123e20 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/SIV.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/SIV.java @@ -27,4 +27,14 @@ public final class SIV extends Mode { public SIV(@Nonnull DetectionLocation detectionLocation) { super("SIV", detectionLocation); } + + private SIV(@Nonnull SIV mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new SIV(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/mode/XTS.java b/mapper/src/main/java/com/ibm/mapper/model/mode/XTS.java index f1023ad0e..e1b7685c5 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/mode/XTS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/mode/XTS.java @@ -28,4 +28,14 @@ public final class XTS extends Mode { public XTS(@Nonnull DetectionLocation detectionLocation) { super("XTS", detectionLocation); } + + private XTS(@Nonnull XTS mode) { + super(mode); + } + + @Nonnull + @Override + protected Mode copy() { + return new XTS(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/ANSIX923.java b/mapper/src/main/java/com/ibm/mapper/model/padding/ANSIX923.java index 324493554..5e3736d90 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/ANSIX923.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/ANSIX923.java @@ -30,4 +30,14 @@ public final class ANSIX923 extends Padding { public ANSIX923(@Nonnull DetectionLocation detectionLocation) { super(NAME, detectionLocation); } + + private ANSIX923(@Nonnull ANSIX923 padding) { + super(padding); + } + + @Nonnull + @Override + protected ANSIX923 copy() { + return new ANSIX923(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/ISO10126.java b/mapper/src/main/java/com/ibm/mapper/model/padding/ISO10126.java index ea871f5d5..da691dd56 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/ISO10126.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/ISO10126.java @@ -28,4 +28,14 @@ public final class ISO10126 extends Padding { public ISO10126(@Nonnull DetectionLocation detectionLocation) { super("ISO 10126", detectionLocation); } + + private ISO10126(@Nonnull ISO10126 padding) { + super(padding); + } + + @Nonnull + @Override + protected ISO10126 copy() { + return new ISO10126(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/ISO7816.java b/mapper/src/main/java/com/ibm/mapper/model/padding/ISO7816.java index 30c4dbf21..328bfd531 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/ISO7816.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/ISO7816.java @@ -28,4 +28,14 @@ public final class ISO7816 extends Padding { public ISO7816(@Nonnull DetectionLocation detectionLocation) { super("ISO 7816", detectionLocation); } + + private ISO7816(@Nonnull ISO7816 padding) { + super(padding); + } + + @Nonnull + @Override + protected ISO7816 copy() { + return new ISO7816(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/ISO9796Padding.java b/mapper/src/main/java/com/ibm/mapper/model/padding/ISO9796Padding.java index 747b7ebde..1503e8982 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/ISO9796Padding.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/ISO9796Padding.java @@ -28,4 +28,14 @@ public final class ISO9796Padding extends Padding { public ISO9796Padding(@Nonnull DetectionLocation detectionLocation) { super("ISO 9796", detectionLocation); } + + private ISO9796Padding(@Nonnull ISO9796Padding padding) { + super(padding); + } + + @Nonnull + @Override + protected ISO9796Padding copy() { + return new ISO9796Padding(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/OAEP.java b/mapper/src/main/java/com/ibm/mapper/model/padding/OAEP.java index 71339560a..0d4453cb8 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/OAEP.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/OAEP.java @@ -68,16 +68,12 @@ public Optional getMGF() { } private OAEP(@Nonnull OAEP oaep) { - super(oaep.getName(), oaep.getDetectionContext(), Padding.class); + super(oaep); } @Nonnull @Override - public INode deepCopy() { - OAEP copy = new OAEP(this); - for (INode child : this.children.values()) { - copy.children.put(child.getKind(), child.deepCopy()); - } - return copy; + protected OAEP copy() { + return new OAEP(this); } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS1.java b/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS1.java index dfb0e2a13..42bf3ceb2 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS1.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS1.java @@ -28,4 +28,14 @@ public final class PKCS1 extends Padding { public PKCS1(@Nonnull DetectionLocation detectionLocation) { super("PKCS1", detectionLocation); } + + private PKCS1(@Nonnull PKCS1 padding) { + super(padding); + } + + @Nonnull + @Override + protected PKCS1 copy() { + return new PKCS1(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS5.java b/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS5.java index 641dec983..7f793dcd9 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS5.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS5.java @@ -28,4 +28,14 @@ public final class PKCS5 extends Padding { public PKCS5(@Nonnull DetectionLocation detectionLocation) { super("PKCS5", detectionLocation); } + + private PKCS5(@Nonnull PKCS5 padding) { + super(padding); + } + + @Nonnull + @Override + protected PKCS5 copy() { + return new PKCS5(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS7.java b/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS7.java index 3d6604aa6..cc3d62cfd 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS7.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/PKCS7.java @@ -27,4 +27,14 @@ public final class PKCS7 extends Padding { public PKCS7(@Nonnull DetectionLocation detectionLocation) { super("PKCS7", detectionLocation); } + + private PKCS7(@Nonnull PKCS7 padding) { + super(padding); + } + + @Nonnull + @Override + protected PKCS7 copy() { + return new PKCS7(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/Raw.java b/mapper/src/main/java/com/ibm/mapper/model/padding/Raw.java new file mode 100644 index 000000000..51c036be7 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/Raw.java @@ -0,0 +1,42 @@ +/* + * 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.model.padding; + +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.utils.DetectionLocation; +import javax.annotation.Nonnull; + +/** No padding: the data is processed as it is, e.g. RSA without padding ("textbook RSA"). */ +public final class Raw extends Padding { + + public Raw(@Nonnull DetectionLocation detectionLocation) { + super("Raw", detectionLocation); + } + + private Raw(@Nonnull Raw padding) { + super(padding); + } + + @Nonnull + @Override + protected Raw copy() { + return new Raw(this); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/TBC.java b/mapper/src/main/java/com/ibm/mapper/model/padding/TBC.java index b24638c5e..c10d7ffc0 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/TBC.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/TBC.java @@ -30,4 +30,14 @@ public final class TBC extends Padding { public TBC(@Nonnull DetectionLocation detectionLocation) { super(NAME, detectionLocation); } + + private TBC(@Nonnull TBC padding) { + super(padding); + } + + @Nonnull + @Override + protected TBC copy() { + return new TBC(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/padding/Zero.java b/mapper/src/main/java/com/ibm/mapper/model/padding/Zero.java index d41823b89..261c70cf4 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/padding/Zero.java +++ b/mapper/src/main/java/com/ibm/mapper/model/padding/Zero.java @@ -31,4 +31,14 @@ public final class Zero extends Padding { public Zero(@Nonnull DetectionLocation detectionLocation) { super(NAME, detectionLocation); } + + private Zero(@Nonnull Zero padding) { + super(padding); + } + + @Nonnull + @Override + protected Zero copy() { + return new Zero(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/protocol/IKE.java b/mapper/src/main/java/com/ibm/mapper/model/protocol/IKE.java index 71ed7b30f..3f0474ccd 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/protocol/IKE.java +++ b/mapper/src/main/java/com/ibm/mapper/model/protocol/IKE.java @@ -34,4 +34,14 @@ public IKE(@Nonnull Version version) { super(new Protocol("IKE" + version.asString(), version.getDetectionContext()), IKE.class); this.put(version); } + + private IKE(@Nonnull IKE protocol) { + super(protocol); + } + + @Nonnull + @Override + protected IKE copy() { + return new IKE(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/protocol/IPSec.java b/mapper/src/main/java/com/ibm/mapper/model/protocol/IPSec.java index 321260066..251138a4f 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/protocol/IPSec.java +++ b/mapper/src/main/java/com/ibm/mapper/model/protocol/IPSec.java @@ -28,4 +28,14 @@ public final class IPSec extends Protocol { public IPSec(@Nonnull DetectionLocation detectionLocation) { super(new Protocol("IPSec", detectionLocation), IPSec.class); } + + private IPSec(@Nonnull IPSec protocol) { + super(protocol); + } + + @Nonnull + @Override + protected IPSec copy() { + return new IPSec(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/model/protocol/TLS.java b/mapper/src/main/java/com/ibm/mapper/model/protocol/TLS.java index 7b47aeb9b..bba2a2786 100644 --- a/mapper/src/main/java/com/ibm/mapper/model/protocol/TLS.java +++ b/mapper/src/main/java/com/ibm/mapper/model/protocol/TLS.java @@ -38,6 +38,37 @@ public TLS(@Nonnull Version version) { this.put(version); } + /** + * @param originalValue the detected algorithm/protocol string this version was parsed from + * (e.g. {@code "SSLv3"}, {@code "DTLSv1.2"}, {@code "TLSv1.2"}, or a language-specific + * constant name like Go's {@code "VersionSSL30"}); its {@code tls}/{@code ssl}/{@code dtls} + * family is preserved in the node's name instead of always labeling it {@code TLS}. + */ + public TLS(@Nonnull String originalValue, @Nonnull Version version) { + super( + new Protocol( + namePrefix(originalValue) + "v" + version.asString(), + version.getDetectionContext()), + TLS.class); + this.put(version); + } + + @Nonnull + private static String namePrefix(@Nonnull String originalValue) { + String lower = originalValue.toLowerCase(); + // "contains", not "startsWith": callers pass either a bare protocol string ("SSLv3", + // "DTLSv1.2") or a language-specific constant name the family token is embedded in + // (Go's "VersionSSL30", "VersionTLS12"). Check dtls before ssl since "dtls" itself + // contains "ls" but not "ssl", so the two never collide. + if (lower.contains("dtls")) { + return "DTLS"; + } + if (lower.contains("ssl")) { + return "SSL"; + } + return "TLS"; + } + @Nonnull public Optional getVersion() { INode node = this.getChildren().get(Version.class); @@ -55,4 +86,14 @@ public Optional getCipherSuits() { } return Optional.of((CipherSuiteCollection) node); } + + private TLS(@Nonnull TLS protocol) { + super(protocol); + } + + @Nonnull + @Override + protected TLS copy() { + return new TLS(this); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/Reorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/Reorganizer.java index 7c371bc3d..46241bbf2 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/Reorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/Reorganizer.java @@ -20,7 +20,7 @@ package com.ibm.mapper.reorganizer; import com.ibm.mapper.model.INode; -import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -43,7 +43,9 @@ public final class Reorganizer implements IReorganizer { public Reorganizer(@Nonnull List rules) { this.rules = rules; - this.alreadyAppliedRules = new HashMap<>(); + // by identity: equal nodes of different trees (e.g. the copies of a root made for each + // value of a property) are reorganized each + this.alreadyAppliedRules = new IdentityHashMap<>(); } @Override diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/UsualPerformActions.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/UsualPerformActions.java index 4d4a8d6be..53c1f005c 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/UsualPerformActions.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/UsualPerformActions.java @@ -91,7 +91,7 @@ public static IFunctionPerformReorganization performMergeParentAndChildOfSameKin // Create a copy of the roots list List rootsCopy = new ArrayList<>(roots); for (int i = 0; i < rootsCopy.size(); i++) { - if (rootsCopy.get(i).equals(node)) { + if (rootsCopy.get(i) == node) { rootsCopy.set(i, newKindNode); break; } @@ -105,6 +105,29 @@ public static IFunctionPerformReorganization performMergeParentAndChildOfSameKin }; } + /** + * Merges the child of the given kind of a node into the node: the node keeps its name and takes + * the children of the child it does not have, e.g. the key length of the key set up for a + * cipher operation. + * + * @param kind - The kind of the node and of its child + * @return The {@code Function3} returning the updated list of root nodes + */ + @Nonnull + public static IFunctionPerformReorganization performMergeChildIntoParentOfSameKind( + Class kind) { + return (node, parent, roots) -> { + final INode child = node.getChildren().get(kind); + node.removeChildOfType(kind); + for (INode grandChild : child.getChildren().values()) { + if (!node.getChildren().containsKey(grandChild.getKind())) { + node.put(grandChild); + } + } + return roots; + }; + } + /** * This action is a helper function to replace a node: provide a {@code Function3} that returns * an updated node, and this action will replace the original node by this updated node. @@ -138,7 +161,7 @@ private static List replaceNode( // Create a copy of the root nodes List rootsCopy = new ArrayList<>(roots); for (int i = 0; i < rootsCopy.size(); i++) { - if (rootsCopy.get(i).equals(originalNode)) { + if (rootsCopy.get(i) == originalNode) { rootsCopy.set(i, newNode); break; } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/AsymmetricBlockCipherReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/AsymmetricBlockCipherReorganizer.java index 725d55381..e58cb1057 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/AsymmetricBlockCipherReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/AsymmetricBlockCipherReorganizer.java @@ -22,11 +22,17 @@ import com.ibm.mapper.ITranslator; import com.ibm.mapper.model.DigestSize; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; +import com.ibm.mapper.model.MaskGenerationFunction; import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Padding; import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.padding.OAEP; import com.ibm.mapper.reorganizer.IReorganizerRule; import com.ibm.mapper.reorganizer.UsualPerformActions; import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import java.util.ArrayList; import java.util.List; import javax.annotation.Nonnull; @@ -52,6 +58,73 @@ private AsymmetricBlockCipherReorganizer() { UsualPerformActions.performMergeParentAndChildOfSameKind( PublicKeyEncryption.class)); + /** + * A reorganizer rule that merges an algorithm of the given kind with a child that is the same + * algorithm, e.g. a DH key generated for DH parameters described in more detail by the child. + * The child takes the place of the parent and receives the parent's other children. + */ + @Nonnull + public static IReorganizerRule mergeParentAndChildOfSameAlgorithm( + @Nonnull Class kind) { + return new ReorganizerRuleBuilder() + .createReorganizerRule("MERGE_PARENT_AND_CHILD_OF_SAME_ALGORITHM") + .forNodeKind(kind) + .withDetectionCondition( + (node, parent, roots) -> + node.hasChildOfType(kind) + .map(child -> child.getClass().equals(node.getClass())) + .orElse(false)) + .perform(UsualPerformActions.performMergeParentAndChildOfSameKind(kind)); + } + + /** + * An OAEP padding found with no algorithm holding it, e.g. the digest of RSA-OAEP set on a + * context whose key is not known in the analyzed code ({@code + * EVP_PKEY_CTX_set_rsa_oaep_md_name}): OAEP is an RSA encryption scheme, so the padding is that + * of an RSA encryption. + */ + @Nonnull + public static final IReorganizerRule MAKE_RSA_ENCRYPTION_OF_AN_OAEP_PADDING = + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(Padding.class) + .withDetectionCondition( + (node, parent, roots) -> parent == null && node instanceof OAEP) + .perform( + (node, parent, roots) -> { + final RSA rsa = new RSA(((OAEP) node).getDetectionContext()); + rsa.put(node); + final List newRoots = new ArrayList<>(roots); + newRoots.replaceAll(root -> root == node ? rsa : root); + return newRoots; + }); + + /** + * A mask generation function found next to an OAEP padding, e.g. the MGF1 digest set for an + * RSA-OAEP encryption with {@code EVP_PKEY_CTX_set_rsa_mgf1_md}, is the mask generation + * function of that padding. + */ + @Nonnull + public static final IReorganizerRule MOVE_MASK_GENERATION_FUNCTION_UNDER_OAEP = + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(PublicKeyEncryption.class) + .withDetectionCondition( + (node, parent, roots) -> + node.hasChildOfType(MaskGenerationFunction.class).isPresent() + && node.hasChildOfType(Padding.class) + .filter(OAEP.class::isInstance) + .isPresent()) + .perform( + (node, parent, roots) -> { + final INode mgf = + node.hasChildOfType(MaskGenerationFunction.class) + .orElseThrow(); + node.removeChildOfType(MaskGenerationFunction.class); + node.hasChildOfType(Padding.class).orElseThrow().put(mgf); + return roots; + }); + @Nonnull public static final IReorganizerRule INVERT_DIGEST_AND_ITS_SIZE = new ReorganizerRuleBuilder() diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizer.java index 0bcd546c0..d8a3c5fa2 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizer.java @@ -50,12 +50,33 @@ private BlockCipherReorganizer() { UsualPerformActions.performMergeParentAndChildOfSameKind( BlockCipher.class)); + /** + * A block cipher with a block cipher child, where the parent names the cipher and the child + * holds properties of it, e.g. a cipher operation of the OpenSSL legacy API with the key setup + * of its key schedule: {@code DES_ede3_cbc_encrypt} encrypts with 3DES using key schedules set + * up for single DES. The parent keeps its name and takes what the child holds. + */ + public static final IReorganizerRule MERGE_BLOCK_CIPHER_CHILD_INTO_PARENT = + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(BlockCipher.class) + .includingChildren( + List.of( + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(BlockCipher.class) + .noAction())) + .perform( + UsualPerformActions.performMergeChildIntoParentOfSameKind( + BlockCipher.class)); + public static final IReorganizerRule DEDUPLICATE_OVERLAPPING_ROOTS = new IReorganizerRule() { @Override public boolean match( @Nonnull INode node, @Nonnull INode parent, @Nonnull List roots) { - if (!roots.contains(node) || !(node instanceof Algorithm algA)) { + if (roots.stream().noneMatch(root -> root == node) + || !(node instanceof Algorithm algA)) { return false; } @@ -124,7 +145,8 @@ && isRelatedCipherFamily( toKeep.put(child); } } - newRoots.remove(toRemove); + final INode removed = toRemove; + newRoots.removeIf(root -> root == removed); currentBase = toKeep; merged = true; } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizer.java index 5859b4076..78469f03b 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizer.java @@ -19,15 +19,23 @@ */ package com.ibm.mapper.reorganizer.rules; +import com.ibm.mapper.model.IAlgorithm; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.TagLength; +import com.ibm.mapper.model.algorithms.Blowfish; +import com.ibm.mapper.model.algorithms.RC2; +import com.ibm.mapper.model.algorithms.RC4; +import com.ibm.mapper.model.algorithms.RC5; +import com.ibm.mapper.model.algorithms.cast.CAST128; import com.ibm.mapper.model.functionality.Decrypt; import com.ibm.mapper.model.functionality.Encrypt; import com.ibm.mapper.reorganizer.IReorganizerRule; import com.ibm.mapper.reorganizer.UsualPerformActions; import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import javax.annotation.Nonnull; public final class CipherParameterReorganizer { @@ -63,6 +71,48 @@ private CipherParameterReorganizer() { } }); + /** + * A key length set for an encryption with a cipher (in C, {@code EVP_CIPHER_CTX_set_key_length} + * on the context the cipher is initialized on) is the key length of a cipher whose key length + * is variable: RC2, RC4, RC5, Blowfish and CAST5, the ciphers OpenSSL marks {@code + * EVP_CIPH_VARIABLE_LENGTH}. A cipher whose key length is fixed keeps the key length it has, as + * OpenSSL refuses another one. + */ + @Nonnull + public static final IReorganizerRule KEEP_THE_FIXED_KEY_LENGTH_OF_THE_CIPHER_OF_AN_ENCRYPTION = + keepTheFixedKeyLengthOfTheCipher(Encrypt.class); + + @Nonnull + public static final IReorganizerRule KEEP_THE_FIXED_KEY_LENGTH_OF_THE_CIPHER_OF_A_DECRYPTION = + keepTheFixedKeyLengthOfTheCipher(Decrypt.class); + + @Nonnull + private static IReorganizerRule keepTheFixedKeyLengthOfTheCipher( + @Nonnull Class operationClazz) { + return new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(operationClazz) + .withDetectionCondition( + (node, parent, roots) -> + parent instanceof IAlgorithm + && parent.hasChildOfType(KeyLength.class).isPresent() + && node.hasChildOfType(KeyLength.class).isPresent() + && !hasVariableKeyLength(parent)) + .perform( + (node, parent, roots) -> { + node.removeChildOfType(KeyLength.class); + return roots; + }); + } + + private static boolean hasVariableKeyLength(@Nonnull INode cipher) { + return cipher instanceof RC2 + || cipher instanceof RC4 + || cipher instanceof RC5 + || cipher instanceof Blowfish + || cipher instanceof CAST128; + } + @Nonnull public static final IReorganizerRule MOVE_NODES_UNDER_ENCRYPT_UP = new ReorganizerRuleBuilder() @@ -78,4 +128,44 @@ private CipherParameterReorganizer() { .forNodeKind(Decrypt.class) .withAnyNonNullChildren() .perform(UsualPerformActions.performMovingChildrenUp); + + /** + * An encryption or decryption operation detected before the cipher it uses (as in C, where + * {@code EVP_EncryptInit_ex(ctx, cipher, ...)} names the cipher) has the cipher as its child. + * The cipher becomes the root node and the operation its child, the same shape as when the + * cipher is detected first. + */ + @Nonnull + public static final IReorganizerRule MOVE_ENCRYPT_UNDER_ITS_CIPHER = + moveOperationUnderItsCipher(Encrypt.class); + + @Nonnull + public static final IReorganizerRule MOVE_DECRYPT_UNDER_ITS_CIPHER = + moveOperationUnderItsCipher(Decrypt.class); + + @Nonnull + private static IReorganizerRule moveOperationUnderItsCipher( + @Nonnull Class operationClazz) { + return new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(operationClazz) + .withDetectionCondition( + (node, parent, roots) -> parent == null && cipherOf(node).isPresent()) + .perform( + (node, parent, roots) -> { + final INode cipher = cipherOf(node).orElseThrow(); + node.removeChildOfType(cipher.getKind()); + cipher.put(node); + final List newRoots = new ArrayList<>(roots); + newRoots.replaceAll(root -> root == node ? cipher : root); + return newRoots; + }); + } + + @Nonnull + private static Optional cipherOf(@Nonnull INode operation) { + return operation.getChildren().values().stream() + .filter(IAlgorithm.class::isInstance) + .findFirst(); + } } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizer.java index f58f83002..f3d8333a8 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizer.java @@ -21,6 +21,7 @@ import com.ibm.mapper.model.CipherSuite; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.CipherSuiteCollection; import com.ibm.mapper.model.protocol.TLS; import com.ibm.mapper.reorganizer.IReorganizerRule; import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; @@ -52,6 +53,36 @@ private CipherSuiteReorganizer() { return roots; }); + /** + * Cipher suites configured without the protocol they are used with, e.g. a cipher list set on a + * TLS context created elsewhere, are the cipher suites of a TLS protocol. + */ + @Nonnull + public static final IReorganizerRule ADD_TLS_PROTOCOL_AS_PARENT_OF_CIPHER_SUITES = + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(CipherSuiteCollection.class) + .withDetectionCondition( + (node, parent, roots) -> + parent == null + && node instanceof CipherSuiteCollection cipherSuites + && !cipherSuites.getCollection().isEmpty()) + .perform( + (node, parent, roots) -> { + final CipherSuiteCollection cipherSuites = + (CipherSuiteCollection) node; + final TLS tls = + new TLS( + cipherSuites + .getCollection() + .get(0) + .getDetectionContext()); + tls.put(cipherSuites); + final List rootsCopy = new ArrayList<>(roots); + rootsCopy.replaceAll(root -> root == node ? tls : root); + return rootsCopy; + }); + @Nonnull public static final IReorganizerRule REPLACE_TLS_WITH_VERSIONED_CHILD = new ReorganizerRuleBuilder() @@ -71,7 +102,8 @@ private CipherSuiteReorganizer() { if (parent == null) { List rootsCopy = new ArrayList<>(roots); for (int i = 0; i < rootsCopy.size(); i++) { - if (rootsCopy.get(i).equals(node)) { + // the matched root, not an equal copy of it + if (rootsCopy.get(i) == node) { rootsCopy.set(i, childTls); break; } diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizer.java index 5d427fc5e..2310de080 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizer.java @@ -21,7 +21,9 @@ import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.IAlgorithm; import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.IPrimitive; import com.ibm.mapper.model.Key; import com.ibm.mapper.model.KeyLength; import com.ibm.mapper.model.PrivateKey; @@ -144,7 +146,7 @@ private KeyReorgenizer() { // Create a copy of the root nodes List rootsCopy = new ArrayList<>(roots); for (int i = 0; i < rootsCopy.size(); i++) { - if (rootsCopy.get(i).equals(node)) { + if (rootsCopy.get(i) == node) { rootsCopy.set(i, newNode); break; } @@ -157,6 +159,50 @@ private KeyReorgenizer() { } }); + /** + * A reorganizer rule that turns an algorithm of the given kind whose private key is generated + * into a private key holding the algorithm. + * + *

This rule applies when the algorithm has a {@link KeyGeneration} child for a private key + * and is not already held by a key. The algorithm keeps its children, e.g. its key length or + * curve, and the private key takes its place among the roots or under its parent. The private + * key also has the key length of the algorithm, which is the size of the key. + */ + @Nonnull + public static IReorganizerRule makePrivateKeyOfGeneratedAlgorithm( + @Nonnull Class kind) { + return new ReorganizerRuleBuilder() + .createReorganizerRule("MAKE_PRIVATE_KEY_OF_GENERATED_ALGORITHM") + .forNodeKind(kind) + .withDetectionCondition( + (node, parent, roots) -> + node instanceof IAlgorithm + && !(parent instanceof Key) + && generatesPrivateKey(node)) + .perform( + (node, parent, roots) -> { + final PrivateKey privateKey = + new PrivateKey(new Key((IAlgorithm) node)); + node.hasChildOfType(KeyLength.class) + .ifPresent(keyLength -> privateKey.put(keyLength.deepCopy())); + if (parent == null) { + final List rootsCopy = new ArrayList<>(roots); + rootsCopy.replaceAll(root -> root == node ? privateKey : root); + return rootsCopy; + } + parent.removeChildOfType(node.getKind()); + parent.put(privateKey); + return roots; + }); + } + + private static boolean generatesPrivateKey(@Nonnull INode node) { + return node.hasChildOfType(KeyGeneration.class) + .flatMap(keyGeneration -> ((KeyGeneration) keyGeneration).getSpecification()) + .map(KeyGeneration.Specification.PRIVATE_KEY::equals) + .orElse(false); + } + /** * A reorganizer rule for propagating KeyLength from a SecretKey node to its child BlockCipher. * diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyUsageReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyUsageReorganizer.java new file mode 100644 index 000000000..e4496a9f2 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/KeyUsageReorganizer.java @@ -0,0 +1,475 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2025 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.IAlgorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.DH; +import com.ibm.mapper.model.algorithms.DHKEM; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSASVE; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.algorithms.X448; +import com.ibm.mapper.model.functionality.Decapsulate; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Encapsulate; +import com.ibm.mapper.model.functionality.Functionality; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Reorganizer rules for the operations performed with a key, when the operation is found on the + * key's algorithm rather than named by its own algorithm, e.g. an EC key used to sign. + */ +public final class KeyUsageReorganizer { + + private KeyUsageReorganizer() { + // private + } + + /** + * A reorganizer rule for a decryption performed with an EC, DH, X25519 or X448 private key, + * which has no decryption operation of its own: the content encryption key of a CMS message is + * then agreed with the key of its originator (a key agreement recipient), so the decryption is + * reported as a key derivation, e.g. ECDH with an EC key. + */ + @Nonnull + public static final IReorganizerRule + MAKE_KEY_DERIVATION_OF_A_DECRYPTION_WITH_A_KEY_AGREEMENT_KEY = + new ReorganizerRuleBuilder() + .createReorganizerRule( + "MAKE_KEY_DERIVATION_OF_A_DECRYPTION_WITH_A_KEY_AGREEMENT_KEY") + .forNodeKind(PrivateKey.class) + .withDetectionCondition( + (node, parent, roots) -> + keyAlgorithm(node) + .filter(KeyUsageReorganizer::agreesKeysOnly) + .flatMap( + algorithm -> + algorithm.hasChildOfType( + Decrypt.class)) + .isPresent()) + .perform( + (node, parent, roots) -> { + final INode algorithm = keyAlgorithm(node).orElseThrow(); + final Functionality decryption = + algorithm + .hasChildOfType(Decrypt.class) + .map(Functionality.class::cast) + .orElseThrow(); + algorithm.removeChildOfType(Decrypt.class); + if (algorithm + .hasChildOfType(KeyDerivation.class) + .isEmpty()) { + final KeyDerivation derivation = + new KeyDerivation( + decryption.getDetectionContext()); + decryption + .getChildren() + .values() + .forEach(derivation::put); + algorithm.put(derivation); + } + return roots; + }); + + /** + * A reorganizer rule that reports each operation performed with a private key as the algorithm + * of that operation, held by the key: a signature or its verification with an EC key is ECDSA, + * with an RSA key an RSA signature (RSA-PSS when that padding is selected); key agreement with + * an EC key is ECDH and with a DH key DH; key encapsulation with an RSA key is RSASVE and with + * an EC, X25519 or X448 key DHKEM. The operation algorithm gets the key's curve or key length + * and the digest the operation uses. + * + *

The key's algorithm is removed from the key once all its operations are reported this way, + * as the key is then described by the algorithms of its operations. + */ + @Nonnull + public static final IReorganizerRule MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS = + new ReorganizerRuleBuilder() + .createReorganizerRule("MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS") + .forNodeKind(PrivateKey.class) + .withDetectionCondition( + (node, parent, roots) -> + keyAlgorithm(node) + .map( + algorithm -> + operations(algorithm).stream() + .anyMatch( + operation -> + selectsAnotherAlgorithm( + algorithm, + operation))) + .orElse(false)) + .perform( + (node, parent, roots) -> { + final INode algorithm = keyAlgorithm(node).orElseThrow(); + for (Functionality operation : operations(algorithm)) { + if (!selectsAnotherAlgorithm(algorithm, operation)) { + continue; + } + final INode operationAlgorithm = + operationAlgorithm(algorithm, operation); + algorithm.removeChildOfType(operation.getKind()); + moveUnder(operationAlgorithm, operation); + final Optional sameAlgorithm = + node.hasChildOfType(operationAlgorithm.getKind()) + .filter( + existing -> + existing.getClass() + .equals( + operationAlgorithm + .getClass())); + if (sameAlgorithm.isPresent()) { + operationAlgorithm + .getChildren() + .values() + .forEach(sameAlgorithm.get()::put); + } else { + node.put(operationAlgorithm); + } + } + if (operations(algorithm).isEmpty()) { + algorithm + .hasChildOfType(KeyGeneration.class) + .ifPresent(node::put); + node.removeChildOfType(algorithm.getKind()); + } + return roots; + }); + + /** + * A reorganizer rule for the operations performed with a private key that are found on the + * algorithm the key is named after as algorithms of their own, e.g. an ECDSA signature or an + * ECDH key agreement of the OpenSSL legacy API made with a generated EC key: each is held by + * the key, with the key's curve or key length, as {@link + * #MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS} reports the operations named by the key's + * algorithm. + * + *

The key's algorithm is removed from the key once all its operations are reported this way, + * as the key is then described by the algorithms of its operations. + */ + @Nonnull + public static final IReorganizerRule MOVE_OPERATION_ALGORITHMS_OF_PRIVATE_KEY_TO_THE_KEY = + new ReorganizerRuleBuilder() + .createReorganizerRule("MOVE_OPERATION_ALGORITHMS_OF_PRIVATE_KEY_TO_THE_KEY") + .forNodeKind(PrivateKey.class) + .withDetectionCondition( + (node, parent, roots) -> + ownAlgorithm(node) + .map( + algorithm -> + !operationAlgorithms(algorithm) + .isEmpty()) + .orElse(false)) + .perform( + (node, parent, roots) -> { + final INode algorithm = ownAlgorithm(node).orElseThrow(); + for (INode operationAlgorithm : operationAlgorithms(algorithm)) { + algorithm.removeChildOfType(operationAlgorithm.getKind()); + withKeyLengthOf( + algorithm, withCurveOf(algorithm, operationAlgorithm)); + final Optional sameAlgorithm = + node.hasChildOfType(operationAlgorithm.getKind()) + .filter( + existing -> + existing.getClass() + .equals( + operationAlgorithm + .getClass())); + if (sameAlgorithm.isPresent()) { + operationAlgorithm + .getChildren() + .values() + .forEach(sameAlgorithm.get()::put); + } else { + node.put(operationAlgorithm); + } + } + if (operations(algorithm).isEmpty()) { + algorithm + .hasChildOfType(KeyGeneration.class) + .ifPresent(node::put); + node.removeChildOfType(algorithm.getKind()); + } + return roots; + }); + + /** + * A reorganizer rule for a key of the given kind created from raw bytes, i.e. not generated: + * the operations performed with the key, and the algorithms they use (e.g. the cipher of a CMAC + * key), are found on the key and are moved under the key's algorithm. + */ + @Nonnull + public static IReorganizerRule moveOperationsOfImportedKeyUnderItsAlgorithm( + @Nonnull Class keyKind) { + return new ReorganizerRuleBuilder() + .createReorganizerRule("MOVE_OPERATIONS_OF_IMPORTED_KEY_UNDER_ITS_ALGORITHM") + .forNodeKind(keyKind) + .withDetectionCondition( + (node, parent, roots) -> + ownAlgorithm(node).isPresent() + && !isGenerated(node) + && !movedUnderAlgorithm(node).isEmpty()) + .perform( + (node, parent, roots) -> { + final INode algorithm = ownAlgorithm(node).orElseThrow(); + for (INode child : movedUnderAlgorithm(node)) { + algorithm.put(child); + node.removeChildOfType(child.getKind()); + } + return roots; + }); + } + + /** + * A reorganizer rule for a MAC key: the signature computed with it through a digest sign + * operation (e.g. EVP_DigestSign with an HMAC key) is the MAC's tag, computed with the digest + * of the operation. + */ + @Nonnull + public static final IReorganizerRule MAKE_TAGS_OF_SECRET_KEY_OPERATIONS = + new ReorganizerRuleBuilder() + .createReorganizerRule("MAKE_TAGS_OF_SECRET_KEY_OPERATIONS") + .forNodeKind(SecretKey.class) + .withDetectionCondition( + (node, parent, roots) -> + keyAlgorithm(node) + .filter(algorithm -> algorithm.is(Mac.class)) + .map( + algorithm -> + !signatureOperations(algorithm) + .isEmpty()) + .orElse(false)) + .perform( + (node, parent, roots) -> { + final INode mac = keyAlgorithm(node).orElseThrow(); + for (Functionality operation : signatureOperations(mac)) { + mac.removeChildOfType(operation.getKind()); + for (INode used : + List.copyOf(operation.getChildren().values())) { + mac.put(used); + } + mac.put(new Tag(operation.getDetectionContext())); + } + return roots; + }); + + /** The algorithm a key was created with, which gives the key its name. */ + @Nonnull + private static Optional ownAlgorithm(@Nonnull INode key) { + return key.getChildren().values().stream() + .filter(IAlgorithm.class::isInstance) + .filter(child -> ((IAlgorithm) child).getName().equals(key.asString())) + .findFirst(); + } + + private static boolean isGenerated(@Nonnull INode key) { + return key.hasChildOfType(KeyGeneration.class).isPresent() + || ownAlgorithm(key) + .flatMap(algorithm -> algorithm.hasChildOfType(KeyGeneration.class)) + .isPresent(); + } + + /** The operations and used algorithms found on a key, other than its own algorithm. */ + @Nonnull + private static List movedUnderAlgorithm(@Nonnull INode key) { + final Optional own = ownAlgorithm(key); + return key.getChildren().values().stream() + .filter(child -> own.map(algorithm -> algorithm != child).orElse(true)) + .filter( + child -> + (child instanceof Functionality + && !(child instanceof KeyGeneration)) + || child instanceof IAlgorithm) + .toList(); + } + + @Nonnull + private static List signatureOperations(@Nonnull INode algorithm) { + return algorithm.getChildren().values().stream() + .filter(child -> child instanceof Sign || child instanceof Verify) + .map(Functionality.class::cast) + .toList(); + } + + @Nonnull + private static Optional keyAlgorithm(@Nonnull INode key) { + return key.getChildren().values().stream().filter(IAlgorithm.class::isInstance).findFirst(); + } + + /** + * The algorithms of the operations found on a key's algorithm, of another kind than the key's + * algorithm: its signatures, key agreements and key encapsulations. + */ + @Nonnull + private static List operationAlgorithms(@Nonnull INode algorithm) { + return algorithm.getChildren().values().stream() + .filter(IAlgorithm.class::isInstance) + .filter(child -> !child.getKind().equals(algorithm.getKind())) + .filter( + child -> + child.is(Signature.class) + || child.is(KeyAgreement.class) + || child.is(KeyEncapsulationMechanism.class)) + .toList(); + } + + /** The operations found on an algorithm, other than the generation of the key. */ + @Nonnull + private static List operations(@Nonnull INode algorithm) { + return algorithm.getChildren().values().stream() + .filter(Functionality.class::isInstance) + .filter(child -> !(child instanceof KeyGeneration)) + .map(Functionality.class::cast) + .toList(); + } + + /** Whether a key of the given algorithm agrees keys, and neither decrypts nor encrypts. */ + private static boolean agreesKeysOnly(@Nonnull INode algorithm) { + return algorithm instanceof EllipticCurveAlgorithm + || algorithm instanceof DH + || algorithm instanceof X25519 + || algorithm instanceof X448; + } + + /** + * Whether an operation performed with a key of the given algorithm has another algorithm than + * the key's, i.e. not e.g. an Ed25519 signature or an X25519 key agreement. + */ + private static boolean selectsAnotherAlgorithm( + @Nonnull INode algorithm, @Nonnull Functionality operation) { + if (operation instanceof Sign || operation instanceof Verify) { + return algorithm instanceof EllipticCurveAlgorithm + || (algorithm instanceof RSA && !algorithm.is(Signature.class)); + } else if (operation instanceof KeyDerivation) { + return algorithm instanceof EllipticCurveAlgorithm + || (algorithm instanceof DH && !algorithm.is(KeyAgreement.class)); + } else if (operation instanceof Encapsulate || operation instanceof Decapsulate) { + return algorithm instanceof RSA + || algorithm instanceof EllipticCurveAlgorithm + || algorithm instanceof X25519 + || algorithm instanceof X448; + } + return false; + } + + /** + * The algorithm of an operation performed with a key of the given algorithm, for an operation + * that {@link #selectsAnotherAlgorithm selects another algorithm}. An RSA-PSS scheme selected + * for the operation is taken from the operation. + */ + @Nonnull + private static INode operationAlgorithm( + @Nonnull INode algorithm, @Nonnull Functionality operation) { + final DetectionLocation location = operation.getDetectionContext(); + if (operation instanceof Sign || operation instanceof Verify) { + if (algorithm instanceof EllipticCurveAlgorithm) { + return withCurveOf(algorithm, new ECDSA(location)); + } + if (algorithm instanceof RSA && !algorithm.is(Signature.class)) { + final Optional pss = + operation.hasChildOfType(ProbabilisticSignatureScheme.class); + if (pss.isPresent()) { + operation.removeChildOfType(ProbabilisticSignatureScheme.class); + return withKeyLengthOf(algorithm, pss.get()); + } + return withKeyLengthOf(algorithm, new RSA(Signature.class, location)); + } + } else if (operation instanceof KeyDerivation) { + if (algorithm instanceof EllipticCurveAlgorithm) { + return withCurveOf(algorithm, new ECDH(location)); + } + if (algorithm instanceof DH && !algorithm.is(KeyAgreement.class)) { + return withKeyLengthOf(algorithm, new DH(KeyAgreement.class, location)); + } + } else if (operation instanceof Encapsulate || operation instanceof Decapsulate) { + if (algorithm instanceof RSA) { + return withKeyLengthOf(algorithm, new RSASVE(location)); + } + if (algorithm instanceof EllipticCurveAlgorithm) { + return new DHKEM(withCurveOf(algorithm, new ECDH(location)), location); + } + if (algorithm instanceof X25519 || algorithm instanceof X448) { + return new DHKEM(withoutOperations(algorithm.deepCopy()), location); + } + } + throw new IllegalArgumentException( + "no other algorithm for " + operation.asString() + " with " + algorithm.asString()); + } + + /** The algorithm without the operations performed with it, e.g. for use inside DHKEM. */ + @Nonnull + private static INode withoutOperations(@Nonnull INode algorithm) { + algorithm.getChildren().values().stream() + .filter(Functionality.class::isInstance) + .map(INode::getKind) + .toList() + .forEach(algorithm::removeChildOfType); + return algorithm; + } + + @Nonnull + private static INode withCurveOf(@Nonnull INode key, @Nonnull INode operationAlgorithm) { + key.hasChildOfType(EllipticCurve.class) + .ifPresent(curve -> operationAlgorithm.put(curve.deepCopy())); + return operationAlgorithm; + } + + @Nonnull + private static INode withKeyLengthOf(@Nonnull INode key, @Nonnull INode operationAlgorithm) { + if (operationAlgorithm.hasChildOfType(KeyLength.class).isEmpty()) { + key.hasChildOfType(KeyLength.class) + .ifPresent(keyLength -> operationAlgorithm.put(keyLength.deepCopy())); + } + return operationAlgorithm; + } + + /** Puts the operation under its algorithm, together with what it uses, e.g. its digest. */ + private static void moveUnder(@Nonnull INode operationAlgorithm, @Nonnull INode operation) { + for (INode used : List.copyOf(operation.getChildren().values())) { + operationAlgorithm.put(used); + operation.removeChildOfType(used.getKind()); + } + operationAlgorithm.put(operation); + } +} diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MacReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MacReorganizer.java index 93eb7d053..2db839dd4 100644 --- a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MacReorganizer.java +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/MacReorganizer.java @@ -21,20 +21,25 @@ import com.ibm.mapper.ITranslator; import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.AuthenticatedEncryption; 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.Mode; +import com.ibm.mapper.model.Oid; import com.ibm.mapper.model.Padding; import com.ibm.mapper.model.StreamCipher; import com.ibm.mapper.model.TagLength; +import com.ibm.mapper.model.algorithms.GMAC; import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.UsualPerformActions; import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.annotation.Nonnull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,8 +67,7 @@ private MacReorganizer() { (node, parent, roots) -> { Algorithm blockCipher = (Algorithm) node.getChildren().get(BlockCipher.class); - /* TODO: doing this is not ideal because we "lose" the original class (i.e. AES) of the node, which prevents class-specific enrichment */ - INode newMac = new Algorithm(blockCipher, Mac.class); + INode newMac = blockCipher.asKind(Mac.class); for (Map.Entry, INode> childKeyValue : node.getChildren().entrySet()) { @@ -77,7 +81,7 @@ private MacReorganizer() { // Create a copy of the roots list List rootsCopy = new ArrayList<>(roots); for (int i = 0; i < rootsCopy.size(); i++) { - if (rootsCopy.get(i).equals(node)) { + if (rootsCopy.get(i) == node) { rootsCopy.set(i, newMac); break; } @@ -90,6 +94,43 @@ private MacReorganizer() { } }); + /** + * A GMAC whose cipher is given separately as a cipher in GCM mode (e.g. OpenSSL's {@code + * EVP_MAC_fetch(..., "GMAC", ...)} with the {@code "cipher"} parameter {@code "AES-128-GCM"}) + * becomes that cipher as a {@code Mac} in GMAC mode ({@code AES-128-GMAC}), the same shape as a + * GMAC built on a block cipher. The cipher is a {@code BlockCipher} or, once enriched, an + * {@code AuthenticatedEncryption}; its GCM mode and OID do not apply to the MAC and are + * dropped. + */ + @Nonnull + public static final IReorganizerRule MERGE_GMAC_PARENT_AND_GCM_CIPHER_CHILD = + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(Mac.class) + .forNodeValue(GMAC.NAME) + .withAnyNonNullChildren() + .withDetectionCondition((node, parent, roots) -> gmacCipher(node).isPresent()) + .perform( + UsualPerformActions.performReplacingNode( + (node, parent, roots) -> { + INode cipher = gmacCipher(node).orElseThrow(); + node.removeChildOfType(cipher.getKind()); + Algorithm mac = ((Algorithm) cipher).asKind(Mac.class); + mac.removeChildOfType(Mode.class); + mac.removeChildOfType(Oid.class); + mac.put( + new com.ibm.mapper.model.mode.GMAC( + mac.getDetectionContext())); + return mac; + })); + + /** The cipher of a GMAC: a child block cipher or authenticated encryption. */ + @Nonnull + private static Optional gmacCipher(@Nonnull INode gmac) { + return gmac.hasChildOfType(BlockCipher.class) + .or(() -> gmac.hasChildOfType(AuthenticatedEncryption.class)); + } + /** * A reorganizer rule for moving cipher configuration nodes (e.g., Mode, Padding, BlockSize) * under their respective cipher parent nodes (BlockCipher or StreamCipher) within a {@code Mac} diff --git a/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/ProtocolVersionReorganizer.java b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/ProtocolVersionReorganizer.java new file mode 100644 index 000000000..5935182d5 --- /dev/null +++ b/mapper/src/main/java/com/ibm/mapper/reorganizer/rules/ProtocolVersionReorganizer.java @@ -0,0 +1,343 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Version; +import com.ibm.mapper.model.collections.ProtocolVersionSettings; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** + * Resolves the range of versions a protocol uses from the versions set on it ({@link + * ProtocolVersionSettings}): the versions disabled on it, e.g. by the {@code SSL_OP_NO_TLSv1} + * option of an OpenSSL context, and its minimum and maximum versions. The settings add up, as + * OpenSSL applies them together: the protocol uses the lowest run of versions that no setting + * disables, e.g. with {@code SSL_OP_NO_TLSv1} and then {@code SSL_OP_NO_TLSv1_1} it uses TLS 1.2 + * and above, and with only {@code SSL_OP_NO_TLSv1_1} it uses TLS 1.0 only. + * + *

The protocol is given the lowest version of the range when its minimum is set or raised above + * the lowest version, and the highest version of the range when its maximum is set or lowered below + * the highest version: a second version is a copy of the tree of the protocol with that version, as + * for a protocol configured with two versions. Each version is located where it is set, or where + * the version next to it is disabled. A protocol whose method fixes its version keeps it. + */ +public final class ProtocolVersionReorganizer { + + private ProtocolVersionReorganizer() { + // private + } + + /** + * The versions of a protocol family, lowest first, and the lowest version used by default: SSL + * 3.0 is not built into OpenSSL by default since 1.1.0, so it is used only when it is set. + */ + private record Family(@Nonnull String prefix, @Nonnull List versions, int lowest) { + + boolean includes(@Nonnull String version) { + return familyOf(version) == this; + } + } + + private static final Family DTLS = new Family("DTLS", List.of("DTLSv1.0", "DTLSv1.2"), 0); + + private static final Family TLS_FAMILY = + new Family("TLS", List.of("SSLv3.0", "TLSv1.0", "TLSv1.1", "TLSv1.2", "TLSv1.3"), 1); + + private static final List> SETTINGS = + List.of( + ProtocolVersionSettings.Disabled.class, + ProtocolVersionSettings.Minimum.class, + ProtocolVersionSettings.Maximum.class); + + /** + * Resolves the versions set on a protocol of the given kind, e.g. an OpenSSL context reported + * as a {@link TLS} node, to the bounds of the range of versions it uses. + */ + @Nonnull + public static IReorganizerRule resolveVersionsSetOn( + @Nonnull Class protocolKind) { + return new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(protocolKind) + .withDetectionCondition((node, parent, roots) -> !settingsOf(node).isEmpty()) + .perform( + (node, parent, roots) -> { + final List settings = settingsOf(node); + settings.forEach(setting -> node.removeChildOfType(setting.getKind())); + if (hasFixedVersion(node)) { + return roots; + } + // a method negotiating the version, given to the context after its + // creation, is the protocol the versions bound + final INode protocol = node.hasChildOfType(TLS.class).orElse(node); + return withVersions(protocol, resolve(settings), roots); + }); + } + + /** + * Resolves the versions set on a protocol created elsewhere, e.g. by {@code + * SSL_CTX_set_options} on a context given to a function, found on their own: each setting found + * as a root is replaced by the bounds of the range of versions it leaves. + */ + @Nonnull + public static List resolveVersionsSetOnTheirOwn() { + return SETTINGS.stream() + .map( + kind -> + new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(kind) + .withDetectionCondition( + (node, parent, roots) -> parent == null) + .perform( + (node, parent, roots) -> { + final List settings = + new ArrayList<>(); + settings.add((ProtocolVersionSettings) node); + settings.addAll(settingsOf(node)); + final List result = new ArrayList<>(); + for (INode root : roots) { + if (root == node) { + result.addAll(resolve(settings)); + } else { + result.add(root); + } + } + return result; + })) + .toList(); + } + + @Nonnull + private static List settingsOf(@Nonnull INode node) { + return SETTINGS.stream() + .map(node::hasChildOfType) + .flatMap(Optional::stream) + .map(ProtocolVersionSettings.class::cast) + .toList(); + } + + /** Whether the protocol, or the method given to it, is of one version, e.g. TLS 1.2 only. */ + private static boolean hasFixedVersion(@Nonnull INode node) { + return node.hasChildOfType(Version.class).isPresent() + || node.hasChildOfType(TLS.class) + .flatMap(method -> method.hasChildOfType(Version.class)) + .isPresent(); + } + + /** + * The roots, the protocol given the first version, and a copy of the tree of the protocol with + * each other version. + */ + @Nonnull + private static List withVersions( + @Nonnull INode protocol, @Nonnull List versions, @Nonnull List roots) { + if (versions.isEmpty()) { + return roots; + } + final List result = new ArrayList<>(roots); + for (INode version : versions.subList(1, versions.size())) { + result.add(copyWith(roots, protocol, version)); + } + protocol.put(versions.get(0)); + return result; + } + + /** + * A copy of the root holding the protocol, with the version put in the copy of the protocol. + */ + @Nonnull + private static INode copyWith( + @Nonnull List roots, @Nonnull INode protocol, @Nonnull INode version) { + for (INode root : roots) { + final Optional>> path = pathTo(root, protocol); + if (path.isPresent()) { + final INode copy = root.deepCopy(); + INode node = copy; + for (Class kind : path.get()) { + node = node.getChildren().get(kind); + } + node.put(version); + return copy; + } + } + final INode copy = protocol.deepCopy(); + copy.put(version); + return copy; + } + + /** The kinds of the children leading from the node to the target, when the target is below. */ + @Nonnull + private static Optional>> pathTo( + @Nonnull INode node, @Nonnull INode target) { + if (node == target) { + return Optional.of(new ArrayList<>()); + } + for (Map.Entry, INode> child : node.getChildren().entrySet()) { + final Optional>> below = pathTo(child.getValue(), target); + if (below.isPresent()) { + below.get().add(0, child.getKey()); + return below; + } + } + return Optional.empty(); + } + + /** The bounds of the range of versions left by the settings, for each family they set. */ + @Nonnull + private static List resolve(@Nonnull List settings) { + return Stream.of(TLS_FAMILY, DTLS).flatMap(family -> resolve(family, settings)).toList(); + } + + @Nonnull + private static Stream resolve( + @Nonnull Family family, @Nonnull List settings) { + final List disabled = + versionsOf(ProtocolVersionSettings.Disabled.class, family, settings); + final List minimums = + versionsOf(ProtocolVersionSettings.Minimum.class, family, settings); + final List maximums = + versionsOf(ProtocolVersionSettings.Maximum.class, family, settings); + if (disabled.isEmpty() && minimums.isEmpty() && maximums.isEmpty()) { + return Stream.empty(); + } + final List versions = family.versions(); + final Set off = new HashSet<>(); + disabled.forEach(version -> off.add(versions.indexOf(version.asString()))); + // the versions below the default lowest one are used only when they are set + final boolean lowerVersionSet = + Stream.concat(minimums.stream(), maximums.stream()) + .anyMatch( + version -> versions.indexOf(version.asString()) < family.lowest()); + if (!lowerVersionSet) { + for (int i = 0; i < family.lowest(); i++) { + off.add(i); + } + } + for (TLS minimum : minimums) { + for (int i = 0; i < versions.indexOf(minimum.asString()); i++) { + off.add(i); + } + } + for (TLS maximum : maximums) { + for (int i = versions.indexOf(maximum.asString()) + 1; i < versions.size(); i++) { + off.add(i); + } + } + int lowest = 0; + while (lowest < versions.size() && off.contains(lowest)) { + lowest++; + } + if (lowest == versions.size()) { + return Stream.empty(); + } + int highest = lowest; + while (highest + 1 < versions.size() && !off.contains(highest + 1)) { + highest++; + } + + final List bounds = new ArrayList<>(2); + final boolean minimumBound = lowest > family.lowest() || !minimums.isEmpty(); + if (minimumBound) { + final String version = versions.get(lowest); + final Optional disabledBelow = + lowest > 0 ? locationOf(versions.get(lowest - 1), disabled) : Optional.empty(); + bounds.add( + version( + version, + locationOf(version, minimums) + .or(() -> disabledBelow) + .orElseGet(() -> firstLocation(settings)))); + } + final boolean maximumBound = highest < versions.size() - 1 || !maximums.isEmpty(); + if (maximumBound && !(minimumBound && highest == lowest)) { + final String version = versions.get(highest); + final Optional disabledAbove = + highest + 1 < versions.size() + ? locationOf(versions.get(highest + 1), disabled) + : Optional.empty(); + bounds.add( + version( + version, + locationOf(version, maximums) + .or(() -> disabledAbove) + .orElseGet(() -> firstLocation(settings)))); + } + return bounds.stream(); + } + + /** The versions of a family set by the settings of a kind. */ + @Nonnull + private static List versionsOf( + @Nonnull Class kind, + @Nonnull Family family, + @Nonnull List settings) { + return settings.stream() + .filter(setting -> setting.is(kind)) + .flatMap(setting -> setting.getCollection().stream()) + .filter(TLS.class::isInstance) + .map(TLS.class::cast) + .filter(version -> family.includes(version.asString())) + .toList(); + } + + @Nonnull + private static Family familyOf(@Nonnull String version) { + return version.startsWith(DTLS.prefix()) ? DTLS : TLS_FAMILY; + } + + @Nonnull + private static Optional locationOf( + @Nonnull String version, @Nonnull List versions) { + return versions.stream() + .filter(node -> node.asString().equals(version)) + .map(TLS::getDetectionContext) + .findFirst(); + } + + @Nonnull + private static DetectionLocation firstLocation( + @Nonnull List settings) { + return settings.stream() + .flatMap(setting -> setting.getCollection().stream()) + .filter(TLS.class::isInstance) + .map(TLS.class::cast) + .map(TLS::getDetectionContext) + .findFirst() + .orElseThrow(); + } + + /** The protocol node of a version, e.g. {@code TLSv1.2} with its version {@code 1.2}. */ + @Nonnull + private static INode version(@Nonnull String name, @Nonnull DetectionLocation location) { + return new TLS(name, new Version(name.substring(name.indexOf('v') + 1), location)); + } +} 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..0b65a09f6 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 @@ -20,7 +20,9 @@ package com.ibm.mapper.reorganizer.rules; import com.ibm.mapper.ITranslator; +import com.ibm.mapper.model.Algorithm; import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.IAlgorithm; import com.ibm.mapper.model.INode; import com.ibm.mapper.model.MessageDigest; import com.ibm.mapper.model.PrivateKey; @@ -30,6 +32,7 @@ import com.ibm.mapper.model.algorithms.RSA; import com.ibm.mapper.model.functionality.Functionality; 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.UsualPerformActions; import com.ibm.mapper.reorganizer.builder.ReorganizerRuleBuilder; @@ -61,6 +64,62 @@ private SignatureReorganizer() { UsualPerformActions.performMergeParentAndChildOfSameKind( Signature.class)); + /** + * A signature or its verification found with no algorithm holding it, e.g. made with a key + * whose type is not known in the analyzed code: a signature scheme the operation names (e.g. + * RSA-PSS, selected by the padding set for it) holds the operation and what it uses, else a + * signature of an unknown scheme does, with the digest the operation uses. + */ + @Nonnull + public static final IReorganizerRule MAKE_SIGNATURE_OF_A_SIGNING_OPERATION_WITHOUT_SCHEME = + signatureOfOperationWithoutScheme(Sign.class); + + @Nonnull + public static final IReorganizerRule MAKE_SIGNATURE_OF_A_VERIFYING_OPERATION_WITHOUT_SCHEME = + signatureOfOperationWithoutScheme(Verify.class); + + @Nonnull + private static IReorganizerRule signatureOfOperationWithoutScheme( + @Nonnull Class operationClazz) { + return new ReorganizerRuleBuilder() + .createReorganizerRule() + .forNodeKind(operationClazz) + .withDetectionCondition((node, parent, roots) -> parent == null) + .perform( + (node, parent, roots) -> { + final INode signature = + schemeOf(node) + .orElseGet( + () -> + new Algorithm( + ITranslator.UNKNOWN, + Signature.class, + ((Functionality) node) + .getDetectionContext())); + node.removeChildOfType(signature.getKind()); + for (INode used : List.copyOf(node.getChildren().values())) { + signature.put(used); + node.removeChildOfType(used.getKind()); + } + signature.put(node); + final List newRoots = new LinkedList<>(roots); + newRoots.replaceAll(root -> root == node ? signature : root); + return newRoots; + }); + } + + /** The signature scheme an operation names, e.g. RSA-PSS selected by the padding. */ + @Nonnull + private static Optional schemeOf(@Nonnull INode operation) { + return operation.getChildren().values().stream() + .filter(IAlgorithm.class::isInstance) + .filter( + child -> + child.is(Signature.class) + || child.is(ProbabilisticSignatureScheme.class)) + .findFirst(); + } + @Nonnull public static final IReorganizerRule MERGE_SIGNATURE_PARENT_AND_CHILD = new ReorganizerRuleBuilder() diff --git a/mapper/src/test/java/com/ibm/mapper/ITranslatorTraverserTest.java b/mapper/src/test/java/com/ibm/mapper/ITranslatorTraverserTest.java new file mode 100644 index 000000000..7a420ba80 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/ITranslatorTraverserTest.java @@ -0,0 +1,319 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.Handler; +import com.ibm.engine.executive.IStatusReporting; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.CipherSuite; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.Version; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.mode.ECB; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +/** The node a translated detection store tree is rooted at. */ +class ITranslatorTraverserTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @SuppressWarnings("unchecked") + private final IDetectionRule rule = mock(IDetectionRule.class); + + @SuppressWarnings("unchecked") + private final IScanContext scanContext = mock(IScanContext.class); + + @SuppressWarnings("unchecked") + private final Handler handler = mock(Handler.class); + + @SuppressWarnings("unchecked") + private final IStatusReporting statusReporting = + mock(IStatusReporting.class); + + private final Map, INode> nodes = + new IdentityHashMap<>(); + + /** The parameter a store's node is the value of; -1 (the call itself) when absent. */ + private final Map, Integer> parameterIds = + new IdentityHashMap<>(); + + @Test + void rootWithoutNodeIsDescribedByTheArgumentsOfItsCall() { + // SSL_CTX_new(TLS_method()); SSL_CTX_set_ciphersuites(ctx, "...") + final DetectionStore context = store(null); + context.attach(store(suites())); + context.attach(0, store(new TLS(location))); + + final List roots = traverse(context); + + assertThat(roots).singleElement().isInstanceOf(TLS.class); + assertThat(roots.get(0).hasChildOfType(CipherSuiteCollection.class)).isPresent(); + } + + @Test + void rootWithNodeKeepsIt() { + final TLS tls = new TLS(location); + final DetectionStore context = store(tls); + context.attach(store(suites())); + context.attach(0, store(new Version("1.2", location))); + + final List roots = traverse(context); + + assertThat(roots).containsExactly(tls); + assertThat(tls.hasChildOfType(CipherSuiteCollection.class)).isPresent(); + assertThat(tls.hasChildOfType(Version.class)).isPresent(); + } + + @Test + void anotherValueOfTheRootGivesAnotherRoot() { + final AES cipher = new AES(location); + final DetectionStore selection = store(cipher); + selection.attach(store(new KeyLength(128, location))); + selection.attach(store(new KeyLength(256, location))); + + final List roots = traverse(selection); + + assertThat(roots).hasSize(2); + assertThat(roots.get(0)).isSameAs(cipher); + assertThat(roots.get(0).hasChildOfType(KeyLength.class).map(INode::asString)) + .contains("128"); + assertThat(roots.get(1).hasChildOfType(KeyLength.class).map(INode::asString)) + .contains("256"); + } + + @Test + void anotherValueBelowTheRootGivesACopyOfTheWholeTree() { + // AES_set_encrypt_key(k, 128, &key); AES_set_encrypt_key(k, 256, &key); + // AES_ecb_encrypt(in, out, &key, AES_ENCRYPT) + final AES operation = new AES(new ECB(location), location); + final DetectionStore encryption = store(operation); + final DetectionStore keySetup = store(new AES(location)); + encryption.attach(keySetup); + keySetup.attach(store(new KeyLength(128, location))); + keySetup.attach(store(new KeyLength(256, location))); + + final List roots = traverse(encryption); + + assertThat(roots).hasSize(2); + assertThat(roots.get(0)).isSameAs(operation); + assertThat(keyLengthOfKeySetup(roots.get(0))).isEqualTo("128"); + final INode alternative = roots.get(1); + assertThat(alternative).isInstanceOf(AES.class).isNotSameAs(operation); + assertThat(alternative.hasChildOfType(Mode.class)) + .get() + .isInstanceOf(ECB.class) + .isNotSameAs(operation.getChildren().get(Mode.class)); + assertThat(keyLengthOfKeySetup(alternative)).isEqualTo("256"); + } + + @Test + void anotherValueOfTheRootGivesATreeWithWhatIsFoundAfterIt() { + final AES cipher = new AES(location); + final DetectionStore selection = store(cipher); + selection.attach(store(new KeyLength(128, location))); + selection.attach(store(new KeyLength(256, location))); + selection.attach(store(new ECB(location))); + + final List roots = traverse(selection); + + assertThat(roots).hasSize(2); + assertThat(roots) + .allSatisfy(root -> assertThat(root.hasChildOfType(Mode.class)).isPresent()); + assertThat(roots.get(1).hasChildOfType(KeyLength.class).map(INode::asString)) + .contains("256"); + } + + @Test + void anotherValueKeepsWhatIsFoundBelowIt() { + final AES cipher = new AES(location); + final DetectionStore selection = store(cipher); + selection.attach(store(new KeyLength(128, location))); + final DetectionStore other = + store(new KeyLength(256, location)); + selection.attach(other); + other.attach(store(new Oid("1.2.3", location))); + + final List roots = traverse(selection); + + assertThat(roots).hasSize(2); + assertThat(roots.get(1).hasChildOfType(KeyLength.class)) + .get() + .satisfies(keyLength -> assertThat(keyLength.asString()).isEqualTo("256")) + .satisfies( + keyLength -> assertThat(keyLength.hasChildOfType(Oid.class)).isPresent()); + } + + @Test + void aCallBringingAnotherAlgorithmToAnObjectCreatedWithoutArgumentsIsAnotherUse() { + // ctx = EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, cipher, ...); + // EVP_DecryptInit_ex(ctx, other_cipher, ...); EVP_CIPHER_CTX_set_key_length(ctx, 32) + final DetectionStore context = store(null); + final AES first = new AES(new ECB(location), location); + final AES second = new AES(location); + context.attach(store(first)); + context.attach(store(second)); + context.attach(store(new KeyLength(256, location))); + + final List roots = traverse(context); + + assertThat(roots).containsExactly(first, second); + assertThat(first.hasChildOfType(KeyLength.class)).isEmpty(); + assertThat(second.hasChildOfType(KeyLength.class).map(INode::asString)).contains("256"); + } + + @Test + void anObjectDescribedByTheArgumentsOfItsCreationHasOneUse() { + // ctx = EVP_MAC_CTX_new(mac); EVP_MAC_init(ctx, key, len, params with a cipher) + final DetectionStore context = store(null); + final AES created = new AES(location); + context.attach(0, store(created)); + context.attach(store(new AES(new ECB(location), location))); + + final List roots = traverse(context); + + assertThat(roots).containsExactly(created); + } + + @Test + void anArgumentWithoutNodeConfiguresTheOperatedObjectWhereTheTranslatorSaysSo() { + // kdf = EVP_KDF_fetch(...); EVP_KDF_derive(ctx, out, 32, params with a mode) + final AES operated = new AES(location); + final DetectionStore fetch = store(operated); + final KeyLength keyLength = new KeyLength(256, location); + final DetectionStore derive = parameterStore(2, keyLength); + fetch.attach(derive); + derive.attach(3, store(new ECB(location))); + + traverse(fetch, true); + + assertThat(operated.hasChildOfType(Mode.class)).isPresent(); + assertThat(keyLength.hasChildOfType(Mode.class)).isEmpty(); + } + + @Test + void anArgumentWithoutNodeAddsToTheFirstNodeOfTheCallByDefault() { + final AES operated = new AES(location); + final DetectionStore fetch = store(operated); + final KeyLength keyLength = new KeyLength(256, location); + final DetectionStore derive = parameterStore(2, keyLength); + fetch.attach(derive); + derive.attach(3, store(new ECB(location))); + + traverse(fetch, false); + + assertThat(keyLength.hasChildOfType(Mode.class)).isPresent(); + } + + @Test + void aValueFoundAgainAddsWhatIsFoundBelowItToTheFirst() { + // EVP_DigestSignInit(mdctx, &pctx, md, NULL, key); + // EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING); + // EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, 32) + final AES operation = new AES(location); + final DetectionStore signing = store(operation); + final AES first = new AES(location); + signing.attach(store(first)); + final DetectionStore again = store(new AES(location)); + signing.attach(again); + again.attach(store(new KeyLength(256, location))); + + final List roots = traverse(signing); + + assertThat(roots).containsExactly(operation); + assertThat(operation.hasChildOfType(BlockCipher.class)).containsSame(first); + assertThat(first.hasChildOfType(KeyLength.class).map(INode::asString)).contains("256"); + } + + @Nonnull + private static String keyLengthOfKeySetup(@Nonnull INode operation) { + return operation + .hasChildOfType(BlockCipher.class) + .flatMap(keySetup -> keySetup.hasChildOfType(KeyLength.class)) + .map(INode::asString) + .orElse("none"); + } + + @Nonnull + private List traverse(@Nonnull DetectionStore root) { + return traverse(root, false); + } + + @Nonnull + private List traverse( + @Nonnull DetectionStore root, + boolean argumentsConfigureTheOperatedObject) { + return new ITranslator.Traverser<>( + root, + store -> { + final Map> translated = new HashMap<>(); + final INode node = nodes.get(store); + if (node != null) { + translated.put( + parameterIds.getOrDefault(store, -1), + new ArrayList<>(List.of(node))); + } + return translated; + }, + argumentsConfigureTheOperatedObject) + .translate(); + } + + @Nonnull + private DetectionStore parameterStore( + int parameterId, @Nonnull INode node) { + final DetectionStore store = store(node); + parameterIds.put(store, parameterId); + return store; + } + + @Nonnull + private DetectionStore store(INode node) { + final DetectionStore store = + new DetectionStore<>(0, rule, scanContext, handler, statusReporting); + if (node != null) { + nodes.put(store, node); + } + return store; + } + + @Nonnull + private CipherSuiteCollection suites() { + return new CipherSuiteCollection( + List.of(new CipherSuite("TLS_AES_128_GCM_SHA256", location))); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/jca/JcaPRNGMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/jca/JcaPRNGMapperTest.java index c3f0af66d..0ecfeec39 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/jca/JcaPRNGMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/jca/JcaPRNGMapperTest.java @@ -30,7 +30,8 @@ class JcaPRNGMapperTest { - @Disabled + @Disabled( + "JcaPRNGMapper returns Optional.empty() for NativePRNGBlocking — mapping not yet implemented (see JcaPRNGMapper line 51)") @Test void base() { DetectionLocation testDetectionLocation = diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCipherMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCipherMapperTest.java new file mode 100644 index 000000000..c05c08bf9 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCipherMapperTest.java @@ -0,0 +1,131 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.ChaCha20Poly1305; +import com.ibm.mapper.model.algorithms.DESX; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.mode.CBC; +import com.ibm.mapper.model.mode.CFB; +import com.ibm.mapper.model.mode.ECB; +import com.ibm.mapper.model.mode.OFB; +import com.ibm.mapper.model.padding.OAEP; +import com.ibm.mapper.model.padding.Raw; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslCipherMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslCipherMapper mapper = new OpenSslCipherMapper(); + + private INode parse(String name) { + return mapper.parse(name, location).orElseThrow(); + } + + @Test + void aesWithKeyLengthAndMode() { + INode aes = parse("aes-256-gcm"); + assertThat(aes).isInstanceOf(AES.class); + assertThat(aes.getChildren().get(KeyLength.class).asString()).isEqualTo("256"); + assertThat(aes.getChildren().get(Mode.class).asString()).isEqualTo("GCM"); + } + + @Test + void cfbWithItsFeedbackSize() { + INode aes = parse("AES-128-CFB8"); + assertThat(aes.getChildren().get(Mode.class)).isInstanceOf(CFB.class); + assertThat(aes.getChildren().get(Mode.class).asString()).isEqualTo("CFB8"); + } + + @Test + void legacyAesFunctionsWithTheirModeClass() { + assertThat(parse("AES-CBC").getChildren().get(Mode.class)).isInstanceOf(CBC.class); + assertThat(parse("AES-ECB").getChildren().get(Mode.class)).isInstanceOf(ECB.class); + assertThat(parse("AES-OFB").getChildren().get(Mode.class)).isInstanceOf(OFB.class); + assertThat(parse("AES-CFB128").getChildren().get(Mode.class)).isInstanceOf(CFB.class); + assertThat(parse("AES-IGE").getChildren().get(Mode.class).asString()).isEqualTo("IGE"); + } + + @Test + void legacyFunctionsTakeTheKeyLengthOfTheirKey() { + // EVP_bf_cbc: 128 bits unless set otherwise + assertThat(parse("BLOWFISH-CBC").getChildren().get(KeyLength.class).asString()) + .isEqualTo("128"); + // BF_cbc_encrypt: the key length of the key set up by BF_set_key + for (String legacy : List.of("BLOWFISH-CBC", "CAST5-ECB", "RC2-OFB", "RC5-CFB")) { + final INode cipher = mapper.parseLegacy(legacy, location).orElseThrow(); + assertThat(cipher.hasChildOfType(KeyLength.class)).as(legacy).isEmpty(); + assertThat(cipher.hasChildOfType(Mode.class)).as(legacy).isPresent(); + } + // a cipher whose key length is fixed keeps it + assertThat( + mapper.parseLegacy("DES-CBC", location) + .orElseThrow() + .getChildren() + .get(KeyLength.class) + .asString()) + .isEqualTo("56"); + } + + @Test + void desx() { + INode desx = parse("DESX-CBC"); + assertThat(desx).isInstanceOf(DESX.class); + assertThat(desx.asString()).isEqualTo("DESX-184-CBC"); + } + + @Test + void aeadAndPublicKeyCiphers() { + assertThat(parse("ChaCha20-Poly1305")).isInstanceOf(ChaCha20Poly1305.class); + INode rsaOaep = parse("RSA-OAEP"); + assertThat(rsaOaep).isInstanceOf(RSA.class); + assertThat(rsaOaep.getChildren().get(Padding.class)).isInstanceOf(OAEP.class); + } + + @Test + void unknownAndNullNames() { + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } + + @Test + void rsaWithoutPaddingHasRawPadding() { + final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + assertThat(new OpenSslCipherMapper().parse("RSA-NO-PADDING", location)) + .get() + .satisfies( + rsa -> + assertThat(rsa.hasChildOfType(Padding.class)) + .get() + .isInstanceOf(Raw.class)); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCipherStringMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCipherStringMapperTest.java new file mode 100644 index 000000000..fadf0a306 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCipherStringMapperTest.java @@ -0,0 +1,55 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslCipherStringMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslCipherStringMapper mapper = new OpenSslCipherStringMapper(); + + @Test + void suitesNamedInTheString() { + INode node = + mapper.parse( + "ECDHE-RSA-AES256-GCM-SHA384:!aNULL:HIGH:@SECLEVEL=2," + + " TLS_AES_128_GCM_SHA256", + location) + .orElseThrow(); + assertThat(node).isInstanceOf(CipherSuiteCollection.class); + assertThat(((CipherSuiteCollection) node).getCollection()) + .extracting(INode::asString) + .containsExactly("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"); + } + + @Test + void keywordsAndExclusionsOnly() { + assertThat(mapper.parse("HIGH:!aNULL:-MD5:+RC4", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCurveMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCurveMapperTest.java new file mode 100644 index 000000000..81c97a7d7 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslCurveMapperTest.java @@ -0,0 +1,179 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.mapper.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.curves.Brainpoolp256r1; +import com.ibm.mapper.model.curves.Secp192r1; +import com.ibm.mapper.model.curves.Secp256k1; +import com.ibm.mapper.model.curves.Secp256r1; +import com.ibm.mapper.model.curves.Secp521r1; +import com.ibm.mapper.model.curves.Sect163r2; +import com.ibm.mapper.model.curves.Sect283k1; +import com.ibm.mapper.model.curves.Sect571r1; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class OpenSslCurveMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslCurveMapper mapper = new OpenSslCurveMapper(); + + @Test + void nistSecAndX962NamesOfACurve() { + assertThat(mapper.parse("P-256", location)).containsInstanceOf(Secp256r1.class); + assertThat(mapper.parse("secp256r1", location)).containsInstanceOf(Secp256r1.class); + assertThat(mapper.parse("prime256v1", location)).containsInstanceOf(Secp256r1.class); + assertThat(mapper.parse("P-521", location)).containsInstanceOf(Secp521r1.class); + assertThat(mapper.parse("prime192v1", location)).containsInstanceOf(Secp192r1.class); + } + + @Test + void koblitzAndBrainpoolCurves() { + assertThat(mapper.parse("secp256k1", location)).containsInstanceOf(Secp256k1.class); + assertThat(mapper.parse("brainpoolP256r1", location)) + .containsInstanceOf(Brainpoolp256r1.class); + } + + @Test + void binaryCurvesByNistAndSecNames() { + assertThat(mapper.parse("sect283k1", location)).containsInstanceOf(Sect283k1.class); + assertThat(mapper.parse("K-283", location)).containsInstanceOf(Sect283k1.class); + assertThat(mapper.parse("sect163r2", location)).containsInstanceOf(Sect163r2.class); + assertThat(mapper.parse("B-163", location)).containsInstanceOf(Sect163r2.class); + assertThat(mapper.parse("B-571", location)).containsInstanceOf(Sect571r1.class); + } + + @Test + void unknownNames() { + assertThat(mapper.parse("P256", location)).isEmpty(); + assertThat(mapper.parse("X25519", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } + + /** The named curves of OpenSSL 3 ({@code openssl ecparam -list_curves}). */ + @ParameterizedTest + @ValueSource( + strings = { + "prime192v1", + "prime192v2", + "prime192v3", + "prime239v1", + "prime239v2", + "prime239v3", + "prime256v1", + "c2pnb163v1", + "c2pnb163v2", + "c2pnb163v3", + "c2pnb176v1", + "c2tnb191v1", + "c2tnb191v2", + "c2tnb191v3", + "c2pnb208w1", + "c2tnb239v1", + "c2tnb239v2", + "c2tnb239v3", + "c2pnb272w1", + "c2pnb304w1", + "c2tnb359v1", + "c2pnb368w1", + "c2tnb431r1", + "secp112r1", + "secp112r2", + "secp128r1", + "secp128r2", + "secp160k1", + "secp160r1", + "secp160r2", + "secp192k1", + "secp224k1", + "secp224r1", + "secp256k1", + "secp384r1", + "secp521r1", + "sect113r1", + "sect113r2", + "sect131r1", + "sect131r2", + "sect163k1", + "sect163r1", + "sect163r2", + "sect193r1", + "sect193r2", + "sect233k1", + "sect233r1", + "sect239k1", + "sect283k1", + "sect283r1", + "sect409k1", + "sect409r1", + "sect571k1", + "sect571r1", + "wap-wsg-idm-ecid-wtls1", + "wap-wsg-idm-ecid-wtls3", + "wap-wsg-idm-ecid-wtls4", + "wap-wsg-idm-ecid-wtls5", + "wap-wsg-idm-ecid-wtls6", + "wap-wsg-idm-ecid-wtls7", + "wap-wsg-idm-ecid-wtls8", + "wap-wsg-idm-ecid-wtls9", + "wap-wsg-idm-ecid-wtls10", + "wap-wsg-idm-ecid-wtls11", + "wap-wsg-idm-ecid-wtls12", + "Oakley-EC2N-3", + "Oakley-EC2N-4", + "brainpoolP160r1", + "brainpoolP160t1", + "brainpoolP192r1", + "brainpoolP192t1", + "brainpoolP224r1", + "brainpoolP224t1", + "brainpoolP256r1", + "brainpoolP256t1", + "brainpoolP320r1", + "brainpoolP320t1", + "brainpoolP384r1", + "brainpoolP384t1", + "brainpoolP512r1", + "brainpoolP512t1", + "SM2" + }) + void everyNamedCurveOfOpenSsl(String name) { + assertThat(mapper.parse(name, location)).as(name).isPresent(); + } + + @Test + void aCurveWithoutAModelOfItsOwnIsNamedByItsShortName() { + assertThat(mapper.parse("PRIME239V1", location)) + .get() + .extracting(EllipticCurve::asString) + .isEqualTo("prime239v1"); + assertThat(mapper.parse("wap-wsg-idm-ecid-wtls7", location)) + .get() + .extracting(EllipticCurve::asString) + .isEqualTo("wap-wsg-idm-ecid-wtls7"); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKdfMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKdfMapperTest.java new file mode 100644 index 000000000..0ad154f98 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKdfMapperTest.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.mapper.mapper.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.PasswordBasedKeyDerivationFunction; +import com.ibm.mapper.model.algorithms.Argon2; +import com.ibm.mapper.model.algorithms.HKDF; +import com.ibm.mapper.model.algorithms.HMACDRBGKDF; +import com.ibm.mapper.model.algorithms.KRB5KDF; +import com.ibm.mapper.model.algorithms.PBKDF2; +import com.ibm.mapper.model.algorithms.PKCS12KDF; +import com.ibm.mapper.model.algorithms.PKCS12PBE; +import com.ibm.mapper.model.algorithms.PVKKDF; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslKdfMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslKdfMapper mapper = new OpenSslKdfMapper(); + + @Test + void namesAcceptedByEvpKdfFetch() { + assertThat(mapper.parse("pbkdf2", location).orElseThrow()).isInstanceOf(PBKDF2.class); + assertThat(mapper.parse("HKDF-SHA256", location).orElseThrow()).isInstanceOf(HKDF.class); + assertThat(mapper.parse("HKDF-SHA256", location).orElseThrow().asString()) + .isEqualTo("HKDF-SHA-256"); + assertThat(mapper.parse("ARGON2ID", location).orElseThrow().asString()) + .isEqualTo("Argon2id"); + assertThat(mapper.parse("ARGON2ID", location).orElseThrow()).isInstanceOf(Argon2.class); + } + + @Test + void kdfsWithTheirOwnModel() { + assertThat(mapper.parse("PKCS12KDF", location).orElseThrow()).isInstanceOf(PKCS12KDF.class); + assertThat(mapper.parse("PKCS12KDF", location).orElseThrow().getKind()) + .isEqualTo(PasswordBasedKeyDerivationFunction.class); + assertThat(mapper.parse("PVKKDF", location).orElseThrow()).isInstanceOf(PVKKDF.class); + assertThat(mapper.parse("KRB5KDF", location).orElseThrow()).isInstanceOf(KRB5KDF.class); + assertThat(mapper.parse("HMAC-DRBG-KDF", location).orElseThrow()) + .isInstanceOf(HMACDRBGKDF.class); + assertThat(mapper.parse("HMAC-DRBG-KDF", location).orElseThrow().getKind()) + .isEqualTo(KeyDerivationFunction.class); + } + + @Test + void passwordBasedEncryptionSchemes() { + assertThat(mapper.parse("PBE-SHA1-3DES", location).orElseThrow()) + .isInstanceOf(PKCS12PBE.class); + } + + @Test + void noKdfAndUnknownNames() { + assertThat(mapper.parse("NONE", location)).isEmpty(); + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKemMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKemMapperTest.java new file mode 100644 index 000000000..a2754cecf --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKemMapperTest.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.mapper.mapper.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyDerivationFunction; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.model.algorithms.DHKEM; +import com.ibm.mapper.model.algorithms.HPKE; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.RSASVE; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslKemMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslKemMapper mapper = new OpenSslKemMapper(); + + @Test + void rsaEncapsulationIsRsasve() { + assertThat(mapper.parse("RSA", location).orElseThrow()).isInstanceOf(RSASVE.class); + } + + @Test + void ellipticCurveEncapsulationIsDhkem() { + INode dhkem = mapper.parse("X25519", location).orElseThrow(); + assertThat(dhkem).isInstanceOf(DHKEM.class); + assertThat(dhkem.getKind()).isEqualTo(KeyEncapsulationMechanism.class); + assertThat(dhkem.getChildren().get(KeyAgreement.class).asString()).isEqualTo("x25519"); + } + + @Test + void otherNamesAreKeyAgreementNames() { + assertThat(mapper.parse("ML-KEM-512", location).orElseThrow()).isInstanceOf(MLKEM.class); + } + + @Test + void hpkeSuite() { + HPKE hpke = mapper.parseHpkeSuite("P-256,HKDF-SHA256,AES-128-GCM", location).orElseThrow(); + INode dhkem = hpke.getChildren().get(KeyEncapsulationMechanism.class); + assertThat(dhkem).isInstanceOf(DHKEM.class); + assertThat( + dhkem.getChildren() + .get(KeyAgreement.class) + .getChildren() + .get(EllipticCurve.class) + .asString()) + .isEqualTo("secp256r1"); + assertThat(hpke.getChildren().get(KeyDerivationFunction.class).asString()) + .isEqualTo("HKDF-SHA-256"); + assertThat(hpke.getChildren().get(BlockCipher.class)).isNotNull(); + } + + @Test + void hpkeSuiteWithExportOnlyAeadHasNoCipher() { + HPKE hpke = mapper.parseHpkeSuite("X25519,HKDF-SHA256,EXPORTONLY", location).orElseThrow(); + assertThat(hpke.hasChildOfType(BlockCipher.class)).isEmpty(); + assertThat(hpke.hasChildOfType(AuthenticatedEncryption.class)).isEmpty(); + } + + @Test + void malformedSuites() { + assertThat(mapper.parseHpkeSuite("X25519,HKDF-SHA256", location)).isEmpty(); + assertThat(mapper.parseHpkeSuite("UNKNOWN,HKDF-SHA256,AES-128-GCM", location)).isEmpty(); + assertThat(mapper.parseHpkeSuite(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKeyAgreementMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKeyAgreementMapperTest.java new file mode 100644 index 000000000..96031f7ae --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKeyAgreementMapperTest.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.mapper.mapper.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.algorithms.DH; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.MLKEM; +import com.ibm.mapper.model.algorithms.SM2; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class OpenSslKeyAgreementMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslKeyAgreementMapper mapper = new OpenSslKeyAgreementMapper(); + + private INode parse(String name) { + return mapper.parse(name, location).orElseThrow(); + } + + @ParameterizedTest + @CsvSource({"DH-2048,2048", "DH-3072,3072", "dh-4096,4096"}) + void ffdheGroupsKeepTheirSize(String name, String keyLength) { + INode dh = parse(name); + assertThat(dh).isInstanceOf(DH.class); + assertThat(dh.getKind()).isEqualTo(KeyAgreement.class); + assertThat(dh.getChildren().get(KeyLength.class).asString()).isEqualTo(keyLength); + } + + @ParameterizedTest + @CsvSource({ + "ECDH-P256,secp256r1", + "ECDH-P384,secp384r1", + "ECDH-P521,secp521r1", + "ECDH-SECP256K1,secp256k1" + }) + void ecdhKeepsItsCurve(String name, String curve) { + INode ecdh = parse(name); + assertThat(ecdh).isInstanceOf(ECDH.class); + assertThat(ecdh.getChildren().get(EllipticCurve.class).asString()).isEqualTo(curve); + } + + @Test + void sm2KeyExchangeIsAKeyAgreement() { + INode sm2 = parse("SM2"); + assertThat(sm2).isInstanceOf(SM2.class); + assertThat(sm2.getKind()).isEqualTo(KeyAgreement.class); + } + + @Test + void curveAndPostQuantumAlgorithms() { + assertThat(parse("X25519")).isInstanceOf(X25519.class); + assertThat(parse("ML-KEM-768")).isInstanceOf(MLKEM.class); + } + + @Test + void unknownAndNullNames() { + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKeyMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKeyMapperTest.java new file mode 100644 index 000000000..f900680b5 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslKeyMapperTest.java @@ -0,0 +1,95 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.mapper.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +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.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SPHINCSPlus; +import com.ibm.mapper.model.curves.Brainpoolp256r1; +import com.ibm.mapper.model.curves.Secp256r1; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslKeyMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslKeyMapper mapper = new OpenSslKeyMapper(); + + private INode parse(String name) { + return mapper.parse(name, location).orElseThrow(); + } + + @Test + void ecKeyOnANamedCurve() { + INode ec = parse("EC-P-256"); + assertThat(ec.asString()).isEqualTo("EC-secp256r1"); + assertThat(ec.getChildren().get(EllipticCurve.class)).isInstanceOf(Secp256r1.class); + } + + @Test + void rsaAndDsaKeepAnyBitLength() { + INode rsa = parse("RSA-3000"); + assertThat(rsa).isInstanceOf(RSA.class); + assertThat(rsa.getChildren().get(KeyLength.class).asString()).isEqualTo("3000"); + INode dsa = parse("DSA-2048"); + assertThat(dsa).isInstanceOf(DSA.class); + assertThat(dsa.getChildren().get(KeyLength.class).asString()).isEqualTo("2048"); + } + + @Test + void rsaPssIsNotAnRsaKeyLength() { + assertThat(parse("RSA-PSS")).isInstanceOf(RSAssaPSS.class); + } + + @Test + void dhGroupsKeepTheirPrimeSize() { + assertThat(parse("DH-3072").getChildren().get(KeyLength.class).asString()) + .isEqualTo("3072"); + assertThat(parse("DH-2048-224")).isInstanceOf(DH.class); + } + + @Test + void postQuantumParameterSets() { + assertThat(parse("SLH-DSA-SHAKE-256F")).isInstanceOf(SPHINCSPlus.class); + } + + @Test + void curves() { + assertThat(mapper.parseCurve("ec-brainpoolP256r1", location)) + .containsInstanceOf(Brainpoolp256r1.class); + assertThat(mapper.parseCurve("EC-UNKNOWN", location)).isEmpty(); + } + + @Test + void invalidBitLengthsAndUnknownNames() { + assertThat(mapper.parse("RSA-0", location)).isEmpty(); + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslMacMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslMacMapperTest.java new file mode 100644 index 000000000..fe0f14bcb --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslMacMapperTest.java @@ -0,0 +1,74 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.CMAC; +import com.ibm.mapper.model.algorithms.GMAC; +import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslMacMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslMacMapper mapper = new OpenSslMacMapper(); + + private INode parse(String name) { + return mapper.parse(name, location).orElseThrow(); + } + + @Test + void hmacAndCmacWithTheirAlgorithm() { + assertThat(parse("HMAC-SHA256")).isInstanceOf(HMAC.class); + assertThat(parse("HMAC-SHA256").asString()).isEqualTo("HMAC-SHA-256"); + assertThat(parse("CMAC-AES-128")).isInstanceOf(CMAC.class); + } + + @Test + void gmacWithAKnownCipherIsTheCipherInGmacMode() { + INode gmac = parse("GMAC-AES-128"); + assertThat(gmac).isInstanceOf(AES.class); + assertThat(gmac.getKind()).isEqualTo(Mac.class); + assertThat(gmac.getChildren().get(Mode.class)) + .isInstanceOf(com.ibm.mapper.model.mode.GMAC.class); + assertThat(gmac.asString()).isEqualTo("AES-128-GMAC"); + } + + @Test + void gmacWithoutCipher() { + INode gmac = parse("GMAC"); + assertThat(gmac).isInstanceOf(GMAC.class); + assertThat(gmac.asString()).isEqualTo("GMAC"); + } + + @Test + void unknownAndNullNames() { + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslMessageDigestMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslMessageDigestMapperTest.java new file mode 100644 index 000000000..2dcf77464 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslMessageDigestMapperTest.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.mapper.mapper.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.DigestSize; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.algorithms.MD5SHA1; +import com.ibm.mapper.model.algorithms.MDC2; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.shake.SHAKE; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslMessageDigestMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslMessageDigestMapper mapper = new OpenSslMessageDigestMapper(); + + @Test + void namesSharedWithTheJca() { + assertThat(mapper.parse("SHA256", location).orElseThrow()).isInstanceOf(SHA2.class); + } + + @Test + void openSslOnlyDigests() { + assertThat(mapper.parse("shake128", location).orElseThrow()).isInstanceOf(SHAKE.class); + MessageDigest mdc2 = mapper.parse("MDC2", location).orElseThrow(); + assertThat(mdc2).isInstanceOf(MDC2.class); + assertThat(mdc2.getChildren().get(DigestSize.class).asString()).isEqualTo("128"); + } + + @Test + void theConcatenatedTlsDigest() { + MessageDigest md5Sha1 = mapper.parse("MD5-SHA1", location).orElseThrow(); + assertThat(md5Sha1).isInstanceOf(MD5SHA1.class); + assertThat(md5Sha1.asString()).isEqualTo("MD5-SHA1"); + assertThat(md5Sha1.getChildren().get(DigestSize.class).asString()).isEqualTo("288"); + } + + @Test + void theNullDigestAndUnknownNames() { + assertThat(mapper.parse("NULL", location)).isEmpty(); + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslPRNGMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslPRNGMapperTest.java new file mode 100644 index 000000000..d67bd3139 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslPRNGMapperTest.java @@ -0,0 +1,58 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.PseudorandomNumberGenerator; +import com.ibm.mapper.model.algorithms.CTRDRBG; +import com.ibm.mapper.model.algorithms.HMACDRBG; +import com.ibm.mapper.model.algorithms.HashDRBG; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslPRNGMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslPRNGMapper mapper = new OpenSslPRNGMapper(); + + @Test + void drbgs() { + assertThat(mapper.parse("CTR-DRBG", location).orElseThrow()).isInstanceOf(CTRDRBG.class); + assertThat(mapper.parse("hash-drbg", location).orElseThrow()).isInstanceOf(HashDRBG.class); + assertThat(mapper.parse("HMAC-DRBG", location).orElseThrow()).isInstanceOf(HMACDRBG.class); + } + + @Test + void entropySources() { + assertThat(mapper.parse("SEED-SRC", location).orElseThrow().getKind()) + .isEqualTo(PseudorandomNumberGenerator.class); + assertThat(mapper.parse("RAND", location).orElseThrow().asString()).isEqualTo("RAND"); + } + + @Test + void seedingOperationsAndUnknownNames() { + assertThat(mapper.parse("RAND-SEED", location)).isEmpty(); + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslRsaSignatureSchemesTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslRsaSignatureSchemesTest.java new file mode 100644 index 000000000..13620bf28 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslRsaSignatureSchemesTest.java @@ -0,0 +1,75 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.ANSIX931; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.padding.PKCS1; +import com.ibm.mapper.model.padding.Raw; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslRsaSignatureSchemesTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + + @Test + void pkcs1v15() { + final INode scheme = OpenSslRsaSignatureSchemes.pkcs1v15(location); + assertThat(scheme).isInstanceOf(RSA.class); + assertThat(scheme.getKind()).isEqualTo(Signature.class); + assertThat(scheme.hasChildOfType(Padding.class)).get().isInstanceOf(PKCS1.class); + } + + @Test + void x931() { + final INode scheme = OpenSslRsaSignatureSchemes.x931(location); + assertThat(scheme).isInstanceOf(ANSIX931.class); + assertThat(scheme.getKind()).isEqualTo(Signature.class); + } + + @Test + void withoutPadding() { + final INode scheme = OpenSslRsaSignatureSchemes.withoutPadding(location); + assertThat(scheme).isInstanceOf(RSA.class); + assertThat(scheme.getKind()).isEqualTo(Signature.class); + assertThat(scheme.hasChildOfType(Padding.class)).get().isInstanceOf(Raw.class); + } + + @Test + void theCipherAndSignatureMappersGiveTheSameScheme() { + for (String padding : List.of("RSA-PKCS1", "RSA-X931")) { + final INode fromCipher = + new OpenSslCipherMapper().parse(padding, location).orElseThrow(); + final INode fromSignature = + new OpenSslSignatureAlgorithmMapper().parse(padding, location).orElseThrow(); + assertThat(fromCipher.getClass()).as(padding).isEqualTo(fromSignature.getClass()); + assertThat(fromCipher.asString()).as(padding).isEqualTo(fromSignature.asString()); + assertThat(fromCipher.getKind()).as(padding).isEqualTo(Signature.class); + } + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslSignatureAlgorithmMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslSignatureAlgorithmMapperTest.java new file mode 100644 index 000000000..fbeed157a --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslSignatureAlgorithmMapperTest.java @@ -0,0 +1,115 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.Padding; +import com.ibm.mapper.model.ParameterSetIdentifier; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.DSA; +import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.Ed25519; +import com.ibm.mapper.model.algorithms.MD5SHA1; +import com.ibm.mapper.model.algorithms.MLDSA; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.SHA3; +import com.ibm.mapper.model.algorithms.SPHINCSPlus; +import com.ibm.mapper.model.padding.PKCS1; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslSignatureAlgorithmMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslSignatureAlgorithmMapper mapper = new OpenSslSignatureAlgorithmMapper(); + + private INode parse(String name) { + return mapper.parse(name, location).orElseThrow(); + } + + @Test + void rsaWithDigest() { + INode rsa = parse("RSA-SHA256"); + assertThat(rsa).isInstanceOf(RSA.class); + assertThat(rsa.getKind()).isEqualTo(Signature.class); + assertThat(rsa.getChildren().get(MessageDigest.class)).isInstanceOf(SHA2.class); + assertThat(rsa.getChildren().get(MessageDigest.class).asString()).isEqualTo("SHA-256"); + } + + @Test + void rsaWithTheConcatenatedTlsDigest() { + INode rsa = parse("RSA-MD5-SHA1"); + assertThat(rsa.getChildren().get(MessageDigest.class)).isInstanceOf(MD5SHA1.class); + } + + @Test + void rsaPssWithDigestIsNotAPkcs1Signature() { + INode rsaPss = parse("rsa-pss-sha384"); + assertThat(rsaPss).isInstanceOf(RSAssaPSS.class); + assertThat(rsaPss.getChildren().get(MessageDigest.class).asString()).isEqualTo("SHA-384"); + } + + @Test + void dsaAndEcdsaWithDigest() { + assertThat(parse("DSA-SHA224")).isInstanceOf(DSA.class); + assertThat(parse("DSA-SHA224").getChildren().get(MessageDigest.class).asString()) + .isEqualTo("SHA-224"); + INode ecdsa = parse("ECDSA-SHA3-256"); + assertThat(ecdsa).isInstanceOf(ECDSA.class); + assertThat(ecdsa.getChildren().get(MessageDigest.class)).isInstanceOf(SHA3.class); + } + + @Test + void aSchemeWithAnUnknownDigestKeepsTheScheme() { + INode rsa = parse("RSA-UNKNOWNDIGEST"); + assertThat(rsa).isInstanceOf(RSA.class); + assertThat(rsa.hasChildOfType(MessageDigest.class)).isEmpty(); + } + + @Test + void rsaSchemesSelectedByPadding() { + assertThat(parse("RSA-PKCS1").getChildren().get(Padding.class)).isInstanceOf(PKCS1.class); + assertThat(parse("RSA-X931").asString()).isEqualTo("ANSI X9.31"); + assertThat(parse("RSA-NO-PADDING").getKind()).isEqualTo(Signature.class); + } + + @Test + void edwardsAndPostQuantumSchemes() { + assertThat(parse("ED25519")).isInstanceOf(Ed25519.class); + assertThat(parse("ML-DSA-65")).isInstanceOf(MLDSA.class); + INode slhDsa = parse("SLH-DSA-SHA2-128s"); + assertThat(slhDsa).isInstanceOf(SPHINCSPlus.class); + assertThat(slhDsa.getChildren().get(ParameterSetIdentifier.class).asString()) + .isEqualTo("SHA2-128S"); + } + + @Test + void unknownAndNullNames() { + assertThat(mapper.parse("XYZ", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslSrtpProfileMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslSrtpProfileMapperTest.java new file mode 100644 index 000000000..c94428b16 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/openssl/OpenSslSrtpProfileMapperTest.java @@ -0,0 +1,54 @@ +/* + * 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.openssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class OpenSslSrtpProfileMapperTest { + + private final DetectionLocation location = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "OpenSSL"); + private final OpenSslSrtpProfileMapper mapper = new OpenSslSrtpProfileMapper(); + + @Test + void profilesOpenSslAccepts() { + INode srtp = + mapper.parse("SRTP_AES128_CM_SHA1_80:BOGUS:SRTP_AEAD_AES_256_GCM", location) + .orElseThrow(); + assertThat(srtp.asString()).isEqualTo("SRTP"); + CipherSuiteCollection profiles = + (CipherSuiteCollection) srtp.getChildren().get(CipherSuiteCollection.class); + assertThat(profiles.getCollection()) + .extracting(INode::asString) + .containsExactly("SRTP_AES128_CM_SHA1_80", "SRTP_AEAD_AES_256_GCM"); + } + + @Test + void noKnownProfile() { + assertThat(mapper.parse("BOGUS", location)).isEmpty(); + assertThat(mapper.parse(null, location)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/AuthenticationAlgorithmMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/AuthenticationAlgorithmMapperTest.java index 854eac46f..e709d6ebd 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/AuthenticationAlgorithmMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/AuthenticationAlgorithmMapperTest.java @@ -29,12 +29,12 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class AuthenticationAlgorithmMapperTest { +class AuthenticationAlgorithmMapperTest { @Test - public void test() { + void test() { final DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapperTest.java index c3dfc6289..37db09fbd 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/CipherSuiteMapperTest.java @@ -29,16 +29,52 @@ import com.ibm.mapper.model.algorithms.DH; import com.ibm.mapper.model.algorithms.DSA; import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.ascon.Ascon128; +import com.ibm.mapper.model.algorithms.ascon.AsconHash; +import com.ibm.mapper.model.mode.GCM; import com.ibm.mapper.utils.DetectionLocation; import com.ibm.mapper.utils.Utils; import java.util.List; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; -public class CipherSuiteMapperTest { +class CipherSuiteMapperTest { + + @ParameterizedTest + @CsvSource({ + "RC4-MD5, TLS_RSA_WITH_RC4_128_MD5", + "EXP-RC4-MD5, TLS_RSA_EXPORT_WITH_RC4_40_MD5", + "EDH-RSA-DES-CBC-SHA, TLS_DHE_RSA_WITH_DES_CBC_SHA", + "DHE-RSA-DES-CBC-SHA, TLS_DHE_RSA_WITH_DES_CBC_SHA", + "KRB5-DES-CBC-SHA, TLS_KRB5_WITH_DES_CBC_SHA", + "ECDH-RSA-AES128-GCM-SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" + }) + void anOpenSslNameMissingFromTheDataNamesItsSuite(String opensslName, String ianaName) { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final Optional node = + new CipherSuiteMapper().parse(opensslName, testDetectionLocation); + + assertThat(node).get().extracting(INode::asString).isEqualTo(ianaName); + assertThat(((CipherSuite) node.get()).getAssetCollection()).isPresent(); + } @Test - public void test1() { + void theFortezzaNameOfACodeAssignedToAnotherSuiteNamesNoSuite() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + assertThat(new CipherSuiteMapper().parse("FZA-RC4-SHA", testDetectionLocation)) + .get() + .extracting(INode::asString) + .isEqualTo("FZA-RC4-SHA"); + } + + @Test + void test1() { DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); @@ -70,10 +106,62 @@ public void test1() { } @Test - public void findingTest() { + void asconAeadSuite() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final CipherSuiteMapper mapper = new CipherSuiteMapper(); + final Optional node = + mapper.parse("TLS_ASCONAEAD128_SHA256", testDetectionLocation); + assertThat(node).isPresent(); + assertThat(node.get().is(CipherSuite.class)).isTrue(); + final CipherSuite cipherSuite = (CipherSuite) node.get(); + + assertThat(cipherSuite.getIdentifierCollection()).isPresent(); + assertThat( + cipherSuite.getIdentifierCollection().get().getCollection().stream() + .map(Identifier::asString)) + .contains("0x00", "0x6F"); + + assertThat(cipherSuite.getAssetCollection()).isPresent(); + assertThat(cipherSuite.getAssetCollection().get().getCollection()) + .contains( + new Ascon128(testDetectionLocation), new SHA2(256, testDetectionLocation)); + } + + @Test + void asconHashSuite() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final CipherSuiteMapper mapper = new CipherSuiteMapper(); + final Optional node = + mapper.parse("TLS_AES_128_GCM_ASCONHASH256", testDetectionLocation); + assertThat(node).isPresent(); + assertThat(node.get().is(CipherSuite.class)).isTrue(); + final CipherSuite cipherSuite = (CipherSuite) node.get(); + + assertThat(cipherSuite.getIdentifierCollection()).isPresent(); + assertThat( + cipherSuite.getIdentifierCollection().get().getCollection().stream() + .map(Identifier::asString)) + .contains("0x00", "0x70"); + + assertThat(cipherSuite.getAssetCollection()).isPresent(); + assertThat(cipherSuite.getAssetCollection().get().getCollection()) + .contains( + new AES(128, new GCM(testDetectionLocation), testDetectionLocation), + new AsconHash(testDetectionLocation)); + } + + @Test + void findingTest() { assertThat(CipherSuiteMapper.findCipherSuite("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256")) .isPresent(); assertThat(CipherSuiteMapper.findCipherSuite("TLS_DHE_DSS_AES_256_CBC_SHA256")).isPresent(); assertThat(CipherSuiteMapper.findCipherSuite("DHE-DSS-AES256-SHA256")).isPresent(); + assertThat(CipherSuiteMapper.findCipherSuite("TLS_ASCONAEAD128_SHA256")).isPresent(); + assertThat(CipherSuiteMapper.findCipherSuite("TLS_AES_128_GCM_ASCONHASH256")).isPresent(); + assertThat(CipherSuiteMapper.findCipherSuite("TLS_AES_128_CCM_ASCONHASH256")).isPresent(); } } diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapperTest.java index 341d13a0a..3d6a92333 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/EncryptionAlgorithmMapperTest.java @@ -29,12 +29,12 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class EncryptionAlgorithmMapperTest { +class EncryptionAlgorithmMapperTest { @Test - public void test() { + void test() { final DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapperTest.java index 2471eb9de..251ab0eaa 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/HashAlgorithmMapperTest.java @@ -29,12 +29,12 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class HashAlgorithmMapperTest { +class HashAlgorithmMapperTest { @Test - public void test() { + void test() { final DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/KeyExchangeAlgorithmMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/KeyExchangeAlgorithmMapperTest.java index 111df8f7b..459f8dd03 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/KeyExchangeAlgorithmMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/KeyExchangeAlgorithmMapperTest.java @@ -29,12 +29,12 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class KeyExchangeAlgorithmMapperTest { +class KeyExchangeAlgorithmMapperTest { @Test - public void test() { + void test() { final DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/OpenSslSignatureMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/OpenSslSignatureMapperTest.java new file mode 100644 index 000000000..05952a542 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/OpenSslSignatureMapperTest.java @@ -0,0 +1,146 @@ +/* + * 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.ssl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.Ed25519; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +public class OpenSslSignatureMapperTest { + + private static final DetectionLocation TEST_LOCATION = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + @Test + public void legacyAlgPlusHashFormResolvesToTheAlgorithm() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + final Optional node = mapper.parse("ECDSA+SHA256", TEST_LOCATION); + + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(ECDSA.class); + } + + @Test + public void bareUppercaseNameResolvesToTheAlgorithm() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + assertThat(mapper.parse("ED25519", TEST_LOCATION).get()).isInstanceOf(Ed25519.class); + assertThat(mapper.parse("RSA-PSS", TEST_LOCATION).get()) + .isInstanceOf(ProbabilisticSignatureScheme.class); + } + + @Test + public void tls13RsaPssWireFormatNameResolvesToRsaPss() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + final Optional node = mapper.parse("rsa_pss_rsae_sha256", TEST_LOCATION); + + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(ProbabilisticSignatureScheme.class); + } + + @Test + public void tls13RsaPssPssWireFormatNameResolvesToRsaPss() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + assertThat(mapper.parse("rsa_pss_pss_sha384", TEST_LOCATION).get()) + .isInstanceOf(ProbabilisticSignatureScheme.class); + } + + @Test + public void tls12RsaPkcs1WireFormatNameResolvesToRsa() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + assertThat(mapper.parse("rsa_pkcs1_sha256", TEST_LOCATION).get()).isInstanceOf(RSA.class); + } + + @Test + public void tls13EcdsaWireFormatNameResolvesToEcdsa() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + final Optional node = + mapper.parse("ecdsa_secp256r1_sha256", TEST_LOCATION); + + assertThat(node).isPresent(); + assertThat(node.get()).isInstanceOf(ECDSA.class); + } + + @Test + public void rsaWithAHashIsAPkcs1v15SignatureWithThatDigest() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + assertThat(mapper.parse("RSA+SHA1", TEST_LOCATION).map(INode::asString)) + .contains("RSA-PKCS1-1.5-SHA-1"); + assertThat(mapper.parse("rsa_pkcs1_sha256", TEST_LOCATION).map(INode::asString)) + .contains("RSA-PKCS1-1.5-SHA-256"); + } + + @Test + public void rsaPssWithAHashIsAnRsaPssSignatureWithThatDigest() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + for (String name : List.of("RSA-PSS+SHA256", "rsa_pss_rsae_sha256", "rsa_pss_pss_sha256")) { + final INode node = mapper.parse(name, TEST_LOCATION).orElseThrow(); + assertThat(node.is(ProbabilisticSignatureScheme.class)).as(name).isTrue(); + assertThat(node.hasChildOfType(MessageDigest.class).map(INode::asString)) + .as(name) + .contains("SHA-256"); + } + } + + @Test + public void ecdsaWireFormatNameHasItsCurveAndDigest() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + final INode node = mapper.parse("ecdsa_secp384r1_sha384", TEST_LOCATION).orElseThrow(); + + assertThat(node).isInstanceOf(ECDSA.class); + assertThat(node.hasChildOfType(EllipticCurve.class).map(INode::asString)) + .contains("secp384r1"); + assertThat(node.hasChildOfType(MessageDigest.class).map(INode::asString)) + .contains("SHA-384"); + } + + @Test + public void ecdsaAndDsaWithAHashHaveThatDigest() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + for (String name : List.of("ECDSA+SHA256", "ecdsa_sha1", "DSA+SHA256")) { + final INode node = mapper.parse(name, TEST_LOCATION).orElseThrow(); + assertThat(node.is(Signature.class)).as(name).isTrue(); + assertThat(node.hasChildOfType(MessageDigest.class)).as(name).isPresent(); + } + } + + @Test + public void mlDsaNameHasItsParameterSet() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + assertThat(mapper.parse("mldsa65", TEST_LOCATION).map(INode::asString)) + .contains("ML-DSA-65"); + } + + @Test + public void unknownNameResolvesToEmpty() { + final OpenSslSignatureMapper mapper = new OpenSslSignatureMapper(); + assertThat(mapper.parse("NOT-A-REAL-SIGALG", TEST_LOCATION)).isEmpty(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/SSLVersionMapperTest.java b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/SSLVersionMapperTest.java index dd5207e03..608070f28 100644 --- a/mapper/src/test/java/com/ibm/mapper/mapper/ssl/SSLVersionMapperTest.java +++ b/mapper/src/test/java/com/ibm/mapper/mapper/ssl/SSLVersionMapperTest.java @@ -26,12 +26,12 @@ import com.ibm.mapper.utils.DetectionLocation; import java.util.List; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SSLVersionMapperTest { @Test - public void test1() { + public void tlsv12IsParsedAsVersion1_2() { DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); @@ -44,7 +44,7 @@ public void test1() { } @Test - public void test2() { + public void tlsv1WithoutPatchIsParsedAsVersion1_0() { DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); @@ -57,7 +57,7 @@ public void test2() { } @Test - public void test3() { + public void tlsv13LowercaseIsParsedAsVersion1_3() { DetectionLocation testDetectionLocation = new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); @@ -68,4 +68,51 @@ public void test3() { assertThat(version.get().is(Version.class)).isTrue(); assertThat(version.get().asString()).isEqualTo("1.3"); } + + @Test + public void dtlsVersionIsParsedAsVersion() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final SSLVersionMapper mapper = new SSLVersionMapper(); + Optional dtls12 = mapper.parse("DTLSv1.2", testDetectionLocation); + assertThat(dtls12).isPresent(); + assertThat(dtls12.get().asString()).isEqualTo("1.2"); + + Optional dtls10 = mapper.parse("DTLSv1.0", testDetectionLocation); + assertThat(dtls10).isPresent(); + assertThat(dtls10.get().asString()).isEqualTo("1.0"); + } + + @Test + public void sslv3WithExplicitMinorIsParsedAsVersion3_0() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final SSLVersionMapper mapper = new SSLVersionMapper(); + Optional sslv3 = mapper.parse("SSLv3.0", testDetectionLocation); + assertThat(sslv3).isPresent(); + assertThat(sslv3.get().asString()).isEqualTo("3.0"); + } + + @Test + public void sslv3WithoutMinorIsParsedAsVersion3_0() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final SSLVersionMapper mapper = new SSLVersionMapper(); + Optional sslv3 = mapper.parse("SSLv3", testDetectionLocation); + assertThat(sslv3).isPresent(); + assertThat(sslv3.get().asString()).isEqualTo("3.0"); + } + + @Test + public void unknownFallbackStringReturnsEmpty() { + DetectionLocation testDetectionLocation = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "SSL"); + + final SSLVersionMapper mapper = new SSLVersionMapper(); + assertThat(mapper.parse("TLS-MIN-VERSION", testDetectionLocation)).isEmpty(); + assertThat(mapper.parse("TLS-MAX-VERSION", testDetectionLocation)).isEmpty(); + } } diff --git a/mapper/src/test/java/com/ibm/mapper/model/AlgorithmAsKindTest.java b/mapper/src/test/java/com/ibm/mapper/model/AlgorithmAsKindTest.java new file mode 100644 index 000000000..93b63467c --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/model/AlgorithmAsKindTest.java @@ -0,0 +1,103 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.model; + +import static com.ibm.mapper.model.ModelNodes.TEST; +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.mode.GMAC; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +/** + * An algorithm as another kind is an algorithm of the same class, e.g. an AES block cipher used as + * a MAC is still an AES: the class gives its composed name, how the enrichers complete it and how + * the output reports it. Every concrete algorithm class of the model is checked. + */ +class AlgorithmAsKindTest { + + @Test + void everyAlgorithmClassIsTheSameAlgorithmAsAnotherKind() + throws IOException, URISyntaxException { + final List failures = new ArrayList<>(); + for (Class nodeClass : ModelNodes.concreteNodeClasses()) { + if (ModelNodes.build(nodeClass) instanceof Algorithm algorithm) { + failures.addAll(differencesAsOtherKind(algorithm)); + } + } + assertThat(failures).isEmpty(); + } + + @Test + void aCipherAsAMacInGmacModeIsNamedAfterItsKeyLengthAndMode() { + final Algorithm gmac = new AES(128, new GMAC(TEST), TEST).asKind(Mac.class); + + assertThat(gmac).isInstanceOf(AES.class); + assertThat(gmac.getKind()).isEqualTo(Mac.class); + assertThat(gmac.asString()).isEqualTo("AES-128-GMAC"); + } + + /** + * What differs between the algorithm and the algorithm as another kind, other than its kind. + */ + @Nonnull + private static List differencesAsOtherKind(@Nonnull Algorithm algorithm) { + final String name = algorithm.getClass().getName(); + final Class otherKind = + algorithm.getKind().equals(Mac.class) ? BlockCipher.class : Mac.class; + final Algorithm asOtherKind = algorithm.asKind(otherKind); + final List differences = new ArrayList<>(); + if (asOtherKind.getClass() != algorithm.getClass()) { + differences.add(name + ": as another kind is a " + asOtherKind.getClass().getName()); + return differences; + } + if (!asOtherKind.getKind().equals(otherKind)) { + differences.add(name + ": kind " + asOtherKind.getKind() + " instead of " + otherKind); + } + if (!asOtherKind.getName().equals(algorithm.getName())) { + differences.add( + name + + ": named " + + asOtherKind.getName() + + " instead of " + + algorithm.getName()); + } + if (asOtherKind.getDetectionContext() != algorithm.getDetectionContext()) { + differences.add(name + ": another detection location"); + } + if (asOtherKind.getOrigin() != algorithm.getOrigin()) { + differences.add( + name + + ": origin " + + asOtherKind.getOrigin() + + " instead of " + + algorithm.getOrigin()); + } + if (!asOtherKind.getChildren().equals(algorithm.getChildren())) { + differences.add(name + ": other children"); + } + return differences; + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/model/ModelNodes.java b/mapper/src/test/java/com/ibm/mapper/model/ModelNodes.java new file mode 100644 index 000000000..79f60f019 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/model/ModelNodes.java @@ -0,0 +1,174 @@ +/* + * 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.model; + +import static java.util.Map.entry; + +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.Argon2; +import com.ibm.mapper.model.algorithms.HQC; +import com.ibm.mapper.model.algorithms.blake.BLAKE2X; +import com.ibm.mapper.model.algorithms.blake.BLAKE2b; +import com.ibm.mapper.model.collections.AssetCollection; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.collections.IdentifierCollection; +import com.ibm.mapper.model.collections.MergeableCollection; +import com.ibm.mapper.utils.DetectionLocation; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +/** The concrete node classes of the model, and a node of each of them, for the model tests. */ +final class ModelNodes { + + static final DetectionLocation TEST = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "TEST"); + + /** + * The node classes whose constructors take other arguments than a detection location, numbers + * and names; every other class is built from its constructor with the fewest such arguments. + */ + private static final Map, Supplier> BUILT_EXPLICITLY = + Map.ofEntries( + entry(Algorithm.class, () -> new Algorithm("TEST", BlockCipher.class, TEST)), + entry(Key.class, () -> new Key(new AES(TEST))), + entry(PrivateKey.class, () -> new PrivateKey(new Key(new AES(TEST)))), + entry(PublicKey.class, () -> new PublicKey(new Key(new AES(TEST)))), + entry(SecretKey.class, () -> new SecretKey(new Key(new AES(TEST)))), + entry(Argon2.class, () -> new Argon2(Argon2.Variant.ID, TEST)), + entry(HQC.class, () -> new HQC(KeyEncapsulationMechanism.class, TEST)), + entry(BLAKE2X.class, () -> new BLAKE2X(new BLAKE2b(TEST), TEST)), + entry(AssetCollection.class, () -> new AssetCollection(List.of(new AES(TEST)))), + entry( + CipherSuiteCollection.class, + () -> + new CipherSuiteCollection( + List.of( + new CipherSuite( + "TLS_AES_128_GCM_SHA256", TEST)))), + entry( + IdentifierCollection.class, + () -> + new IdentifierCollection( + List.of(new Identifier("x25519", TEST)))), + entry( + MergeableCollection.class, + () -> new MergeableCollection(List.of(new AES(TEST))))); + + private ModelNodes() { + // utility class + } + + /** + * A node of the class, built by {@link #BUILT_EXPLICITLY} or by its constructor with the fewest + * arguments that only takes a detection location, numbers and names. + */ + @Nonnull + static INode build(@Nonnull Class nodeClass) { + final Supplier explicit = BUILT_EXPLICITLY.get(nodeClass); + if (explicit != null) { + return explicit.get(); + } + return Arrays.stream(nodeClass.getConstructors()) + .sorted(Comparator.comparingInt(Constructor::getParameterCount)) + .map(ModelNodes::buildWith) + .flatMap(Optional::stream) + .findFirst() + .orElseThrow( + () -> + new AssertionError( + nodeClass.getName() + + " has no constructor this test can use:" + + " add it to BUILT_EXPLICITLY")); + } + + /** The concrete classes of the model, read from the directory it is compiled to. */ + @Nonnull + static List> concreteNodeClasses() + throws IOException, URISyntaxException { + final Path classes = + Path.of(INode.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + final Path model = classes.resolve(INode.class.getPackageName().replace('.', '/')); + try (Stream files = Files.walk(model)) { + return files.map(classes::relativize) + .map(Path::toString) + .filter(file -> file.endsWith(".class") && !file.contains("$")) + .map( + file -> + file.substring(0, file.length() - ".class".length()) + .replace(File.separatorChar, '.')) + .sorted() + .>map(ModelNodes::load) + .filter(INode.class::isAssignableFrom) + .filter(type -> !type.isInterface()) + .filter(type -> !Modifier.isAbstract(type.getModifiers())) + .>map(type -> type.asSubclass(INode.class)) + .toList(); + } + } + + /** + * The node built by the constructor, when it only takes a detection location, numbers and + * names. + */ + @Nonnull + private static Optional buildWith(@Nonnull Constructor constructor) { + final Class[] types = constructor.getParameterTypes(); + final Object[] arguments = new Object[types.length]; + for (int i = 0; i < types.length; i++) { + if (types[i] == DetectionLocation.class) { + arguments[i] = TEST; + } else if (types[i] == int.class || types[i] == Integer.class) { + arguments[i] = 128; + } else if (types[i] == String.class) { + arguments[i] = "TEST"; + } else { + return Optional.empty(); + } + } + try { + return Optional.of((INode) constructor.newInstance(arguments)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + return Optional.empty(); + } + } + + @Nonnull + private static Class load(@Nonnull String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(className, e); + } + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/model/NodeDeepCopyTest.java b/mapper/src/test/java/com/ibm/mapper/model/NodeDeepCopyTest.java new file mode 100644 index 000000000..b9e884da7 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/model/NodeDeepCopyTest.java @@ -0,0 +1,124 @@ +/* + * 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.model; + +import static com.ibm.mapper.model.ModelNodes.TEST; +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.collections.IAssetCollection; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +/** + * A copy of a node is a node of the same class, with a copy of each of its children: the class of a + * node gives its composed name (e.g. {@code AES-256-CBC}), the OID the enrichers give it and the + * way the output reports it. Every concrete node class of the model is checked. + */ +class NodeDeepCopyTest { + + @Test + void everyNodeClassIsCopiedAsItself() throws IOException, URISyntaxException { + final List> nodeClasses = ModelNodes.concreteNodeClasses(); + assertThat(nodeClasses).contains(AES.class, Mode.class, PrivateKey.class); + + final List failures = new ArrayList<>(); + for (Class nodeClass : nodeClasses) { + final INode node = ModelNodes.build(nodeClass); + node.put(new Oid("1.2.3", TEST)); + failures.addAll(differencesOfCopy(node)); + } + assertThat(failures).isEmpty(); + } + + @Test + void theOriginIsKept() { + final Algorithm enriched = + new Algorithm("TEST", BlockCipher.class, TEST, NodeOrigin.ENRICHED); + assertThat(enriched.deepCopy().getOrigin()).isEqualTo(NodeOrigin.ENRICHED); + + final KeyLength keyLength = new KeyLength(128, TEST, NodeOrigin.ENRICHED); + assertThat(keyLength.deepCopy().getOrigin()).isEqualTo(NodeOrigin.ENRICHED); + } + + /** What differs between the node and its copy, other than being another object. */ + @Nonnull + private static List differencesOfCopy(@Nonnull INode node) { + final String name = node.getClass().getName(); + final INode copy = node.deepCopy(); + final List differences = new ArrayList<>(); + if (copy == node) { + differences.add(name + ": the copy is the node itself"); + } + if (copy.getClass() != node.getClass()) { + differences.add(name + ": copied as " + copy.getClass().getName()); + return differences; + } + if (!copy.getKind().equals(node.getKind())) { + differences.add(name + ": kind " + copy.getKind() + " instead of " + node.getKind()); + } + if (!copy.asString().equals(node.asString())) { + differences.add(name + ": named " + copy.asString() + " instead of " + node.asString()); + } + if (copy.getOrigin() != node.getOrigin()) { + differences.add( + name + ": origin " + copy.getOrigin() + " instead of " + node.getOrigin()); + } + node.getChildren() + .forEach( + (kind, child) -> { + final INode copiedChild = copy.getChildren().get(kind); + if (copiedChild == null) { + differences.add(name + ": child " + kind.getSimpleName() + " lost"); + } else if (copiedChild == child) { + differences.add( + name + ": child " + kind.getSimpleName() + " not copied"); + } else if (copiedChild.getClass() != child.getClass()) { + differences.add( + name + + ": child " + + kind.getSimpleName() + + " copied as " + + copiedChild.getClass().getName()); + } + }); + final INode oid = node.getChildren().get(Oid.class); + copy.removeChildOfType(Oid.class); + if (node.getChildren().get(Oid.class) != oid) { + differences.add(name + ": the copy shares the children of the node"); + } + if (node instanceof IAssetCollection collection + && copy instanceof IAssetCollection copiedCollection) { + final List items = collection.getCollection(); + final List copiedItems = copiedCollection.getCollection(); + for (int i = 0; i < items.size(); i++) { + if (copiedItems.get(i) == items.get(i) + || copiedItems.get(i).getClass() != items.get(i).getClass()) { + differences.add(name + ": item " + i + " not copied as itself"); + } + } + } + return differences; + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/model/algorithms/AlgorithmNameCompositionTest.java b/mapper/src/test/java/com/ibm/mapper/model/algorithms/AlgorithmNameCompositionTest.java index de24fa420..a06701ad4 100644 --- a/mapper/src/test/java/com/ibm/mapper/model/algorithms/AlgorithmNameCompositionTest.java +++ b/mapper/src/test/java/com/ibm/mapper/model/algorithms/AlgorithmNameCompositionTest.java @@ -34,6 +34,7 @@ import com.ibm.mapper.model.algorithms.cast.CAST128; import com.ibm.mapper.model.algorithms.cast.CAST256; import com.ibm.mapper.model.mode.CBC; +import com.ibm.mapper.model.mode.GMAC; import com.ibm.mapper.model.padding.PKCS5; import com.ibm.mapper.utils.DetectionLocation; import java.util.List; @@ -51,6 +52,17 @@ void aesComposesKeyLengthAndModeWithHyphens() { assertThat(aes.asString()).isEqualTo("AES-256-CBC"); } + @Test + void aesAsAMacNamesOnlyItsMacMode() { + // the registry names AES as a MAC AES[-(128|192|256)][-(GMAC|CMAC)] + assertThat(new AES(128, new GMAC(TEST), TEST).asKind(Mac.class).asString()) + .isEqualTo("AES-128-GMAC"); + // a CBC-MAC or CFB-MAC over AES keeps its mode as a child, not in its name + assertThat(new AES(128, new CBC(TEST), new PKCS5(TEST), TEST).asKind(Mac.class).asString()) + .isEqualTo("AES-128"); + assertThat(new AES(new CBC(TEST), TEST).asKind(Mac.class).asString()).isEqualTo("AES"); + } + @Test void sha3UsesSchemaTokenWithoutHyphenAfterSha() { SHA3 sha3 = new SHA3(256, TEST); diff --git a/mapper/src/test/java/com/ibm/mapper/model/mode/ModeDeepCopyTest.java b/mapper/src/test/java/com/ibm/mapper/model/mode/ModeDeepCopyTest.java new file mode 100644 index 000000000..25b806214 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/model/mode/ModeDeepCopyTest.java @@ -0,0 +1,85 @@ +/* + * 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.model.mode; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.BlockSize; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ModeDeepCopyTest { + + static final DetectionLocation TEST = + new DetectionLocation("testfile", 1, 1, List.of("test"), () -> "TEST"); + + @Test + void aCopyIsOfTheSameModeClass() { + final List modes = + List.of( + new CBC(TEST), + new CCM(TEST), + new CFB(TEST), + new CFB(8, TEST), + new CNT(TEST), + new CTR(TEST), + new CTS(TEST), + new EAX(TEST), + new ECB(TEST), + new GCM(TEST), + new GCMSIV(TEST), + new GMAC(TEST), + new KW(TEST), + new KWP(TEST), + new MGM(TEST), + new OCB(TEST), + new OFB(TEST), + new OFB(8, TEST), + new PCBC(TEST), + new SIV(TEST), + new XTS(TEST), + new Mode("IGE", TEST)); + for (Mode mode : modes) { + final INode copy = mode.deepCopy(); + assertThat(copy).as(mode.asString()).isExactlyInstanceOf(mode.getClass()); + assertThat(copy.asString()).isEqualTo(mode.asString()); + assertThat(copy.getKind()).isEqualTo(Mode.class); + } + } + + @Test + void aCopyHasItsOwnChildren() { + final BlockSize blockSize = new BlockSize(64, TEST); + final Mode cfb = new CFB(TEST); + cfb.put(blockSize); + + final INode copy = cfb.deepCopy(); + + assertThat(cfb.getChildren().get(BlockSize.class)).isSameAs(blockSize); + assertThat(copy.getChildren().get(BlockSize.class)) + .isNotSameAs(blockSize) + .isEqualTo(blockSize); + copy.removeChildOfType(BlockSize.class); + assertThat(cfb.getChildren()).containsKey(BlockSize.class); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizerTest.java new file mode 100644 index 000000000..8bb85af27 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/BlockCipherReorganizerTest.java @@ -0,0 +1,106 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.DES; +import com.ibm.mapper.model.algorithms.DESede; +import com.ibm.mapper.model.mode.CBC; +import com.ibm.mapper.model.mode.ECB; +import com.ibm.mapper.reorganizer.Reorganizer; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class BlockCipherReorganizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @Test + void equalBlockCiphersOfDifferentTreesAreEachMergedWithTheirChild() { + // the encryption with a key of two possible lengths gives a tree per key length + final AES first = new AES(new ECB(location), location); + first.put(new AES(128, location)); + final AES second = new AES(new ECB(location), location); + second.put(new AES(256, location)); + assertThat(first).isEqualTo(second); + + final List result = + new Reorganizer(List.of(BlockCipherReorganizer.MERGE_BLOCK_CIPHER_PARENT_AND_CHILD)) + .reorganize(new ArrayList<>(List.of(first, second))); + + assertThat(result) + .extracting(INode::asString) + .containsExactly("AES-128-ECB", "AES-256-ECB"); + } + + @Test + void theParentKeepsItsNameAndTakesWhatTheChildHolds() { + // DES_ede3_cbc_encrypt with key schedules set up by DES_set_key + final DESede operation = new DESede(location); + operation.put(new ECB(location)); + final DES keySetup = new DES(location); + keySetup.put(new KeyLength(56, location)); + keySetup.put(new CBC(location)); + operation.put(keySetup); + + final List result = + new Reorganizer( + List.of( + BlockCipherReorganizer + .MERGE_BLOCK_CIPHER_CHILD_INTO_PARENT)) + .reorganize(new ArrayList<>(List.of(operation))); + + assertThat(result).singleElement().isSameAs(operation); + assertThat(operation.hasChildOfType(BlockCipher.class)).isEmpty(); + assertThat(operation.hasChildOfType(Mode.class)).get().isInstanceOf(ECB.class); + assertThat(operation.hasChildOfType(KeyLength.class).map(INode::asString)).contains("56"); + } + + @Test + void theRetainedRootOfOverlappingRootsIsKept() { + final AES retained = new AES(location); + retained.put(new KeyLength(128, location)); + final AES removed = new AES(location); + removed.put(new ECB(location)); + assertThat(retained).isEqualTo(removed); + final List roots = new ArrayList<>(List.of(retained, removed)); + + assertThat( + BlockCipherReorganizer.DEDUPLICATE_OVERLAPPING_ROOTS.match( + retained, null, roots)) + .isTrue(); + final List result = + BlockCipherReorganizer.DEDUPLICATE_OVERLAPPING_ROOTS.applyReorganization( + retained, null, roots); + + assertThat(result).singleElement().isSameAs(retained); + assertThat(retained.hasChildOfType(Mode.class)).isPresent(); + assertThat(retained.hasChildOfType(KeyLength.class)).isPresent(); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizerTest.java new file mode 100644 index 000000000..621bbebf2 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/CipherParameterReorganizerTest.java @@ -0,0 +1,107 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.Blowfish; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.reorganizer.Reorganizer; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class CipherParameterReorganizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @Test + void aKeyLengthSetForACipherWithAFixedKeyLengthIsDropped() { + final AES cipher = new AES(128, location); + final Encrypt encrypt = new Encrypt(location); + encrypt.put(new KeyLength(256, location)); + cipher.put(encrypt); + + new Reorganizer( + List.of( + CipherParameterReorganizer + .KEEP_THE_FIXED_KEY_LENGTH_OF_THE_CIPHER_OF_AN_ENCRYPTION, + CipherParameterReorganizer.MOVE_NODES_UNDER_ENCRYPT_UP)) + .reorganize(new ArrayList<>(List.of(cipher))); + + assertThat(cipher.hasChildOfType(KeyLength.class).map(INode::asString)).contains("128"); + } + + @Test + void aKeyLengthSetForACipherWithAVariableKeyLengthIsItsKeyLength() { + final Blowfish cipher = new Blowfish(location); + final Encrypt encrypt = new Encrypt(location); + encrypt.put(new KeyLength(256, location)); + cipher.put(encrypt); + + new Reorganizer( + List.of( + CipherParameterReorganizer + .KEEP_THE_FIXED_KEY_LENGTH_OF_THE_CIPHER_OF_AN_ENCRYPTION, + CipherParameterReorganizer.MOVE_NODES_UNDER_ENCRYPT_UP)) + .reorganize(new ArrayList<>(List.of(cipher))); + + assertThat(cipher.hasChildOfType(KeyLength.class).map(INode::asString)).contains("256"); + } + + @Test + void anEncryptionIsMovedUnderItsCipher() { + final Encrypt encrypt = new Encrypt(location); + final AES cipher = new AES(128, location); + encrypt.put(cipher); + + final List result = + new Reorganizer(List.of(CipherParameterReorganizer.MOVE_ENCRYPT_UNDER_ITS_CIPHER)) + .reorganize(new ArrayList<>(List.of(encrypt))); + + assertThat(result).singleElement().isSameAs(cipher); + assertThat(cipher.hasChildOfType(Encrypt.class)).get().isSameAs(encrypt); + } + + @Test + void theEncryptionIsMovedInTheTreeOfItsRoot() { + // an equal encryption of another tree comes before it among the roots + final Encrypt first = new Encrypt(location); + first.put(new AES(128, location)); + final Encrypt second = new Encrypt(location); + final AES cipher = new AES(256, location); + second.put(cipher); + assertThat(first).isEqualTo(second); + final List roots = new ArrayList<>(List.of(first, second)); + + final List result = + CipherParameterReorganizer.MOVE_ENCRYPT_UNDER_ITS_CIPHER.applyReorganization( + second, null, roots); + + assertThat(result).hasSize(2); + assertThat(result.get(0)).isSameAs(first); + assertThat(result.get(1)).isSameAs(cipher); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizerTest.java new file mode 100644 index 000000000..0588f8fdc --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/CipherSuiteReorganizerTest.java @@ -0,0 +1,105 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.model.CipherSuite; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Version; +import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.Reorganizer; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class CipherSuiteReorganizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @Test + void cipherSuitesWithoutProtocolAreTheSuitesOfATlsProtocol() { + final IReorganizerRule rule = + CipherSuiteReorganizer.ADD_TLS_PROTOCOL_AS_PARENT_OF_CIPHER_SUITES; + final CipherSuiteCollection suites = suites("TLS_AES_128_GCM_SHA256"); + final List roots = new ArrayList<>(List.of(suites)); + + assertThat(rule.match(suites, null, roots)).isTrue(); + final List result = rule.applyReorganization(suites, null, roots); + + assertThat(result).singleElement().isInstanceOf(TLS.class); + assertThat(result.get(0).getChildren().get(CipherSuiteCollection.class)).isSameAs(suites); + } + + @Test + void cipherSuitesOfAProtocolAreLeftWithIt() { + final IReorganizerRule rule = + CipherSuiteReorganizer.ADD_TLS_PROTOCOL_AS_PARENT_OF_CIPHER_SUITES; + final CipherSuiteCollection suites = suites("TLS_AES_128_GCM_SHA256"); + final TLS tls = new TLS(location); + tls.put(suites); + + assertThat(rule.match(suites, tls, List.of(tls))).isFalse(); + } + + @Test + void versionedChildReplacesTheProtocolWithItsOtherChildren() { + final IReorganizerRule rule = CipherSuiteReorganizer.REPLACE_TLS_WITH_VERSIONED_CHILD; + final TLS tls = new TLS(location); + tls.put(suites("TLS_AES_128_GCM_SHA256")); + tls.put(tls("1.2")); + final List roots = new ArrayList<>(List.of(tls)); + + assertThat(rule.match(tls, null, roots)).isTrue(); + final List result = rule.applyReorganization(tls, null, roots); + + assertThat(result).singleElement().extracting(INode::asString).isEqualTo("TLSv1.2"); + assertThat(result.get(0).hasChildOfType(CipherSuiteCollection.class)).isPresent(); + assertThat(result.get(0).getChildren().get(Version.class).asString()).isEqualTo("1.2"); + } + + @Test + void equalProtocolsOfDifferentTreesAreEachReplacedByTheirVersionedChild() { + // a version range gives a copy of the protocol per version + final TLS minimum = new TLS(location); + minimum.put(tls("1.2")); + final TLS maximum = new TLS(location); + maximum.put(tls("1.3")); + assertThat(minimum).isEqualTo(maximum); + + final List result = + new Reorganizer(List.of(CipherSuiteReorganizer.REPLACE_TLS_WITH_VERSIONED_CHILD)) + .reorganize(new ArrayList<>(List.of(minimum, maximum))); + + assertThat(result).extracting(INode::asString).containsExactly("TLSv1.2", "TLSv1.3"); + } + + private TLS tls(String version) { + return new TLS("TLSv" + version, new Version(version, location)); + } + + private CipherSuiteCollection suites(String name) { + return new CipherSuiteCollection(List.of(new CipherSuite(name, location))); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizerTest.java new file mode 100644 index 000000000..9f335e746 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/KeyReorgenizerTest.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.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.PublicKey; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class KeyReorgenizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @Test + void theKeyIsReplacedInTheTreeOfItsRoot() { + // an equal key of another tree comes before it among the roots + final Key first = new Key(new RSA(location)); + first.put(new KeyGeneration(KeyGeneration.Specification.PRIVATE_KEY, location)); + final Key second = new Key(new RSA(location)); + second.put(new KeyGeneration(KeyGeneration.Specification.PUBLIC_KEY, location)); + assertThat(first).isEqualTo(second); + final List roots = new ArrayList<>(List.of(first, second)); + + final List result = + KeyReorgenizer.SPECIFY_KEY_TYPE_BY_LOOKING_AT_KEY_GENERATION.applyReorganization( + second, null, roots); + + assertThat(result).hasSize(2); + assertThat(result.get(0)).isSameAs(first); + assertThat(result.get(1)).isInstanceOf(PublicKey.class); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/KeyUsageReorganizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/KeyUsageReorganizerTest.java new file mode 100644 index 000000000..d542c1137 --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/KeyUsageReorganizerTest.java @@ -0,0 +1,390 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.model.EllipticCurve; +import com.ibm.mapper.model.EllipticCurveAlgorithm; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.Key; +import com.ibm.mapper.model.KeyAgreement; +import com.ibm.mapper.model.KeyEncapsulationMechanism; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.PrivateKey; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.PublicKeyEncryption; +import com.ibm.mapper.model.SecretKey; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.DH; +import com.ibm.mapper.model.algorithms.DHKEM; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.ECDSA; +import com.ibm.mapper.model.algorithms.Ed25519; +import com.ibm.mapper.model.algorithms.HMAC; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.RSASVE; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.curves.Secp256r1; +import com.ibm.mapper.model.functionality.Decapsulate; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Encapsulate; +import com.ibm.mapper.model.functionality.KeyDerivation; +import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.mapper.model.functionality.Verify; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.reorganizer.Reorganizer; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class KeyUsageReorganizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @Test + void anOperationAlgorithmOnTheKeysAlgorithmIsHeldByTheKeyWithTheKeysCurve() { + // EC_KEY_generate_key(key); ECDSA_do_sign(dgst, len, key) + final EllipticCurveAlgorithm algorithm = + new EllipticCurveAlgorithm(new Secp256r1(location)); + algorithm.put(new KeyGeneration(location)); + algorithm.put(new ECDSA(location)); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + + final List result = + new Reorganizer( + List.of( + KeyUsageReorganizer + .MOVE_OPERATION_ALGORITHMS_OF_PRIVATE_KEY_TO_THE_KEY)) + .reorganize(new ArrayList<>(List.of(key))); + + assertThat(result).singleElement().isSameAs(key); + assertThat(key.hasChildOfType(PublicKeyEncryption.class)).isEmpty(); + assertThat(key.hasChildOfType(KeyGeneration.class)).isPresent(); + assertThat(key.hasChildOfType(Signature.class)) + .get() + .isInstanceOf(ECDSA.class) + .satisfies( + signature -> + assertThat( + signature + .hasChildOfType(EllipticCurve.class) + .map(INode::asString)) + .contains("secp256r1")); + } + + @Test + void anAlgorithmInsideAnOperationOfTheKeyStaysThere() { + // EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); EVP_PKEY_encapsulate(...): DHKEM over X25519 + final ECDSA component = new ECDSA(location); + final DHKEM kem = new DHKEM(component, location); + final EllipticCurveAlgorithm algorithm = + new EllipticCurveAlgorithm(new Secp256r1(location)); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + key.removeChildOfType(PublicKeyEncryption.class); + key.put(kem); + + new Reorganizer( + List.of( + KeyUsageReorganizer + .MOVE_OPERATION_ALGORITHMS_OF_PRIVATE_KEY_TO_THE_KEY)) + .reorganize(new ArrayList<>(List.of(key))); + + assertThat(key.hasChildOfType(Signature.class)).isEmpty(); + assertThat(kem.hasChildOfType(Signature.class)).isPresent(); + } + + @Test + void aDecryptionWithAnEcKeyIsAnEcdhKeyAgreement() { + // EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); CMS_decrypt(cms, pkey, ...) + final EllipticCurveAlgorithm algorithm = + new EllipticCurveAlgorithm(new Secp256r1(location)); + algorithm.put(new KeyGeneration(location)); + algorithm.put(new Decrypt(location)); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + + new Reorganizer( + List.of( + KeyUsageReorganizer + .MAKE_KEY_DERIVATION_OF_A_DECRYPTION_WITH_A_KEY_AGREEMENT_KEY, + KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS)) + .reorganize(new ArrayList<>(List.of(key))); + + assertThat(key.hasChildOfType(PublicKeyEncryption.class)).isEmpty(); + assertThat(key.hasChildOfType(KeyAgreement.class)) + .get() + .isInstanceOf(ECDH.class) + .satisfies( + agreement -> { + assertThat(agreement.hasChildOfType(KeyDerivation.class)).isPresent(); + assertThat(agreement.hasChildOfType(Decrypt.class)).isEmpty(); + assertThat( + agreement + .hasChildOfType(EllipticCurve.class) + .map(INode::asString)) + .contains("secp256r1"); + }); + } + + @Test + void aDecryptionWithAnX25519KeyIsItsKeyAgreement() { + // EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); CMS_decrypt(cms, pkey, ...) + final X25519 algorithm = new X25519(location); + algorithm.put(new KeyGeneration(location)); + algorithm.put(new Decrypt(location)); + final PrivateKey key = new PrivateKey(new Key(algorithm)); + + new Reorganizer( + List.of( + KeyUsageReorganizer + .MAKE_KEY_DERIVATION_OF_A_DECRYPTION_WITH_A_KEY_AGREEMENT_KEY)) + .reorganize(new ArrayList<>(List.of(key))); + + assertThat(algorithm.hasChildOfType(KeyDerivation.class)).isPresent(); + assertThat(algorithm.hasChildOfType(Decrypt.class)).isEmpty(); + } + + @Test + void aDecryptionWithAnRsaKeyStaysADecryption() { + // EVP_PKEY_Q_keygen(NULL, NULL, "RSA", 2048); CMS_decrypt(cms, pkey, ...) + final RSA algorithm = new RSA(location); + algorithm.put(new KeyGeneration(location)); + algorithm.put(new Decrypt(location)); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + + new Reorganizer( + List.of( + KeyUsageReorganizer + .MAKE_KEY_DERIVATION_OF_A_DECRYPTION_WITH_A_KEY_AGREEMENT_KEY)) + .reorganize(new ArrayList<>(List.of(key))); + + assertThat(algorithm.hasChildOfType(Decrypt.class)).isPresent(); + assertThat(algorithm.hasChildOfType(KeyDerivation.class)).isEmpty(); + } + + private void reorganize(INode key, IReorganizerRule... rules) { + new Reorganizer(List.of(rules)).reorganize(new ArrayList<>(List.of(key))); + } + + @Test + void signaturesWithAnEcKeyAreEcdsaOnTheKeysCurve() { + // EVP_PKEY_Q_keygen(NULL, NULL, "EC", "P-256"); EVP_DigestSignInit(...); ...VerifyInit(...) + final EllipticCurveAlgorithm algorithm = + new EllipticCurveAlgorithm(new Secp256r1(location)); + algorithm.put(new KeyGeneration(location)); + algorithm.put(new Sign(location)); + algorithm.put(new Verify(location)); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + + reorganize(key, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + + assertThat(key.hasChildOfType(PublicKeyEncryption.class)).isEmpty(); + assertThat(key.hasChildOfType(KeyGeneration.class)).isPresent(); + assertThat(key.hasChildOfType(Signature.class)) + .get() + .isInstanceOf(ECDSA.class) + .satisfies( + ecdsa -> { + assertThat(ecdsa.hasChildOfType(Sign.class)).isPresent(); + assertThat(ecdsa.hasChildOfType(Verify.class)).isPresent(); + assertThat(ecdsa.hasChildOfType(EllipticCurve.class)) + .map(INode::asString) + .contains("secp256r1"); + }); + } + + @Test + void aSignatureWithAnRsaKeyIsAnRsaSignatureOfTheKeysLength() { + // RSA key of 2048 bits, signing and decrypting + final RSA algorithm = new RSA(2048, location); + algorithm.put(new KeyGeneration(location)); + algorithm.put(new Sign(location)); + algorithm.put(new Decrypt(location)); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + + reorganize(key, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + + assertThat(key.hasChildOfType(Signature.class)) + .get() + .isInstanceOf(RSA.class) + .satisfies( + signature -> { + assertThat(signature.hasChildOfType(Sign.class)).isPresent(); + assertThat(signature.hasChildOfType(KeyLength.class)) + .map(INode::asString) + .contains("2048"); + }); + // the decryption is the key's own algorithm, which stays with it + assertThat(algorithm.hasChildOfType(Decrypt.class)).isPresent(); + assertThat(key.hasChildOfType(PublicKeyEncryption.class)).get().isSameAs(algorithm); + } + + @Test + void aSignatureWithTheRsaPssPaddingIsRsaPss() { + final RSA algorithm = new RSA(3072, location); + final Sign sign = new Sign(location); + sign.put(new RSAssaPSS(location)); + algorithm.put(sign); + final PrivateKey key = new PrivateKey((PublicKeyEncryption) algorithm); + + reorganize(key, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + + assertThat(key.hasChildOfType(ProbabilisticSignatureScheme.class)) + .get() + .isInstanceOf(RSAssaPSS.class) + .satisfies( + pss -> + assertThat(pss.hasChildOfType(KeyLength.class)) + .map(INode::asString) + .contains("3072")); + } + + @Test + void keyAgreementWithAnEcOrDhKeyIsEcdhOrDh() { + final EllipticCurveAlgorithm ec = new EllipticCurveAlgorithm(new Secp256r1(location)); + ec.put(new KeyDerivation(location)); + final PrivateKey ecKey = new PrivateKey((PublicKeyEncryption) ec); + final DH dh = new DH(location); + dh.put(new KeyLength(2048, location)); + dh.put(new KeyDerivation(location)); + final PrivateKey dhKey = new PrivateKey((PublicKeyEncryption) dh); + + reorganize(ecKey, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + reorganize(dhKey, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + + assertThat(ecKey.hasChildOfType(KeyAgreement.class)).get().isInstanceOf(ECDH.class); + assertThat(dhKey.hasChildOfType(KeyAgreement.class)) + .get() + .isInstanceOf(DH.class) + .satisfies( + agreement -> + assertThat(agreement.hasChildOfType(KeyLength.class)) + .map(INode::asString) + .contains("2048")); + } + + @Test + void keyEncapsulationIsRsasveWithAnRsaKeyAndDhkemWithAnEcKey() { + final RSA rsa = new RSA(2048, location); + rsa.put(new Encapsulate(location)); + final PrivateKey rsaKey = new PrivateKey((PublicKeyEncryption) rsa); + final EllipticCurveAlgorithm ec = new EllipticCurveAlgorithm(new Secp256r1(location)); + ec.put(new Decapsulate(location)); + final PrivateKey ecKey = new PrivateKey((PublicKeyEncryption) ec); + + reorganize(rsaKey, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + reorganize(ecKey, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + + assertThat(rsaKey.hasChildOfType(KeyEncapsulationMechanism.class)) + .get() + .isInstanceOf(RSASVE.class); + assertThat(ecKey.hasChildOfType(KeyEncapsulationMechanism.class)) + .get() + .isInstanceOf(DHKEM.class) + .satisfies( + dhkem -> + assertThat(dhkem.hasChildOfType(KeyAgreement.class)) + .get() + .isInstanceOf(ECDH.class)); + } + + @Test + void aSignatureWithAnEd25519KeyIsTheKeysOwnAlgorithm() { + final Ed25519 algorithm = new Ed25519(location); + algorithm.put(new Sign(location)); + final PrivateKey key = new PrivateKey((Signature) algorithm); + + reorganize(key, KeyUsageReorganizer.MAKE_ALGORITHMS_OF_PRIVATE_KEY_OPERATIONS); + + assertThat(key.hasChildOfType(Signature.class)).get().isSameAs(algorithm); + assertThat(algorithm.hasChildOfType(Sign.class)).isPresent(); + } + + @Test + void theOperationsOfAnImportedKeyAreMovedUnderItsAlgorithm() { + // EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, ...); EVP_DigestSign(...) + final Ed25519 algorithm = new Ed25519(location); + final PrivateKey key = new PrivateKey(new Key(algorithm)); + key.put(new Sign(location)); + + reorganize( + key, + KeyUsageReorganizer.moveOperationsOfImportedKeyUnderItsAlgorithm(PrivateKey.class)); + + assertThat(key.hasChildOfType(Sign.class)).isEmpty(); + assertThat(algorithm.hasChildOfType(Sign.class)).isPresent(); + } + + @Test + void theOperationsOfAGeneratedKeyStayOnTheKey() { + final Ed25519 algorithm = new Ed25519(location); + algorithm.put(new KeyGeneration(location)); + final PrivateKey key = new PrivateKey(new Key(algorithm)); + key.put(new Sign(location)); + + reorganize( + key, + KeyUsageReorganizer.moveOperationsOfImportedKeyUnderItsAlgorithm(PrivateKey.class)); + + assertThat(key.hasChildOfType(Sign.class)).isPresent(); + assertThat(algorithm.hasChildOfType(Sign.class)).isEmpty(); + } + + @Test + void theOperationsOfAKeyMarkedGeneratedStayOnTheKey() { + final Ed25519 algorithm = new Ed25519(location); + final PrivateKey key = new PrivateKey(new Key(algorithm)); + key.put(new KeyGeneration(location)); + key.put(new Sign(location)); + + reorganize( + key, + KeyUsageReorganizer.moveOperationsOfImportedKeyUnderItsAlgorithm(PrivateKey.class)); + + assertThat(key.hasChildOfType(Sign.class)).isPresent(); + assertThat(algorithm.hasChildOfType(Sign.class)).isEmpty(); + } + + @Test + void aSignatureWithAMacKeyIsATagOfTheMac() { + // EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, ...); EVP_DigestSignInit(mdctx, NULL, md, NULL, key) + final HMAC hmac = new HMAC(location); + final Sign sign = new Sign(location); + final MessageDigest digest = new SHA2(256, location); + sign.put(digest); + hmac.put(sign); + final SecretKey key = new SecretKey(new Key(hmac)); + + reorganize(key, KeyUsageReorganizer.MAKE_TAGS_OF_SECRET_KEY_OPERATIONS); + + assertThat(hmac.hasChildOfType(Sign.class)).isEmpty(); + assertThat(hmac.hasChildOfType(Tag.class)).isPresent(); + assertThat(hmac.hasChildOfType(MessageDigest.class)).get().isSameAs(digest); + assertThat(key.hasChildOfType(Mac.class)).get().isSameAs(hmac); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/MacReorganizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/MacReorganizerTest.java new file mode 100644 index 000000000..ba5faa87e --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/MacReorganizerTest.java @@ -0,0 +1,177 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.model.Algorithm; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.BlockCipher; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.KeyLength; +import com.ibm.mapper.model.Mac; +import com.ibm.mapper.model.Mode; +import com.ibm.mapper.model.Oid; +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.functionality.Tag; +import com.ibm.mapper.model.mode.GCM; +import com.ibm.mapper.model.mode.GMAC; +import com.ibm.mapper.reorganizer.IReorganizerRule; +import com.ibm.mapper.utils.DetectionLocation; +import com.ibm.mapper.utils.Utils; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class MacReorganizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + private final IReorganizerRule rule = MacReorganizer.MERGE_GMAC_PARENT_AND_GCM_CIPHER_CHILD; + + @Test + void gmacWithAGcmCipherBecomesTheCipherAsAMacInGmacMode() { + INode gmac = new com.ibm.mapper.model.algorithms.GMAC(location); + gmac.put(new Tag(location)); + gmac.put(aes128Gcm()); + List roots = new ArrayList<>(List.of(gmac)); + + assertThat(rule.match(gmac, null, roots)).isTrue(); + List result = rule.applyReorganization(gmac, null, roots); + + assertThat(result).hasSize(1); + INode mac = result.get(0); + assertThat(mac).isInstanceOf(AES.class); + assertThat(mac.getKind()).isEqualTo(Mac.class); + assertThat(mac.asString()).isEqualTo("AES-128-GMAC"); + assertThat(mac.getChildren().get(Mode.class)).isInstanceOf(GMAC.class); + assertThat(mac.getChildren().get(KeyLength.class).asString()).isEqualTo("128"); + assertThat(mac.hasChildOfType(Tag.class)).isPresent(); + assertThat(mac.hasChildOfType(Oid.class)).isEmpty(); + assertThat(mac.hasChildOfType(AuthenticatedEncryption.class)).isEmpty(); + } + + @Test + void gmacUnderAParentIsReplacedInTheParent() { + INode gmac = new com.ibm.mapper.model.algorithms.GMAC(location); + gmac.put(aes128Gcm()); + INode parent = new Algorithm("parent", AuthenticatedEncryption.class, location); + parent.put(gmac); + List roots = new ArrayList<>(List.of(parent)); + + assertThat(rule.match(gmac, parent, roots)).isTrue(); + List result = rule.applyReorganization(gmac, parent, roots); + + assertThat(result).containsExactly(parent); + INode mac = parent.getChildren().get(Mac.class); + assertThat(((Algorithm) mac).getName()).isEqualTo("AES"); + assertThat(mac.getChildren().get(Mode.class)).isInstanceOf(GMAC.class); + } + + @Test + void gmacWithABlockCipherInGcmModeBecomesTheCipherAsAMacInGmacMode() { + // before enrichment, AES in GCM mode is still a block cipher + INode gmac = new com.ibm.mapper.model.algorithms.GMAC(location); + gmac.put(new AES(128, new GCM(location), location)); + List roots = new ArrayList<>(List.of(gmac)); + + assertThat(rule.match(gmac, null, roots)).isTrue(); + INode mac = rule.applyReorganization(gmac, null, roots).get(0); + + assertThat(mac.getKind()).isEqualTo(Mac.class); + assertThat(((Algorithm) mac).getName()).isEqualTo("AES"); + assertThat(mac.getChildren().get(Mode.class)).isInstanceOf(GMAC.class); + assertThat(mac.hasChildOfType(BlockCipher.class)).isEmpty(); + } + + @Test + void aMacOtherThanGmacDoesNotMatch() { + INode mac = Utils.unknown(Mac.class, location); + mac.put(aes128Gcm()); + + assertThat(rule.match(mac, null, List.of(mac))).isFalse(); + } + + @Test + void aCopiedGmacStillMatches() { + // the translation copies a tree for each alternative value found in it + INode gmac = new com.ibm.mapper.model.algorithms.GMAC(location); + gmac.put(aes128Gcm()); + INode copy = gmac.deepCopy(); + + assertThat(rule.match(copy, null, List.of(copy))).isTrue(); + } + + @Test + void gmacWithoutACipherDoesNotMatch() { + INode gmac = new com.ibm.mapper.model.algorithms.GMAC(location); + + assertThat(rule.match(gmac, null, List.of(gmac))).isFalse(); + } + + @Test + void theGmacIsReplacedInTheTreeOfItsRoot() { + // an equal GMAC of another tree comes before it among the roots + INode first = new com.ibm.mapper.model.algorithms.GMAC(location); + first.put(aesGcm(128)); + INode second = new com.ibm.mapper.model.algorithms.GMAC(location); + second.put(aesGcm(256)); + assertThat(first).isEqualTo(second); + List roots = new ArrayList<>(List.of(first, second)); + + List result = rule.applyReorganization(second, null, roots); + + assertThat(result).hasSize(2); + assertThat(result.get(0)).isSameAs(first); + assertThat(result.get(1).getChildren().get(KeyLength.class).asString()).isEqualTo("256"); + } + + @Test + void theUnknownMacIsReplacedInTheTreeOfItsRoot() { + // an equal MAC of another tree comes before it among the roots + INode first = Utils.unknown(Mac.class, location); + first.put(new AES(128, location)); + INode second = Utils.unknown(Mac.class, location); + second.put(new AES(256, location)); + assertThat(first).isEqualTo(second); + List roots = new ArrayList<>(List.of(first, second)); + + List result = + MacReorganizer.MERGE_UNKNOWN_MAC_PARENT_AND_CIPHER_CHILD.applyReorganization( + second, null, roots); + + assertThat(result).hasSize(2); + assertThat(result.get(0)).isSameAs(first); + assertThat(result.get(1)).isInstanceOf(AES.class); + assertThat(result.get(1).getKind()).isEqualTo(Mac.class); + assertThat(result.get(1).getChildren().get(KeyLength.class).asString()).isEqualTo("256"); + } + + private INode aesGcm(int keyLength) { + return new AES( + AuthenticatedEncryption.class, new AES(keyLength, new GCM(location), location)); + } + + private INode aes128Gcm() { + AES aes = new AES(128, new GCM(location), location); + aes.put(new Oid("2.16.840.1.101.3.4.1.6", location)); + return new AES(AuthenticatedEncryption.class, aes); + } +} diff --git a/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizerTest.java b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizerTest.java new file mode 100644 index 000000000..8cc62ba1e --- /dev/null +++ b/mapper/src/test/java/com/ibm/mapper/reorganizer/rules/SignatureReorganizerTest.java @@ -0,0 +1,89 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.mapper.reorganizer.rules; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import com.ibm.mapper.ITranslator; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.MessageDigest; +import com.ibm.mapper.model.ProbabilisticSignatureScheme; +import com.ibm.mapper.model.Signature; +import com.ibm.mapper.model.algorithms.RSAssaPSS; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.functionality.Sign; +import com.ibm.mapper.reorganizer.Reorganizer; +import com.ibm.mapper.utils.DetectionLocation; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class SignatureReorganizerTest { + + private final DetectionLocation location = mock(DetectionLocation.class); + + @Test + void aSigningOperationWithoutSchemeIsASignatureOfAnUnknownScheme() { + // X509_sign(cert, pkey, EVP_sha256()) with a key whose type is not known + final Sign sign = new Sign(location); + sign.put(new SHA2(256, location)); + + final List result = + new Reorganizer( + List.of( + SignatureReorganizer + .MAKE_SIGNATURE_OF_A_SIGNING_OPERATION_WITHOUT_SCHEME)) + .reorganize(new ArrayList<>(List.of(sign))); + + assertThat(result) + .singleElement() + .satisfies( + signature -> { + assertThat(signature.is(Signature.class)).isTrue(); + assertThat(signature.asString()).isEqualTo(ITranslator.UNKNOWN); + assertThat(signature.hasChildOfType(Sign.class)).containsSame(sign); + assertThat(signature.hasChildOfType(MessageDigest.class)).isPresent(); + assertThat(sign.getChildren()).isEmpty(); + }); + } + + @Test + void aSigningOperationNamingItsSchemeIsASignatureOfThatScheme() { + // EVP_DigestSignInit(mdctx, &pctx, md, NULL, pkey); + // EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) + final Sign sign = new Sign(location); + final RSAssaPSS pss = new RSAssaPSS(location); + sign.put(pss); + sign.put(new SHA2(256, location)); + + final List result = + new Reorganizer( + List.of( + SignatureReorganizer + .MAKE_SIGNATURE_OF_A_SIGNING_OPERATION_WITHOUT_SCHEME)) + .reorganize(new ArrayList<>(List.of(sign))); + + assertThat(result).singleElement().isSameAs(pss); + assertThat(pss.is(ProbabilisticSignatureScheme.class)).isTrue(); + assertThat(pss.hasChildOfType(Sign.class)).containsSame(sign); + assertThat(pss.hasChildOfType(MessageDigest.class)).isPresent(); + } +} diff --git a/output/pom.xml b/output/pom.xml index ce650db96..15a7ec84a 100644 --- a/output/pom.xml +++ b/output/pom.xml @@ -12,8 +12,6 @@ output - 17 - 17 UTF-8 diff --git a/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java b/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java index 72e12de3f..1da3664c3 100644 --- a/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java +++ b/output/src/main/java/com/ibm/output/cyclondx/CBOMOutputFile.java @@ -39,7 +39,9 @@ import com.ibm.mapper.model.PasswordLength; import com.ibm.mapper.model.Protocol; import com.ibm.mapper.model.SaltLength; +import com.ibm.mapper.model.collections.AssetCollection; import com.ibm.mapper.model.collections.CipherSuiteCollection; +import com.ibm.mapper.model.collections.MergeableCollection; import com.ibm.mapper.model.functionality.Decapsulate; import com.ibm.mapper.model.functionality.Decrypt; import com.ibm.mapper.model.functionality.Digest; @@ -87,6 +89,8 @@ import org.cyclonedx.model.Metadata; import org.cyclonedx.model.OrganizationalEntity; import org.cyclonedx.model.Service; +import org.cyclonedx.model.component.crypto.AlgorithmProperties; +import org.cyclonedx.model.component.crypto.enums.CryptoFunction; import org.cyclonedx.model.component.evidence.Occurrence; import org.cyclonedx.model.metadata.ToolInformation; import org.slf4j.Logger; @@ -127,6 +131,12 @@ private void add(@Nullable final String parentBomRef, @Nonnull List nodes || node instanceof NonceLength) { final IProperty property = (IProperty) node; createRelatedCryptoMaterialComponent(parentBomRef, property); + } else if (node instanceof AssetCollection assetCollection) { + add(parentBomRef, assetCollection.getCollection()); + add(parentBomRef, node.getChildren().values().stream().toList()); + } else if (node instanceof MergeableCollection mergeableCollection) { + add(parentBomRef, mergeableCollection.getCollection()); + add(parentBomRef, node.getChildren().values().stream().toList()); } else if (node.hasChildren()) { add(parentBomRef, node.getChildren().values().stream().toList()); } @@ -281,6 +291,7 @@ private void addComponentAndDependencies( + " ")) .toList(); c.getEvidence().setOccurrences(merge); + mergeCryptoFunctions(c, component); return c; }); } @@ -356,6 +367,35 @@ private Function> getIdentifierFunction() { return (component -> Optional.ofNullable(component.getName())); } + /** + * An algorithm found more than once, e.g. used for key generation in one place and for key + * derivation in another, has the crypto functions of each finding. + */ + private static void mergeCryptoFunctions( + @Nonnull Component component, @Nonnull Component sameAlgorithm) { + final AlgorithmProperties properties = algorithmPropertiesOf(component); + final AlgorithmProperties otherProperties = algorithmPropertiesOf(sameAlgorithm); + if (properties == null + || otherProperties == null + || otherProperties.getCryptoFunctions() == null) { + return; + } + final List functions = + Stream.concat( + Optional.ofNullable(properties.getCryptoFunctions()).stream() + .flatMap(List::stream), + otherProperties.getCryptoFunctions().stream()) + .distinct() + .toList(); + properties.setCryptoFunctions(functions.isEmpty() ? null : functions); + } + + @Nullable private static AlgorithmProperties algorithmPropertiesOf(@Nonnull Component component) { + return component.getCryptoProperties() == null + ? null + : component.getCryptoProperties().getAlgorithmProperties(); + } + @Nonnull private Occurrence createOccurrenceForm(@Nonnull DetectionLocation detectionLocation) { final Occurrence occurrence = new Occurrence(); diff --git a/output/src/main/java/com/ibm/output/cyclondx/builder/AlgorithmComponentBuilder.java b/output/src/main/java/com/ibm/output/cyclondx/builder/AlgorithmComponentBuilder.java index 67f932faf..086156860 100644 --- a/output/src/main/java/com/ibm/output/cyclondx/builder/AlgorithmComponentBuilder.java +++ b/output/src/main/java/com/ibm/output/cyclondx/builder/AlgorithmComponentBuilder.java @@ -60,6 +60,7 @@ import com.ibm.mapper.model.padding.PKCS1; import com.ibm.mapper.model.padding.PKCS5; import com.ibm.mapper.model.padding.PKCS7; +import com.ibm.mapper.model.padding.Raw; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -272,6 +273,8 @@ public static IAlgorithmComponentBuilder create() { cxPadding = Padding.PKCS7; } else if (padding instanceof PKCS1) { cxPadding = Padding.PKCS1V15; + } else if (padding instanceof Raw) { + cxPadding = Padding.RAW; } else { cxPadding = Padding.OTHER; } diff --git a/output/src/main/java/com/ibm/output/cyclondx/builder/Utils.java b/output/src/main/java/com/ibm/output/cyclondx/builder/Utils.java index 221c03eae..4bb71e3d3 100644 --- a/output/src/main/java/com/ibm/output/cyclondx/builder/Utils.java +++ b/output/src/main/java/com/ibm/output/cyclondx/builder/Utils.java @@ -47,7 +47,8 @@ private Utils() { .findFirst(); } - public static @Nonnull Predicate distinctByKey( + @Nonnull + public static Predicate distinctByKey( @Nonnull Function keyExtractor) { Map map = new ConcurrentHashMap<>(); return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; diff --git a/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java b/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java index d7a6f60eb..c7a8d87ec 100644 --- a/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java +++ b/output/src/test/java/com/ibm/output/cyclonedx/AlgorithmTest.java @@ -37,14 +37,18 @@ import com.ibm.mapper.model.algorithms.RSAssaPSS; import com.ibm.mapper.model.algorithms.SHA; import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.algorithms.X25519; import com.ibm.mapper.model.curves.Secp256r1; import com.ibm.mapper.model.curves.Secp384r1; import com.ibm.mapper.model.functionality.Decrypt; import com.ibm.mapper.model.functionality.Digest; import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.mapper.model.functionality.KeyDerivation; import com.ibm.mapper.model.functionality.KeyGeneration; import com.ibm.mapper.model.functionality.Sign; import com.ibm.mapper.model.padding.OAEP; +import com.ibm.mapper.model.padding.Raw; +import java.util.List; import org.cyclonedx.model.Component; import org.cyclonedx.model.component.crypto.AlgorithmProperties; import org.cyclonedx.model.component.crypto.CryptoProperties; @@ -78,6 +82,26 @@ void baseRSA() { }); } + @Test + void rsaWithoutPaddingHasRawPadding() { + this.assertsNode( + () -> { + final RSA rsa = new RSA(detectionLocation); + rsa.put(new Raw(detectionLocation)); + return rsa; + }, + bom -> { + assertThat(bom.getComponents()).hasSize(1); + assertThat( + bom.getComponents() + .get(0) + .getCryptoProperties() + .getAlgorithmProperties() + .getPadding()) + .isEqualTo(org.cyclonedx.model.component.crypto.enums.Padding.RAW); + }); + } + @Test void algorithmWithMultipleCryptoFunctions() { this.assertsNode( @@ -410,4 +434,28 @@ void eccKeyAgree() { assertThat(cryptoProperties.getOid()).isEqualTo("1.3.132.1.12"); }); } + + @Test + void functionsOfTheSameAlgorithmFoundTwice() { + this.assertsNodes( + () -> { + final X25519 group = new X25519(detectionLocation); + final X25519 keyAgreement = new X25519(detectionLocation); + keyAgreement.put(new KeyGeneration(detectionLocation)); + keyAgreement.put(new KeyDerivation(detectionLocation)); + return List.of(group, keyAgreement); + }, + bom -> { + assertThat(bom.getComponents()).hasSize(1); + final Component component = bom.getComponents().get(0); + assertThat(component.getName()).isEqualTo("x25519"); + assertThat( + component + .getCryptoProperties() + .getAlgorithmProperties() + .getCryptoFunctions()) + .containsExactlyInAnyOrder( + CryptoFunction.KEYGEN, CryptoFunction.KEYDERIVE); + }); + } } diff --git a/output/src/test/java/com/ibm/output/cyclonedx/AssetCollectionTest.java b/output/src/test/java/com/ibm/output/cyclonedx/AssetCollectionTest.java new file mode 100644 index 000000000..e96c97401 --- /dev/null +++ b/output/src/test/java/com/ibm/output/cyclonedx/AssetCollectionTest.java @@ -0,0 +1,53 @@ +/* + * 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.output.cyclonedx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.collections.AssetCollection; +import java.util.List; +import org.cyclonedx.model.Component; +import org.junit.jupiter.api.Test; + +/** + * The assets of a collection that is not held by another asset, such as the key exchange groups + * configured for TLS, are each added to the CBOM. + */ +class AssetCollectionTest extends TestBase { + + @Test + void assetsOfATopLevelCollection() { + this.assertsNode( + () -> + new AssetCollection( + List.of( + new X25519(detectionLocation), + new ECDH(detectionLocation))), + bom -> { + assertThat(bom.getComponents()) + .extracting(Component::getName) + .containsExactlyInAnyOrder("x25519", "ECDH"); + bom.getComponents().forEach(component -> asserts(component.getEvidence())); + }); + } +} diff --git a/output/src/test/java/com/ibm/output/cyclonedx/MergeableCollectionTest.java b/output/src/test/java/com/ibm/output/cyclonedx/MergeableCollectionTest.java new file mode 100644 index 000000000..f3039c8f6 --- /dev/null +++ b/output/src/test/java/com/ibm/output/cyclonedx/MergeableCollectionTest.java @@ -0,0 +1,78 @@ +/* + * 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.output.cyclonedx; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.algorithms.ECDH; +import com.ibm.mapper.model.algorithms.X25519; +import com.ibm.mapper.model.collections.MergeableCollection; +import com.ibm.mapper.model.protocol.TLS; +import java.util.List; +import org.cyclonedx.model.Component; +import org.cyclonedx.model.component.crypto.ProtocolProperties; +import org.junit.jupiter.api.Test; + +/** + * The assets of a mergeable collection, such as the groups and signature algorithms configured on a + * TLS context, are each added to the CBOM; held by a protocol, they are the algorithms it uses. + */ +class MergeableCollectionTest extends TestBase { + + @Test + void assetsOfATopLevelCollection() { + this.assertsNode( + () -> new MergeableCollection(algorithms()), + bom -> { + assertThat(bom.getComponents()) + .extracting(Component::getName) + .containsExactlyInAnyOrder("x25519", "ECDH"); + bom.getComponents().forEach(component -> asserts(component.getEvidence())); + }); + } + + @Test + void assetsOfACollectionHeldByAProtocol() { + this.assertsNode( + () -> { + final TLS tls = new TLS(detectionLocation); + tls.put(new MergeableCollection(algorithms())); + return tls; + }, + bom -> { + assertThat(bom.getComponents()) + .extracting(Component::getName) + .containsExactlyInAnyOrder("TLS", "x25519", "ECDH"); + final ProtocolProperties protocolProperties = + bom.getComponents().stream() + .filter(component -> component.getName().equals("TLS")) + .findFirst() + .orElseThrow() + .getCryptoProperties() + .getProtocolProperties(); + assertThat(protocolProperties.getCryptoRefArray()).hasSize(2); + }); + } + + private List algorithms() { + return List.of(new X25519(detectionLocation), new ECDH(detectionLocation)); + } +} diff --git a/pom.xml b/pom.xml index 17923d698..88db7e9a7 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ python go csharp + cpp engine output common @@ -37,8 +38,8 @@ - 17 - 17 + 21 + 21 UTF-8 @@ -46,9 +47,10 @@ 2.9.2 10.15.0 + 0.8.13 25.9.0.112764 - 9.14.0.375 + 11.2.0.2797 13.11.0.4459 8.22.0.41895 @@ -59,10 +61,22 @@ a7de104eea09927509592bf68fa7ac69a7ecc075 f05477beb84a17dfed5c3ffbe57038b1fa260702 2.30.0.5193 + + com.github.SonarOpenCommunity.sonar-cxx + 4d3b01d807dac4b487ca7bbe0fb7ab067eba45f3 1.28.0 + 1.6.3 + + + jitpack.io + https://jitpack.io + + + github @@ -73,6 +87,15 @@ + + + com.fasterxml.jackson + jackson-bom + 2.22.3 + pom + import + org.junit junit-bom @@ -83,15 +106,22 @@ ch.qos.logback logback-core - 1.5.35 + ${logback.version} test ch.qos.logback logback-classic - 1.6.3 + ${logback.version} test + + + org.codehaus.woodstox + stax2-api + 4.3.1 + org.slf4j slf4j-api @@ -122,7 +152,7 @@ com.google.code.gson gson - 2.13.2 + 2.14.0 @@ -146,6 +176,22 @@ sonar-go-plugin ${sonar.go.version} + + + + ${sonar.cxx.groupId} + cxx-squid-bridge + ${sonar.cxx.version} + provided + + + ${sonar.cxx.groupId} + cxx-squid + ${sonar.cxx.version} + provided + @@ -311,6 +357,27 @@ + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + prepare-agent + + prepare-agent + + + + report + test + + report + + + + + org.cyclonedx ${cyclonedx-maven-plugin.version} diff --git a/python/pom.xml b/python/pom.xml index 6a855421d..cadf32944 100644 --- a/python/pom.xml +++ b/python/pom.xml @@ -12,8 +12,6 @@ python - 17 - 17 UTF-8 diff --git a/rules/pom.xml b/rules/pom.xml index 12d4ef22a..4046fc790 100644 --- a/rules/pom.xml +++ b/rules/pom.xml @@ -12,8 +12,6 @@ rules - 17 - 17 UTF-8 diff --git a/rules/src/test/java/com/ibm/rules/InventoryRuleTest.java b/rules/src/test/java/com/ibm/rules/InventoryRuleTest.java new file mode 100644 index 000000000..17ff63baf --- /dev/null +++ b/rules/src/test/java/com/ibm/rules/InventoryRuleTest.java @@ -0,0 +1,96 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.rules; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.mapper.model.algorithms.AES; +import com.ibm.mapper.model.algorithms.RSA; +import com.ibm.mapper.model.algorithms.SHA2; +import com.ibm.mapper.model.protocol.TLS; +import com.ibm.rules.issue.Issue; +import java.util.List; +import org.junit.jupiter.api.Test; + +final class InventoryRuleTest { + + // reproduces the exact detection shape of the openssl-cxx-demo scan: one top-level node + // per detected algorithm/protocol, no children, as InventoryRule.report receives it. + + @Test + void reportsTopLevelAlgorithm() { + final AES aes = new AES(256, detectionLocation()); + + final InventoryRule rule = new InventoryRule<>(); + final List> issues = rule.report(new MockTree(), List.of(aes)); + + assertThat(issues).hasSize(1); + } + + @Test + void reportsTopLevelMessageDigestAlgorithm() { + final SHA2 sha256 = new SHA2(256, detectionLocation()); + + final InventoryRule rule = new InventoryRule<>(); + final List> issues = rule.report(new MockTree(), List.of(sha256)); + + assertThat(issues).hasSize(1); + } + + @Test + void reportsTopLevelPublicKeyAlgorithm() { + final RSA rsa = new RSA(2048, detectionLocation()); + + final InventoryRule rule = new InventoryRule<>(); + final List> issues = rule.report(new MockTree(), List.of(rsa)); + + assertThat(issues).hasSize(1); + } + + @Test + void reportsTopLevelProtocol() { + final TLS tls = new TLS(detectionLocation()); + + final InventoryRule rule = new InventoryRule<>(); + final List> issues = rule.report(new MockTree(), List.of(tls)); + + assertThat(issues).hasSize(1); + } + + @Test + void reportsAllFourTogetherLikeOpensslDemoScan() { + final List nodes = + List.of( + new AES(256, detectionLocation()), + new SHA2(256, detectionLocation()), + new RSA(2048, detectionLocation()), + new TLS(detectionLocation())); + + final InventoryRule rule = new InventoryRule<>(); + final List> issues = rule.report(new MockTree(), nodes); + + assertThat(issues).hasSize(4); + } + + private static com.ibm.mapper.utils.DetectionLocation detectionLocation() { + return new com.ibm.mapper.utils.DetectionLocation( + "test.cc", 1, 1, java.util.Collections.emptyList(), () -> "Test"); + } +} diff --git a/rules/src/test/java/com/ibm/rules/NoMD5UseForMessageDigestRuleTest.java b/rules/src/test/java/com/ibm/rules/NoMD5UseForMessageDigestRuleTest.java index 0d8e6f013..9f2bcfd98 100644 --- a/rules/src/test/java/com/ibm/rules/NoMD5UseForMessageDigestRuleTest.java +++ b/rules/src/test/java/com/ibm/rules/NoMD5UseForMessageDigestRuleTest.java @@ -27,12 +27,12 @@ import com.ibm.mapper.model.functionality.Tag; import com.ibm.rules.issue.Issue; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public final class NoMD5UseForMessageDigestRuleTest extends TestBase { +final class NoMD5UseForMessageDigestRuleTest extends TestBase { @Test - public void non() { + void non() { final RSA rsa = new RSA(detectionLocation); final NoMD5UseForMessageDigestRule rule = new NoMD5UseForMessageDigestRule<>(); @@ -42,7 +42,7 @@ public void non() { } @Test - public void valid() { + void valid() { final MD5 md5 = new MD5(detectionLocation); md5.put(new Tag(detectionLocation)); @@ -53,7 +53,7 @@ public void valid() { } @Test - public void inValid() { + void inValid() { final MD5 md5 = new MD5(detectionLocation); md5.put(new Digest(detectionLocation)); diff --git a/sonar-cryptography-plugin/pom.xml b/sonar-cryptography-plugin/pom.xml index c12384d73..38634e774 100644 --- a/sonar-cryptography-plugin/pom.xml +++ b/sonar-cryptography-plugin/pom.xml @@ -16,11 +16,9 @@ 2023 - 17 - 17 UTF-8 - 17 + 21 org.sonarsource.java,org.sonarsource.python,org.sonarsource.go @@ -54,6 +52,33 @@ 2.0.0-SNAPSHOT compile + + com.ibm + cpp + 2.0.0-SNAPSHOT + compile + + + + ${sonar.cxx.groupId} + sonar-cxx-plugin + ${sonar.cxx.version} + compile + + + + ${sonar.cxx.groupId} + cxx-squid-bridge + ${sonar.cxx.version} + compile + + + ${sonar.cxx.groupId} + cxx-squid + ${sonar.cxx.version} + compile + @@ -150,12 +175,12 @@ sonar-crypto-plugin Sonar Crypto Plugin com.ibm.plugin.CryptographyPlugin - - java,jsp,py,ipynb,go,cs + + java,jsp,py,ipynb,go,cs,cxx,cpp,c++,c ${sonar.minVersion} true true - 17 + ${sonar.jreMinVersion} @@ -180,9 +205,112 @@ META-INF/*.SF + + + ${sonar.cxx.groupId}:* + + ** + + + + + ${sonar.cxx.groupId}:sonar-cxx-plugin + + META-INF/lib/** + + + + + com.fasterxml.woodstox:woodstox-core + + ** + + + + org.codehaus.woodstox:stax2-api + + ** + + + + com.fasterxml.staxmate:staxmate + + ** + + + + jaxen:jaxen + + ** + + + + org.jdom:jdom2 + + ** + + + + org.apache.ant:* + + ** + + + + commons-lang:commons-lang + + ** + + + + com.googlecode.plist:dd-plist + + ** + + + + io.github.hakky54:* + + ** + + + + org.ow2.asm:asm + + ** + + + + com.google.guava:* + + ** + + + + com.google.code.gson:gson + + ** + + + + com.fasterxml.jackson.core:* + + ** + + + + @@ -194,11 +322,36 @@ maven-compiler-plugin 3.15.0 - 17 - 17 - 17 + 21 + + + org.apache.maven.plugins + maven-clean-plugin + 3.5.0 + + + clean-sonarqube-plugins + package + + clean + + + true + + + ../.SonarQube/plugins + + sonar-cryptography-plugin-*.jar + + + + + + + org.apache.maven.plugins maven-resources-plugin @@ -249,7 +402,7 @@ - 17 + 21 diff --git a/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/CryptographyPlugin.java b/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/CryptographyPlugin.java index 415b5a011..35c12f3b3 100644 --- a/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/CryptographyPlugin.java +++ b/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/CryptographyPlugin.java @@ -24,6 +24,7 @@ import org.sonar.api.Plugin; import org.sonar.api.SonarProduct; import org.sonar.api.SonarRuntime; +import org.sonar.plugins.cxx.CxxPlugin; public class CryptographyPlugin implements Plugin { @@ -53,5 +54,10 @@ public void define(Context context) { CryptoCSharpSensor.class, // Custom sensor (sonar-csharp has no CheckRegistrar API) // general OutputFileJob.class); + // cxx: the language and its sensor come from sonar-cxx, bundled with this plugin + new CxxPlugin().define(context); + context.addExtensions( + CxxScannerRuleDefinition.class, // Define C++ rules + CxxCheckRegistrar.class); // Register C++ rules by sonar-cxx sensor } } diff --git a/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/ScannerManager.java b/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/ScannerManager.java index fd717b5d0..2c18d3603 100644 --- a/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/ScannerManager.java +++ b/sonar-cryptography-plugin/src/main/java/com/ibm/plugin/ScannerManager.java @@ -68,6 +68,7 @@ private List getAggregatedNodes() { nodes.addAll(PythonAggregator.getDetectedNodes()); nodes.addAll(GoAggregator.getDetectedNodes()); nodes.addAll(CSharpAggregator.getDetectedNodes()); + nodes.addAll(CxxAggregator.getDetectedNodes()); return nodes; } @@ -90,5 +91,6 @@ public void reset() { PythonAggregator.reset(); GoAggregator.reset(); CSharpAggregator.reset(); + CxxAggregator.reset(); } } diff --git a/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/PluginTest.java b/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/PluginTest.java index d7a409553..23f75c62a 100644 --- a/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/PluginTest.java +++ b/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/PluginTest.java @@ -28,6 +28,9 @@ import org.sonar.api.internal.PluginContextImpl; import org.sonar.api.internal.SonarRuntimeImpl; import org.sonar.api.utils.Version; +import org.sonar.plugins.cxx.CxxLanguage; +import org.sonar.plugins.cxx.CxxPlugin; +import org.sonar.plugins.cxx.CxxSquidSensor; class PluginTest { @@ -39,6 +42,17 @@ void testExtensions() { Plugin.Context context = new PluginContextImpl.Builder().setSonarRuntime(runtime).build(); CryptographyPlugin plugin = new CryptographyPlugin(); plugin.define(context); - Assertions.assertEquals(10, context.getExtensions().size()); + // the bundled sonar-cxx provides the C/C++ language and sensor + Assertions.assertTrue(context.getExtensions().contains(CxxLanguage.class)); + Assertions.assertTrue(context.getExtensions().contains(CxxSquidSensor.class)); + Assertions.assertTrue(context.getExtensions().contains(CxxScannerRuleDefinition.class)); + Assertions.assertTrue(context.getExtensions().contains(CxxCheckRegistrar.class)); + Assertions.assertEquals(12 + cxxPluginExtensions(runtime), context.getExtensions().size()); + } + + private static int cxxPluginExtensions(SonarRuntime runtime) { + Plugin.Context context = new PluginContextImpl.Builder().setSonarRuntime(runtime).build(); + new CxxPlugin().define(context); + return context.getExtensions().size(); } } diff --git a/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/ScannerManagerTest.java b/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/ScannerManagerTest.java index a6eeac5f4..b6b884ff2 100644 --- a/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/ScannerManagerTest.java +++ b/sonar-cryptography-plugin/src/test/java/com/ibm/plugin/ScannerManagerTest.java @@ -24,6 +24,10 @@ import com.ibm.mapper.model.INode; import com.ibm.mapper.model.algorithms.AES; import com.ibm.mapper.utils.DetectionLocation; +import com.ibm.output.IOutputFile; +import com.ibm.output.IOutputFileFactory; +import java.io.File; +import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -35,6 +39,9 @@ class ScannerManagerTest { @AfterEach void clearAggregators() { JavaAggregator.reset(); + PythonAggregator.reset(); + GoAggregator.reset(); + CxxAggregator.reset(); } @Test @@ -54,4 +61,75 @@ void heapAttributionCountsRetainedDetectedNodes() { HeapAttributionSummary summary = new ScannerManager(null).heapAttribution(); assertThat(summary.detectedNodes()).isEqualTo(1); } + + @Test + void getOutputFileIncludesNodesFromEveryLanguageAggregator() { + JavaAggregator.addNodes(List.of(new AES(128, javaLocation()))); + PythonAggregator.addNodes(List.of(new AES(128, pythonLocation()))); + GoAggregator.addNodes(List.of(new AES(128, goLocation()))); + CxxAggregator.addNodes(List.of(new AES(128, cxxLocation()))); + + RecordingOutputFileFactory factory = new RecordingOutputFileFactory(); + new ScannerManager(factory).getOutputFile(); + + assertThat(factory.recordedNodes).hasSize(4); + } + + @Test + void hasResultsIsTrueWhenOnlyCxxAggregatorHasNodes() { + CxxAggregator.addNodes(List.of(new AES(128, cxxLocation()))); + + assertThat(new ScannerManager(null).hasResults()).isTrue(); + } + + @Test + void hasResultsIsTrueWhenOnlyPythonAggregatorHasNodes() { + PythonAggregator.addNodes(List.of(new AES(128, pythonLocation()))); + + assertThat(new ScannerManager(null).hasResults()).isTrue(); + } + + @Test + void hasResultsIsTrueWhenOnlyGoAggregatorHasNodes() { + GoAggregator.addNodes(List.of(new AES(128, goLocation()))); + + assertThat(new ScannerManager(null).hasResults()).isTrue(); + } + + private static DetectionLocation javaLocation() { + return new DetectionLocation("Test.java", 1, 0, List.of("aes"), () -> "test"); + } + + private static DetectionLocation pythonLocation() { + return new DetectionLocation("test.py", 1, 0, List.of("aes"), () -> "test"); + } + + private static DetectionLocation goLocation() { + return new DetectionLocation("test.go", 1, 0, List.of("aes"), () -> "test"); + } + + private static DetectionLocation cxxLocation() { + return new DetectionLocation("test.cc", 1, 0, List.of("aes"), () -> "test"); + } + + /** Records the node list it is asked to format, so the test can inspect it directly. */ + private static final class RecordingOutputFileFactory implements IOutputFileFactory { + private List recordedNodes = List.of(); + + @Override + public IOutputFile createOutputFormat(List nodes) { + this.recordedNodes = new ArrayList<>(nodes); + return new IOutputFile() { + @Override + public void add(List nodes) { + // not exercised by this test + } + + @Override + public void saveTo(File file) { + // not exercised by this test + } + }; + } + } }