diff --git a/csharp/src/main/java/com/ibm/plugin/rules/detection/CSharpDetectionRules.java b/csharp/src/main/java/com/ibm/plugin/rules/detection/CSharpDetectionRules.java index b7d1b57ae..27729f6d9 100755 --- a/csharp/src/main/java/com/ibm/plugin/rules/detection/CSharpDetectionRules.java +++ b/csharp/src/main/java/com/ibm/plugin/rules/detection/CSharpDetectionRules.java @@ -21,28 +21,7 @@ import com.ibm.engine.language.csharp.tree.CSharpTree; import com.ibm.engine.rule.IDetectionRule; -import com.ibm.plugin.rules.detection.dotnet.DotNetAES; -import com.ibm.plugin.rules.detection.dotnet.DotNetAlgorithmFactory; -import com.ibm.plugin.rules.detection.dotnet.DotNetDES; -import com.ibm.plugin.rules.detection.dotnet.DotNetDSA; -import com.ibm.plugin.rules.detection.dotnet.DotNetECDiffieHellman; -import com.ibm.plugin.rules.detection.dotnet.DotNetECDsa; -import com.ibm.plugin.rules.detection.dotnet.DotNetHMAC; -import com.ibm.plugin.rules.detection.dotnet.DotNetKMAC; -import com.ibm.plugin.rules.detection.dotnet.DotNetKeyDerivation; -import com.ibm.plugin.rules.detection.dotnet.DotNetLegacyFormatters; -import com.ibm.plugin.rules.detection.dotnet.DotNetMLDsa; -import com.ibm.plugin.rules.detection.dotnet.DotNetMLKem; -import com.ibm.plugin.rules.detection.dotnet.DotNetProtectedData; -import com.ibm.plugin.rules.detection.dotnet.DotNetRC2; -import com.ibm.plugin.rules.detection.dotnet.DotNetRSA; -import com.ibm.plugin.rules.detection.dotnet.DotNetRandomNumberGenerator; -import com.ibm.plugin.rules.detection.dotnet.DotNetRfc2898DeriveBytes; -import com.ibm.plugin.rules.detection.dotnet.DotNetSHA; -import com.ibm.plugin.rules.detection.dotnet.DotNetSHA3; -import com.ibm.plugin.rules.detection.dotnet.DotNetSlhDsa; -import com.ibm.plugin.rules.detection.dotnet.DotNetTripleDES; -import com.ibm.plugin.rules.detection.dotnet.DotNetX25519DiffieHellman; +import com.ibm.plugin.rules.detection.dotnet.*; import java.util.List; import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -58,6 +37,7 @@ private CSharpDetectionRules() { public static List> rules() { return Stream.of( DotNetAES.rules().stream(), + DotNetChaCha20Poly1305.rules().stream(), DotNetDES.rules().stream(), DotNetTripleDES.rules().stream(), DotNetRC2.rules().stream(), diff --git a/csharp/src/main/java/com/ibm/plugin/rules/detection/dotnet/DotNetChaCha20Poly1305.java b/csharp/src/main/java/com/ibm/plugin/rules/detection/dotnet/DotNetChaCha20Poly1305.java new file mode 100644 index 000000000..2d047025e --- /dev/null +++ b/csharp/src/main/java/com/ibm/plugin/rules/detection/dotnet/DotNetChaCha20Poly1305.java @@ -0,0 +1,81 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2026 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.plugin.rules.detection.dotnet; + +import com.ibm.engine.detection.MethodMatcher; +import com.ibm.engine.language.csharp.tree.CSharpTree; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.engine.model.factory.CipherActionFactory; +import com.ibm.engine.model.factory.ValueActionFactory; +import com.ibm.engine.rule.IDetectionRule; +import com.ibm.engine.rule.builder.DetectionRuleBuilder; +import java.util.List; +import javax.annotation.Nonnull; + +public final class DotNetChaCha20Poly1305 { + private DotNetChaCha20Poly1305() { + // nothing + } + + // chaCha20Poly1305.Encrypt(nonce, plaintext, ciphertext, tag [, associatedData]) + private static final IDetectionRule CHACHA20POLY1305_ENCRYPT_OP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(MethodMatcher.ANY) + .forMethods("Encrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.ENCRYPT)) + .withAnyParameters() // Byte[] or ReadOnlySpan overloads + .buildForContext(new CipherContext()) + .inBundle(() -> "DotNet") + .withoutDependingDetectionRules(); + + // chaCha20Poly1305.Decrypt(nonce, ciphertext, tag, plaintext [, associatedData]) + private static final IDetectionRule CHACHA20POLY1305_DECRYPT_OP = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes(MethodMatcher.ANY) + .forMethods("Decrypt") + .shouldBeDetectedAs(new CipherActionFactory<>(CipherAction.Action.DECRYPT)) + .withAnyParameters() // Byte[] or ReadOnlySpan overloads + .buildForContext(new CipherContext()) + .inBundle(() -> "DotNet") + .withoutDependingDetectionRules(); + + private static final List> CHACHA20POLY1305_OP_RULES = + List.of(CHACHA20POLY1305_ENCRYPT_OP, CHACHA20POLY1305_DECRYPT_OP); + + // new ChaCha20Poly1305(key) + private static final IDetectionRule CHACHA20POLY1305 = + new DetectionRuleBuilder() + .createDetectionRule() + .forObjectTypes("ChaCha20Poly1305") + .forMethods("") + .shouldBeDetectedAs(new ValueActionFactory<>("CHACHA20POLY1305")) + .withAnyParameters() // Byte[] or ReadOnlySpan, 1 parameter + .buildForContext(new CipherContext()) + .inBundle(() -> "DotNet") + .withDependingDetectionRules(CHACHA20POLY1305_OP_RULES); + + @Nonnull + public static List> rules() { + return List.of(CHACHA20POLY1305); + } +} diff --git a/csharp/src/main/java/com/ibm/plugin/translation/translator/contexts/CSharpCipherContextTranslator.java b/csharp/src/main/java/com/ibm/plugin/translation/translator/contexts/CSharpCipherContextTranslator.java index ed6f17517..be956e860 100755 --- a/csharp/src/main/java/com/ibm/plugin/translation/translator/contexts/CSharpCipherContextTranslator.java +++ b/csharp/src/main/java/com/ibm/plugin/translation/translator/contexts/CSharpCipherContextTranslator.java @@ -19,15 +19,7 @@ */ package com.ibm.plugin.translation.translator.contexts; -import com.ibm.engine.model.Algorithm; -import com.ibm.engine.model.BlockSize; -import com.ibm.engine.model.CipherAction; -import com.ibm.engine.model.IValue; -import com.ibm.engine.model.KeySize; -import com.ibm.engine.model.Mode; -import com.ibm.engine.model.OperationMode; -import com.ibm.engine.model.Padding; -import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.*; import com.ibm.engine.model.context.IDetectionContext; import com.ibm.engine.rule.IBundle; import com.ibm.mapper.IContextTranslation; @@ -37,15 +29,8 @@ import com.ibm.mapper.model.Cipher; 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.DES; -import com.ibm.mapper.model.algorithms.DESede; -import com.ibm.mapper.model.algorithms.RC2; -import com.ibm.mapper.model.algorithms.RSA; -import com.ibm.mapper.model.functionality.Decrypt; -import com.ibm.mapper.model.functionality.Encrypt; -import com.ibm.mapper.model.functionality.Generate; -import com.ibm.mapper.model.functionality.KeyGeneration; +import com.ibm.mapper.model.algorithms.*; +import com.ibm.mapper.model.functionality.*; import com.ibm.mapper.utils.DetectionLocation; import java.util.Optional; import javax.annotation.Nonnull; @@ -70,6 +55,8 @@ public final class CSharpCipherContextTranslator Optional result = switch (valueStr) { case "AES" -> Optional.of(new AES(detectionLocation)); + case "CHACHA20POLY1305" -> + Optional.of(new ChaCha20Poly1305(detectionLocation)); case "DES" -> Optional.of(new DES(detectionLocation)); case "3DES", "DESEDE", "TRIPLEDES" -> Optional.of(new DESede(detectionLocation)); @@ -120,6 +107,12 @@ public final class CSharpCipherContextTranslator } else if (value instanceof BlockSize blockSize) { return Optional.of( new com.ibm.mapper.model.BlockSize(blockSize.getValue(), detectionLocation)); + } else if (value instanceof CipherAction cipherAction) { + return switch (cipherAction.getAction()) { + case DECRYPT -> Optional.of(new Decrypt(detectionLocation)); + case ENCRYPT -> Optional.of(new Encrypt(detectionLocation)); + default -> Optional.empty(); + }; } else if (value instanceof KeySize keySize) { return Optional.of(new KeyLength(keySize.getValue(), detectionLocation)); } else if (value instanceof OperationMode operationMode) { diff --git a/csharp/src/test/files/rules/detection/dotnet/DotNetChaCha20Poly1305TestFile.cs b/csharp/src/test/files/rules/detection/dotnet/DotNetChaCha20Poly1305TestFile.cs new file mode 100644 index 000000000..823f2efbc --- /dev/null +++ b/csharp/src/test/files/rules/detection/dotnet/DotNetChaCha20Poly1305TestFile.cs @@ -0,0 +1,289 @@ +/* + * Test file for System.Security.Cryptography.ChaCha20Poly1305 detection rules. + * + * Covers the full API surface of the ChaCha20Poly1305 class: + * - constructor overloads (byte[] key, ReadOnlySpan key) + * - Encrypt overloads (byte[] and ReadOnlySpan), with and without associatedData + * - Decrypt overloads (byte[] and ReadOnlySpan), with and without associatedData + * + * Since the constructor accepts either a byte[] key or a ReadOnlySpan key, and + * Encrypt/Decrypt each accept either byte[] or ReadOnlySpan buffers, there are + * 4 key-type/buffer-type combinations for Encrypt and 4 for Decrypt. Sections 2-5 pair + * a byte[] key with both buffer overloads; Sections 6-9 pair a ReadOnlySpan key + * with both buffer overloads. + * + * Architecture note: Encrypt/Decrypt are depending rules attached to the constructor + * (see DotNetChaCha20Poly1305.java), so each method below that constructs-then-uses + * the object produces a single finding with nested Encrypt/Decrypt children, not separate + * disconnected findings. + */ + +using System; +using System.Security.Cryptography; + +public class DotNetChaCha20Poly1305ComprehensiveTest +{ + // ------------------------------------------------------------------------- + // Section 1: Constructor overloads + // ------------------------------------------------------------------------- + + public void TestConstructByteArrayKey() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + } + + public void TestConstructSpanKey() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + } + + // ------------------------------------------------------------------------- + // Section 2: byte[] key + Encrypt — byte[] overload + // ------------------------------------------------------------------------- + + public void TestEncryptByteArrayWithAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var plainText = new byte[32]; + var cipherText = new byte[32]; + var tag = new byte[16]; + var associatedData = new byte[32]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag, associatedData); + } + + public void TestEncryptByteArrayNoAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var plainText = new byte[32]; + var cipherText = new byte[32]; + var tag = new byte[16]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag); + } + + // ------------------------------------------------------------------------- + // Section 3: byte[] key + Encrypt — ReadOnlySpan overload + // ------------------------------------------------------------------------- + + public void TestEncryptSpanWithAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span plainText = stackalloc byte[32]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + Span associatedData = stackalloc byte[32]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag, associatedData); + } + + public void TestEncryptSpanNoAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span plainText = stackalloc byte[32]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag); + } + + // ------------------------------------------------------------------------- + // Section 4: byte[] key + Decrypt — byte[] overload + // ------------------------------------------------------------------------- + + public void TestDecryptByteArrayWithAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var cipherText = new byte[32]; + var tag = new byte[16]; + var plainText = new byte[32]; + var associatedData = new byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText, associatedData); + } + + public void TestDecryptByteArrayNoAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var cipherText = new byte[32]; + var tag = new byte[16]; + var plainText = new byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText); + } + + // ------------------------------------------------------------------------- + // Section 5: byte[] key + Decrypt — ReadOnlySpan overload + // ------------------------------------------------------------------------- + + public void TestDecryptSpanWithAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + Span plainText = stackalloc byte[32]; + Span associatedData = stackalloc byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText, associatedData); + } + + public void TestDecryptSpanNoAad() + { + var key = new byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + Span plainText = stackalloc byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText); + } + + // ------------------------------------------------------------------------- + // Section 6: ReadOnlySpan key + Encrypt — byte[] overload + // ------------------------------------------------------------------------- + + public void TestSpanKeyEncryptByteArrayWithAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var plainText = new byte[32]; + var cipherText = new byte[32]; + var tag = new byte[16]; + var associatedData = new byte[32]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag, associatedData); + } + + public void TestSpanKeyEncryptByteArrayNoAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var plainText = new byte[32]; + var cipherText = new byte[32]; + var tag = new byte[16]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag); + } + + // ------------------------------------------------------------------------- + // Section 7: ReadOnlySpan key + Encrypt — ReadOnlySpan overload + // ------------------------------------------------------------------------- + + public void TestSpanKeyEncryptSpanWithAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span plainText = stackalloc byte[32]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + Span associatedData = stackalloc byte[32]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag, associatedData); + } + + public void TestSpanKeyEncryptSpanNoAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span plainText = stackalloc byte[32]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + + chaCha.Encrypt(nonce, plainText, cipherText, tag); + } + + // ------------------------------------------------------------------------- + // Section 8: ReadOnlySpan key + Decrypt — byte[] overload + // ------------------------------------------------------------------------- + + public void TestSpanKeyDecryptByteArrayWithAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var cipherText = new byte[32]; + var tag = new byte[16]; + var plainText = new byte[32]; + var associatedData = new byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText, associatedData); + } + + public void TestSpanKeyDecryptByteArrayNoAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + var nonce = new byte[12]; + var cipherText = new byte[32]; + var tag = new byte[16]; + var plainText = new byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText); + } + + // ------------------------------------------------------------------------- + // Section 9: ReadOnlySpan key + Decrypt — ReadOnlySpan overload + // ------------------------------------------------------------------------- + + public void TestSpanKeyDecryptSpanWithAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + Span plainText = stackalloc byte[32]; + Span associatedData = stackalloc byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText, associatedData); + } + + public void TestSpanKeyDecryptSpanNoAad() + { + Span key = stackalloc byte[32]; + var chaCha = new ChaCha20Poly1305(key); + + Span nonce = stackalloc byte[12]; + Span cipherText = stackalloc byte[32]; + Span tag = stackalloc byte[16]; + Span plainText = stackalloc byte[32]; + + chaCha.Decrypt(nonce, cipherText, tag, plainText); + } +} diff --git a/csharp/src/test/java/com/ibm/plugin/rules/detection/dotnet/DotNetChaCha20Poly1305Test.java b/csharp/src/test/java/com/ibm/plugin/rules/detection/dotnet/DotNetChaCha20Poly1305Test.java new file mode 100644 index 000000000..abc04256a --- /dev/null +++ b/csharp/src/test/java/com/ibm/plugin/rules/detection/dotnet/DotNetChaCha20Poly1305Test.java @@ -0,0 +1,233 @@ +/* + * 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.dotnet; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.language.csharp.CSharpCheck; +import com.ibm.engine.language.csharp.CSharpScanContext; +import com.ibm.engine.language.csharp.CSharpSymbol; +import com.ibm.engine.language.csharp.tree.CSharpTree; +import com.ibm.engine.model.CipherAction; +import com.ibm.engine.model.IValue; +import com.ibm.engine.model.ValueAction; +import com.ibm.engine.model.context.CipherContext; +import com.ibm.mapper.model.AuthenticatedEncryption; +import com.ibm.mapper.model.INode; +import com.ibm.mapper.model.functionality.Decrypt; +import com.ibm.mapper.model.functionality.Encrypt; +import com.ibm.plugin.CSharpVerifier; +import com.ibm.plugin.TestBase; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +/** + * Test for all ChaCha20Poly1305-related detection rules (DotNetChaCha20Poly1305.java). + * + *

Covers the full API surface of {@code System.Security.Cryptography.ChaCha20Poly1305}: + * + *

    + *
  • constructor overloads (byte[] key, ReadOnlySpan<byte> key) + *
  • Encrypt overloads (byte[] and ReadOnlySpan<byte>), with and without associatedData + *
  • Decrypt overloads (byte[] and ReadOnlySpan<byte>), with and without associatedData + *
+ * + *

The constructor accepts either a {@code byte[]} key or a {@code ReadOnlySpan} key, and + * Encrypt/Decrypt each accept either {@code byte[]} or {@code ReadOnlySpan} buffers, giving 4 + * key-type/buffer-type combinations for Encrypt and 4 for Decrypt (Sections 2–5 pair a {@code + * byte[]} key with both buffer overloads; Sections 6–9 pair a {@code ReadOnlySpan} key with + * both buffer overloads). + * + *

Finding mapping (one finding per test method in DotNetChaCha20Poly1305TestFile.cs): + * + *

+ * Section 1 – constructor overloads (findings 0–1):
+ *   0  TestConstructByteArrayKey             → ChaCha20-Poly1305
+ *   1  TestConstructSpanKey                  → ChaCha20-Poly1305
+ *
+ * Section 2 – byte[] key + Encrypt, byte[] overload (findings 2–3):
+ *   2  TestEncryptByteArrayWithAad           → ChaCha20-Poly1305 + Encrypt
+ *   3  TestEncryptByteArrayNoAad             → ChaCha20-Poly1305 + Encrypt
+ *
+ * Section 3 – byte[] key + Encrypt, ReadOnlySpan<byte> overload (findings 4–5):
+ *   4  TestEncryptSpanWithAad                → ChaCha20-Poly1305 + Encrypt
+ *   5  TestEncryptSpanNoAad                  → ChaCha20-Poly1305 + Encrypt
+ *
+ * Section 4 – byte[] key + Decrypt, byte[] overload (findings 6–7):
+ *   6  TestDecryptByteArrayWithAad           → ChaCha20-Poly1305 + Decrypt
+ *   7  TestDecryptByteArrayNoAad             → ChaCha20-Poly1305 + Decrypt
+ *
+ * Section 5 – byte[] key + Decrypt, ReadOnlySpan<byte> overload (findings 8–9):
+ *   8  TestDecryptSpanWithAad                → ChaCha20-Poly1305 + Decrypt
+ *   9  TestDecryptSpanNoAad                  → ChaCha20-Poly1305 + Decrypt
+ *
+ * Section 6 – ReadOnlySpan<byte> key + Encrypt, byte[] overload (findings 10–11):
+ *   10 TestSpanKeyEncryptByteArrayWithAad    → ChaCha20-Poly1305 + Encrypt
+ *   11 TestSpanKeyEncryptByteArrayNoAad      → ChaCha20-Poly1305 + Encrypt
+ *
+ * Section 7 – ReadOnlySpan<byte> key + Encrypt, ReadOnlySpan<byte> overload
+ * (findings 12–13):
+ *   12 TestSpanKeyEncryptSpanWithAad         → ChaCha20-Poly1305 + Encrypt
+ *   13 TestSpanKeyEncryptSpanNoAad           → ChaCha20-Poly1305 + Encrypt
+ *
+ * Section 8 – ReadOnlySpan<byte> key + Decrypt, byte[] overload (findings 14–15):
+ *   14 TestSpanKeyDecryptByteArrayWithAad    → ChaCha20-Poly1305 + Decrypt
+ *   15 TestSpanKeyDecryptByteArrayNoAad      → ChaCha20-Poly1305 + Decrypt
+ *
+ * Section 9 – ReadOnlySpan<byte> key + Decrypt, ReadOnlySpan<byte> overload
+ * (findings 16–17):
+ *   16 TestSpanKeyDecryptSpanWithAad         → ChaCha20-Poly1305 + Decrypt
+ *   17 TestSpanKeyDecryptSpanNoAad           → ChaCha20-Poly1305 + Decrypt
+ *
+ * 
+ */ +class DotNetChaCha20Poly1305Test extends TestBase { + + @Test + void test() throws Exception { + CSharpVerifier.verify("rules/detection/dotnet/DotNetChaCha20Poly1305TestFile.cs", this); + } + + @Override + public void asserts( + int findingId, + @Nonnull + DetectionStore + detectionStore, + @Nonnull List nodes) { + + // Every top-level finding must be CHACHA20POLY1305 + assertThat(detectionStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(detectionStore.getDetectionValues()).hasSize(1); + IValue primary = detectionStore.getDetectionValues().get(0); + assertThat(primary).isInstanceOf(ValueAction.class); + assertThat(primary.asString()).isEqualTo("CHACHA20POLY1305"); + + assertThat(nodes).hasSize(1); + INode node = nodes.get(0); + assertThat(node.getKind()).isEqualTo(AuthenticatedEncryption.class); + assertThat(node.asString()).isEqualTo("ChaCha20-Poly1305"); + + switch (findingId) { + + // ----------------------------------------------------------------- + // Section 1: constructor overloads — no Encrypt/Decrypt children + // ----------------------------------------------------------------- + case 0, 1 -> { + assertThat(node.getChildren().get(Encrypt.class)).isNull(); + assertThat(node.getChildren().get(Decrypt.class)).isNull(); + } + + // ----------------------------------------------------------------- + // Section 2: Encrypt — byte[] overload + // ----------------------------------------------------------------- + case 2, 3 -> assertEncryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 3: Encrypt — ReadOnlySpan overload + // ----------------------------------------------------------------- + case 4, 5 -> assertEncryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 4: Decrypt — byte[] overload + // ----------------------------------------------------------------- + case 6, 7 -> assertDecryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 5: Decrypt — ReadOnlySpan overload + // ----------------------------------------------------------------- + case 8, 9 -> assertDecryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 6: ReadOnlySpan key + Encrypt — byte[] overload + // ----------------------------------------------------------------- + case 10, 11 -> assertEncryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 7: ReadOnlySpan key + Encrypt — ReadOnlySpan overload + // ----------------------------------------------------------------- + case 12, 13 -> assertEncryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 8: ReadOnlySpan key + Decrypt — byte[] overload + // ----------------------------------------------------------------- + case 14, 15 -> assertDecryptFindings(findingId, detectionStore, node); + + // ----------------------------------------------------------------- + // Section 9: ReadOnlySpan key + Decrypt — ReadOnlySpan overload + // ----------------------------------------------------------------- + case 16, 17 -> assertDecryptFindings(findingId, detectionStore, node); + + default -> throw new IllegalStateException("Unexpected findingId: " + findingId); + } + } + + // ------------------------------------------------------------------------- + // Assertion helpers + // ------------------------------------------------------------------------- + + private void assertEncryptFindings( + int findingId, + @Nonnull DetectionStore store, + @Nonnull INode node) { + + DetectionStore encryptStore = + getStoreOfValueType(CipherAction.class, store.getChildren()); + assertThat(encryptStore) + .as("finding %d: expected an ENCRYPT child detection store", findingId) + .isNotNull(); + assertThat(encryptStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(encryptStore.getDetectionValues()).hasSize(1); + IValue encryptValue = encryptStore.getDetectionValues().get(0); + assertThat(encryptValue).isInstanceOf(CipherAction.class); + assertThat(((CipherAction) encryptValue).getAction()) + .isEqualTo(CipherAction.Action.ENCRYPT); + + assertThat(node.getChildren().get(Encrypt.class)) + .as("finding %d: expected an Encrypt child node", findingId) + .isNotNull(); + assertThat(node.getChildren().get(Decrypt.class)).isNull(); + } + + private void assertDecryptFindings( + int findingId, + @Nonnull DetectionStore store, + @Nonnull INode node) { + + DetectionStore decryptStore = + getStoreOfValueType(CipherAction.class, store.getChildren()); + assertThat(decryptStore) + .as("finding %d: expected a DECRYPT child detection store", findingId) + .isNotNull(); + assertThat(decryptStore.getDetectionValueContext()).isInstanceOf(CipherContext.class); + assertThat(decryptStore.getDetectionValues()).hasSize(1); + IValue decryptValue = decryptStore.getDetectionValues().get(0); + assertThat(decryptValue).isInstanceOf(CipherAction.class); + assertThat(((CipherAction) decryptValue).getAction()) + .isEqualTo(CipherAction.Action.DECRYPT); + + assertThat(node.getChildren().get(Decrypt.class)) + .as("finding %d: expected a Decrypt child node", findingId) + .isNotNull(); + assertThat(node.getChildren().get(Encrypt.class)).isNull(); + } +}