From 9decd51b1c90f4fcb713e7ba3904394721885a40 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Mon, 16 Jun 2025 11:59:34 +0200 Subject: [PATCH 01/33] Initial quantum support for C# --- .../ql/lib/experimental/quantum/Language.qll | 42 +++++++++++++++++++ csharp/ql/lib/qlpack.yml | 1 + 2 files changed, 43 insertions(+) create mode 100644 csharp/ql/lib/experimental/quantum/Language.qll diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll new file mode 100644 index 000000000000..b94be0021000 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -0,0 +1,42 @@ +private import csharp as Language +private import codeql.quantum.experimental.Model + +private class UnknownLocation extends Language::Location { + UnknownLocation() { this.getFile().getAbsolutePath() = "" } +} + +/** + * A dummy location which is used when something doesn't have a location in + * the source code but needs to have a `Location` associated with it. There + * may be several distinct kinds of unknown locations. For example: one for + * expressions, one for statements and one for other program elements. + */ +private class UnknownDefaultLocation extends UnknownLocation { + UnknownDefaultLocation() { locations_default(this, _, 0, 0, 0, 0) } +} + +module CryptoInput implements InputSig { + class DataFlowNode = DataFlow::Node; + + class LocatableElement = Language::Element; + + class UnknownLocation = UnknownDefaultLocation; + + string locationToFileBaseNameAndLineNumberString(Location location) { + result = location.getFile().getBaseName() + ":" + location.getStartLine() + } + + LocatableElement dfn_to_element(DataFlow::Node node) { + result = node.asExpr() or + result = node.asParameter() + } + + predicate artifactOutputFlowsToGenericInput( + DataFlow::Node artifactOutput, DataFlow::Node otherInput + ) { + ArtifactFlow::flow(artifactOutput, otherInput) + } +} + +// Instantiate the `CryptographyBase` module +module Crypto = CryptographyBase; diff --git a/csharp/ql/lib/qlpack.yml b/csharp/ql/lib/qlpack.yml index 464284c56cb4..17298206902c 100644 --- a/csharp/ql/lib/qlpack.yml +++ b/csharp/ql/lib/qlpack.yml @@ -9,6 +9,7 @@ dependencies: codeql/controlflow: ${workspace} codeql/dataflow: ${workspace} codeql/mad: ${workspace} + codeql/quantum: ${workspace} codeql/ssa: ${workspace} codeql/threat-models: ${workspace} codeql/tutorial: ${workspace} From 66eee73b300c6f18b75f0d80e1ea041d60627321 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Mon, 16 Jun 2025 12:14:35 +0200 Subject: [PATCH 02/33] Fixed issues in C# Language.qll --- .../ql/lib/experimental/quantum/Language.qll | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll index b94be0021000..a0eba97b2eb5 100644 --- a/csharp/ql/lib/experimental/quantum/Language.qll +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -16,23 +16,23 @@ private class UnknownDefaultLocation extends UnknownLocation { } module CryptoInput implements InputSig { - class DataFlowNode = DataFlow::Node; + class DataFlowNode = Language::DataFlow::Node; class LocatableElement = Language::Element; class UnknownLocation = UnknownDefaultLocation; - string locationToFileBaseNameAndLineNumberString(Location location) { + string locationToFileBaseNameAndLineNumberString(Language::Location location) { result = location.getFile().getBaseName() + ":" + location.getStartLine() } - LocatableElement dfn_to_element(DataFlow::Node node) { + LocatableElement dfn_to_element(Language::DataFlow::Node node) { result = node.asExpr() or result = node.asParameter() } predicate artifactOutputFlowsToGenericInput( - DataFlow::Node artifactOutput, DataFlow::Node otherInput + Language::DataFlow::Node artifactOutput, Language::DataFlow::Node otherInput ) { ArtifactFlow::flow(artifactOutput, otherInput) } @@ -40,3 +40,27 @@ module CryptoInput implements InputSig { // Instantiate the `CryptographyBase` module module Crypto = CryptographyBase; + +module ArtifactFlowConfig implements Language::DataFlow::ConfigSig { + predicate isSource(Language::DataFlow::Node source) { + source = any(Crypto::ArtifactInstance artifact).getOutputNode() + } + + predicate isSink(Language::DataFlow::Node sink) { + sink = any(Crypto::FlowAwareElement other).getInputNode() + } + + predicate isBarrierOut(Language::DataFlow::Node node) { + node = any(Crypto::FlowAwareElement element).getInputNode() + } + + predicate isBarrierIn(Language::DataFlow::Node node) { + node = any(Crypto::FlowAwareElement element).getOutputNode() + } + + predicate isAdditionalFlowStep(Language::DataFlow::Node node1, Language::DataFlow::Node node2) { + none() + } +} + +module ArtifactFlow = Language::DataFlow::Global; From 69e6308856db728769f71befecae6ea6ccf692bf Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Thu, 19 Jun 2025 11:59:59 +0200 Subject: [PATCH 03/33] Added C# query to generate CBOM graph --- .../experimental/quantum/PrintCBOMGraph.ql | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 csharp/ql/src/experimental/quantum/PrintCBOMGraph.ql diff --git a/csharp/ql/src/experimental/quantum/PrintCBOMGraph.ql b/csharp/ql/src/experimental/quantum/PrintCBOMGraph.ql new file mode 100644 index 000000000000..4dd33d070c07 --- /dev/null +++ b/csharp/ql/src/experimental/quantum/PrintCBOMGraph.ql @@ -0,0 +1,23 @@ +/** + * @name Print CBOM Graph + * @description Outputs a graph representation of the cryptographic bill of materials. + * This query only supports DGML output, as CodeQL DOT output omits properties. + * @kind graph + * @id csharp/print-cbom-graph + * @tags quantum + * experimental + */ + +import experimental.quantum.Language + +query predicate nodes(Crypto::NodeBase node, string key, string value) { + Crypto::nodes_graph_impl(node, key, value) +} + +query predicate edges(Crypto::NodeBase source, Crypto::NodeBase target, string key, string value) { + Crypto::edges_graph_impl(source, target, key, value) +} + +query predicate graphProperties(string key, string value) { + key = "semmle.graphKind" and value = "graph" +} From 07a30323dbbaf3553215d67762f45a619b5172d3 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Thu, 19 Jun 2025 15:34:14 +0100 Subject: [PATCH 04/33] quantum-c#: initial support for ECDSA and RSA signatures --- .../ql/lib/experimental/quantum/Language.qll | 2 + csharp/ql/lib/experimental/quantum/dotnet.qll | 4 + .../quantum/dotnet/AlgorithmInstances.qll | 26 ++++ .../dotnet/AlgorithmValueConsumers.qll | 16 +++ .../quantum/dotnet/Cryptography.qll | 125 ++++++++++++++++++ .../quantum/dotnet/FlowAnalysis.qll | 32 +++++ .../quantum/dotnet/OperationInstances.qll | 48 +++++++ 7 files changed, 253 insertions(+) create mode 100644 csharp/ql/lib/experimental/quantum/dotnet.qll create mode 100644 csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll create mode 100644 csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll create mode 100644 csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll create mode 100644 csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll create mode 100644 csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll index a0eba97b2eb5..3591837697e4 100644 --- a/csharp/ql/lib/experimental/quantum/Language.qll +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -64,3 +64,5 @@ module ArtifactFlowConfig implements Language::DataFlow::ConfigSig { } module ArtifactFlow = Language::DataFlow::Global; + +import dotnet \ No newline at end of file diff --git a/csharp/ql/lib/experimental/quantum/dotnet.qll b/csharp/ql/lib/experimental/quantum/dotnet.qll new file mode 100644 index 000000000000..1deb6b492613 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet.qll @@ -0,0 +1,4 @@ +import dotnet.AlgorithmInstances +import dotnet.AlgorithmValueConsumers +import dotnet.FlowAnalysis +import dotnet.Cryptography diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll new file mode 100644 index 000000000000..dc7363117905 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -0,0 +1,26 @@ +private import csharp +private import experimental.quantum.Language +private import AlgorithmValueConsumers +private import Cryptography +private import FlowAnalysis + +class SigningNamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof SigningNamedCurvePropertyAccess +{ + ECDsaAlgorithmValueConsumer consumer; + + SigningNamedCurveAlgorithmInstance() { + SigningNamedCurveToSignatureCreateFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) + } + + ECDsaAlgorithmValueConsumer getConsumer() { result = consumer } + + override string getRawEllipticCurveName() { result = super.getCurveName() } + + override Crypto::TEllipticCurveType getEllipticCurveType() { + Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _, result) + } + + override int getKeySize() { + Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), result, _) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll new file mode 100644 index 000000000000..14bd1dba39b5 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -0,0 +1,16 @@ +private import csharp +private import experimental.quantum.Language +private import AlgorithmInstances +private import Cryptography + +class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { + ECDsaCreateCall call; + + ECDsaAlgorithmValueConsumer() { this = call.getAlgorithmArg() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + exists(SigningNamedCurveAlgorithmInstance l | l.getConsumer() = this and result = l) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll new file mode 100644 index 000000000000..0c52a72a9d28 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -0,0 +1,125 @@ +private import csharp + +// This class models Create calls for the ECDsa and RSA classes in .NET. +class CryptographyCreateCall extends MethodCall { + CryptographyCreateCall() { + this.getTarget().getName() = "Create" and + this.getQualifier().getType().hasFullyQualifiedName("System.Security.Cryptography", _) + } + + Expr getAlgorithmArg() { + this.hasNoArguments() and result = this + or + result = this.(ECDsaCreateCallWithParameters).getArgument(0) + or + result = this.(ECDsaCreateCallWithECCurve).getArgument(0) + } +} + +class ECDsaCreateCall extends CryptographyCreateCall { + ECDsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } +} + +class RSACreateCall extends CryptographyCreateCall { + RSACreateCall() { this.getQualifier().getType().hasName("RSA") } +} + +class CryptographyType extends Type { + CryptographyType() { this.hasFullyQualifiedName("System.Security.Cryptography", _) } +} + +class ECParameters extends CryptographyType { + ECParameters() { this.hasName("ECParameters") } +} + +class RSAParameters extends CryptographyType { + RSAParameters() { this.hasName("RSAParameters") } +} + +class ECCurve extends CryptographyType { + ECCurve() { this.hasName("ECCurve") } +} + +// This class is used to model the `ECDsa.Create(ECParameters)` call +class ECDsaCreateCallWithParameters extends ECDsaCreateCall { + ECDsaCreateCallWithParameters() { this.getArgument(0).getType() instanceof ECParameters } +} + +class ECDsaCreateCallWithECCurve extends ECDsaCreateCall { + ECDsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof ECCurve } +} + +class SigningNamedCurvePropertyAccess extends PropertyAccess { + string curveName; + + SigningNamedCurvePropertyAccess() { + super.getType().getName() = "ECCurve" and + eccurveNameMapping(super.getProperty().toString().toUpperCase(), curveName) + } + + string getCurveName() { result = curveName } +} + +/** + * Private predicate mapping NIST names to SEC names and leaving all others the same. + */ +bindingset[nist] +private predicate eccurveNameMapping(string nist, string secp) { + if nist.matches("NIST%") + then + nist = "NISTP256" and secp = "secp256r1" + or + nist = "NISTP384" and secp = "secp384r1" + or + nist = "NISTP521" and secp = "secp521r1" + else secp = nist +} + +// OPERATION INSTANCES +private class ECDsaClass extends Type { + ECDsaClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "ECDsa") } +} + +private class RSAClass extends Type { + RSAClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "RSA") } +} + +// TODO +// class ByteArrayTypeUnionReadOnlyByteSpan extends ArrayType { +// ByteArrayTypeUnionReadOnlyByteSpan() { +// this.hasFullyQualifiedName("System", "Byte[]") or +// this.hasFullyQualifiedName("System", "ReadOnlySpan`1") or +// } +// } +abstract class DotNetSigner extends MethodCall { + DotNetSigner() { this.getTarget().getName().matches(["Verify%", "Sign%"]) } + + Expr getMessageArg() { + // Both Sign and Verify methods take the message as the first argument. + // Some cases the message is a hash. + result = this.getArgument(0) + } + + Expr getSignatureArg() { + this.isVerifier() and + // TODO: Should replace getAChild* with the proper two types byte[] and ReadOnlySpan + (result = this.getArgument([1, 3]) and result.getType().getAChild*() instanceof ByteType) + } + + Expr getSignatureOutput() { + this.isSigner() and + result = this + } + + predicate isSigner() { this.getTarget().getName().matches("Sign%") } + + predicate isVerifier() { this.getTarget().getName().matches("Verify%") } +} + +private class ECDsaSigner extends DotNetSigner { + ECDsaSigner() { this.getQualifier().getType() instanceof ECDsaClass } +} + +private class RSASigner extends DotNetSigner { + RSASigner() { this.getQualifier().getType() instanceof RSAClass } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll new file mode 100644 index 000000000000..93abb7470102 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -0,0 +1,32 @@ +private import csharp +private import Cryptography +private import AlgorithmValueConsumers + +/** + * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. + */ +module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SigningNamedCurvePropertyAccess } + + predicate isSink(DataFlow::Node sink) { + exists(ECDsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) + } +} + +module SigningNamedCurveToSignatureCreateFlow = + DataFlow::Global; + +/** + * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. + */ +private module CreateToUseFlowConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CryptographyCreateCall } + + predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof DotNetSigner } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + node2.asExpr().(DotNetSigner).getQualifier() = node1.asExpr() + } +} + +module CryptographyCreateToUseFlow = DataFlow::Global; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll new file mode 100644 index 000000000000..2a9f8c778ad9 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -0,0 +1,48 @@ +private import csharp +private import experimental.quantum.Language +private import DataFlow +private import FlowAnalysis +private import Cryptography + +class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInstance instanceof DotNetSigner +{ + CryptographyCreateCall creator; + + ECDsaORRSASigningOperationInstance() { + this instanceof DotNetSigner and + CryptographyCreateToUseFlow::flow(DataFlow::exprNode(creator), DataFlow::exprNode(this)) + } + + // TODO FIXME + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result = creator.getAlgorithmArg() + } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if super.isSigner() + then result = Crypto::TSignMode() + else + if super.isVerifier() + then result = Crypto::TVerifyMode() + else result = Crypto::TUnknownKeyOperationMode() + } + + // TODO FIXME + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + result.asExpr() = creator.getAlgorithmArg() + } + + override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() } + + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result.asExpr() = super.getMessageArg() + } + + override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() { + result.asExpr() = super.getSignatureArg() + } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = super.getSignatureOutput() + } +} From d690a34e3c4bceb0c0a63ecb095a4c3d33c035fe Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Thu, 19 Jun 2025 18:30:09 +0100 Subject: [PATCH 05/33] quantum-c#: make type precise --- .../quantum/dotnet/Cryptography.qll | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 0c52a72a9d28..135fcffe3760 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -84,13 +84,14 @@ private class RSAClass extends Type { RSAClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "RSA") } } -// TODO -// class ByteArrayTypeUnionReadOnlyByteSpan extends ArrayType { -// ByteArrayTypeUnionReadOnlyByteSpan() { -// this.hasFullyQualifiedName("System", "Byte[]") or -// this.hasFullyQualifiedName("System", "ReadOnlySpan`1") or -// } -// } +class ByteArrayType extends Type { + ByteArrayType() { this.getName() = "Byte[]" } +} + +class ReadOnlyByteSpanType extends Type { + ReadOnlyByteSpanType() { this.getName() = "ReadOnlySpan" } +} + abstract class DotNetSigner extends MethodCall { DotNetSigner() { this.getTarget().getName().matches(["Verify%", "Sign%"]) } @@ -102,8 +103,13 @@ abstract class DotNetSigner extends MethodCall { Expr getSignatureArg() { this.isVerifier() and - // TODO: Should replace getAChild* with the proper two types byte[] and ReadOnlySpan - (result = this.getArgument([1, 3]) and result.getType().getAChild*() instanceof ByteType) + ( + result = this.getArgument([1, 3]) and + ( + result.getType() instanceof ByteArrayType or + result.getType() instanceof ReadOnlyByteSpanType + ) + ) } Expr getSignatureOutput() { From 9970e586b247b477df24e6fdc04a903bc0ff5edd Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Thu, 19 Jun 2025 18:58:35 +0100 Subject: [PATCH 06/33] quantum-c#: add HashAlgorithms --- .../quantum/dotnet/Cryptography.qll | 30 +++++++++++++++---- .../quantum/dotnet/OperationInstances.qll | 5 ++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 135fcffe3760..675156680506 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -60,6 +60,21 @@ class SigningNamedCurvePropertyAccess extends PropertyAccess { string getCurveName() { result = curveName } } +class HashAlgorithmNameType extends CryptographyType { + HashAlgorithmNameType() { this.hasName("HashAlgorithmName") } +} + +class HashAlgorithmName extends PropertyAccess { + string algorithmName; + + HashAlgorithmName() { + this.getType() instanceof HashAlgorithmNameType and + this.getProperty().getName() = algorithmName + } + + string getAlgorithmName() { result = algorithmName } +} + /** * Private predicate mapping NIST names to SEC names and leaving all others the same. */ @@ -76,12 +91,12 @@ private predicate eccurveNameMapping(string nist, string secp) { } // OPERATION INSTANCES -private class ECDsaClass extends Type { - ECDsaClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "ECDsa") } +private class ECDsaClass extends CryptographyType { + ECDsaClass() { this.hasName("ECDsa") } } -private class RSAClass extends Type { - RSAClass() { this.hasFullyQualifiedName("System.Security.Cryptography", "RSA") } +private class RSAClass extends CryptographyType { + RSAClass() { this.hasName("RSA") } } class ByteArrayType extends Type { @@ -92,7 +107,7 @@ class ReadOnlyByteSpanType extends Type { ReadOnlyByteSpanType() { this.getName() = "ReadOnlySpan" } } -abstract class DotNetSigner extends MethodCall { +class DotNetSigner extends MethodCall { DotNetSigner() { this.getTarget().getName().matches(["Verify%", "Sign%"]) } Expr getMessageArg() { @@ -117,6 +132,11 @@ abstract class DotNetSigner extends MethodCall { result = this } + Expr getHashAlgorithmArg() { + // Get the hash algorithm argument if it has the correct type. + result = this.getAnArgument() and result.getType() instanceof HashAlgorithmNameType + } + predicate isSigner() { this.getTarget().getName().matches("Sign%") } predicate isVerifier() { this.getTarget().getName().matches("Verify%") } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 2a9f8c778ad9..8970015abaf2 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -9,13 +9,14 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta CryptographyCreateCall creator; ECDsaORRSASigningOperationInstance() { - this instanceof DotNetSigner and CryptographyCreateToUseFlow::flow(DataFlow::exprNode(creator), DataFlow::exprNode(this)) } - // TODO FIXME override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = creator.getAlgorithmArg() + or + // FIXME: currently not working + result = super.getHashAlgorithmArg() } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { From 17629594ba75459b89c036cc45865e3067b4a43e Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Mon, 23 Jun 2025 09:38:48 +0100 Subject: [PATCH 07/33] quantum-c#: Add HashAlgorithmInstance --- .../quantum/dotnet/AlgorithmInstances.qll | 17 +++++++ .../dotnet/AlgorithmValueConsumers.qll | 12 +++++ .../quantum/dotnet/Cryptography.qll | 46 +++++++++++++++++++ .../quantum/dotnet/FlowAnalysis.qll | 10 ++++ .../quantum/dotnet/OperationInstances.qll | 4 +- 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index dc7363117905..38bf13b0e16f 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -24,3 +24,20 @@ class SigningNamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance i Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), result, _) } } + +class HashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { + HashAlgorithmConsumer consumer; + + HashAlgorithmInstance() { + HashAlgorithmNameToUse::flow(DataFlow::exprNode(this), consumer.getInputNode()) + } + + // Q: super.getHashFamily does not work because it is ambigous. But super.(HashAlgorithmName) does not work either. + override Crypto::THashType getHashFamily() { result = this.(HashAlgorithmName).getHashFamily() } + + override string getRawHashAlgorithmName() { result = super.getAlgorithmName() } + + override int getFixedDigestLength() { result = this.(HashAlgorithmName).getFixedDigestLength() } + + Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 14bd1dba39b5..607a1b6c9f80 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -14,3 +14,15 @@ class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { exists(SigningNamedCurveAlgorithmInstance l | l.getConsumer() = this and result = l) } } + +class HashAlgorithmConsumer extends Crypto::AlgorithmValueConsumer { + HashAlgorithmUser call; + + HashAlgorithmConsumer() { this = call.getHashAlgorithmUser() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + exists(HashAlgorithmInstance l | l.getConsumer() = this and result = l) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 675156680506..01cb31be04d7 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -1,4 +1,5 @@ private import csharp +private import experimental.quantum.Language // This class models Create calls for the ECDsa and RSA classes in .NET. class CryptographyCreateCall extends MethodCall { @@ -14,6 +15,15 @@ class CryptographyCreateCall extends MethodCall { or result = this.(ECDsaCreateCallWithECCurve).getArgument(0) } + + Expr getKeyConsumer() { + this.hasNoArguments() and result = this + or + result = this.(ECDsaCreateCallWithParameters).getArgument(0) + or + result = this.(ECDsaCreateCallWithECCurve) + } + } class ECDsaCreateCall extends CryptographyCreateCall { @@ -73,6 +83,42 @@ class HashAlgorithmName extends PropertyAccess { } string getAlgorithmName() { result = algorithmName } + + Crypto::THashType getHashFamily() { hashAlgorithmToFamily(this.getAlgorithmName(), result, _) } + + int getFixedDigestLength() { hashAlgorithmToFamily(this.getAlgorithmName(), _, result) } +} + +private predicate hashAlgorithmToFamily( + string hashName, Crypto::THashType hashFamily, int digestLength +) { + hashName = "MD5" and hashFamily = Crypto::MD5() and digestLength = 128 + or + hashName = "SHA1" and hashFamily = Crypto::SHA1() and digestLength = 160 + or + hashName = "SHA256" and hashFamily = Crypto::SHA2() and digestLength = 256 + or + hashName = "SHA384" and hashFamily = Crypto::SHA2() and digestLength = 384 + or + hashName = "SHA512" and hashFamily = Crypto::SHA2() and digestLength = 512 + or + hashName = "SHA3_256" and hashFamily = Crypto::SHA3() and digestLength = 256 + or + hashName = "SHA3_384" and hashFamily = Crypto::SHA3() and digestLength = 384 + or + hashName = "SHA3_512" and hashFamily = Crypto::SHA3() and digestLength = 512 + // Q: is there an idiomatic way to add a default type here? +} + +class HashAlgorithmUser extends MethodCall { + Expr arg; + + HashAlgorithmUser() { + arg = this.getAnArgument() and + arg.getType() instanceof HashAlgorithmNameType + } + + Expr getHashAlgorithmUser() { result = arg } } /** diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 93abb7470102..b201f7414cd1 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -30,3 +30,13 @@ private module CreateToUseFlowConfig implements DataFlow::ConfigSig { } module CryptographyCreateToUseFlow = DataFlow::Global; + +module HashAlgorithmNameToUseConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HashAlgorithmName } + + predicate isSink(DataFlow::Node sink) { + exists(HashAlgorithmConsumer consumer | sink = consumer.getInputNode()) + } +} + +module HashAlgorithmNameToUse = DataFlow::Global; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 8970015abaf2..7fce808691fb 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -15,7 +15,6 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = creator.getAlgorithmArg() or - // FIXME: currently not working result = super.getHashAlgorithmArg() } @@ -28,9 +27,8 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta else result = Crypto::TUnknownKeyOperationMode() } - // TODO FIXME override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { - result.asExpr() = creator.getAlgorithmArg() + result.asExpr() = creator.getKeyConsumer() } override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() } From 01b98fc822ed83c3a63dd28d1f1ebbafa41e2608 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Mon, 23 Jun 2025 11:17:12 +0100 Subject: [PATCH 08/33] quantum-c#: Add generic dataflow module --- .../quantum/dotnet/AlgorithmInstances.qll | 7 +- .../dotnet/AlgorithmValueConsumers.qll | 8 +- .../quantum/dotnet/Cryptography.qll | 24 ++++-- .../quantum/dotnet/FlowAnalysis.qll | 77 +++++++++++++++---- .../quantum/dotnet/OperationInstances.qll | 2 +- 5 files changed, 87 insertions(+), 31 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 38bf13b0e16f..28e09f822cae 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -25,14 +25,13 @@ class SigningNamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance i } } -class HashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { - HashAlgorithmConsumer consumer; +class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { + HashAlgorithmNameConsumer consumer; - HashAlgorithmInstance() { + HashAlgorithmNameInstance() { HashAlgorithmNameToUse::flow(DataFlow::exprNode(this), consumer.getInputNode()) } - // Q: super.getHashFamily does not work because it is ambigous. But super.(HashAlgorithmName) does not work either. override Crypto::THashType getHashFamily() { result = this.(HashAlgorithmName).getHashFamily() } override string getRawHashAlgorithmName() { result = super.getAlgorithmName() } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 607a1b6c9f80..462ab58aefb3 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -15,14 +15,14 @@ class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { } } -class HashAlgorithmConsumer extends Crypto::AlgorithmValueConsumer { - HashAlgorithmUser call; +class HashAlgorithmNameConsumer extends Crypto::AlgorithmValueConsumer { + HashAlgorithmNameUser call; - HashAlgorithmConsumer() { this = call.getHashAlgorithmUser() } + HashAlgorithmNameConsumer() { this = call.getHashAlgorithmNameUser() } override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - exists(HashAlgorithmInstance l | l.getConsumer() = this and result = l) + exists(HashAlgorithmNameInstance l | l.getConsumer() = this and result = l) } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 01cb31be04d7..c3e265674161 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -23,13 +23,19 @@ class CryptographyCreateCall extends MethodCall { or result = this.(ECDsaCreateCallWithECCurve) } - } class ECDsaCreateCall extends CryptographyCreateCall { ECDsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } } +// TODO +// class HashAlgorithmCreateCall extends CryptographyCreateCall { +// ValueOrRefType type; + +// HashAlgorithmCreateCall() { type = this.getQualifier().getType().getDeclaringType() } +// } + class RSACreateCall extends CryptographyCreateCall { RSACreateCall() { this.getQualifier().getType().hasName("RSA") } } @@ -84,7 +90,11 @@ class HashAlgorithmName extends PropertyAccess { string getAlgorithmName() { result = algorithmName } - Crypto::THashType getHashFamily() { hashAlgorithmToFamily(this.getAlgorithmName(), result, _) } + Crypto::THashType getHashFamily() { + if hashAlgorithmToFamily(this.getAlgorithmName(), _, _) + then hashAlgorithmToFamily(this.getAlgorithmName(), result, _) + else result = Crypto::OtherHashType() + } int getFixedDigestLength() { hashAlgorithmToFamily(this.getAlgorithmName(), _, result) } } @@ -107,18 +117,18 @@ private predicate hashAlgorithmToFamily( hashName = "SHA3_384" and hashFamily = Crypto::SHA3() and digestLength = 384 or hashName = "SHA3_512" and hashFamily = Crypto::SHA3() and digestLength = 512 - // Q: is there an idiomatic way to add a default type here? + // TODO: is there an idiomatic way to add a default type here? } -class HashAlgorithmUser extends MethodCall { +class HashAlgorithmNameUser extends MethodCall { Expr arg; - HashAlgorithmUser() { + HashAlgorithmNameUser() { arg = this.getAnArgument() and arg.getType() instanceof HashAlgorithmNameType } - Expr getHashAlgorithmUser() { result = arg } + Expr getHashAlgorithmNameUser() { result = arg } } /** @@ -173,6 +183,8 @@ class DotNetSigner extends MethodCall { ) } + predicate isIntermediate() { none() } + Expr getSignatureOutput() { this.isSigner() and result = this diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index b201f7414cd1..5302aeb61fb6 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -16,27 +16,72 @@ module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigS module SigningNamedCurveToSignatureCreateFlow = DataFlow::Global; -/** - * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. - */ -private module CreateToUseFlowConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CryptographyCreateCall } - - predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof DotNetSigner } - - predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - node2.asExpr().(DotNetSigner).getQualifier() = node1.asExpr() - } -} - -module CryptographyCreateToUseFlow = DataFlow::Global; - module HashAlgorithmNameToUseConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HashAlgorithmName } predicate isSink(DataFlow::Node sink) { - exists(HashAlgorithmConsumer consumer | sink = consumer.getInputNode()) + exists(HashAlgorithmNameConsumer consumer | sink = consumer.getInputNode()) } } module HashAlgorithmNameToUse = DataFlow::Global; + +signature class CreationCallSig instanceof Call; + +signature class UseCallSig instanceof QualifiableExpr { + predicate isIntermediate(); +} + +module CryptographyCreateToUseFlow = CreationToUseFlow; + +module CreationToUseFlow { + private module CreationToUseConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.asExpr() instanceof Creation + or + exists(Use use | + source.asExpr() = use.(QualifiableExpr).getQualifier() and use.isIntermediate() + ) + } + + predicate isSink(DataFlow::Node sink) { + exists(Use use | sink.asExpr() = use.(QualifiableExpr).getQualifier()) + } + } + + private module CreationToUseFlow = DataFlow::Global; + + Creation getCreationFromUse( + Use use, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink + ) { + source.getNode().asExpr() = result and + sink.getNode().asExpr() = use.(MethodCall).getQualifier() and + CreationToUseFlow::flowPath(source, sink) + } + + Use getUseFromCreation( + Creation creation, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink + ) { + source.getNode().asExpr() = creation and + sink.getNode().asExpr() = result.(MethodCall).getQualifier() and + CreationToUseFlow::flowPath(source, sink) + } + + Use getIntermediateUseFromUse( + Use use, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink + ) { + // Use sources are always intermediate uses. + source.getNode().asExpr() = result.(QualifiableExpr).getQualifier() and + sink.getNode().asExpr() = use.(QualifiableExpr).getQualifier() and + CreationToUseFlow::flowPath(source, sink) + } + + // TODO: Remove this. + Expr flowsTo(Expr expr) { + exists(CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink | + source.getNode().asExpr() = expr and + sink.getNode().asExpr() = result and + CreationToUseFlow::flowPath(source, sink) + ) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 7fce808691fb..c4fb5519aa56 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -9,7 +9,7 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta CryptographyCreateCall creator; ECDsaORRSASigningOperationInstance() { - CryptographyCreateToUseFlow::flow(DataFlow::exprNode(creator), DataFlow::exprNode(this)) + creator = CryptographyCreateToUseFlow::getCreationFromUse(this, _, _) } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { From 3913f449f2940d6b2860685bc1ef9092deb226a2 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Mon, 23 Jun 2025 11:29:49 +0100 Subject: [PATCH 09/33] quanntum-c#: refactor class names --- .../quantum/dotnet/Cryptography.qll | 81 +++++++++++-------- .../quantum/dotnet/FlowAnalysis.qll | 2 +- .../quantum/dotnet/OperationInstances.qll | 6 +- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index c3e265674161..15d472490d76 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -1,11 +1,27 @@ private import csharp private import experimental.quantum.Language +class CryptographyType extends Type { + CryptographyType() { this.hasFullyQualifiedName("System.Security.Cryptography", _) } +} + +class ECParameters extends CryptographyType { + ECParameters() { this.hasName("ECParameters") } +} + +class RSAParameters extends CryptographyType { + RSAParameters() { this.hasName("RSAParameters") } +} + +class ECCurve extends CryptographyType { + ECCurve() { this.hasName("ECCurve") } +} + // This class models Create calls for the ECDsa and RSA classes in .NET. -class CryptographyCreateCall extends MethodCall { - CryptographyCreateCall() { +class SigningCreateCall extends MethodCall { + SigningCreateCall() { this.getTarget().getName() = "Create" and - this.getQualifier().getType().hasFullyQualifiedName("System.Security.Cryptography", _) + this.getQualifier().getType() instanceof CryptographyType } Expr getAlgorithmArg() { @@ -25,37 +41,10 @@ class CryptographyCreateCall extends MethodCall { } } -class ECDsaCreateCall extends CryptographyCreateCall { +class ECDsaCreateCall extends SigningCreateCall { ECDsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } } -// TODO -// class HashAlgorithmCreateCall extends CryptographyCreateCall { -// ValueOrRefType type; - -// HashAlgorithmCreateCall() { type = this.getQualifier().getType().getDeclaringType() } -// } - -class RSACreateCall extends CryptographyCreateCall { - RSACreateCall() { this.getQualifier().getType().hasName("RSA") } -} - -class CryptographyType extends Type { - CryptographyType() { this.hasFullyQualifiedName("System.Security.Cryptography", _) } -} - -class ECParameters extends CryptographyType { - ECParameters() { this.hasName("ECParameters") } -} - -class RSAParameters extends CryptographyType { - RSAParameters() { this.hasName("RSAParameters") } -} - -class ECCurve extends CryptographyType { - ECCurve() { this.hasName("ECCurve") } -} - // This class is used to model the `ECDsa.Create(ECParameters)` call class ECDsaCreateCallWithParameters extends ECDsaCreateCall { ECDsaCreateCallWithParameters() { this.getArgument(0).getType() instanceof ECParameters } @@ -65,6 +54,28 @@ class ECDsaCreateCallWithECCurve extends ECDsaCreateCall { ECDsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof ECCurve } } +class RSACreateCall extends SigningCreateCall { + RSACreateCall() { this.getQualifier().getType().hasName("RSA") } +} + +class HashAlgorithmCreateCall extends SigningCreateCall { + HashAlgorithmCreateCall() { + this.getQualifier() + .getType() + .hasName([ + "MD5", + "RIPEMD160", + "SHA1", + "SHA256", + "SHA384", + "SHA512", + "SHA3_256", + "SHA3_384", + "SHA3_512" + ]) + } +} + class SigningNamedCurvePropertyAccess extends PropertyAccess { string curveName; @@ -163,8 +174,8 @@ class ReadOnlyByteSpanType extends Type { ReadOnlyByteSpanType() { this.getName() = "ReadOnlySpan" } } -class DotNetSigner extends MethodCall { - DotNetSigner() { this.getTarget().getName().matches(["Verify%", "Sign%"]) } +class SignerUse extends MethodCall { + SignerUse() { this.getTarget().getName().matches(["Verify%", "Sign%"]) } Expr getMessageArg() { // Both Sign and Verify methods take the message as the first argument. @@ -200,10 +211,10 @@ class DotNetSigner extends MethodCall { predicate isVerifier() { this.getTarget().getName().matches("Verify%") } } -private class ECDsaSigner extends DotNetSigner { +private class ECDsaSigner extends SignerUse { ECDsaSigner() { this.getQualifier().getType() instanceof ECDsaClass } } -private class RSASigner extends DotNetSigner { +private class RSASigner extends SignerUse { RSASigner() { this.getQualifier().getType() instanceof RSAClass } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 5302aeb61fb6..6a13ad04aa4c 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -32,7 +32,7 @@ signature class UseCallSig instanceof QualifiableExpr { predicate isIntermediate(); } -module CryptographyCreateToUseFlow = CreationToUseFlow; +module SigningCreateToUseFlow = CreationToUseFlow; module CreationToUseFlow { private module CreationToUseConfig implements DataFlow::ConfigSig { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index c4fb5519aa56..cc4863f17829 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -4,12 +4,12 @@ private import DataFlow private import FlowAnalysis private import Cryptography -class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInstance instanceof DotNetSigner +class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInstance instanceof SignerUse { - CryptographyCreateCall creator; + SigningCreateCall creator; ECDsaORRSASigningOperationInstance() { - creator = CryptographyCreateToUseFlow::getCreationFromUse(this, _, _) + creator = SigningCreateToUseFlow::getCreationFromUse(this, _, _) } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { From b1bbd970666536ba66408611e345016c46c087c8 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Mon, 23 Jun 2025 14:10:39 +0200 Subject: [PATCH 10/33] Added initial support for C# block ciphers --- .../ql/lib/experimental/quantum/Language.qll | 2 +- csharp/ql/lib/experimental/quantum/dotnet.qll | 5 +- .../quantum/dotnet/AlgorithmInstances.qll | 65 ++++++ .../dotnet/AlgorithmValueConsumers.qll | 14 ++ .../quantum/dotnet/FlowAnalysis.qll | 186 +++++++++++++--- .../quantum/dotnet/OperationInstances.qll | 200 +++++++++++++++++- 6 files changed, 440 insertions(+), 32 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll index 3591837697e4..d3b8c808424b 100644 --- a/csharp/ql/lib/experimental/quantum/Language.qll +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -65,4 +65,4 @@ module ArtifactFlowConfig implements Language::DataFlow::ConfigSig { module ArtifactFlow = Language::DataFlow::Global; -import dotnet \ No newline at end of file +import dotnet diff --git a/csharp/ql/lib/experimental/quantum/dotnet.qll b/csharp/ql/lib/experimental/quantum/dotnet.qll index 1deb6b492613..463f00f1ed18 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet.qll @@ -1,4 +1,3 @@ -import dotnet.AlgorithmInstances import dotnet.AlgorithmValueConsumers -import dotnet.FlowAnalysis -import dotnet.Cryptography +import dotnet.AlgorithmInstances +import dotnet.OperationInstances diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 28e09f822cae..c641d5d514e9 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -1,6 +1,7 @@ private import csharp private import experimental.quantum.Language private import AlgorithmValueConsumers +private import OperationInstances private import Cryptography private import FlowAnalysis @@ -40,3 +41,67 @@ class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } } + +class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SymmetricAlgorithmCreation +{ + override string getRawAlgorithmName() { result = super.getSymmetricAlgorithm().getName() } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + if exists(symmetricAlgorithmNameToType(this.getRawAlgorithmName())) + then result = symmetricAlgorithmNameToType(this.getRawAlgorithmName()) + else result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::OtherSymmetricCipherType()) + } + + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() } + + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } + + override int getKeySizeFixed() { none() } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } +} + +/** + * A padding mode literal, such as `PaddingMode.PKCS7`. + */ +class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instanceof MemberConstantAccess +{ + Crypto::AlgorithmValueConsumer consumer; + + PaddingModeLiteralInstance() { + this = any(PaddingMode mode).getAnAccess() and + consumer = PaddingModeLiteralFlow::getConsumer(this, _, _) + } + + override string getRawPaddingAlgorithmName() { result = super.getTarget().getName() } + + override Crypto::TPaddingType getPaddingType() { + if exists(paddingNameToType(this.getRawPaddingAlgorithmName())) + then result = paddingNameToType(this.getRawPaddingAlgorithmName()) + else result = Crypto::OtherPadding() + } + + Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } +} + +private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) { + algorithmName = "Aes" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) + or + algorithmName = "DES" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::DES()) + or + algorithmName = "RC2" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::RC2()) + or + algorithmName = "Rijndael" and + result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) + or + algorithmName = "TripleDES" and + result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::DES()) +} + +private Crypto::TPaddingType paddingNameToType(string paddingName) { + paddingName = "ANSIX923" and result = Crypto::ANSI_X9_23() + or + paddingName = "None" and result = Crypto::NoPadding() + or + paddingName = "PKCS7" and result = Crypto::PKCS7() +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 462ab58aefb3..350900cd5899 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -1,6 +1,7 @@ private import csharp private import experimental.quantum.Language private import AlgorithmInstances +private import OperationInstances private import Cryptography class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { @@ -26,3 +27,16 @@ class HashAlgorithmNameConsumer extends Crypto::AlgorithmValueConsumer { exists(HashAlgorithmNameInstance l | l.getConsumer() = this and result = l) } } + +/** + * A write access to the `Padding` property of a `SymmetricAlgorithm` instance. + */ +class PaddingPropertyWrite extends Crypto::AlgorithmValueConsumer instanceof SymmetricAlgorithmUse { + PaddingPropertyWrite() { super.isPaddingConsumer() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + result.(PaddingModeLiteralInstance).getConsumer() = this + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 5302aeb61fb6..d9953a2136b3 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -1,30 +1,8 @@ private import csharp -private import Cryptography +private import semmle.code.csharp.dataflow.DataFlow +private import OperationInstances private import AlgorithmValueConsumers - -/** - * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. - */ -module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SigningNamedCurvePropertyAccess } - - predicate isSink(DataFlow::Node sink) { - exists(ECDsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) - } -} - -module SigningNamedCurveToSignatureCreateFlow = - DataFlow::Global; - -module HashAlgorithmNameToUseConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HashAlgorithmName } - - predicate isSink(DataFlow::Node sink) { - exists(HashAlgorithmNameConsumer consumer | sink = consumer.getInputNode()) - } -} - -module HashAlgorithmNameToUse = DataFlow::Global; +private import Cryptography signature class CreationCallSig instanceof Call; @@ -32,8 +10,6 @@ signature class UseCallSig instanceof QualifiableExpr { predicate isIntermediate(); } -module CryptographyCreateToUseFlow = CreationToUseFlow; - module CreationToUseFlow { private module CreationToUseConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { @@ -85,3 +61,159 @@ module CreationToUseFlow { ) } } + +/** + * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. + */ +module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SigningNamedCurvePropertyAccess } + + predicate isSink(DataFlow::Node sink) { + exists(ECDsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) + } +} + +module SigningNamedCurveToSignatureCreateFlow = + DataFlow::Global; + +module HashAlgorithmNameToUseConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HashAlgorithmName } + + predicate isSink(DataFlow::Node sink) { + exists(HashAlgorithmNameConsumer consumer | sink = consumer.getInputNode()) + } +} + +module HashAlgorithmNameToUse = DataFlow::Global; + +module CryptographyCreateToUseFlow = CreationToUseFlow; + +/** + * A flow analysis module that tracks the flow from a `CryptoStreamMode.READ` or + * `CryptoStreamMode.WRITE` access to the corresponding `CryptoStream` object + * creation. + */ +module CryptoStreamModeFlow { + private module CryptoStreamModeConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.asExpr() = any(CryptoStreamMode mode).getAnAccess() + } + + predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(CryptoStreamCreation creation).getModeArg() + } + } + + private module CryptoStreamModeFlow = DataFlow::Global; + + CryptoStreamMode getModeFromCreation(CryptoStreamCreation creation) { + exists(CryptoStreamModeFlow::PathNode source, CryptoStreamModeFlow::PathNode sink | + source.getNode().asExpr() = result.getAnAccess() and + sink.getNode().asExpr() = creation.getAnArgument() and + CryptoStreamModeFlow::flowPath(source, sink) + ) + } +} + +/** + * A flow analysis module that tracks data flow from a `ICryptoTransform` + * creation (e.g. `Aes.CreateEncryptor()`) to the transform argument of a + * `CryptoStream` object creation. + */ +module CryptoTransformFlow { + private module CryptoTransformConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CryptoTransformCreation } + + predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(ObjectCreation creation).getAnArgument() + } + } + + private module CryptoTransformFlow = DataFlow::Global; + + CryptoTransformCreation getCreationFromUse(ObjectCreation creation) { + exists(CryptoTransformFlow::PathNode source, CryptoTransformFlow::PathNode sink | + source.getNode().asExpr() = result and + sink.getNode().asExpr() = creation.getAnArgument() and + CryptoTransformFlow::flowPath(source, sink) + ) + } +} + +/** + * A flow analysis module that tracks the flow from a `PaddingMode` member + * access (e.g. `PaddingMode.PKCS7`) to a `Padding` property write on a + * `SymmetricAlgorithm` instance. + * + * Example: + * ``` + * Aes aes = Aes.Create(); + * aes.Padding = PaddingMode.PKCS7; + * ... + * ``` + */ +module PaddingModeLiteralFlow { + private module PaddingModeLiteralConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.asExpr() = any(PaddingMode mode).getAnAccess() + } + + predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof PaddingPropertyWrite } + + // TODO: Figure out why this is needed. + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(Assignment assign | + node1.asExpr() = assign.getRValue() and + node2.asExpr() = assign.getLValue() + ) + } + } + + private module PaddingModeLiteralFlow = DataFlow::Global; + + SymmetricAlgorithmUse getConsumer( + Expr mode, PaddingModeLiteralFlow::PathNode source, PaddingModeLiteralFlow::PathNode sink + ) { + source.getNode().asExpr() = mode and + sink.getNode().asExpr() = result and + PaddingModeLiteralFlow::flowPath(source, sink) + } +} + +/** + * A flow analysis module that tracks the flow from a `MemoryStream` object + * creation to the `stream` argument passed to a `CryptoStream` constructor + * call. + * + * TODO: This should probably be made generic over multiple stream types. + */ +module MemoryStreamFlow { + private class MemoryStreamCreation extends ObjectCreation { + MemoryStreamCreation() { + this.getObjectType().hasFullyQualifiedName("System.IO", "MemoryStream") + } + + Expr getBufferArg() { result = this.getArgument(0) } + } + + // (Note that we cannot use `CreationToUseFlow` here, because the use is not a + // `QualifiableExpr`.) + private module MemoryStreamConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof MemoryStreamCreation } + + predicate isSink(DataFlow::Node sink) { + exists(CryptoStreamCreation creation | sink.asExpr() = creation.getStreamArg()) + } + } + + private module MemoryStreamFlow = DataFlow::Global; + + MemoryStreamCreation getCreationFromUse( + CryptoStreamCreation creation, MemoryStreamFlow::PathNode source, + MemoryStreamFlow::PathNode sink + ) { + source.getNode().asExpr() = result and + sink.getNode().asExpr() = creation.getStreamArg() and + MemoryStreamFlow::flowPath(source, sink) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index c4fb5519aa56..31486e81e23f 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -1,6 +1,7 @@ private import csharp -private import experimental.quantum.Language private import DataFlow +private import experimental.quantum.Language +private import AlgorithmValueConsumers private import FlowAnalysis private import Cryptography @@ -45,3 +46,200 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta result.asExpr() = super.getSignatureOutput() } } + +/** + * A symmetric algorithm class, such as AES or DES. + */ +class SymmetricAlgorithm extends Class { + SymmetricAlgorithm() { + this.getABaseType().hasFullyQualifiedName("System.Security.Cryptography", "SymmetricAlgorithm") + } + + CryptoTransformCreation getCreateTransformCall() { result = this.getAMethod().getACall() } +} + +/** + * A symmetric algorithm creation, such as `Aes.Create()`. + */ +class SymmetricAlgorithmCreation extends MethodCall { + SymmetricAlgorithmCreation() { + this.getTarget().hasName("Create") and + this.getQualifier().getType() instanceof SymmetricAlgorithm + } + + SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } +} + +class SymmetricAlgorithmUse extends QualifiableExpr { + SymmetricAlgorithmUse() { + this.getQualifier().getType() instanceof SymmetricAlgorithm and + this.getQualifiedDeclaration() + .hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding"]) + } + + Expr getSymmetricAlgorithm() { result = this.getQualifier() } + + predicate isIntermediate() { + not this.getQualifiedDeclaration().hasName(["CreateEncryptor", "CreateDecryptor"]) + } + + // The key may be set by assigning it to the `Key` property of the symmetric algorithm. + predicate isKeyConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Key" + } + + // The IV may be set by assigning it to the `IV` property of the symmetric algorithm. + predicate isIvConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "IV" + } + + // The padding mode may be set by assigning it to the `Padding` property of the symmetric algorithm. + predicate isPaddingConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Padding" + } +} + +module SymmetricAlgorithmFlow = + CreationToUseFlow; + +// TODO: Remove this. +SymmetricAlgorithmUse getUseFromUse(SymmetricAlgorithmUse use) { + result = SymmetricAlgorithmFlow::getIntermediateUseFromUse(use, _, _) +} + +/** + * A call to `CreateEncryptor` or `CreateDecryptor` on a `SymmetricAlgorithm`. + */ +class CryptoTransformCreation extends MethodCall { + CryptoTransformCreation() { + this.getTarget().hasName(["CreateEncryptor", "CreateDecryptor"]) and + this.getQualifier().getType() instanceof SymmetricAlgorithm + } + + predicate isEncryptor() { this.getTarget().getName() = "CreateEncryptor" } + + predicate isDecryptor() { this.getTarget().getName() = "CreateDecryptor" } + + Expr getKeyArg() { result = this.getArgument(0) } + + Expr getIvArg() { result = this.getArgument(1) } + + SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } +} + +class CryptoStream extends Class { + CryptoStream() { this.hasFullyQualifiedName("System.Security.Cryptography", "CryptoStream") } +} + +class CryptoStreamMode extends MemberConstant { + CryptoStreamMode() { + this.getDeclaringType() + .hasFullyQualifiedName("System.Security.Cryptography", "CryptoStreamMode") + } + + predicate isRead() { this.getName() = "Read" } + + predicate isWrite() { this.getName() = "Write" } +} + +class PaddingMode extends MemberConstant { + PaddingMode() { + this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "PaddingMode") + } +} + +class CryptoStreamCreation extends ObjectCreation { + CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream } + + Expr getStreamArg() { result = this.getArgument(0) } + + Expr getTransformArg() { result = this.getArgument(1) } + + Expr getModeArg() { result = this.getArgument(2) } + + Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if CryptoTransformFlow::getCreationFromUse(this.getTransformArg()).isEncryptor() + then result = Crypto::TEncryptMode() + else + if CryptoTransformFlow::getCreationFromUse(this.getTransformArg()).isDecryptor() + then result = Crypto::TDecryptMode() + else result = Crypto::TUnknownKeyOperationMode() + } +} + +private class CryptoStreamUse extends MethodCall { + CryptoStreamUse() { + this.getQualifier().getType() instanceof CryptoStream and + this.getTarget().hasName(["Write", "FlushFinalBlock", "FlushFinalBlockAsync", "Close"]) + } + + predicate isIntermediate() { this.getTarget().getName() = "Write" } + + Expr getInputArg() { + this.isIntermediate() and + result = this.getArgument(0) + } +} + +private module CryptoStreamFlow = CreationToUseFlow; + +/** + * An instantiation of a `CryptoStream` object where the transform is a symmetric + * encryption or decryption operation (e.g. an encryption transform created by a + * call to `Aes.CreateEncryptor()`) + */ +class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanceof CryptoStreamCreation +{ + CryptoTransformCreation transform; + + CryptoStreamOperationInstance() { + transform = CryptoTransformFlow::getCreationFromUse(this) and + (transform.isEncryptor() or transform.isDecryptor()) + } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if transform.isEncryptor() + then result = Crypto::TEncryptMode() + else + if transform.isDecryptor() + then result = Crypto::TDecryptMode() + else result = Crypto::TUnknownKeyOperationMode() + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + // If a key is explicitly provided as an argument when the transform is + // created, this takes precedence over any key that may be set on the + // symmetric algorithm instance. + if exists(transform.getKeyArg()) + then result.asExpr() = transform.getKeyArg() + else ( + result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(transform, _, _) and + result.asExpr().(SymmetricAlgorithmUse).isKeyConsumer() + ) + } + + override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { + // If an IV is explicitly provided as an argument when the transform is + // created, this takes precedence over any IV that may be set on the + // symmetric algorithm instance. + if exists(transform.getIvArg()) + then result.asExpr() = transform.getIvArg() + else ( + result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(transform, _, _) and + result.asExpr().(SymmetricAlgorithmUse).isIvConsumer() + ) + } + + // Inputs to the operation can be passed either through the stream argument + // when the `CryptoStream` is created, or through calls to + // `CryptoStream.Write()`. If the input is passed through the stream argument, + // it is wrapped using a `MemoryStream` object. + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result.asExpr() = MemoryStreamFlow::getCreationFromUse(this, _, _).getBufferArg() or + result.asExpr() = CryptoStreamFlow::getUseFromCreation(this, _, _).getInputArg() + } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { none() } +} From 791c26068e412700cc31364eb85c6de5f02adb5b Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Mon, 23 Jun 2025 14:48:59 +0100 Subject: [PATCH 11/33] quantum-c#: Add HashAlgorithmInstance --- .../quantum/dotnet/AlgorithmInstances.qll | 28 ++++++- .../dotnet/AlgorithmValueConsumers.qll | 2 +- .../quantum/dotnet/Cryptography.qll | 73 ++++++++++++++----- .../quantum/dotnet/FlowAnalysis.qll | 2 + .../quantum/dotnet/OperationInstances.qll | 14 +++- 5 files changed, 94 insertions(+), 25 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 28e09f822cae..acaaeaa6114e 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -4,11 +4,11 @@ private import AlgorithmValueConsumers private import Cryptography private import FlowAnalysis -class SigningNamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof SigningNamedCurvePropertyAccess +class NamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof SigningNamedCurvePropertyAccess { ECDsaAlgorithmValueConsumer consumer; - SigningNamedCurveAlgorithmInstance() { + NamedCurveAlgorithmInstance() { SigningNamedCurveToSignatureCreateFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) } @@ -25,6 +25,30 @@ class SigningNamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance i } } +class EcdsaAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof ECDsaCreateCall +{ + EcdsaAlgorithmInstance() { + // SigningNamedCurveToSignatureCreateFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) + this instanceof ECDsaCreateCall + } + + ECDsaAlgorithmValueConsumer getConsumer() { result = super.getQualifier() } + + override string getRawAlgorithmName() { result = "ECDsa" } + + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() } + + // TODO: PaddingAlgorithmInstance errors with "call to empty relation: class test for Model::CryptographyBase::PaddingAlgorithmInstance" + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + + override int getKeySizeFixed() { none() } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::ECDSA()) + } +} + class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { HashAlgorithmNameConsumer consumer; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 462ab58aefb3..30e4af7ea526 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -11,7 +11,7 @@ class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - exists(SigningNamedCurveAlgorithmInstance l | l.getConsumer() = this and result = l) + exists(NamedCurveAlgorithmInstance l | l.getConsumer() = this and result = l) } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 15d472490d76..25af8af6f439 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -17,9 +17,25 @@ class ECCurve extends CryptographyType { ECCurve() { this.hasName("ECCurve") } } +class HashAlgorithmType extends CryptographyType { + HashAlgorithmType() { + this.hasName([ + "MD5", + "RIPEMD160", + "SHA1", + "SHA256", + "SHA384", + "SHA512", + "SHA3_256", + "SHA3_384", + "SHA3_512" + ]) + } +} + // This class models Create calls for the ECDsa and RSA classes in .NET. -class SigningCreateCall extends MethodCall { - SigningCreateCall() { +class CryptographyCreateCall extends MethodCall { + CryptographyCreateCall() { this.getTarget().getName() = "Create" and this.getQualifier().getType() instanceof CryptographyType } @@ -41,7 +57,7 @@ class SigningCreateCall extends MethodCall { } } -class ECDsaCreateCall extends SigningCreateCall { +class ECDsaCreateCall extends CryptographyCreateCall { ECDsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } } @@ -54,28 +70,21 @@ class ECDsaCreateCallWithECCurve extends ECDsaCreateCall { ECDsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof ECCurve } } -class RSACreateCall extends SigningCreateCall { +class RSACreateCall extends CryptographyCreateCall { RSACreateCall() { this.getQualifier().getType().hasName("RSA") } } -class HashAlgorithmCreateCall extends SigningCreateCall { - HashAlgorithmCreateCall() { - this.getQualifier() - .getType() - .hasName([ - "MD5", - "RIPEMD160", - "SHA1", - "SHA256", - "SHA384", - "SHA512", - "SHA3_256", - "SHA3_384", - "SHA3_512" - ]) +class SigningCreateCall extends CryptographyCreateCall { + SigningCreateCall() { + this instanceof ECDsaCreateCall or + this instanceof RSACreateCall } } +class HashAlgorithmCreateCall extends CryptographyCreateCall { + HashAlgorithmCreateCall() { this.getQualifier().getType() instanceof HashAlgorithmType } +} + class SigningNamedCurvePropertyAccess extends PropertyAccess { string curveName; @@ -166,6 +175,13 @@ private class RSAClass extends CryptographyType { RSAClass() { this.hasName("RSA") } } +private class SignerType extends Type { + SignerType() { + this instanceof ECDsaClass or + this instanceof RSAClass + } +} + class ByteArrayType extends Type { ByteArrayType() { this.getName() = "Byte[]" } } @@ -174,8 +190,25 @@ class ReadOnlyByteSpanType extends Type { ReadOnlyByteSpanType() { this.getName() = "ReadOnlySpan" } } +class HashUse extends MethodCall { + HashUse() { + this.getQualifier().getType() instanceof HashAlgorithmType and + this.getTarget() + .getName() + .matches([ + "ComputeHash", "ComputeHashAsync", "HashCore", "HashData", "HashDataAsync", + "TransformBlock", "TransformFinalBlock", "TryComputeHash", "TryHashData", "TryHashFinal" + ]) + } + + predicate isIntermediate() { this.getTarget().hasName("HashCore") } +} + class SignerUse extends MethodCall { - SignerUse() { this.getTarget().getName().matches(["Verify%", "Sign%"]) } + SignerUse() { + this.getTarget().getName().matches(["Verify%", "Sign%"]) and + this.getQualifier().getType() instanceof SignerType + } Expr getMessageArg() { // Both Sign and Verify methods take the message as the first argument. diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 6a13ad04aa4c..87d0350aae3f 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -34,6 +34,8 @@ signature class UseCallSig instanceof QualifiableExpr { module SigningCreateToUseFlow = CreationToUseFlow; +module HashCreateToUseFlow = CreationToUseFlow; + module CreationToUseFlow { private module CreationToUseConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index cc4863f17829..c777dbcd0e7c 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -14,8 +14,6 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = creator.getAlgorithmArg() - or - result = super.getHashAlgorithmArg() } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { @@ -45,3 +43,15 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta result.asExpr() = super.getSignatureOutput() } } + +class HashOperationInstance extends Crypto::HashOperationInstance instanceof HashUse { + HashAlgorithmCreateCall creator; + + HashOperationInstance() { creator = HashCreateToUseFlow::getCreationFromUse(this, _, _) } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { none() } + + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { none() } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() } +} From 6d0024d700b3a946c40fe8329d5055efb93025fd Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Mon, 23 Jun 2025 16:21:04 +0100 Subject: [PATCH 12/33] quantum-c#: refactoring --- .../quantum/dotnet/AlgorithmInstances.qll | 44 ++++++++++--------- .../dotnet/AlgorithmValueConsumers.qll | 6 +-- .../quantum/dotnet/Cryptography.qll | 4 +- .../quantum/dotnet/FlowAnalysis.qll | 6 +-- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 7bd3806b274e..a6e707a92366 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -5,15 +5,9 @@ private import OperationInstances private import Cryptography private import FlowAnalysis -class NamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof SigningNamedCurvePropertyAccess +class NamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof NamedCurvePropertyAccess { - ECDsaAlgorithmValueConsumer consumer; - - NamedCurveAlgorithmInstance() { - SigningNamedCurveToSignatureCreateFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) - } - - ECDsaAlgorithmValueConsumer getConsumer() { result = consumer } + NamedCurveAlgorithmInstance() { this instanceof NamedCurvePropertyAccess } override string getRawEllipticCurveName() { result = super.getCurveName() } @@ -26,30 +20,40 @@ class NamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instance } } -class EcdsaAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof ECDsaCreateCall -{ - EcdsaAlgorithmInstance() { - // SigningNamedCurveToSignatureCreateFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) - this instanceof ECDsaCreateCall - } - - ECDsaAlgorithmValueConsumer getConsumer() { result = super.getQualifier() } - - override string getRawAlgorithmName() { result = "ECDsa" } - +abstract class SigningAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance { override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() } - // TODO: PaddingAlgorithmInstance errors with "call to empty relation: class test for Model::CryptographyBase::PaddingAlgorithmInstance" override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + override int getKeySizeFixed() { none() } +} + +class EcdsaAlgorithmInstance extends SigningAlgorithmInstance instanceof SigningCreateCall { + EcdsaAlgorithmInstance() { this instanceof ECDsaCreateCall } + + EcdsaAlgorithmValueConsumer getConsumer() { result = super.getQualifier() } + + override string getRawAlgorithmName() { result = "ECDsa" } override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::ECDSA()) } } +class RsaAlgorithmInstance extends SigningAlgorithmInstance { + RsaAlgorithmInstance() { this = any(RSACreateCall c).getQualifier() } + + override string getRawAlgorithmName() { result = "RSA" } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + // TODO there is no RSA TSignature type, so we use OtherSignatureAlgorithmType + result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::OtherSignatureAlgorithmType()) + } +} + class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { HashAlgorithmNameConsumer consumer; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index a74ab6d97bbb..138f1e48382f 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -4,15 +4,15 @@ private import AlgorithmInstances private import OperationInstances private import Cryptography -class ECDsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { +class EcdsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { ECDsaCreateCall call; - ECDsaAlgorithmValueConsumer() { this = call.getAlgorithmArg() } + EcdsaAlgorithmValueConsumer() { this = call.getAlgorithmArg() } override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - exists(NamedCurveAlgorithmInstance l | l.getConsumer() = this and result = l) + exists(EcdsaAlgorithmInstance l | l.getConsumer() = this and result = l) } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 25af8af6f439..e8bca36c2df5 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -85,10 +85,10 @@ class HashAlgorithmCreateCall extends CryptographyCreateCall { HashAlgorithmCreateCall() { this.getQualifier().getType() instanceof HashAlgorithmType } } -class SigningNamedCurvePropertyAccess extends PropertyAccess { +class NamedCurvePropertyAccess extends PropertyAccess { string curveName; - SigningNamedCurvePropertyAccess() { + NamedCurvePropertyAccess() { super.getType().getName() = "ECCurve" and eccurveNameMapping(super.getProperty().toString().toUpperCase(), curveName) } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index b66dd2e7c122..97c4e8246ce0 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -10,8 +10,6 @@ signature class UseCallSig instanceof QualifiableExpr { predicate isIntermediate(); } - - module CreationToUseFlow { private module CreationToUseConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { @@ -68,10 +66,10 @@ module CreationToUseFlow { * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. */ module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SigningNamedCurvePropertyAccess } + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof NamedCurvePropertyAccess } predicate isSink(DataFlow::Node sink) { - exists(ECDsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) + exists(EcdsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) } } From c7caf97307afe85e749ac802655f65cbc27981c7 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Mon, 23 Jun 2025 17:47:51 +0200 Subject: [PATCH 13/33] Extended CryptoStreamKeyOperationInstance - Added support to get input consumers and output artifacts - Added padding and cipher mode algorithm instances, as well as dataflow to link these to `CryptoStream` key operations --- .../quantum/dotnet/AlgorithmInstances.qll | 63 +++++++++- .../dotnet/AlgorithmValueConsumers.qll | 29 +++++ .../quantum/dotnet/FlowAnalysis.qll | 110 +++++++++++++----- .../quantum/dotnet/OperationInstances.qll | 34 ++++-- 4 files changed, 196 insertions(+), 40 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index c641d5d514e9..e4eb778458a1 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -44,6 +44,10 @@ class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SymmetricAlgorithmCreation { + SymmetricAlgorithmConsumer consumer; + + SymmetricAlgorithmInstance() { consumer = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) } + override string getRawAlgorithmName() { result = super.getSymmetricAlgorithm().getName() } override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { @@ -52,13 +56,33 @@ class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance i else result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::OtherSymmetricCipherType()) } - override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() } + // The cipher mode is set by assigning it to the `Mode` property of the + // symmetric algorithm. + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { + result.(CipherModeLiteralInstance).getConsumer() = this.getCipherModeAlgorithmValueConsumer() + } + + // The padding mode is set by assigning it to the `Padding` property of the + // symmetric algorithm. + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { + result.(PaddingModeLiteralInstance).getConsumer() = this.getPaddingAlgorithmValueConsumer() + } + + Crypto::AlgorithmValueConsumer getPaddingAlgorithmValueConsumer() { + result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and + result instanceof PaddingPropertyWrite + } - override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } + Crypto::AlgorithmValueConsumer getCipherModeAlgorithmValueConsumer() { + result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and + result instanceof CipherModePropertyWrite + } override int getKeySizeFixed() { none() } override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + + Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } } /** @@ -70,7 +94,7 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan PaddingModeLiteralInstance() { this = any(PaddingMode mode).getAnAccess() and - consumer = PaddingModeLiteralFlow::getConsumer(this, _, _) + consumer = ModeLiteralFlow::getConsumer(this, _, _) } override string getRawPaddingAlgorithmName() { result = super.getTarget().getName() } @@ -84,6 +108,29 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } } +/** + * A padding mode literal, such as `PaddingMode.PKCS7`. + */ +class CipherModeLiteralInstance extends Crypto::ModeOfOperationAlgorithmInstance instanceof MemberConstantAccess +{ + Crypto::AlgorithmValueConsumer consumer; + + CipherModeLiteralInstance() { + this = any(CipherMode mode).getAnAccess() and + consumer = ModeLiteralFlow::getConsumer(this, _, _) + } + + override string getRawModeAlgorithmName() { result = super.getTarget().getName() } + + override Crypto::TBlockCipherModeOfOperationType getModeType() { + if exists(modeNameToType(this.getRawModeAlgorithmName())) + then result = modeNameToType(this.getRawModeAlgorithmName()) + else result = Crypto::OtherMode() + } + + Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } +} + private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) { algorithmName = "Aes" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) or @@ -105,3 +152,13 @@ private Crypto::TPaddingType paddingNameToType(string paddingName) { or paddingName = "PKCS7" and result = Crypto::PKCS7() } + +private Crypto::TBlockCipherModeOfOperationType modeNameToType(string modeName) { + modeName = "CBC" and result = Crypto::CBC() + or + modeName = "CFB" and result = Crypto::CFB() + or + modeName = "ECB" and result = Crypto::ECB() + or + modeName = "OFB" and result = Crypto::OFB() +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 350900cd5899..4c6527d6737b 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -40,3 +40,32 @@ class PaddingPropertyWrite extends Crypto::AlgorithmValueConsumer instanceof Sym result.(PaddingModeLiteralInstance).getConsumer() = this } } + +/** + * A write access to the `Mode` property of a `SymmetricAlgorithm` instance. + */ +class CipherModePropertyWrite extends Crypto::AlgorithmValueConsumer instanceof SymmetricAlgorithmUse +{ + CipherModePropertyWrite() { super.isModeConsumer() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + result.(CipherModeLiteralInstance).getConsumer() = this + } +} + +/** + * A call to a `SymmetricAlgorithm.CreateEncryptor` or `SymmetricAlgorithm.CreateDecryptor` + * method that returns a `CryptoTransform` instance. + */ +class SymmetricAlgorithmConsumer extends Crypto::AlgorithmValueConsumer instanceof CryptoTransformCreation +{ + override Crypto::ConsumerInputDataFlowNode getInputNode() { + result.asExpr() = super.getQualifier() + } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + result.(SymmetricAlgorithmInstance).getConsumer() = this + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index d9953a2136b3..a4b658c6dbe8 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -31,7 +31,7 @@ module CreationToUseFlow { Use use, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink ) { source.getNode().asExpr() = result and - sink.getNode().asExpr() = use.(MethodCall).getQualifier() and + sink.getNode().asExpr() = use.(QualifiableExpr).getQualifier() and CreationToUseFlow::flowPath(source, sink) } @@ -39,7 +39,7 @@ module CreationToUseFlow { Creation creation, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink ) { source.getNode().asExpr() = creation and - sink.getNode().asExpr() = result.(MethodCall).getQualifier() and + sink.getNode().asExpr() = result.(QualifiableExpr).getQualifier() and CreationToUseFlow::flowPath(source, sink) } @@ -143,7 +143,9 @@ module CryptoTransformFlow { /** * A flow analysis module that tracks the flow from a `PaddingMode` member * access (e.g. `PaddingMode.PKCS7`) to a `Padding` property write on a - * `SymmetricAlgorithm` instance. + * `SymmetricAlgorithm` instance, or from a `CipherMode` member access + * (e.g. `CipherMode.CBC`) to a `Mode` property write on a `SymmetricAlgorithm` + * instance. * * Example: * ``` @@ -152,13 +154,18 @@ module CryptoTransformFlow { * ... * ``` */ -module PaddingModeLiteralFlow { - private module PaddingModeLiteralConfig implements DataFlow::ConfigSig { +module ModeLiteralFlow { + private module ModeLiteralConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source.asExpr() = any(PaddingMode mode).getAnAccess() + or + source.asExpr() = any(CipherMode mode).getAnAccess() } - predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof PaddingPropertyWrite } + predicate isSink(DataFlow::Node sink) { + sink.asExpr() instanceof PaddingPropertyWrite or + sink.asExpr() instanceof CipherModePropertyWrite + } // TODO: Figure out why this is needed. predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { @@ -169,51 +176,96 @@ module PaddingModeLiteralFlow { } } - private module PaddingModeLiteralFlow = DataFlow::Global; + private module ModeLiteralFlow = DataFlow::Global; SymmetricAlgorithmUse getConsumer( - Expr mode, PaddingModeLiteralFlow::PathNode source, PaddingModeLiteralFlow::PathNode sink + Expr mode, ModeLiteralFlow::PathNode source, ModeLiteralFlow::PathNode sink ) { source.getNode().asExpr() = mode and sink.getNode().asExpr() = result and - PaddingModeLiteralFlow::flowPath(source, sink) + ModeLiteralFlow::flowPath(source, sink) } } /** - * A flow analysis module that tracks the flow from a `MemoryStream` object - * creation to the `stream` argument passed to a `CryptoStream` constructor - * call. + * A flow analysis module that tracks the flow from an arbitrary `Stream` object + * creation to the creation of a second `Stream` object wrapping the first one. * - * TODO: This should probably be made generic over multiple stream types. + * This is useful for tracking the flow of data from a buffer passed to a + * `MemoryStream` to a `CryptoStream` wrapping the original `MemoryStream`. It + * can also be used to track dataflow from a `Stream` object to a call to + * `ToArray()` on the stream, or a wrapped stream. */ -module MemoryStreamFlow { - private class MemoryStreamCreation extends ObjectCreation { - MemoryStreamCreation() { - this.getObjectType().hasFullyQualifiedName("System.IO", "MemoryStream") +module StreamFlow { + private class Stream extends Class { + Stream() { this.getABaseType().hasFullyQualifiedName("System.IO", "Stream") } + } + + /** + * A `Stream` object creation. + */ + private class StreamCreation extends ObjectCreation { + StreamCreation() { this.getObjectType() instanceof Stream } + + Expr getInputArg() { + result = this.getAnArgument() and + result.getType().hasFullyQualifiedName("System", "Byte[]") + } + + Expr getStreamArg() { + result = this.getAnArgument() and + result.getType() instanceof Stream + } + } + + private class StreamUse extends MethodCall { + StreamUse() { + this.getQualifier().getType() instanceof Stream and + this.getTarget().hasName("ToArray") } - Expr getBufferArg() { result = this.getArgument(0) } + Expr getOutput() { result = this } } - // (Note that we cannot use `CreationToUseFlow` here, because the use is not a - // `QualifiableExpr`.) - private module MemoryStreamConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { source.asExpr() instanceof MemoryStreamCreation } + private module StreamConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StreamCreation } predicate isSink(DataFlow::Node sink) { - exists(CryptoStreamCreation creation | sink.asExpr() = creation.getStreamArg()) + sink.asExpr() instanceof StreamCreation + or + exists(StreamUse use | sink.asExpr() = use.getQualifier()) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + // Allow flow from one stream wrapped by a second stream. + exists(StreamCreation creation | + node1.asExpr() = creation.getStreamArg() and + node2.asExpr() = creation + ) + or + exists(MethodCall copy | + node1.asExpr() = copy.getQualifier() and + node2.asExpr() = copy.getAnArgument() and + copy.getTarget().hasName("CopyTo") + ) } } - private module MemoryStreamFlow = DataFlow::Global; + private module StreamFlow = DataFlow::Global; - MemoryStreamCreation getCreationFromUse( - CryptoStreamCreation creation, MemoryStreamFlow::PathNode source, - MemoryStreamFlow::PathNode sink + StreamCreation getWrappedStream( + StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink ) { source.getNode().asExpr() = result and - sink.getNode().asExpr() = creation.getStreamArg() and - MemoryStreamFlow::flowPath(source, sink) + sink.getNode().asExpr() = stream and + StreamFlow::flowPath(source, sink) + } + + StreamUse getStreamUse( + StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink + ) { + source.getNode().asExpr() = stream and + sink.getNode().asExpr() = result.getQualifier() and + StreamFlow::flowPath(source, sink) } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 31486e81e23f..366e896175b9 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -74,7 +74,7 @@ class SymmetricAlgorithmUse extends QualifiableExpr { SymmetricAlgorithmUse() { this.getQualifier().getType() instanceof SymmetricAlgorithm and this.getQualifiedDeclaration() - .hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding"]) + .hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode"]) } Expr getSymmetricAlgorithm() { result = this.getQualifier() } @@ -97,6 +97,11 @@ class SymmetricAlgorithmUse extends QualifiableExpr { predicate isPaddingConsumer() { this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Padding" } + + // The cipher mode may be set by assigning it to the `Mode` property of the symmetric algorithm. + predicate isModeConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode" + } } module SymmetricAlgorithmFlow = @@ -148,6 +153,12 @@ class PaddingMode extends MemberConstant { } } +class CipherMode extends MemberConstant { + CipherMode() { + this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "CipherMode") + } +} + class CryptoStreamCreation extends ObjectCreation { CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream } @@ -197,7 +208,7 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc (transform.isEncryptor() or transform.isDecryptor()) } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() } + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = transform } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { if transform.isEncryptor() @@ -232,14 +243,21 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc ) } - // Inputs to the operation can be passed either through the stream argument - // when the `CryptoStream` is created, or through calls to - // `CryptoStream.Write()`. If the input is passed through the stream argument, - // it is wrapped using a `MemoryStream` object. + // Inputs can be passed either through the `stream` argument when the + // `CryptoStream` is created, or through calls to `Write()` on the + // `CryptoStream` object. override Crypto::ConsumerInputDataFlowNode getInputConsumer() { - result.asExpr() = MemoryStreamFlow::getCreationFromUse(this, _, _).getBufferArg() or + result.asExpr() = StreamFlow::getWrappedStream(this, _, _).getInputArg() or result.asExpr() = CryptoStreamFlow::getUseFromCreation(this, _, _).getInputArg() } - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { none() } + // The output is obtained by calling `ToArray()` on a `Stream` either wrapped + // by the `CryptoStream` object, or copied from the `CryptoStream` object. + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + // We perform backwards dataflow to identify stream objects that are wrapped + // by the `CryptoStream` object, and then we look for calls to `ToArray()` + // on those streams. + result.asExpr() = + StreamFlow::getStreamUse(any(StreamFlow::getWrappedStream(this, _, _)), _, _).getOutput() + } } From a3e93ceefc006eae1d4f5c864a3cdfb4a61ff02e Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Tue, 24 Jun 2025 11:23:34 +0100 Subject: [PATCH 14/33] quantum-c#: add hash operation instance --- .../quantum/dotnet/Cryptography.qll | 20 ++++++++++++++++++- .../quantum/dotnet/OperationInstances.qll | 8 ++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index e8bca36c2df5..c052c436f35d 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -197,11 +197,29 @@ class HashUse extends MethodCall { .getName() .matches([ "ComputeHash", "ComputeHashAsync", "HashCore", "HashData", "HashDataAsync", - "TransformBlock", "TransformFinalBlock", "TryComputeHash", "TryHashData", "TryHashFinal" + "TransformBlock", "TransformFinalBlock", "TryComputeHash", "TryHashData", + "TryHashFinal", "HashFinal" ]) } predicate isIntermediate() { this.getTarget().hasName("HashCore") } + + Expr getOutputArtifact() { + not this.isIntermediate() and + // some functions receive the destination as a parameter + if + this.getTarget().getName() = ["TryComputeHash", "TryHashFinal", "TryHashData"] + or + this.getTarget().getName() = ["HashData"] and this.getNumberOfArguments() = 2 + or + this.getTarget().getName() = ["HashDataAsync"] and this.getNumberOfArguments() = 3 + then result = this.getArgument(1) + else result = this + } + + Expr getInputConsumer() { + not this.getTarget().getName() = "HashFinal" and result = this.getArgument(0) + } } class SignerUse extends MethodCall { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 5a1d55733f64..3cd03a7ed9c8 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -50,9 +50,13 @@ class HashOperationInstance extends Crypto::HashOperationInstance instanceof Has HashOperationInstance() { creator = HashCreateToUseFlow::getCreationFromUse(this, _, _) } - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { none() } + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result = DataFlow::exprNode(this.(HashUse).getOutputArtifact()) + } - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { none() } + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result = DataFlow::exprNode(this.(HashUse).getInputConsumer()) + } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() } } From 9238b1274ae84f0c26a1db8cd28ef3afba482a78 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Tue, 24 Jun 2025 14:08:05 +0200 Subject: [PATCH 15/33] Added support for the system CSPRNG --- .../ql/lib/experimental/quantum/Language.qll | 131 ++++++++++++++++-- .../quantum/dotnet/FlowAnalysis.qll | 27 ++-- 2 files changed, 141 insertions(+), 17 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll index d3b8c808424b..031a31c7b8f9 100644 --- a/csharp/ql/lib/experimental/quantum/Language.qll +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -1,4 +1,5 @@ private import csharp as Language +private import semmle.code.csharp.dataflow.DataFlow private import codeql.quantum.experimental.Model private class UnknownLocation extends Language::Location { @@ -41,28 +42,142 @@ module CryptoInput implements InputSig { // Instantiate the `CryptographyBase` module module Crypto = CryptographyBase; -module ArtifactFlowConfig implements Language::DataFlow::ConfigSig { - predicate isSource(Language::DataFlow::Node source) { +/** + * An additional flow step in generic data-flow configurations. + * Where a step is an edge between nodes `n1` and `n2`, + * `this` = `n1` and `getOutput()` = `n2`. + * + * FOR INTERNAL MODELING USE ONLY. + */ +abstract class AdditionalFlowInputStep extends DataFlow::Node { + abstract DataFlow::Node getOutput(); + + final DataFlow::Node getInput() { result = this } +} + +/** + * Generic data source to node input configuration + */ +module GenericDataSourceFlowConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source = any(Crypto::GenericSourceInstance i).getOutputNode() + } + + predicate isSink(DataFlow::Node sink) { + sink = any(Crypto::FlowAwareElement other).getInputNode() + } + + predicate isBarrierOut(DataFlow::Node node) { + node = any(Crypto::FlowAwareElement element).getInputNode() + } + + predicate isBarrierIn(DataFlow::Node node) { + node = any(Crypto::FlowAwareElement element).getOutputNode() + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + node1.(AdditionalFlowInputStep).getOutput() = node2 + } +} + +module ArtifactFlowConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source = any(Crypto::ArtifactInstance artifact).getOutputNode() } - predicate isSink(Language::DataFlow::Node sink) { + predicate isSink(DataFlow::Node sink) { sink = any(Crypto::FlowAwareElement other).getInputNode() } - predicate isBarrierOut(Language::DataFlow::Node node) { + predicate isBarrierOut(DataFlow::Node node) { node = any(Crypto::FlowAwareElement element).getInputNode() } - predicate isBarrierIn(Language::DataFlow::Node node) { + predicate isBarrierIn(DataFlow::Node node) { node = any(Crypto::FlowAwareElement element).getOutputNode() } - predicate isAdditionalFlowStep(Language::DataFlow::Node node1, Language::DataFlow::Node node2) { - none() + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + node1.(AdditionalFlowInputStep).getOutput() = node2 + } +} + +module GenericDataSourceFlow = TaintTracking::Global; + +module ArtifactFlow = DataFlow::Global; + +/** + * A method access that returns random data or writes random data to an argument. + */ +abstract class RandomnessSource extends MethodCall { + /** + * Gets the expression representing the output of this source. + */ + abstract Expr getOutput(); + + /** + * Gets the type of the source of randomness used by this call. + */ + Type getGenerator() { result = this.getQualifier().getType() } +} + +/** + * A call to `System.Security.Cryptography.RandomNumberGenerator.GetBytes`. + */ +class SecureRandomnessSource extends RandomnessSource { + SecureRandomnessSource() { + this.getTarget() + .hasFullyQualifiedName("System.Security.Cryptography", "RandomNumberGenerator", "GetBytes") + } + + override Expr getOutput() { result = this.getArgument(0) } +} + +/** + * A call to `System.Random.NextBytes`. + */ +class InsecureRandomnessSource extends RandomnessSource { + InsecureRandomnessSource() { + this.getTarget().hasFullyQualifiedName("System", "Random", "NextBytes") + } + + override Expr getOutput() { result = this.getArgument(0) } +} + +/** + * An instance of random number generation, modelled as the expression + * tied to an output node (i.e., the RNG output) + */ +abstract class RandomnessInstance extends Crypto::RandomNumberGenerationInstance { + override DataFlow::Node getOutputNode() { result.asExpr() = this } +} + +/** + * An output instance from the system cryptographically secure RNG. + */ +class SecureRandomnessInstance extends RandomnessInstance { + RandomnessSource source; + + SecureRandomnessInstance() { + source instanceof SecureRandomnessSource and + this = source.getOutput() } + + override string getGeneratorName() { result = source.getGenerator().getName() } } -module ArtifactFlow = Language::DataFlow::Global; +/** + * An output instance from an insecure RNG. + */ +class InsecureRandomnessInstance extends RandomnessInstance { + RandomnessSource source; + + InsecureRandomnessInstance() { + not source instanceof SecureRandomnessSource and + this = source.getOutput() + } + + override string getGeneratorName() { result = source.getGenerator().getName() } +} import dotnet diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index a4b658c6dbe8..66891b3144e8 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -1,5 +1,6 @@ private import csharp private import semmle.code.csharp.dataflow.DataFlow +private import experimental.quantum.Language private import OperationInstances private import AlgorithmValueConsumers private import Cryptography @@ -51,15 +52,6 @@ module CreationToUseFlow { sink.getNode().asExpr() = use.(QualifiableExpr).getQualifier() and CreationToUseFlow::flowPath(source, sink) } - - // TODO: Remove this. - Expr flowsTo(Expr expr) { - exists(CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink | - source.getNode().asExpr() = expr and - sink.getNode().asExpr() = result and - CreationToUseFlow::flowPath(source, sink) - ) - } } /** @@ -269,3 +261,20 @@ module StreamFlow { StreamFlow::flowPath(source, sink) } } + +/** + * An additional flow step across property assignments used to track flow from + * output artifacts to consumers. + * + * TODO: Figure out why this is needed. + */ +class PropertyWriteFlowStep extends AdditionalFlowInputStep { + Assignment assignment; + + PropertyWriteFlowStep() { + this.asExpr() = assignment.getRValue() and + assignment.getLValue() instanceof PropertyWrite + } + + override DataFlow::Node getOutput() { result.asExpr() = assignment.getLValue() } +} From 46c037cbcf79878800f6eb5398934368b57c6c3f Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Tue, 24 Jun 2025 14:11:12 +0200 Subject: [PATCH 16/33] Added unit tests for C# block cipher modes --- .../quantum/dotnet/ciphers/AesCfbExample.cs | 102 ++++++++++++++++++ .../ciphers/cipher_key_sources.expected | 2 + .../dotnet/ciphers/cipher_key_sources.ql | 6 ++ .../ciphers/cipher_nonce_sources.expected | 2 + .../dotnet/ciphers/cipher_nonce_sources.ql | 6 ++ .../dotnet/ciphers/cipher_operations.expected | 2 + .../dotnet/ciphers/cipher_operations.ql | 6 ++ .../quantum/dotnet/ciphers/options | 2 + 8 files changed, 128 insertions(+) create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCfbExample.cs create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/options diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCfbExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCfbExample.cs new file mode 100644 index 000000000000..3e510cf8597a --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCfbExample.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace QuantumExamples.Cryptography +{ + public class AesCfbExample + { + public static void RunExample() + { + const string originalMessage = "This is a secret message!"; + + byte[] key = GenerateRandomKey(); + byte[] iv = GenerateRandomIV(); + + byte[] encryptedData = EncryptStringWithCfb(originalMessage, key, iv); + string decryptedMessage = DecryptStringWithCfb(encryptedData, key, iv); + + bool isSuccessful = originalMessage == decryptedMessage; + Console.WriteLine("Decryption successful: {0}", isSuccessful); + } + + private static byte[] EncryptStringWithCfb(string plainText, byte[] key, byte[] iv) + { + byte[] encrypted; + + using (Aes aes = Aes.Create()) + { + // Set the key and IV on the AES instance. + aes.Key = key; + aes.IV = iv; + aes.Mode = CipherMode.CFB; + aes.Padding = PaddingMode.None; + + ICryptoTransform encryptor = aes.CreateEncryptor(); + byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); + + // Create an empty memory stream and write the plaintext to the crypto stream. + using (MemoryStream msEncrypt = new MemoryStream()) + { + using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + { + csEncrypt.Write(plainBytes, 0, plainBytes.Length); + csEncrypt.FlushFinalBlock(); + } + encrypted = msEncrypt.ToArray(); + } + } + return encrypted; + } + + private static string DecryptStringWithCfb(byte[] cipherText, byte[] key, byte[] iv) + { + string decrypted; + + using (Aes aes = Aes.Create()) + { + aes.Mode = CipherMode.CFB; + aes.Padding = PaddingMode.None; + + // Pass the key and IV to the decryptor directly. + ICryptoTransform decryptor = aes.CreateDecryptor(key, iv); + + // Pass the ciphertext to the memory stream directly. + using (MemoryStream msDecrypt = new MemoryStream(cipherText)) + { + using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) + { + using (MemoryStream msPlain = new MemoryStream()) + { + csDecrypt.CopyTo(msPlain); + byte[] plainBytes = msPlain.ToArray(); + decrypted = Encoding.UTF8.GetString(plainBytes); + } + } + } + } + return decrypted; + } + + private static byte[] GenerateRandomKey() + { + byte[] key = new byte[32]; // 256-bit key + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(key); + } + return key; + } + + private static byte[] GenerateRandomIV() + { + byte[] iv = new byte[16]; // 128-bit IV + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(iv); + } + return iv; + } + } +} diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected new file mode 100644 index 000000000000..c42791688ae8 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected @@ -0,0 +1,2 @@ +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql new file mode 100644 index 000000000000..a9d041efc0d0 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::CipherOperationNode op, Crypto::KeyArtifactNode k +where op.getAKey() = k +select op, k, k.getSourceNode() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected new file mode 100644 index 000000000000..8bf24475a746 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected @@ -0,0 +1,2 @@ +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql new file mode 100644 index 000000000000..4729bdcd5664 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::CipherOperationNode op, Crypto::NonceArtifactNode n +where op.getANonce() = n +select op, n, n.getSourceNode() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected new file mode 100644 index 000000000000..56527de95b8b --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected @@ -0,0 +1,2 @@ +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:44:41:44:50 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:28:30:28:41 | KeyOperationAlgorithm | Encrypt | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:57:30:57:41 | KeyOperationAlgorithm | Decrypt | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql new file mode 100644 index 000000000000..1206a2361480 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::CipherOperationNode n +select n, n.getAnInputArtifact(), n.getAnOutputArtifact(), n.getAKey(), n.getANonce(), + n.getAnAlgorithmOrGenericSource(), n.getKeyOperationSubtype() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/options b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/options new file mode 100644 index 000000000000..f4586e95ef0c --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj From a7c5f7a3767710b678b9d4fb7c507f6e15ab4949 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Tue, 24 Jun 2025 15:05:19 +0100 Subject: [PATCH 17/33] refactoring --- .../quantum/dotnet/Cryptography.qll | 21 ++++++++++---- .../quantum/dotnet/OperationInstances.qll | 28 ++++++++----------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index c052c436f35d..86dbf142d029 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -1,5 +1,6 @@ private import csharp private import experimental.quantum.Language +private import FlowAnalysis class CryptographyType extends Type { CryptographyType() { this.hasFullyQualifiedName("System.Security.Cryptography", _) } @@ -190,6 +191,13 @@ class ReadOnlyByteSpanType extends Type { ReadOnlyByteSpanType() { this.getName() = "ReadOnlySpan" } } +class ByteArrayOrReadOnlyByteSpanType extends Type { + ByteArrayOrReadOnlyByteSpanType() { + this instanceof ByteArrayType or + this instanceof ReadOnlyByteSpanType + } +} + class HashUse extends MethodCall { HashUse() { this.getQualifier().getType() instanceof HashAlgorithmType and @@ -217,9 +225,13 @@ class HashUse extends MethodCall { else result = this } - Expr getInputConsumer() { - not this.getTarget().getName() = "HashFinal" and result = this.getArgument(0) + Expr getInputArg() { + result = this.getAnArgument() and result.getType() instanceof ByteArrayOrReadOnlyByteSpanType } + // Expr getStreamArg() { + // result = this.getAnArgument() and + // result.getType() instanceof Stream + // } } class SignerUse extends MethodCall { @@ -238,10 +250,7 @@ class SignerUse extends MethodCall { this.isVerifier() and ( result = this.getArgument([1, 3]) and - ( - result.getType() instanceof ByteArrayType or - result.getType() instanceof ReadOnlyByteSpanType - ) + result.getType() instanceof ByteArrayOrReadOnlyByteSpanType ) } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index b837ac0ca5e4..7b8f12b07802 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -45,22 +45,18 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta } } -class HashOperationInstance extends Crypto::HashOperationInstance instanceof HashUse { - HashAlgorithmCreateCall creator; - - HashOperationInstance() { creator = HashCreateToUseFlow::getCreationFromUse(this, _, _) } - - override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { - result = DataFlow::exprNode(this.(HashUse).getOutputArtifact()) - } - - override Crypto::ConsumerInputDataFlowNode getInputConsumer() { - result = DataFlow::exprNode(this.(HashUse).getInputConsumer()) - } - - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { none() } -} - +// class HashOperationInstance extends Crypto::HashOperationInstance instanceof HashUse { +// HashOperationInstance() { +// not super.isIntermediate() +// } +// override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { +// result.asExpr() = super.getOutputArtifact() +// } +// override Crypto::ConsumerInputDataFlowNode getInputConsumer() { +// result.asExpr() = super.getInputArg() or result = StreamFlow::getIntermediateUse(this.getStreamArg(), _, _).getInputArg() +// } +// override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = HashCreateToUseFlow::getCreationFromUse(this, _, _) } +// } /** * A symmetric algorithm class, such as AES or DES. */ From 3e70e8ec3f31efd6fa999e571f116dc24b75a446 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Tue, 24 Jun 2025 16:08:43 +0200 Subject: [PATCH 18/33] Updated Stream related data flow --- .../quantum/dotnet/FlowAnalysis.qll | 54 +++++---------- .../quantum/dotnet/OperationInstances.qll | 69 +++++++++++++++++-- 2 files changed, 81 insertions(+), 42 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 66891b3144e8..e7d85e924a00 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -189,43 +189,21 @@ module ModeLiteralFlow { * `ToArray()` on the stream, or a wrapped stream. */ module StreamFlow { - private class Stream extends Class { - Stream() { this.getABaseType().hasFullyQualifiedName("System.IO", "Stream") } - } - - /** - * A `Stream` object creation. - */ - private class StreamCreation extends ObjectCreation { - StreamCreation() { this.getObjectType() instanceof Stream } - - Expr getInputArg() { - result = this.getAnArgument() and - result.getType().hasFullyQualifiedName("System", "Byte[]") - } - - Expr getStreamArg() { - result = this.getAnArgument() and - result.getType() instanceof Stream - } - } - - private class StreamUse extends MethodCall { - StreamUse() { - this.getQualifier().getType() instanceof Stream and - this.getTarget().hasName("ToArray") - } - - Expr getOutput() { result = this } - } - private module StreamConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { source.asExpr() instanceof StreamCreation } + predicate isSource(DataFlow::Node source) { + source.asExpr() instanceof StreamCreation + or + exists(StreamUse use | source.asExpr() = use.getQualifier()) + or + exists(Expr use | source.asExpr() = use and use.getType() instanceof Stream) + } predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof StreamCreation or exists(StreamUse use | sink.asExpr() = use.getQualifier()) + or + exists(Expr use | sink.asExpr() = use and use.getType() instanceof Stream) } predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { @@ -245,7 +223,7 @@ module StreamFlow { private module StreamFlow = DataFlow::Global; - StreamCreation getWrappedStream( + StreamCreation getWrappedStreamCreation( StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink ) { source.getNode().asExpr() = result and @@ -253,13 +231,17 @@ module StreamFlow { StreamFlow::flowPath(source, sink) } - StreamUse getStreamUse( - StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink - ) { - source.getNode().asExpr() = stream and + StreamUse getLaterUse(Expr use, StreamFlow::PathNode source, StreamFlow::PathNode sink) { + source.getNode().asExpr() = use and sink.getNode().asExpr() = result.getQualifier() and StreamFlow::flowPath(source, sink) } + + StreamUse getEarlierUse(Expr use, StreamFlow::PathNode source, StreamFlow::PathNode sink) { + source.getNode().asExpr() = result.getQualifier() and + sink.getNode().asExpr() = use and + StreamFlow::flowPath(source, sink) + } } /** diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 366e896175b9..5e17e6e7bc6d 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -159,6 +159,46 @@ class CipherMode extends MemberConstant { } } +class Stream extends Class { + Stream() { this.getABaseType().hasFullyQualifiedName("System.IO", "Stream") } +} + +/** + * A `Stream` object creation. + */ +class StreamCreation extends ObjectCreation { + StreamCreation() { this.getObjectType() instanceof Stream } + + Expr getInputArg() { + result = this.getAnArgument() and + result.getType().hasFullyQualifiedName("System", "Byte[]") + } + + Expr getStreamArg() { + result = this.getAnArgument() and + result.getType() instanceof Stream + } +} + +class StreamUse extends MethodCall { + StreamUse() { + this.getQualifier().getType() instanceof Stream and + this.getTarget().hasName(["ToArray", "Write"]) + } + + predicate isIntermediate() { this.getTarget().hasName("Write") } + + Expr getInputArg() { + this.isIntermediate() and + result = this.getArgument(0) + } + + Expr getOutput() { + not this.isIntermediate() and + result = this + } +} + class CryptoStreamCreation extends ObjectCreation { CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream } @@ -243,11 +283,16 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc ) } - // Inputs can be passed either through the `stream` argument when the - // `CryptoStream` is created, or through calls to `Write()` on the - // `CryptoStream` object. + // Inputs can be passed to the `CryptoStream` instance in a number of ways. + // + // 1. Through the `stream` argument when the `CryptoStream` is created + // 2. Through calls to `Write()` on (a stream wrapped by) the stream argument + // 3. Through calls to write on this `CryptoStream` object override Crypto::ConsumerInputDataFlowNode getInputConsumer() { - result.asExpr() = StreamFlow::getWrappedStream(this, _, _).getInputArg() or + result.asExpr() = this.getWrappedStreamCreation().getInputArg() + or + result.asExpr() = this.getEarlierWrappedStreamUse().getInputArg() + or result.asExpr() = CryptoStreamFlow::getUseFromCreation(this, _, _).getInputArg() } @@ -257,7 +302,19 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc // We perform backwards dataflow to identify stream objects that are wrapped // by the `CryptoStream` object, and then we look for calls to `ToArray()` // on those streams. - result.asExpr() = - StreamFlow::getStreamUse(any(StreamFlow::getWrappedStream(this, _, _)), _, _).getOutput() + result.asExpr() = this.getLaterWrappedStreamUse().getOutput() + } + + // Gets either this stream, or a stream wrapped by this stream. + StreamCreation getWrappedStreamCreation() { + result = StreamFlow::getWrappedStreamCreation(this, _, _) + } + + StreamUse getEarlierWrappedStreamUse() { + result = StreamFlow::getEarlierUse(this.getWrappedStreamCreation().getStreamArg(), _, _) + } + + StreamUse getLaterWrappedStreamUse() { + result = StreamFlow::getLaterUse(this.getWrappedStreamCreation().getStreamArg(), _, _) } } From f3c436aa55772af50e04c662863f2057c889afa1 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Tue, 24 Jun 2025 16:58:11 +0100 Subject: [PATCH 19/33] quantum-c#: Use stream flows for the HashOperationInstance --- .../quantum/dotnet/Cryptography.qll | 74 +++++++++++++------ .../quantum/dotnet/OperationInstances.qll | 41 +++++----- 2 files changed, 74 insertions(+), 41 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 86dbf142d029..f4d3c61c4666 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -82,8 +82,33 @@ class SigningCreateCall extends CryptographyCreateCall { } } -class HashAlgorithmCreateCall extends CryptographyCreateCall { - HashAlgorithmCreateCall() { this.getQualifier().getType() instanceof HashAlgorithmType } +/** + * A call to create on an hash algorithm instance. + * The hash algorithm is defined by the qualifier. + */ +class HashAlgorithmCreateCall extends Crypto::AlgorithmValueConsumer instanceof CryptographyCreateCall +{ + HashAlgorithmCreateCall() { super.getQualifier().getType() instanceof HashAlgorithmType } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = super.getQualifier() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } +} + +class HashAlgorithmQualifier extends Crypto::HashAlgorithmInstance instanceof Expr { + HashAlgorithmQualifier() { + this = any(HashAlgorithmCreateCall c).(CryptographyCreateCall).getQualifier() + } + + override Crypto::THashType getHashFamily() { + result = getHashFamily(this.getRawHashAlgorithmName()) + } + + override string getRawHashAlgorithmName() { result = super.getType().getName() } + + override int getFixedDigestLength() { + hashAlgorithmToFamily(this.getRawHashAlgorithmName(), _, result) + } } class NamedCurvePropertyAccess extends PropertyAccess { @@ -111,15 +136,18 @@ class HashAlgorithmName extends PropertyAccess { string getAlgorithmName() { result = algorithmName } - Crypto::THashType getHashFamily() { - if hashAlgorithmToFamily(this.getAlgorithmName(), _, _) - then hashAlgorithmToFamily(this.getAlgorithmName(), result, _) - else result = Crypto::OtherHashType() - } + Crypto::THashType getHashFamily() { result = getHashFamily(this.getAlgorithmName()) } int getFixedDigestLength() { hashAlgorithmToFamily(this.getAlgorithmName(), _, result) } } +bindingset[name] +Crypto::THashType getHashFamily(string name) { + if hashAlgorithmToFamily(name, _, _) + then hashAlgorithmToFamily(name, result, _) + else result = Crypto::OtherHashType() +} + private predicate hashAlgorithmToFamily( string hashName, Crypto::THashType hashFamily, int digestLength ) { @@ -198,40 +226,44 @@ class ByteArrayOrReadOnlyByteSpanType extends Type { } } -class HashUse extends MethodCall { +class HashUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall { HashUse() { this.getQualifier().getType() instanceof HashAlgorithmType and this.getTarget() - .getName() - .matches([ + .hasName([ "ComputeHash", "ComputeHashAsync", "HashCore", "HashData", "HashDataAsync", "TransformBlock", "TransformFinalBlock", "TryComputeHash", "TryHashData", "TryHashFinal", "HashFinal" ]) } - predicate isIntermediate() { this.getTarget().hasName("HashCore") } + predicate isIntermediate() { super.getTarget().hasName("HashCore") } - Expr getOutputArtifact() { + Expr getOutput() { not this.isIntermediate() and // some functions receive the destination as a parameter if - this.getTarget().getName() = ["TryComputeHash", "TryHashFinal", "TryHashData"] + super.getTarget().getName() = ["TryComputeHash", "TryHashFinal", "TryHashData"] or - this.getTarget().getName() = ["HashData"] and this.getNumberOfArguments() = 2 + super.getTarget().getName() = ["HashData"] and super.getNumberOfArguments() = 2 or - this.getTarget().getName() = ["HashDataAsync"] and this.getNumberOfArguments() = 3 - then result = this.getArgument(1) + super.getTarget().getName() = ["HashDataAsync"] and super.getNumberOfArguments() = 3 + then result = super.getArgument(1) else result = this } Expr getInputArg() { - result = this.getAnArgument() and result.getType() instanceof ByteArrayOrReadOnlyByteSpanType + result = super.getArgument(0) and result.getType() instanceof ByteArrayOrReadOnlyByteSpanType } - // Expr getStreamArg() { - // result = this.getAnArgument() and - // result.getType() instanceof Stream - // } + + Expr getStreamArg() { + result = super.getAnArgument() and + result.getType() instanceof Stream + } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = super.getQualifier() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } } class SignerUse extends MethodCall { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index d98628b9dfd2..d1649fe1f53a 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -7,14 +7,8 @@ private import Cryptography class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInstance instanceof SignerUse { - SigningCreateCall creator; - - ECDsaORRSASigningOperationInstance() { - creator = SigningCreateToUseFlow::getCreationFromUse(this, _, _) - } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - result = creator.getAlgorithmArg() + result = SigningCreateToUseFlow::getCreationFromUse(this, _, _).getAlgorithmArg() } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { @@ -27,7 +21,7 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta } override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { - result.asExpr() = creator.getKeyConsumer() + result.asExpr() = SigningCreateToUseFlow::getCreationFromUse(this, _, _).getKeyConsumer() } override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() } @@ -45,18 +39,25 @@ class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInsta } } -// class HashOperationInstance extends Crypto::HashOperationInstance instanceof HashUse { -// HashOperationInstance() { -// not super.isIntermediate() -// } -// override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { -// result.asExpr() = super.getOutputArtifact() -// } -// override Crypto::ConsumerInputDataFlowNode getInputConsumer() { -// result.asExpr() = super.getInputArg() or result = StreamFlow::getIntermediateUse(this.getStreamArg(), _, _).getInputArg() -// } -// override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = HashCreateToUseFlow::getCreationFromUse(this, _, _) } -// } +class HashOperationInstance extends Crypto::HashOperationInstance instanceof HashUse { + HashOperationInstance() { not super.isIntermediate() } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = super.getOutput() + } + + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result.asExpr() = super.getInputArg() or + result.asExpr() = StreamFlow::getEarlierUse(super.getStreamArg(), _, _).getInputArg() + } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + if exists(HashCreateToUseFlow::getCreationFromUse(this, _, _)) + then result = HashCreateToUseFlow::getCreationFromUse(this, _, _) + else result = this + } +} + /** * A symmetric algorithm class, such as AES or DES. */ From 45ae4d1fff7dbc6fec5e72be6ff3223d162e6b4b Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Wed, 25 Jun 2025 09:59:56 +0200 Subject: [PATCH 20/33] Added support for AesGcm and AesCcm This commit also reorganizes the dotnet library to move utility classes into the private Cryptography module. --- .../quantum/dotnet/AlgorithmInstances.qll | 36 ++- .../dotnet/AlgorithmValueConsumers.qll | 10 + .../quantum/dotnet/Cryptography.qll | 222 ++++++++++++++++++ .../quantum/dotnet/FlowAnalysis.qll | 7 + .../quantum/dotnet/OperationInstances.qll | 217 +++-------------- 5 files changed, 304 insertions(+), 188 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 8cf0c27b0b1f..58e09d6435ee 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -159,8 +159,42 @@ class CipherModeLiteralInstance extends Crypto::ModeOfOperationAlgorithmInstance Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } } +/** + * A call to either `Encrypt` or `Decrypt` on an `AesGcm` or `AesCcm` instance. + * The algorithm is defined implicitly by this AST node. + */ +class AesModeAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance, + Crypto::ModeOfOperationAlgorithmInstance instanceof AesModeUse +{ + override string getRawAlgorithmName() { result = "Aes" } + + override string getRawModeAlgorithmName() { + this.getRawAlgorithmName() = "AesGcm" and result = "Gcm" + or + this.getRawAlgorithmName() = "AesCcm" and result = "Ccm" + } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) + } + + override Crypto::TBlockCipherModeOfOperationType getModeType() { + this.getRawAlgorithmName() = "AesGcm" and result = Crypto::GCM() + or + this.getRawAlgorithmName() = "AesCcm" and result = Crypto::CCM() + } + + override int getKeySizeFixed() { none() } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { result = this } + + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } +} + private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) { - algorithmName = "Aes" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) + algorithmName = "Aes%" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) or algorithmName = "DES" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::DES()) or diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index cbc2bece75e9..47dd14c1e7e4 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -69,3 +69,13 @@ class SymmetricAlgorithmConsumer extends Crypto::AlgorithmValueConsumer instance result.(SymmetricAlgorithmInstance).getConsumer() = this } } + +/** + * A call to either `Encrypt` or `Decrypt` on an `AesGcm` or `AesCcm` instance. + * The algorithm is defined implicitly by this AST node. + */ +class AesModeAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof AesModeUse { + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index f4d3c61c4666..90402a2486d9 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -310,3 +310,225 @@ private class ECDsaSigner extends SignerUse { private class RSASigner extends SignerUse { RSASigner() { this.getQualifier().getType() instanceof RSAClass } } + +class AesMode extends Class { + AesMode() { this.hasFullyQualifiedName("System.Security.Cryptography", ["AesGcm", "AesCcm"]) } +} + +class AesModeCreation extends ObjectCreation { + AesModeCreation() { this.getObjectType() instanceof AesMode } + + Expr getKeyArg() { result = this.getArgument(0) } +} + +class AesModeUse extends MethodCall { + AesModeUse() { + this.getQualifier().getType() instanceof AesMode and + this.getTarget().hasName(["Encrypt", "Decrypt"]) + } + + // One-shot API only. + predicate isIntermediate() { none() } + + Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if this.isEncrypt() + then result = Crypto::TEncryptMode() + else + if this.isDecrypt() + then result = Crypto::TDecryptMode() + else result = Crypto::TUnknownKeyOperationMode() + } + + predicate isEncrypt() { this.getTarget().getName() = "Encrypt" } + + predicate isDecrypt() { this.getTarget().getName() = "Decrypt" } + + Expr getNonceArg() { result = this.getArgument(0) } + + Expr getMessageArg() { result = this.getArgument(1) } + + Expr getOutputArg() { + this.isEncrypt() and + result = this.getArgument(2) + or + this.isDecrypt() and + result = this.getArgument(3) + } +} + +/** + * A symmetric algorithm class, such as AES or DES. + */ +class SymmetricAlgorithm extends Class { + SymmetricAlgorithm() { + this.getABaseType().hasFullyQualifiedName("System.Security.Cryptography", "SymmetricAlgorithm") + } + + CryptoTransformCreation getCreateTransformCall() { result = this.getAMethod().getACall() } +} + +/** + * A symmetric algorithm creation, such as `Aes.Create()`. + */ +class SymmetricAlgorithmCreation extends MethodCall { + SymmetricAlgorithmCreation() { + this.getTarget().hasName("Create") and + this.getQualifier().getType() instanceof SymmetricAlgorithm + } + + SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } +} + +class SymmetricAlgorithmUse extends QualifiableExpr { + SymmetricAlgorithmUse() { + this.getQualifier().getType() instanceof SymmetricAlgorithm and + this.getQualifiedDeclaration() + .hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode"]) + } + + Expr getSymmetricAlgorithm() { result = this.getQualifier() } + + predicate isIntermediate() { + not this.getQualifiedDeclaration().hasName(["CreateEncryptor", "CreateDecryptor"]) + } + + // The key may be set by assigning it to the `Key` property of the symmetric algorithm. + predicate isKeyConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Key" + } + + // The IV may be set by assigning it to the `IV` property of the symmetric algorithm. + predicate isIvConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "IV" + } + + // The padding mode may be set by assigning it to the `Padding` property of the symmetric algorithm. + predicate isPaddingConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Padding" + } + + // The cipher mode may be set by assigning it to the `Mode` property of the symmetric algorithm. + predicate isModeConsumer() { + this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode" + } +} + +/** + * A call to `CreateEncryptor` or `CreateDecryptor` on a `SymmetricAlgorithm`. + */ +class CryptoTransformCreation extends MethodCall { + CryptoTransformCreation() { + this.getTarget().hasName(["CreateEncryptor", "CreateDecryptor"]) and + this.getQualifier().getType() instanceof SymmetricAlgorithm + } + + predicate isEncryptor() { this.getTarget().getName() = "CreateEncryptor" } + + predicate isDecryptor() { this.getTarget().getName() = "CreateDecryptor" } + + Expr getKeyArg() { result = this.getArgument(0) } + + Expr getIvArg() { result = this.getArgument(1) } + + SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } +} + +class CryptoStream extends Class { + CryptoStream() { this.hasFullyQualifiedName("System.Security.Cryptography", "CryptoStream") } +} + +class CryptoStreamMode extends MemberConstant { + CryptoStreamMode() { + this.getDeclaringType() + .hasFullyQualifiedName("System.Security.Cryptography", "CryptoStreamMode") + } + + predicate isRead() { this.getName() = "Read" } + + predicate isWrite() { this.getName() = "Write" } +} + +class PaddingMode extends MemberConstant { + PaddingMode() { + this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "PaddingMode") + } +} + +class CipherMode extends MemberConstant { + CipherMode() { + this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "CipherMode") + } +} + +class Stream extends Class { + Stream() { this.getABaseType().hasFullyQualifiedName("System.IO", "Stream") } +} + +/** + * A `Stream` object creation. + */ +class StreamCreation extends ObjectCreation { + StreamCreation() { this.getObjectType() instanceof Stream } + + Expr getInputArg() { + result = this.getAnArgument() and + result.getType().hasFullyQualifiedName("System", "Byte[]") + } + + Expr getStreamArg() { + result = this.getAnArgument() and + result.getType() instanceof Stream + } +} + +class StreamUse extends MethodCall { + StreamUse() { + this.getQualifier().getType() instanceof Stream and + this.getTarget().hasName(["ToArray", "Write"]) + } + + predicate isIntermediate() { this.getTarget().hasName("Write") } + + Expr getInputArg() { + this.isIntermediate() and + result = this.getArgument(0) + } + + Expr getOutput() { + not this.isIntermediate() and + result = this + } +} + +class CryptoStreamCreation extends ObjectCreation { + CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream } + + Expr getStreamArg() { result = this.getArgument(0) } + + Expr getTransformArg() { result = this.getArgument(1) } + + Expr getModeArg() { result = this.getArgument(2) } + + Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if CryptoTransformFlow::getCreationFromUse(this.getTransformArg()).isEncryptor() + then result = Crypto::TEncryptMode() + else + if CryptoTransformFlow::getCreationFromUse(this.getTransformArg()).isDecryptor() + then result = Crypto::TDecryptMode() + else result = Crypto::TUnknownKeyOperationMode() + } +} + +class CryptoStreamUse extends MethodCall { + CryptoStreamUse() { + this.getQualifier().getType() instanceof CryptoStream and + this.getTarget().hasName(["Write", "FlushFinalBlock", "FlushFinalBlockAsync", "Close"]) + } + + predicate isIntermediate() { this.getTarget().getName() = "Write" } + + Expr getInputArg() { + this.isIntermediate() and + result = this.getArgument(0) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index c71f3e328332..a2dfc2368fd3 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -82,6 +82,13 @@ module SigningCreateToUseFlow = CreationToUseFlow; module HashCreateToUseFlow = CreationToUseFlow; +module CryptoStreamFlow = CreationToUseFlow; + +module AesModeFlow = CreationToUseFlow; + +module SymmetricAlgorithmFlow = + CreationToUseFlow; + /** * A flow analysis module that tracks the flow from a `CryptoStreamMode.READ` or * `CryptoStreamMode.WRITE` access to the corresponding `CryptoStream` object diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index d1649fe1f53a..c5d37750cc83 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -58,193 +58,6 @@ class HashOperationInstance extends Crypto::HashOperationInstance instanceof Has } } -/** - * A symmetric algorithm class, such as AES or DES. - */ -class SymmetricAlgorithm extends Class { - SymmetricAlgorithm() { - this.getABaseType().hasFullyQualifiedName("System.Security.Cryptography", "SymmetricAlgorithm") - } - - CryptoTransformCreation getCreateTransformCall() { result = this.getAMethod().getACall() } -} - -/** - * A symmetric algorithm creation, such as `Aes.Create()`. - */ -class SymmetricAlgorithmCreation extends MethodCall { - SymmetricAlgorithmCreation() { - this.getTarget().hasName("Create") and - this.getQualifier().getType() instanceof SymmetricAlgorithm - } - - SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } -} - -class SymmetricAlgorithmUse extends QualifiableExpr { - SymmetricAlgorithmUse() { - this.getQualifier().getType() instanceof SymmetricAlgorithm and - this.getQualifiedDeclaration() - .hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode"]) - } - - Expr getSymmetricAlgorithm() { result = this.getQualifier() } - - predicate isIntermediate() { - not this.getQualifiedDeclaration().hasName(["CreateEncryptor", "CreateDecryptor"]) - } - - // The key may be set by assigning it to the `Key` property of the symmetric algorithm. - predicate isKeyConsumer() { - this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Key" - } - - // The IV may be set by assigning it to the `IV` property of the symmetric algorithm. - predicate isIvConsumer() { - this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "IV" - } - - // The padding mode may be set by assigning it to the `Padding` property of the symmetric algorithm. - predicate isPaddingConsumer() { - this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Padding" - } - - // The cipher mode may be set by assigning it to the `Mode` property of the symmetric algorithm. - predicate isModeConsumer() { - this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode" - } -} - -module SymmetricAlgorithmFlow = - CreationToUseFlow; - -// TODO: Remove this. -SymmetricAlgorithmUse getUseFromUse(SymmetricAlgorithmUse use) { - result = SymmetricAlgorithmFlow::getIntermediateUseFromUse(use, _, _) -} - -/** - * A call to `CreateEncryptor` or `CreateDecryptor` on a `SymmetricAlgorithm`. - */ -class CryptoTransformCreation extends MethodCall { - CryptoTransformCreation() { - this.getTarget().hasName(["CreateEncryptor", "CreateDecryptor"]) and - this.getQualifier().getType() instanceof SymmetricAlgorithm - } - - predicate isEncryptor() { this.getTarget().getName() = "CreateEncryptor" } - - predicate isDecryptor() { this.getTarget().getName() = "CreateDecryptor" } - - Expr getKeyArg() { result = this.getArgument(0) } - - Expr getIvArg() { result = this.getArgument(1) } - - SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } -} - -class CryptoStream extends Class { - CryptoStream() { this.hasFullyQualifiedName("System.Security.Cryptography", "CryptoStream") } -} - -class CryptoStreamMode extends MemberConstant { - CryptoStreamMode() { - this.getDeclaringType() - .hasFullyQualifiedName("System.Security.Cryptography", "CryptoStreamMode") - } - - predicate isRead() { this.getName() = "Read" } - - predicate isWrite() { this.getName() = "Write" } -} - -class PaddingMode extends MemberConstant { - PaddingMode() { - this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "PaddingMode") - } -} - -class CipherMode extends MemberConstant { - CipherMode() { - this.getDeclaringType().hasFullyQualifiedName("System.Security.Cryptography", "CipherMode") - } -} - -class Stream extends Class { - Stream() { this.getABaseType().hasFullyQualifiedName("System.IO", "Stream") } -} - -/** - * A `Stream` object creation. - */ -class StreamCreation extends ObjectCreation { - StreamCreation() { this.getObjectType() instanceof Stream } - - Expr getInputArg() { - result = this.getAnArgument() and - result.getType().hasFullyQualifiedName("System", "Byte[]") - } - - Expr getStreamArg() { - result = this.getAnArgument() and - result.getType() instanceof Stream - } -} - -class StreamUse extends MethodCall { - StreamUse() { - this.getQualifier().getType() instanceof Stream and - this.getTarget().hasName(["ToArray", "Write"]) - } - - predicate isIntermediate() { this.getTarget().hasName("Write") } - - Expr getInputArg() { - this.isIntermediate() and - result = this.getArgument(0) - } - - Expr getOutput() { - not this.isIntermediate() and - result = this - } -} - -class CryptoStreamCreation extends ObjectCreation { - CryptoStreamCreation() { this.getObjectType() instanceof CryptoStream } - - Expr getStreamArg() { result = this.getArgument(0) } - - Expr getTransformArg() { result = this.getArgument(1) } - - Expr getModeArg() { result = this.getArgument(2) } - - Crypto::KeyOperationSubtype getKeyOperationSubtype() { - if CryptoTransformFlow::getCreationFromUse(this.getTransformArg()).isEncryptor() - then result = Crypto::TEncryptMode() - else - if CryptoTransformFlow::getCreationFromUse(this.getTransformArg()).isDecryptor() - then result = Crypto::TDecryptMode() - else result = Crypto::TUnknownKeyOperationMode() - } -} - -private class CryptoStreamUse extends MethodCall { - CryptoStreamUse() { - this.getQualifier().getType() instanceof CryptoStream and - this.getTarget().hasName(["Write", "FlushFinalBlock", "FlushFinalBlockAsync", "Close"]) - } - - predicate isIntermediate() { this.getTarget().getName() = "Write" } - - Expr getInputArg() { - this.isIntermediate() and - result = this.getArgument(0) - } -} - -private module CryptoStreamFlow = CreationToUseFlow; - /** * An instantiation of a `CryptoStream` object where the transform is a symmetric * encryption or decryption operation (e.g. an encryption transform created by a @@ -329,3 +142,33 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc result = StreamFlow::getLaterUse(this.getWrappedStreamCreation().getStreamArg(), _, _) } } + +/** + * A call to either `Encrypt` or `Decrypt` on an `AesGcm` or `AesCcm` instance. + */ +class AesModeOperationInstance extends Crypto::KeyOperationInstance instanceof AesModeUse { + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + // See `AesModeAlgorithmInstance` for the algorithm value consumer. + result = this + } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + result = this.(AesModeUse).getKeyOperationSubtype() + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + result.asExpr() = AesModeFlow::getCreationFromUse(this, _, _).getKeyArg() + } + + override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { + result.asExpr() = super.getNonceArg() + } + + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result.asExpr() = super.getMessageArg() + } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = super.getOutputArg() + } +} From e643cc4833e8ea7d83a372be5b8b653a6edbcd27 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Wed, 25 Jun 2025 10:00:59 +0200 Subject: [PATCH 21/33] Updated test suite with AesGcm and AesCcm tests --- .../quantum/dotnet/ciphers/AesGcmExample.cs | 68 +++++++++++++++++++ .../ciphers/cipher_input_sources.expected | 2 + .../dotnet/ciphers/cipher_input_sources.ql | 6 ++ .../ciphers/cipher_key_sources.expected | 2 + .../ciphers/cipher_nonce_sources.expected | 2 + .../dotnet/ciphers/cipher_operations.expected | 2 + 6 files changed, 82 insertions(+) create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesGcmExample.cs create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesGcmExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesGcmExample.cs new file mode 100644 index 000000000000..5f5ec58e4f58 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesGcmExample.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace QuantumExamples.Cryptography +{ + public class AesGcmExample + { + public static void RunExample() + { + const string originalMessage = "This is a secret message!"; + + byte[] key = GenerateRandomKey(); + byte[] nonce = GenerateRandomNonce(); + + var (encryptedMessage, tag) = EncryptStringWithGcm(originalMessage, key, nonce); + string decryptedMessage = DecryptStringWithGcm(encryptedMessage, key, nonce, tag); + + bool isSuccessful = originalMessage == decryptedMessage; + Console.WriteLine("Decryption successful: {0}", isSuccessful); + } + + private static (byte[], byte[]) EncryptStringWithGcm(string plaintext, byte[] key, byte[] nonce) + { + using (var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize)) + { + var plaintextBytes = Encoding.UTF8.GetBytes(plaintext); + var ciphertext = new byte[plaintextBytes.Length]; + var tag = new byte[AesGcm.TagByteSizes.MaxSize]; + aes.Encrypt(nonce, plaintextBytes, ciphertext, tag); + + return (ciphertext, tag); + } + } + + private static string DecryptStringWithGcm(byte[] ciphertext, byte[] key, byte[] nonce, byte[] tag) + { + using (var aes = new AesGcm(key, AesGcm.TagByteSizes.MaxSize)) + { + var plaintextBytes = new byte[ciphertext.Length]; + aes.Decrypt(nonce, ciphertext, tag, plaintextBytes); + + return Encoding.UTF8.GetString(plaintextBytes); + } + } + + private static byte[] GenerateRandomKey() + { + byte[] key = new byte[32]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(key); + } + return key; + } + + private static byte[] GenerateRandomNonce() + { + byte[] nonce = new byte[AesGcm.NonceByteSizes.MaxSize]; + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(nonce); + } + return nonce; + } + } +} diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected new file mode 100644 index 000000000000..4d992470a3c6 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected @@ -0,0 +1,2 @@ +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:36:42:45 | Message | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql new file mode 100644 index 000000000000..5ce237d6af22 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::CipherOperationNode n, Crypto::MessageArtifactNode m +where n.getAnInputArtifact() = m +select n, m, m.getSourceNode() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected index c42791688ae8..6bfa4ee255f7 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected @@ -1,2 +1,4 @@ | AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | | AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:26:41:26:43 | Key | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:39:41:39:43 | Key | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected index 8bf24475a746..f20fd8aaada4 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected @@ -1,2 +1,4 @@ | AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | | AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:31:29:31:33 | Nonce | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:29:42:33 | Nonce | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected index 56527de95b8b..7fd0213156e9 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected @@ -1,2 +1,4 @@ | AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:44:41:44:50 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:28:30:28:41 | KeyOperationAlgorithm | Encrypt | | AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:57:30:57:41 | KeyOperationAlgorithm | Decrypt | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:31:36:31:49 | Message | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | AesGcmExample.cs:26:41:26:43 | Key | AesGcmExample.cs:31:29:31:33 | Nonce | AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | Encrypt | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:36:42:45 | Message | AesGcmExample.cs:42:53:42:66 | KeyOperationOutput | AesGcmExample.cs:39:41:39:43 | Key | AesGcmExample.cs:42:29:42:33 | Nonce | AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | Decrypt | From 5fc267aa9f09b55f731a432c240ecf2ae78e546f Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Wed, 25 Jun 2025 10:52:21 +0200 Subject: [PATCH 22/33] Added support for ChaCha20Poly1305 --- .../quantum/dotnet/AlgorithmInstances.qll | 28 ++++++++++++------- .../dotnet/AlgorithmValueConsumers.qll | 12 +++++--- .../quantum/dotnet/Cryptography.qll | 20 ++++++++----- .../quantum/dotnet/FlowAnalysis.qll | 2 +- .../quantum/dotnet/OperationInstances.qll | 11 ++++---- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 58e09d6435ee..ba576e1a3d70 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -27,7 +27,6 @@ abstract class SigningAlgorithmInstance extends Crypto::KeyOperationAlgorithmIns override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } - override int getKeySizeFixed() { none() } } @@ -160,28 +159,37 @@ class CipherModeLiteralInstance extends Crypto::ModeOfOperationAlgorithmInstance } /** - * A call to either `Encrypt` or `Decrypt` on an `AesGcm` or `AesCcm` instance. - * The algorithm is defined implicitly by this AST node. + * A call to either `Encrypt` or `Decrypt` on an `AesGcm`, `AesCcm`, or + * `ChaCha20Poly1305` instance. The algorithm is defined implicitly by this AST + * node. */ -class AesModeAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance, - Crypto::ModeOfOperationAlgorithmInstance instanceof AesModeUse +class AeadAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance, + Crypto::ModeOfOperationAlgorithmInstance instanceof AeadUse { - override string getRawAlgorithmName() { result = "Aes" } + override string getRawAlgorithmName() { + super.getQualifier().getType().hasName("Aes%") and result = "Aes" + or + super.getQualifier().getType().hasName("ChaCha20%") and result = "ChaCha20" + } override string getRawModeAlgorithmName() { - this.getRawAlgorithmName() = "AesGcm" and result = "Gcm" + super.getQualifier().getType().getName() = "AesGcm" and result = "Gcm" or - this.getRawAlgorithmName() = "AesCcm" and result = "Ccm" + super.getQualifier().getType().getName() = "AesCcm" and result = "Ccm" } override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + this.getRawAlgorithmName() = "Aes" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) + or + this.getRawAlgorithmName() = "ChaCha20" and + result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::CHACHA20()) } override Crypto::TBlockCipherModeOfOperationType getModeType() { - this.getRawAlgorithmName() = "AesGcm" and result = Crypto::GCM() + this.getRawModeAlgorithmName() = "Gcm" and result = Crypto::GCM() or - this.getRawAlgorithmName() = "AesCcm" and result = Crypto::CCM() + this.getRawModeAlgorithmName() = "Ccm" and result = Crypto::CCM() } override int getKeySizeFixed() { none() } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 47dd14c1e7e4..cf6e722f541a 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -71,11 +71,15 @@ class SymmetricAlgorithmConsumer extends Crypto::AlgorithmValueConsumer instance } /** - * A call to either `Encrypt` or `Decrypt` on an `AesGcm` or `AesCcm` instance. - * The algorithm is defined implicitly by this AST node. + * A call to either `Encrypt` or `Decrypt` on an `AesGcm`, `AesCcm` or + * `ChaCha20Poly1305` instance. The algorithm is defined implicitly by this AST + * node. */ -class AesModeAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof AesModeUse { +class AeadAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof AeadUse { override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + // See `AeadAlgorithmInstance` for the algorithm instance. + result = this + } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 90402a2486d9..bcfdc8f477a9 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -311,19 +311,25 @@ private class RSASigner extends SignerUse { RSASigner() { this.getQualifier().getType() instanceof RSAClass } } -class AesMode extends Class { - AesMode() { this.hasFullyQualifiedName("System.Security.Cryptography", ["AesGcm", "AesCcm"]) } +/** + * An AEAD class, such as `AesGcm`, `AesCcm`, or `ChaCha20Poly1305`. + */ +class Aead extends Class { + Aead() { + this.hasFullyQualifiedName("System.Security.Cryptography", + ["AesGcm", "AesCcm", "ChaCha20Poly1305"]) + } } -class AesModeCreation extends ObjectCreation { - AesModeCreation() { this.getObjectType() instanceof AesMode } +class AeadCreation extends ObjectCreation { + AeadCreation() { this.getObjectType() instanceof Aead } Expr getKeyArg() { result = this.getArgument(0) } } -class AesModeUse extends MethodCall { - AesModeUse() { - this.getQualifier().getType() instanceof AesMode and +class AeadUse extends MethodCall { + AeadUse() { + this.getQualifier().getType() instanceof Aead and this.getTarget().hasName(["Encrypt", "Decrypt"]) } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index a2dfc2368fd3..27015d2ba73b 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -84,7 +84,7 @@ module HashCreateToUseFlow = CreationToUseFlow module CryptoStreamFlow = CreationToUseFlow; -module AesModeFlow = CreationToUseFlow; +module AeadFlow = CreationToUseFlow; module SymmetricAlgorithmFlow = CreationToUseFlow; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index c5d37750cc83..cb1a2b56a569 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -144,20 +144,21 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc } /** - * A call to either `Encrypt` or `Decrypt` on an `AesGcm` or `AesCcm` instance. + * A call to either `Encrypt` or `Decrypt` on an `AesGcm`, `AesCcm`, or + * `ChaCha20Poly1305` instance. */ -class AesModeOperationInstance extends Crypto::KeyOperationInstance instanceof AesModeUse { +class AeadOperationInstance extends Crypto::KeyOperationInstance instanceof AeadUse { override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - // See `AesModeAlgorithmInstance` for the algorithm value consumer. + // See `AeadModeAlgorithmValueConsumer` for the algorithm value consumer. result = this } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { - result = this.(AesModeUse).getKeyOperationSubtype() + result = this.(AeadUse).getKeyOperationSubtype() } override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { - result.asExpr() = AesModeFlow::getCreationFromUse(this, _, _).getKeyArg() + result.asExpr() = AeadFlow::getCreationFromUse(this, _, _).getKeyArg() } override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { From e344505283f52b5b8ca414608743715a46dfb5f2 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Wed, 25 Jun 2025 12:35:08 +0100 Subject: [PATCH 23/33] quantum-c#: refactor AVCs for hashes and signatures. --- .../quantum/dotnet/AlgorithmInstances.qll | 24 ------- .../dotnet/AlgorithmValueConsumers.qll | 12 ---- .../quantum/dotnet/Cryptography.qll | 68 +++++++++++++++---- .../quantum/dotnet/FlowAnalysis.qll | 14 ---- .../quantum/dotnet/OperationInstances.qll | 9 +-- 5 files changed, 58 insertions(+), 69 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 8cf0c27b0b1f..e1d591ddc0e7 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -27,33 +27,9 @@ abstract class SigningAlgorithmInstance extends Crypto::KeyOperationAlgorithmIns override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } - override int getKeySizeFixed() { none() } } -class EcdsaAlgorithmInstance extends SigningAlgorithmInstance instanceof SigningCreateCall { - EcdsaAlgorithmInstance() { this instanceof ECDsaCreateCall } - - EcdsaAlgorithmValueConsumer getConsumer() { result = super.getQualifier() } - - override string getRawAlgorithmName() { result = "ECDsa" } - - override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { - result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::ECDSA()) - } -} - -class RsaAlgorithmInstance extends SigningAlgorithmInstance { - RsaAlgorithmInstance() { this = any(RSACreateCall c).getQualifier() } - - override string getRawAlgorithmName() { result = "RSA" } - - override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { - // TODO there is no RSA TSignature type, so we use OtherSignatureAlgorithmType - result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::OtherSignatureAlgorithmType()) - } -} - class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { HashAlgorithmNameConsumer consumer; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index cbc2bece75e9..2975ade6a1fe 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -4,18 +4,6 @@ private import AlgorithmInstances private import OperationInstances private import Cryptography -class EcdsaAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { - ECDsaCreateCall call; - - EcdsaAlgorithmValueConsumer() { this = call.getAlgorithmArg() } - - override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } - - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - exists(EcdsaAlgorithmInstance l | l.getConsumer() = this and result = l) - } -} - class HashAlgorithmNameConsumer extends Crypto::AlgorithmValueConsumer { HashAlgorithmNameUser call; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index f4d3c61c4666..f240326ba179 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -37,7 +37,7 @@ class HashAlgorithmType extends CryptographyType { // This class models Create calls for the ECDsa and RSA classes in .NET. class CryptographyCreateCall extends MethodCall { CryptographyCreateCall() { - this.getTarget().getName() = "Create" and + this.getTarget().hasName("Create") and this.getQualifier().getType() instanceof CryptographyType } @@ -58,27 +58,35 @@ class CryptographyCreateCall extends MethodCall { } } -class ECDsaCreateCall extends CryptographyCreateCall { - ECDsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } +class EcdsaType extends CryptographyType { + EcdsaType() { this.hasName("ECDsa") } +} + +class RsaType extends CryptographyType { + RsaType() { this.hasName("RSA") } +} + +class EcdsaCreateCall extends CryptographyCreateCall { + EcdsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } } // This class is used to model the `ECDsa.Create(ECParameters)` call -class ECDsaCreateCallWithParameters extends ECDsaCreateCall { +class ECDsaCreateCallWithParameters extends EcdsaCreateCall { ECDsaCreateCallWithParameters() { this.getArgument(0).getType() instanceof ECParameters } } -class ECDsaCreateCallWithECCurve extends ECDsaCreateCall { +class ECDsaCreateCallWithECCurve extends EcdsaCreateCall { ECDsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof ECCurve } } -class RSACreateCall extends CryptographyCreateCall { - RSACreateCall() { this.getQualifier().getType().hasName("RSA") } +class RsaCreateCall extends CryptographyCreateCall { + RsaCreateCall() { this.getQualifier().getType().hasName("RSA") } } class SigningCreateCall extends CryptographyCreateCall { SigningCreateCall() { - this instanceof ECDsaCreateCall or - this instanceof RSACreateCall + this instanceof EcdsaCreateCall or + this instanceof RsaCreateCall } } @@ -95,10 +103,9 @@ class HashAlgorithmCreateCall extends Crypto::AlgorithmValueConsumer instanceof override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } } -class HashAlgorithmQualifier extends Crypto::HashAlgorithmInstance instanceof Expr { - HashAlgorithmQualifier() { - this = any(HashAlgorithmCreateCall c).(CryptographyCreateCall).getQualifier() - } +class HashAlgorithmQualifier extends Crypto::AlgorithmValueConsumer, Crypto::HashAlgorithmInstance instanceof Expr +{ + HashAlgorithmQualifier() { this = any(HashUse c).getQualifier() } override Crypto::THashType getHashFamily() { result = getHashFamily(this.getRawHashAlgorithmName()) @@ -109,6 +116,10 @@ class HashAlgorithmQualifier extends Crypto::HashAlgorithmInstance instanceof Ex override int getFixedDigestLength() { hashAlgorithmToFamily(this.getRawHashAlgorithmName(), _, result) } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } } class NamedCurvePropertyAccess extends PropertyAccess { @@ -264,6 +275,37 @@ class HashUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall { override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = super.getQualifier() } override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } + + Expr getQualifier() { result = super.getQualifier() } +} + +abstract class SignerQualifier extends Crypto::AlgorithmValueConsumer, SigningAlgorithmInstance instanceof Expr +{ + SignerQualifier() { this = any(SignerUse s).getQualifier() } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } +} + +class EcdsaSignerQualifier extends SignerQualifier instanceof Expr { + EcdsaSignerQualifier() { super.getType() instanceof EcdsaType } + + override string getRawAlgorithmName() { result = "ECDsa" } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::ECDSA()) + } +} + +class RsaSignerQualifier extends SignerQualifier instanceof Expr { + RsaSignerQualifier() { super.getType() instanceof RsaType } + + override string getRawAlgorithmName() { result = "RSA" } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::OtherSignatureAlgorithmType()) + } } class SignerUse extends MethodCall { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index c71f3e328332..a10326b7b3bb 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -54,20 +54,6 @@ module CreationToUseFlow { } } -/** - * Flow from a known ECDsa property access to a `ECDsa.Create(sink)` call. - */ -module SigningNamedCurveToSignatureCreateFlowConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof NamedCurvePropertyAccess } - - predicate isSink(DataFlow::Node sink) { - exists(EcdsaAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) - } -} - -module SigningNamedCurveToSignatureCreateFlow = - DataFlow::Global; - module HashAlgorithmNameToUseConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { src.asExpr() instanceof HashAlgorithmName } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index d1649fe1f53a..c10a3aeda0f8 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -5,10 +5,9 @@ private import AlgorithmValueConsumers private import FlowAnalysis private import Cryptography -class ECDsaORRSASigningOperationInstance extends Crypto::SignatureOperationInstance instanceof SignerUse -{ +class SigningOperationInstance extends Crypto::SignatureOperationInstance instanceof SignerUse { override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - result = SigningCreateToUseFlow::getCreationFromUse(this, _, _).getAlgorithmArg() + result = super.getQualifier() } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { @@ -52,9 +51,7 @@ class HashOperationInstance extends Crypto::HashOperationInstance instanceof Has } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - if exists(HashCreateToUseFlow::getCreationFromUse(this, _, _)) - then result = HashCreateToUseFlow::getCreationFromUse(this, _, _) - else result = this + result = super.getQualifier() } } From 298d226f0589941be7521393ab8d90be0ed79061 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Wed, 25 Jun 2025 16:11:49 +0200 Subject: [PATCH 24/33] Added support for bare symmetric algorithms --- .../quantum/dotnet/AlgorithmInstances.qll | 61 ++++++++++--- .../dotnet/AlgorithmValueConsumers.qll | 33 +++++-- .../quantum/dotnet/Cryptography.qll | 54 ++++++++++- .../quantum/dotnet/FlowAnalysis.qll | 5 +- .../quantum/dotnet/OperationInstances.qll | 67 ++++++++++---- .../quantum/dotnet/ciphers/AesCbcExample.cs | 91 +++++++++++++++++++ .../ciphers/cipher_input_sources.expected | 1 + .../ciphers/cipher_key_sources.expected | 2 + .../ciphers/cipher_nonce_sources.expected | 2 + .../dotnet/ciphers/cipher_operations.expected | 6 +- 10 files changed, 278 insertions(+), 44 deletions(-) create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCbcExample.cs diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index fdbdbd033c53..4c5735d09528 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -46,13 +46,19 @@ class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } } -class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SymmetricAlgorithmCreation +/** + * A call to an encryption, decryption, or transform creation API (e.g. + * `EncryptCbc` or `CreateEncryptor`) on a `SymmetricAlgorithm` instance. + */ +class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SymmetricAlgorithmUse { - SymmetricAlgorithmConsumer consumer; - - SymmetricAlgorithmInstance() { consumer = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) } + SymmetricAlgorithmInstance() { + this.isEncryptionCall() or this.isDecryptionCall() or this.isCreationCall() + } - override string getRawAlgorithmName() { result = super.getSymmetricAlgorithm().getName() } + override string getRawAlgorithmName() { + result = super.getSymmetricAlgorithm().getType().getName() + } override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { if exists(symmetricAlgorithmNameToType(this.getRawAlgorithmName())) @@ -60,33 +66,62 @@ class SymmetricAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance i else result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::OtherSymmetricCipherType()) } - // The cipher mode is set by assigning it to the `Mode` property of the - // symmetric algorithm. override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { + super.isCreationCall() and result.(CipherModeLiteralInstance).getConsumer() = this.getCipherModeAlgorithmValueConsumer() + or + (super.isEncryptionCall() or super.isDecryptionCall()) and + result = this } - // The padding mode is set by assigning it to the `Padding` property of the - // symmetric algorithm. override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { result.(PaddingModeLiteralInstance).getConsumer() = this.getPaddingAlgorithmValueConsumer() } + // The padding mode is set by assigning it to the `Padding` property of the + // symmetric algorithm. It can also be passed as an argument to `EncryptCbc`, + // `EncryptCfb`, etc. Crypto::AlgorithmValueConsumer getPaddingAlgorithmValueConsumer() { - result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and + super.isCreationCall() and + result = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this, _, _) and result instanceof PaddingPropertyWrite + or + (super.isEncryptionCall() or super.isDecryptionCall()) and + result = super.getPaddingArg() } + // The cipher mode is set by assigning it to the `Mode` property of the + // symmetric algorithm, or if this is an encryption/decryption call, it + // is implicit in the method name. Crypto::AlgorithmValueConsumer getCipherModeAlgorithmValueConsumer() { - result = SymmetricAlgorithmFlow::getUseFromCreation(this, _, _) and + result = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this, _, _) and result instanceof CipherModePropertyWrite } override int getKeySizeFixed() { none() } override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } +} - Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } +/** + * A call to an encryption or decryption API (e.g. `EncryptCbc` or `EncryptCfb`) + * on a `SymmetricAlgorithm` instance. + * + * For these, the cipher mode is given by the method name. + */ +class SymmetricAlgorithmMode extends Crypto::ModeOfOperationAlgorithmInstance instanceof SymmetricAlgorithmUse +{ + SymmetricAlgorithmMode() { this.isEncryptionCall() or this.isDecryptionCall() } + + override string getRawModeAlgorithmName() { + result = this.(SymmetricAlgorithmUse).getRawModeAlgorithmName() + } + + override Crypto::TBlockCipherModeOfOperationType getModeType() { + if exists(modeNameToType(this.getRawModeAlgorithmName().toUpperCase())) + then result = modeNameToType(this.getRawModeAlgorithmName().toUpperCase()) + else result = Crypto::OtherMode() + } } /** @@ -113,7 +148,7 @@ class PaddingModeLiteralInstance extends Crypto::PaddingAlgorithmInstance instan } /** - * A padding mode literal, such as `PaddingMode.PKCS7`. + * A cipher mode literal, such as `CipherMode.CBC`. */ class CipherModeLiteralInstance extends Crypto::ModeOfOperationAlgorithmInstance instanceof MemberConstantAccess { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll index 025ae5cccf15..0464d184aaab 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -44,18 +44,39 @@ class CipherModePropertyWrite extends Crypto::AlgorithmValueConsumer instanceof } /** - * A call to a `SymmetricAlgorithm.CreateEncryptor` or `SymmetricAlgorithm.CreateDecryptor` - * method that returns a `CryptoTransform` instance. + * A padding mode argument passed to a symmetric algorithm method call. */ -class SymmetricAlgorithmConsumer extends Crypto::AlgorithmValueConsumer instanceof CryptoTransformCreation +class PaddingModeArgument extends Crypto::AlgorithmValueConsumer instanceof Expr { + SymmetricAlgorithmUse use; + + PaddingModeArgument() { + (use.isEncryptionCall() or use.isDecryptionCall()) and + this = use.getPaddingArg() + } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + result.(PaddingModeLiteralInstance).getConsumer() = this + } +} + +/** + * A qualified expression where the qualifier is a `SymmetricAlgorithm` + * instance. (e.g. a call to `SymmetricAlgorithm.EncryptCbc` or + * `SymmetricAlgorithm.CreateEncryptor`) + */ +class SymmetricAlgorithmConsumer extends Crypto::AlgorithmValueConsumer instanceof SymmetricAlgorithmUse { + SymmetricAlgorithmConsumer() { + super.isEncryptionCall() or super.isDecryptionCall() or super.isCreationCall() + } + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = super.getQualifier() } - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - result.(SymmetricAlgorithmInstance).getConsumer() = this - } + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } } /** diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 885d1b8c493d..8e0e0ad89272 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -423,21 +423,23 @@ class SymmetricAlgorithmCreation extends MethodCall { this.getTarget().hasName("Create") and this.getQualifier().getType() instanceof SymmetricAlgorithm } - - SymmetricAlgorithm getSymmetricAlgorithm() { result = this.getQualifier().getType() } } class SymmetricAlgorithmUse extends QualifiableExpr { SymmetricAlgorithmUse() { this.getQualifier().getType() instanceof SymmetricAlgorithm and this.getQualifiedDeclaration() - .hasName(["CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode"]) + .hasName([ + "EncryptCbc", "DecryptCbc", "EncryptCfb", "DecryptCfb", "EncryptEcb", "DecryptEcb", + "TryEncryptCbc", "TryDecryptCbc", "TryEncryptCfb", "TryDecryptCfb", "TryEncryptEcb", + "TryDecryptEcb", "CreateEncryptor", "CreateDecryptor", "Key", "IV", "Padding", "Mode" + ]) } Expr getSymmetricAlgorithm() { result = this.getQualifier() } predicate isIntermediate() { - not this.getQualifiedDeclaration().hasName(["CreateEncryptor", "CreateDecryptor"]) + this.getQualifiedDeclaration().hasName(["Key", "IV", "Padding", "Mode"]) } // The key may be set by assigning it to the `Key` property of the symmetric algorithm. @@ -459,6 +461,50 @@ class SymmetricAlgorithmUse extends QualifiableExpr { predicate isModeConsumer() { this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode" } + + predicate isCreationCall() { + // TODO: Matching using `hasName` does not work here for some reason. + this.getQualifiedDeclaration().getName().matches("Create%") + } + + predicate isEncryptionCall() { + // TODO: Matching using `hasName` does not work here for some reason. + this.getQualifiedDeclaration().getName().matches(["Encrypt%", "TryEncrypt%"]) + } + + predicate isDecryptionCall() { + // TODO: Matching using `hasName` does not work here for some reason. + this.getQualifiedDeclaration().getName().matches(["Decrypt%", "TryDecrypt%"]) + } + + string getRawModeAlgorithmName() { + this.isEncryptionCall() and + result = this.getQualifiedDeclaration().getName().splitAt("Encrypt", 1) + or + this.isDecryptionCall() and + result = this.getQualifiedDeclaration().getName().splitAt("Decrypt", 1) + } + + Expr getInputArg() { + (this.isEncryptionCall() or this.isDecryptionCall()) and + result = this.(MethodCall).getArgument(0) + } + + Expr getIvArg() { + (this.isEncryptionCall() or this.isDecryptionCall()) and + this.getRawModeAlgorithmName().matches(["Cbc", "Cfb"]) and + result = this.(MethodCall).getArgument(1) + } + + Expr getPaddingArg() { + (this.isEncryptionCall() or this.isDecryptionCall()) and + result = this.(MethodCall).getArgument(this.(MethodCall).getNumberOfArguments() - 1) + } + + Expr getOutput() { + (this.isEncryptionCall() or this.isDecryptionCall()) and + result = this + } } /** diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index a97c13cc7dc1..60fdb4ee378a 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -151,6 +151,7 @@ module ModeLiteralFlow { predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof PaddingPropertyWrite or + sink.asExpr() instanceof PaddingModeArgument or sink.asExpr() instanceof CipherModePropertyWrite } @@ -165,9 +166,7 @@ module ModeLiteralFlow { private module ModeLiteralFlow = DataFlow::Global; - SymmetricAlgorithmUse getConsumer( - Expr mode, ModeLiteralFlow::PathNode source, ModeLiteralFlow::PathNode sink - ) { + Expr getConsumer(Expr mode, ModeLiteralFlow::PathNode source, ModeLiteralFlow::PathNode sink) { source.getNode().asExpr() = mode and sink.getNode().asExpr() = result and ModeLiteralFlow::flowPath(source, sink) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 82274c14e856..f93671f275f5 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -55,6 +55,43 @@ class HashOperationInstance extends Crypto::HashOperationInstance instanceof Has } } +/** + * A call to an encryption or decryption API (e.g. `EncryptCbc` or `EncryptCfb`) + * on a `SymmetricAlgorithm` instance. + */ +class SymmetricAlgorithmOperationInstance extends Crypto::KeyOperationInstance instanceof SymmetricAlgorithmUse +{ + SymmetricAlgorithmOperationInstance() { super.isEncryptionCall() or super.isDecryptionCall() } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = this } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if super.isEncryptionCall() + then result = Crypto::TEncryptMode() + else + if super.isDecryptionCall() + then result = Crypto::TDecryptMode() + else result = Crypto::TUnknownKeyOperationMode() + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this, _, _) and + result.asExpr().(SymmetricAlgorithmUse).isKeyConsumer() + } + + override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { + result.asExpr() = super.getIvArg() + } + + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result.asExpr() = super.getInputArg() + } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = super.getOutput() + } +} + /** * An instantiation of a `CryptoStream` object where the transform is a symmetric * encryption or decryption operation (e.g. an encryption transform created by a @@ -62,20 +99,13 @@ class HashOperationInstance extends Crypto::HashOperationInstance instanceof Has */ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanceof CryptoStreamCreation { - CryptoTransformCreation transform; - - CryptoStreamOperationInstance() { - transform = CryptoTransformFlow::getCreationFromUse(this) and - (transform.isEncryptor() or transform.isDecryptor()) - } - - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = transform } + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = this.getCryptoTransform() } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { - if transform.isEncryptor() + if this.getCryptoTransform().isEncryptor() then result = Crypto::TEncryptMode() else - if transform.isDecryptor() + if this.getCryptoTransform().isDecryptor() then result = Crypto::TDecryptMode() else result = Crypto::TUnknownKeyOperationMode() } @@ -84,10 +114,10 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc // If a key is explicitly provided as an argument when the transform is // created, this takes precedence over any key that may be set on the // symmetric algorithm instance. - if exists(transform.getKeyArg()) - then result.asExpr() = transform.getKeyArg() + if exists(this.getCryptoTransform().getKeyArg()) + then result.asExpr() = this.getCryptoTransform().getKeyArg() else ( - result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(transform, _, _) and + result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and result.asExpr().(SymmetricAlgorithmUse).isKeyConsumer() ) } @@ -96,10 +126,10 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc // If an IV is explicitly provided as an argument when the transform is // created, this takes precedence over any IV that may be set on the // symmetric algorithm instance. - if exists(transform.getIvArg()) - then result.asExpr() = transform.getIvArg() + if exists(this.getCryptoTransform().getIvArg()) + then result.asExpr() = this.getCryptoTransform().getIvArg() else ( - result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(transform, _, _) and + result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and result.asExpr().(SymmetricAlgorithmUse).isIvConsumer() ) } @@ -126,6 +156,11 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc result.asExpr() = this.getLaterWrappedStreamUse().getOutput() } + CryptoTransformCreation getCryptoTransform() { + result = CryptoTransformFlow::getCreationFromUse(this) and + (result.isEncryptor() or result.isDecryptor()) + } + // Gets either this stream, or a stream wrapped by this stream. StreamCreation getWrappedStreamCreation() { result = StreamFlow::getWrappedStreamCreation(this, _, _) diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCbcExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCbcExample.cs new file mode 100644 index 000000000000..4cdaf439932f --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/AesCbcExample.cs @@ -0,0 +1,91 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace QuantumExamples.Cryptography +{ + public class AesCbcExample + { + public static void RunExample() + { + const string originalMessage = "This is a secret message!"; + + byte[] key = GenerateRandomKey(); + byte[] iv = GenerateRandomIV(); + + byte[] encryptedData = EncryptStringWithCbc(originalMessage, key, iv); + string decryptedMessage = DecryptStringWithCbc(encryptedData, key, iv); + + bool isSuccessful = originalMessage == decryptedMessage; + Console.WriteLine("Decryption successful: {0}", isSuccessful); + } + + private static byte[] EncryptStringWithCbc(string plaintext, byte[] key, byte[] iv) + { + if (plaintext == null) + throw new ArgumentNullException(nameof(plaintext)); + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (iv == null) + throw new ArgumentNullException(nameof(iv)); + + try + { + using (Aes aes = Aes.Create()) + { + aes.Key = key; + byte[] plaintextBytes = Encoding.UTF8.GetBytes(plaintext); + return aes.EncryptCbc(plaintextBytes, iv, PaddingMode.PKCS7); + } + } + catch (CryptographicException ex) + { + throw new CryptographicException("Encryption failed.", ex); + } + } + + private static string DecryptStringWithCbc(byte[] ciphertext, byte[] key, byte[] iv) + { + if (ciphertext == null) + throw new ArgumentNullException(nameof(ciphertext)); + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (iv == null) + throw new ArgumentNullException(nameof(iv)); + + try + { + using (Aes aes = Aes.Create()) + { + aes.Key = key; + byte[] decryptedBytes = aes.DecryptCbc(ciphertext, iv, PaddingMode.PKCS7); + return Encoding.UTF8.GetString(decryptedBytes); + } + } + catch (CryptographicException ex) + { + throw new CryptographicException("Decryption failed.", ex); + } + } + + private static byte[] GenerateRandomKey() + { + byte[] key = new byte[32]; // 256-bit key + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(key); + } + return key; + } + + private static byte[] GenerateRandomIV() + { + byte[] iv = new byte[16]; // 128-bit IV + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(iv); + } + return iv; + } + } +} diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected index 4d992470a3c6..9f8f26c388a3 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected @@ -1,2 +1,3 @@ +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:61:60:61:69 | Message | AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | | AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | | AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:36:42:45 | Message | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected index 6bfa4ee255f7..bc68e7a2d1ea 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected @@ -1,3 +1,5 @@ +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | AesCbcExample.cs:36:21:36:27 | Key | AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:60:21:60:27 | Key | AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | | AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | | AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | | AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:26:41:26:43 | Key | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected index f20fd8aaada4..432e31dd2b35 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected @@ -1,3 +1,5 @@ +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | AesCbcExample.cs:38:59:38:60 | Nonce | AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:61:72:61:73 | Nonce | AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | | AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | | AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | | AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:31:29:31:33 | Nonce | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected index 7fd0213156e9..feaeca5966be 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected @@ -1,4 +1,6 @@ -| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:44:41:44:50 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:28:30:28:41 | KeyOperationAlgorithm | Encrypt | -| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:57:30:57:41 | KeyOperationAlgorithm | Decrypt | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | AesCbcExample.cs:38:43:38:56 | Message | AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | AesCbcExample.cs:36:21:36:27 | Key | AesCbcExample.cs:38:59:38:60 | Nonce | AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | Encrypt | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:61:60:61:69 | Message | AesCbcExample.cs:61:45:61:93 | KeyOperationOutput | AesCbcExample.cs:60:21:60:27 | Key | AesCbcExample.cs:61:72:61:73 | Nonce | AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | Decrypt | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:44:41:44:50 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | Encrypt | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | Decrypt | | AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:31:36:31:49 | Message | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | AesGcmExample.cs:26:41:26:43 | Key | AesGcmExample.cs:31:29:31:33 | Nonce | AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | Encrypt | | AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:36:42:45 | Message | AesGcmExample.cs:42:53:42:66 | KeyOperationOutput | AesGcmExample.cs:39:41:39:43 | Key | AesGcmExample.cs:42:29:42:33 | Nonce | AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | Decrypt | From 2d4af336059f067657f8535243eed4b126db4137 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Wed, 25 Jun 2025 18:27:05 +0100 Subject: [PATCH 25/33] quantum-c#: add tests for signatures --- .../dotnet/signatures/SignatureExample.cs | 138 ++++++++++++++++++ .../quantum/dotnet/signatures/options | 2 + .../signatures/sign_operations.expected | 15 ++ .../dotnet/signatures/sign_operations.ql | 5 + .../sign_operations_algorithm.expected | 13 ++ .../signatures/sign_operations_algorithm.ql | 6 + .../signatures/sign_operations_keys.expected | 13 ++ .../dotnet/signatures/sign_operations_keys.ql | 5 + .../sign_operations_signatures.expected | 8 + .../signatures/sign_operations_signatures.ql | 5 + 10 files changed, 210 insertions(+) create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/options create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.ql diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs new file mode 100644 index 000000000000..29b3a517a405 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs @@ -0,0 +1,138 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace QuantumExamples.Cryptography +{ + public class SignatureExample + { + public static void RunExample() + { + const string originalMessage = "This is a message to sign!"; + + // Demonstrate ECDSA signing and verification + DemonstrateECDSAExample(originalMessage); + + // Demonstrate RSA signing and verification + DemonstrateRSAExample(originalMessage); + + // Demonstrate RSA with formatters + DemonstrateRSAFormatterExample(originalMessage); + } + + private static void DemonstrateECDSAExample(string message) + { + Console.WriteLine("=== ECDSA Example ==="); + + // Create ECDSA instance with P-256 curve + var nistP256 = ECCurve.NamedCurves.nistP256; + using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); + + // Message to sign + var messageBytes = Encoding.UTF8.GetBytes(message); + + Console.WriteLine($"Original message: {message}"); + + // Sign the message + var signature = ecdsa.SignData(messageBytes, HashAlgorithmName.SHA256); + + Console.WriteLine($"Signature: {Convert.ToBase64String(signature)}"); + + // Verify the signature + var isValid = ecdsa.VerifyData(messageBytes, signature, HashAlgorithmName.SHA256); + Console.WriteLine($"Signature valid: {isValid}"); + + // Export public key for verification by others + var publicKey = ecdsa.ExportParameters(false); + Console.WriteLine($"Public key X: {Convert.ToBase64String(publicKey.Q.X)}"); + Console.WriteLine($"Public key Y: {Convert.ToBase64String(publicKey.Q.Y)}"); + + // Demonstrate verification with tampered data + var tamperedMessage = Encoding.UTF8.GetBytes("Hello, ECDSA Modified!"); + var isValidTampered = ecdsa.VerifyData(tamperedMessage, signature, HashAlgorithmName.SHA256); + Console.WriteLine($"Tampered signature valid: {isValidTampered}"); + + // Test with different instance + using var ecdsaNew = ECDsa.Create(); + byte[] newMessageBytes = Encoding.UTF8.GetBytes("Hello, ECDSA!"); + var newSignature = ecdsaNew.SignData(newMessageBytes, HashAlgorithmName.SHA256); + + // Verify the signature + var isNewValid = ecdsaNew.VerifyData(newMessageBytes, newSignature, HashAlgorithmName.SHA256); + Console.WriteLine($"New signature valid: {isNewValid}"); + + var parameters = ecdsaNew.ExportParameters(false); + + var ecdsaFromParams = ECDsa.Create(parameters); + var signatureFromParams = ecdsaFromParams.SignData(newMessageBytes, HashAlgorithmName.SHA256); + var isValidFromParams = ecdsaFromParams.VerifyData(newMessageBytes, signatureFromParams, HashAlgorithmName.SHA256); + Console.WriteLine($"Signature valid with parameters: {isValidFromParams}"); + } + + private static void DemonstrateRSAExample(string message) + { + Console.WriteLine("=== RSA Example ==="); + + using RSA rsa = RSA.Create(); + byte[] data = Encoding.UTF8.GetBytes(message); + byte[] sig = rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + bool isValid = rsa.VerifyData(data, sig, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + Console.WriteLine($"Signature valid: {isValid}"); + + // Create with parameters + RSAParameters parameters = rsa.ExportParameters(true); + using RSA rsaWithParams = RSA.Create(parameters); + byte[] sigWithParams = rsaWithParams.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + bool isValidWithParams = rsaWithParams.VerifyData(data, sigWithParams, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + Console.WriteLine($"Signature valid with parameters: {isValidWithParams}"); + + // Create with specific key size + using RSA rsaWithKeySize = RSA.Create(2048); + byte[] sigWithKeySize = rsaWithKeySize.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + bool isValidWithKeySize = rsaWithKeySize.VerifyData(data, sigWithKeySize, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + Console.WriteLine($"Signature valid with key size: {isValidWithKeySize}"); + } + + private static void DemonstrateRSAFormatterExample(string message) + { + Console.WriteLine("=== RSA Formatter Example ==="); + + using SHA256 alg = SHA256.Create(); + + byte[] data = Encoding.UTF8.GetBytes(message); + byte[] hash = alg.ComputeHash(data); + + RSAParameters sharedParameters; + byte[] signedHash; + + // Generate signature + using (RSA rsa = RSA.Create()) + { + sharedParameters = rsa.ExportParameters(false); + + RSAPKCS1SignatureFormatter rsaFormatter = new(rsa); + rsaFormatter.SetHashAlgorithm(nameof(SHA256)); + + signedHash = rsaFormatter.CreateSignature(hash); + } + + // Verify signature + using (RSA rsa = RSA.Create()) + { + rsa.ImportParameters(sharedParameters); + + RSAPKCS1SignatureDeformatter rsaDeformatter = new(rsa); + rsaDeformatter.SetHashAlgorithm(nameof(SHA256)); + + if (rsaDeformatter.VerifySignature(hash, signedHash)) + { + Console.WriteLine("The signature is valid."); + } + else + { + Console.WriteLine("The signature is not valid."); + } + } + } + } +} \ No newline at end of file diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/options b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/options new file mode 100644 index 000000000000..f4586e95ef0c --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected new file mode 100644 index 000000000000..45f00c027d3c --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected @@ -0,0 +1,15 @@ +| SignatureExample.cs:37:29:37:82 | SignOperation | SignatureExample.cs:37:44:37:55 | Message | +| SignatureExample.cs:42:27:42:93 | VerifyOperation | SignatureExample.cs:42:44:42:55 | Message | +| SignatureExample.cs:52:35:52:104 | VerifyOperation | SignatureExample.cs:52:52:52:66 | Message | +| SignatureExample.cs:58:32:58:91 | SignOperation | SignatureExample.cs:58:50:58:64 | Message | +| SignatureExample.cs:61:30:61:105 | VerifyOperation | SignatureExample.cs:61:50:61:64 | Message | +| SignatureExample.cs:67:39:67:105 | SignOperation | SignatureExample.cs:67:64:67:78 | Message | +| SignatureExample.cs:68:37:68:126 | VerifyOperation | SignatureExample.cs:68:64:68:78 | Message | +| SignatureExample.cs:78:26:78:96 | SignOperation | SignatureExample.cs:78:39:78:42 | Message | +| SignatureExample.cs:79:28:79:105 | VerifyOperation | SignatureExample.cs:79:43:79:46 | Message | +| SignatureExample.cs:85:36:85:116 | SignOperation | SignatureExample.cs:85:59:85:62 | Message | +| SignatureExample.cs:86:38:86:135 | VerifyOperation | SignatureExample.cs:86:63:86:66 | Message | +| SignatureExample.cs:91:37:91:118 | SignOperation | SignatureExample.cs:91:61:91:64 | Message | +| SignatureExample.cs:92:39:92:138 | VerifyOperation | SignatureExample.cs:92:65:92:68 | Message | +| SignatureExample.cs:116:30:116:63 | SignOperation | SignatureExample.cs:116:59:116:62 | Message | +| SignatureExample.cs:127:21:127:68 | VerifyOperation | SignatureExample.cs:127:52:127:55 | Message | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.ql new file mode 100644 index 000000000000..da75dbea1c88 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.ql @@ -0,0 +1,5 @@ +import csharp +import experimental.quantum.Language + +from Crypto::SignatureOperationNode n +select n, n.getAnInputArtifact() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected new file mode 100644 index 000000000000..122a9134daa6 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected @@ -0,0 +1,13 @@ +| SignatureExample.cs:37:29:37:82 | SignOperation | ECDSA | ECDsa | +| SignatureExample.cs:42:27:42:93 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:52:35:52:104 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:58:32:58:91 | SignOperation | ECDSA | ECDsa | +| SignatureExample.cs:61:30:61:105 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:67:39:67:105 | SignOperation | ECDSA | ECDsa | +| SignatureExample.cs:68:37:68:126 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:78:26:78:96 | SignOperation | UnknownSignature | RSA | +| SignatureExample.cs:79:28:79:105 | VerifyOperation | UnknownSignature | RSA | +| SignatureExample.cs:85:36:85:116 | SignOperation | UnknownSignature | RSA | +| SignatureExample.cs:86:38:86:135 | VerifyOperation | UnknownSignature | RSA | +| SignatureExample.cs:91:37:91:118 | SignOperation | UnknownSignature | RSA | +| SignatureExample.cs:92:39:92:138 | VerifyOperation | UnknownSignature | RSA | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.ql new file mode 100644 index 000000000000..ae496b830e2d --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::SignatureOperationNode signature, Crypto::AlgorithmNode algorithm +where algorithm = signature.getAKnownAlgorithm() +select signature, algorithm.getAlgorithmName(), algorithm.getRawAlgorithmName() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected new file mode 100644 index 000000000000..7a12d30e4dd0 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected @@ -0,0 +1,13 @@ +| SignatureExample.cs:37:29:37:82 | SignOperation | SignatureExample.cs:29:31:29:72 | Key | +| SignatureExample.cs:42:27:42:93 | VerifyOperation | SignatureExample.cs:29:31:29:72 | Key | +| SignatureExample.cs:52:35:52:104 | VerifyOperation | SignatureExample.cs:29:31:29:72 | Key | +| SignatureExample.cs:58:32:58:91 | SignOperation | SignatureExample.cs:56:34:56:47 | Key | +| SignatureExample.cs:61:30:61:105 | VerifyOperation | SignatureExample.cs:56:34:56:47 | Key | +| SignatureExample.cs:67:39:67:105 | SignOperation | SignatureExample.cs:66:48:66:57 | Key | +| SignatureExample.cs:68:37:68:126 | VerifyOperation | SignatureExample.cs:66:48:66:57 | Key | +| SignatureExample.cs:78:26:78:96 | SignOperation | SignatureExample.cs:76:29:76:40 | Key | +| SignatureExample.cs:79:28:79:105 | VerifyOperation | SignatureExample.cs:76:29:76:40 | Key | +| SignatureExample.cs:85:36:85:116 | SignOperation | SignatureExample.cs:84:50:84:59 | Key | +| SignatureExample.cs:86:38:86:135 | VerifyOperation | SignatureExample.cs:84:50:84:59 | Key | +| SignatureExample.cs:91:37:91:118 | SignOperation | SignatureExample.cs:90:40:90:55 | Key | +| SignatureExample.cs:92:39:92:138 | VerifyOperation | SignatureExample.cs:90:40:90:55 | Key | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.ql new file mode 100644 index 000000000000..995fe4147d08 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.ql @@ -0,0 +1,5 @@ +import csharp +import experimental.quantum.Language + +from Crypto::SignatureOperationNode n +select n, n.getAKey() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected new file mode 100644 index 000000000000..e26d011250fb --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected @@ -0,0 +1,8 @@ +| SignatureExample.cs:42:27:42:93 | VerifyOperation | SignatureExample.cs:42:44:42:55 | Message | SignatureExample.cs:42:58:42:66 | SignatureInput | +| SignatureExample.cs:52:35:52:104 | VerifyOperation | SignatureExample.cs:52:52:52:66 | Message | SignatureExample.cs:52:69:52:77 | SignatureInput | +| SignatureExample.cs:61:30:61:105 | VerifyOperation | SignatureExample.cs:61:50:61:64 | Message | SignatureExample.cs:61:67:61:78 | SignatureInput | +| SignatureExample.cs:68:37:68:126 | VerifyOperation | SignatureExample.cs:68:64:68:78 | Message | SignatureExample.cs:68:81:68:99 | SignatureInput | +| SignatureExample.cs:79:28:79:105 | VerifyOperation | SignatureExample.cs:79:43:79:46 | Message | SignatureExample.cs:79:49:79:51 | SignatureInput | +| SignatureExample.cs:86:38:86:135 | VerifyOperation | SignatureExample.cs:86:63:86:66 | Message | SignatureExample.cs:86:69:86:81 | SignatureInput | +| SignatureExample.cs:92:39:92:138 | VerifyOperation | SignatureExample.cs:92:65:92:68 | Message | SignatureExample.cs:92:71:92:84 | SignatureInput | +| SignatureExample.cs:127:21:127:68 | VerifyOperation | SignatureExample.cs:127:52:127:55 | Message | SignatureExample.cs:127:58:127:67 | SignatureInput | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.ql new file mode 100644 index 000000000000..a0a2cafebf2c --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.ql @@ -0,0 +1,5 @@ +import csharp +import experimental.quantum.Language + +from Crypto::SignatureOperationNode n +select n, n.getAnInputArtifact(), n.getASignatureArtifact() From 7c72a65021da4ba6d18954c6ad7ca8338a205205 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Wed, 25 Jun 2025 18:28:31 +0100 Subject: [PATCH 26/33] Add RSAPKCS1Signature formatters --- .../quantum/dotnet/Cryptography.qll | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 885d1b8c493d..4cee7ac7b46c 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -42,19 +42,15 @@ class CryptographyCreateCall extends MethodCall { } Expr getAlgorithmArg() { - this.hasNoArguments() and result = this - or - result = this.(ECDsaCreateCallWithParameters).getArgument(0) - or - result = this.(ECDsaCreateCallWithECCurve).getArgument(0) + if this.getArgument(0).getType().getName().matches("%Parameters") + then result = this.getArgument(0) + else result = this } Expr getKeyConsumer() { - this.hasNoArguments() and result = this - or - result = this.(ECDsaCreateCallWithParameters).getArgument(0) - or - result = this.(ECDsaCreateCallWithECCurve) + if this.getArgument(0).getType().getName().matches("%Parameters") + then result = this.getArgument(0) + else result = this } } @@ -215,10 +211,20 @@ private class RSAClass extends CryptographyType { RSAClass() { this.hasName("RSA") } } +private class RSAPKCS1SignatureFormatter extends CryptographyType { + RSAPKCS1SignatureFormatter() { this.hasName("RSAPKCS1SignatureFormatter") } +} + +private class RSAPKCS1SignatureDeformatter extends CryptographyType { + RSAPKCS1SignatureDeformatter() { this.hasName("RSAPKCS1SignatureDeformatter") } +} + private class SignerType extends Type { SignerType() { this instanceof ECDsaClass or - this instanceof RSAClass + this instanceof RSAClass or + this instanceof RSAPKCS1SignatureFormatter or + this instanceof RSAPKCS1SignatureDeformatter } } @@ -310,7 +316,7 @@ class RsaSignerQualifier extends SignerQualifier instanceof Expr { class SignerUse extends MethodCall { SignerUse() { - this.getTarget().getName().matches(["Verify%", "Sign%"]) and + this.getTarget().getName().matches(["Verify%", "Sign%", "CreateSignature"]) and this.getQualifier().getType() instanceof SignerType } @@ -340,19 +346,11 @@ class SignerUse extends MethodCall { result = this.getAnArgument() and result.getType() instanceof HashAlgorithmNameType } - predicate isSigner() { this.getTarget().getName().matches("Sign%") } + predicate isSigner() { this.getTarget().getName().matches(["Sign%", "CreateSignature"]) } predicate isVerifier() { this.getTarget().getName().matches("Verify%") } } -private class ECDsaSigner extends SignerUse { - ECDsaSigner() { this.getQualifier().getType() instanceof ECDsaClass } -} - -private class RSASigner extends SignerUse { - RSASigner() { this.getQualifier().getType() instanceof RSAClass } -} - /** * An AEAD class, such as `AesGcm`, `AesCcm`, or `ChaCha20Poly1305`. */ From 39581e2e980a3327f3b47a629be512ceca50e7df Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Thu, 26 Jun 2025 16:09:33 +0100 Subject: [PATCH 27/33] quantum-c#: add support for RSAPKC1Signature formatters --- .../quantum/dotnet/Cryptography.qll | 12 ++++-- .../quantum/dotnet/FlowAnalysis.qll | 39 ++++++++++++++++++- .../dotnet/signatures/SignatureExample.cs | 1 - .../signatures/sign_operations.expected | 30 +++++++------- .../sign_operations_algorithm.expected | 28 ++++++------- .../signatures/sign_operations_keys.expected | 28 ++++++------- .../sign_operations_signatures.expected | 16 ++++---- 7 files changed, 99 insertions(+), 55 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 4a6bec9d9339..54a4fe9ea8b9 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -62,8 +62,12 @@ class RsaType extends CryptographyType { RsaType() { this.hasName("RSA") } } +class RsaPkcs1Type extends CryptographyType { + RsaPkcs1Type() { this.getName().matches("RSAPKCS1Signature%") } +} + class EcdsaCreateCall extends CryptographyCreateCall { - EcdsaCreateCall() { this.getQualifier().getType().hasName("ECDsa") } + EcdsaCreateCall() { this.getQualifier().getType() instanceof EcdsaType } } // This class is used to model the `ECDsa.Create(ECParameters)` call @@ -305,9 +309,11 @@ class EcdsaSignerQualifier extends SignerQualifier instanceof Expr { } class RsaSignerQualifier extends SignerQualifier instanceof Expr { - RsaSignerQualifier() { super.getType() instanceof RsaType } + RsaSignerQualifier() { + super.getType() instanceof RsaType or super.getType() instanceof RsaPkcs1Type + } - override string getRawAlgorithmName() { result = "RSA" } + override string getRawAlgorithmName() { result = super.getType().getName() } override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::OtherSignatureAlgorithmType()) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 60fdb4ee378a..077721afad14 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -64,8 +64,6 @@ module HashAlgorithmNameToUseConfig implements DataFlow::ConfigSig { module HashAlgorithmNameToUse = DataFlow::Global; -module SigningCreateToUseFlow = CreationToUseFlow; - module HashCreateToUseFlow = CreationToUseFlow; module CryptoStreamFlow = CreationToUseFlow; @@ -254,3 +252,40 @@ class PropertyWriteFlowStep extends AdditionalFlowInputStep { override DataFlow::Node getOutput() { result.asExpr() = assignment.getLValue() } } + +module SigningCreateToUseFlow { + private module SigningCreateToUseFlow implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SigningCreateCall } + + predicate isSink(DataFlow::Node sink) { + sink.asExpr() = any(SignerUse use).(QualifiableExpr).getQualifier() + } + + /** + * An additional flow step across new object creations that use the original objects. + * + * Example: + * ``` + * RSA rsa = RSA.Create() + * RSAPKCS1SignatureFormatter rsaFormatter = new(rsa); + * rsaFormatter.SetHashAlgorithm(nameof(SHA256)); + * signedHash = rsaFormatter.CreateSignature(hash); + * ``` + */ + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(ObjectCreation create | + node2.asExpr() = create and node1.asExpr() = create.getAnArgument() + ) + } + } + + private module CreationToUseFlow = DataFlow::Global; + + SigningCreateCall getCreationFromUse( + SignerUse use, CreationToUseFlow::PathNode source, CreationToUseFlow::PathNode sink + ) { + source.getNode().asExpr() = result and + sink.getNode().asExpr() = use.(QualifiableExpr).getQualifier() and + CreationToUseFlow::flowPath(source, sink) + } +} diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs index 29b3a517a405..a9f1e91e8376 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs @@ -25,7 +25,6 @@ private static void DemonstrateECDSAExample(string message) Console.WriteLine("=== ECDSA Example ==="); // Create ECDSA instance with P-256 curve - var nistP256 = ECCurve.NamedCurves.nistP256; using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); // Message to sign diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected index 45f00c027d3c..7f1714137805 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected @@ -1,15 +1,15 @@ -| SignatureExample.cs:37:29:37:82 | SignOperation | SignatureExample.cs:37:44:37:55 | Message | -| SignatureExample.cs:42:27:42:93 | VerifyOperation | SignatureExample.cs:42:44:42:55 | Message | -| SignatureExample.cs:52:35:52:104 | VerifyOperation | SignatureExample.cs:52:52:52:66 | Message | -| SignatureExample.cs:58:32:58:91 | SignOperation | SignatureExample.cs:58:50:58:64 | Message | -| SignatureExample.cs:61:30:61:105 | VerifyOperation | SignatureExample.cs:61:50:61:64 | Message | -| SignatureExample.cs:67:39:67:105 | SignOperation | SignatureExample.cs:67:64:67:78 | Message | -| SignatureExample.cs:68:37:68:126 | VerifyOperation | SignatureExample.cs:68:64:68:78 | Message | -| SignatureExample.cs:78:26:78:96 | SignOperation | SignatureExample.cs:78:39:78:42 | Message | -| SignatureExample.cs:79:28:79:105 | VerifyOperation | SignatureExample.cs:79:43:79:46 | Message | -| SignatureExample.cs:85:36:85:116 | SignOperation | SignatureExample.cs:85:59:85:62 | Message | -| SignatureExample.cs:86:38:86:135 | VerifyOperation | SignatureExample.cs:86:63:86:66 | Message | -| SignatureExample.cs:91:37:91:118 | SignOperation | SignatureExample.cs:91:61:91:64 | Message | -| SignatureExample.cs:92:39:92:138 | VerifyOperation | SignatureExample.cs:92:65:92:68 | Message | -| SignatureExample.cs:116:30:116:63 | SignOperation | SignatureExample.cs:116:59:116:62 | Message | -| SignatureExample.cs:127:21:127:68 | VerifyOperation | SignatureExample.cs:127:52:127:55 | Message | +| SignatureExample.cs:36:29:36:82 | SignOperation | SignatureExample.cs:36:44:36:55 | Message | +| SignatureExample.cs:41:27:41:93 | VerifyOperation | SignatureExample.cs:41:44:41:55 | Message | +| SignatureExample.cs:51:35:51:104 | VerifyOperation | SignatureExample.cs:51:52:51:66 | Message | +| SignatureExample.cs:57:32:57:91 | SignOperation | SignatureExample.cs:57:50:57:64 | Message | +| SignatureExample.cs:60:30:60:105 | VerifyOperation | SignatureExample.cs:60:50:60:64 | Message | +| SignatureExample.cs:66:39:66:105 | SignOperation | SignatureExample.cs:66:64:66:78 | Message | +| SignatureExample.cs:67:37:67:126 | VerifyOperation | SignatureExample.cs:67:64:67:78 | Message | +| SignatureExample.cs:77:26:77:96 | SignOperation | SignatureExample.cs:77:39:77:42 | Message | +| SignatureExample.cs:78:28:78:105 | VerifyOperation | SignatureExample.cs:78:43:78:46 | Message | +| SignatureExample.cs:84:36:84:116 | SignOperation | SignatureExample.cs:84:59:84:62 | Message | +| SignatureExample.cs:85:38:85:135 | VerifyOperation | SignatureExample.cs:85:63:85:66 | Message | +| SignatureExample.cs:90:37:90:118 | SignOperation | SignatureExample.cs:90:61:90:64 | Message | +| SignatureExample.cs:91:39:91:138 | VerifyOperation | SignatureExample.cs:91:65:91:68 | Message | +| SignatureExample.cs:115:30:115:63 | SignOperation | SignatureExample.cs:115:59:115:62 | Message | +| SignatureExample.cs:126:21:126:68 | VerifyOperation | SignatureExample.cs:126:52:126:55 | Message | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected index 122a9134daa6..0df68b3a2297 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected @@ -1,13 +1,15 @@ -| SignatureExample.cs:37:29:37:82 | SignOperation | ECDSA | ECDsa | -| SignatureExample.cs:42:27:42:93 | VerifyOperation | ECDSA | ECDsa | -| SignatureExample.cs:52:35:52:104 | VerifyOperation | ECDSA | ECDsa | -| SignatureExample.cs:58:32:58:91 | SignOperation | ECDSA | ECDsa | -| SignatureExample.cs:61:30:61:105 | VerifyOperation | ECDSA | ECDsa | -| SignatureExample.cs:67:39:67:105 | SignOperation | ECDSA | ECDsa | -| SignatureExample.cs:68:37:68:126 | VerifyOperation | ECDSA | ECDsa | -| SignatureExample.cs:78:26:78:96 | SignOperation | UnknownSignature | RSA | -| SignatureExample.cs:79:28:79:105 | VerifyOperation | UnknownSignature | RSA | -| SignatureExample.cs:85:36:85:116 | SignOperation | UnknownSignature | RSA | -| SignatureExample.cs:86:38:86:135 | VerifyOperation | UnknownSignature | RSA | -| SignatureExample.cs:91:37:91:118 | SignOperation | UnknownSignature | RSA | -| SignatureExample.cs:92:39:92:138 | VerifyOperation | UnknownSignature | RSA | +| SignatureExample.cs:36:29:36:82 | SignOperation | ECDSA | ECDsa | +| SignatureExample.cs:41:27:41:93 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:51:35:51:104 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:57:32:57:91 | SignOperation | ECDSA | ECDsa | +| SignatureExample.cs:60:30:60:105 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:66:39:66:105 | SignOperation | ECDSA | ECDsa | +| SignatureExample.cs:67:37:67:126 | VerifyOperation | ECDSA | ECDsa | +| SignatureExample.cs:77:26:77:96 | SignOperation | UnknownSignature | RSA | +| SignatureExample.cs:78:28:78:105 | VerifyOperation | UnknownSignature | RSA | +| SignatureExample.cs:84:36:84:116 | SignOperation | UnknownSignature | RSA | +| SignatureExample.cs:85:38:85:135 | VerifyOperation | UnknownSignature | RSA | +| SignatureExample.cs:90:37:90:118 | SignOperation | UnknownSignature | RSA | +| SignatureExample.cs:91:39:91:138 | VerifyOperation | UnknownSignature | RSA | +| SignatureExample.cs:115:30:115:63 | SignOperation | UnknownSignature | RSAPKCS1SignatureFormatter | +| SignatureExample.cs:126:21:126:68 | VerifyOperation | UnknownSignature | RSAPKCS1SignatureDeformatter | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected index 7a12d30e4dd0..4658309c9c41 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected @@ -1,13 +1,15 @@ -| SignatureExample.cs:37:29:37:82 | SignOperation | SignatureExample.cs:29:31:29:72 | Key | -| SignatureExample.cs:42:27:42:93 | VerifyOperation | SignatureExample.cs:29:31:29:72 | Key | -| SignatureExample.cs:52:35:52:104 | VerifyOperation | SignatureExample.cs:29:31:29:72 | Key | -| SignatureExample.cs:58:32:58:91 | SignOperation | SignatureExample.cs:56:34:56:47 | Key | -| SignatureExample.cs:61:30:61:105 | VerifyOperation | SignatureExample.cs:56:34:56:47 | Key | -| SignatureExample.cs:67:39:67:105 | SignOperation | SignatureExample.cs:66:48:66:57 | Key | -| SignatureExample.cs:68:37:68:126 | VerifyOperation | SignatureExample.cs:66:48:66:57 | Key | -| SignatureExample.cs:78:26:78:96 | SignOperation | SignatureExample.cs:76:29:76:40 | Key | -| SignatureExample.cs:79:28:79:105 | VerifyOperation | SignatureExample.cs:76:29:76:40 | Key | -| SignatureExample.cs:85:36:85:116 | SignOperation | SignatureExample.cs:84:50:84:59 | Key | -| SignatureExample.cs:86:38:86:135 | VerifyOperation | SignatureExample.cs:84:50:84:59 | Key | -| SignatureExample.cs:91:37:91:118 | SignOperation | SignatureExample.cs:90:40:90:55 | Key | -| SignatureExample.cs:92:39:92:138 | VerifyOperation | SignatureExample.cs:90:40:90:55 | Key | +| SignatureExample.cs:36:29:36:82 | SignOperation | SignatureExample.cs:28:31:28:72 | Key | +| SignatureExample.cs:41:27:41:93 | VerifyOperation | SignatureExample.cs:28:31:28:72 | Key | +| SignatureExample.cs:51:35:51:104 | VerifyOperation | SignatureExample.cs:28:31:28:72 | Key | +| SignatureExample.cs:57:32:57:91 | SignOperation | SignatureExample.cs:55:34:55:47 | Key | +| SignatureExample.cs:60:30:60:105 | VerifyOperation | SignatureExample.cs:55:34:55:47 | Key | +| SignatureExample.cs:66:39:66:105 | SignOperation | SignatureExample.cs:65:48:65:57 | Key | +| SignatureExample.cs:67:37:67:126 | VerifyOperation | SignatureExample.cs:65:48:65:57 | Key | +| SignatureExample.cs:77:26:77:96 | SignOperation | SignatureExample.cs:75:29:75:40 | Key | +| SignatureExample.cs:78:28:78:105 | VerifyOperation | SignatureExample.cs:75:29:75:40 | Key | +| SignatureExample.cs:84:36:84:116 | SignOperation | SignatureExample.cs:83:50:83:59 | Key | +| SignatureExample.cs:85:38:85:135 | VerifyOperation | SignatureExample.cs:83:50:83:59 | Key | +| SignatureExample.cs:90:37:90:118 | SignOperation | SignatureExample.cs:89:40:89:55 | Key | +| SignatureExample.cs:91:39:91:138 | VerifyOperation | SignatureExample.cs:89:40:89:55 | Key | +| SignatureExample.cs:115:30:115:63 | SignOperation | SignatureExample.cs:108:30:108:41 | Key | +| SignatureExample.cs:126:21:126:68 | VerifyOperation | SignatureExample.cs:119:30:119:41 | Key | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected index e26d011250fb..d95bfcd87baf 100644 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected @@ -1,8 +1,8 @@ -| SignatureExample.cs:42:27:42:93 | VerifyOperation | SignatureExample.cs:42:44:42:55 | Message | SignatureExample.cs:42:58:42:66 | SignatureInput | -| SignatureExample.cs:52:35:52:104 | VerifyOperation | SignatureExample.cs:52:52:52:66 | Message | SignatureExample.cs:52:69:52:77 | SignatureInput | -| SignatureExample.cs:61:30:61:105 | VerifyOperation | SignatureExample.cs:61:50:61:64 | Message | SignatureExample.cs:61:67:61:78 | SignatureInput | -| SignatureExample.cs:68:37:68:126 | VerifyOperation | SignatureExample.cs:68:64:68:78 | Message | SignatureExample.cs:68:81:68:99 | SignatureInput | -| SignatureExample.cs:79:28:79:105 | VerifyOperation | SignatureExample.cs:79:43:79:46 | Message | SignatureExample.cs:79:49:79:51 | SignatureInput | -| SignatureExample.cs:86:38:86:135 | VerifyOperation | SignatureExample.cs:86:63:86:66 | Message | SignatureExample.cs:86:69:86:81 | SignatureInput | -| SignatureExample.cs:92:39:92:138 | VerifyOperation | SignatureExample.cs:92:65:92:68 | Message | SignatureExample.cs:92:71:92:84 | SignatureInput | -| SignatureExample.cs:127:21:127:68 | VerifyOperation | SignatureExample.cs:127:52:127:55 | Message | SignatureExample.cs:127:58:127:67 | SignatureInput | +| SignatureExample.cs:41:27:41:93 | VerifyOperation | SignatureExample.cs:41:44:41:55 | Message | SignatureExample.cs:41:58:41:66 | SignatureInput | +| SignatureExample.cs:51:35:51:104 | VerifyOperation | SignatureExample.cs:51:52:51:66 | Message | SignatureExample.cs:51:69:51:77 | SignatureInput | +| SignatureExample.cs:60:30:60:105 | VerifyOperation | SignatureExample.cs:60:50:60:64 | Message | SignatureExample.cs:60:67:60:78 | SignatureInput | +| SignatureExample.cs:67:37:67:126 | VerifyOperation | SignatureExample.cs:67:64:67:78 | Message | SignatureExample.cs:67:81:67:99 | SignatureInput | +| SignatureExample.cs:78:28:78:105 | VerifyOperation | SignatureExample.cs:78:43:78:46 | Message | SignatureExample.cs:78:49:78:51 | SignatureInput | +| SignatureExample.cs:85:38:85:135 | VerifyOperation | SignatureExample.cs:85:63:85:66 | Message | SignatureExample.cs:85:69:85:81 | SignatureInput | +| SignatureExample.cs:91:39:91:138 | VerifyOperation | SignatureExample.cs:91:65:91:68 | Message | SignatureExample.cs:91:71:91:84 | SignatureInput | +| SignatureExample.cs:126:21:126:68 | VerifyOperation | SignatureExample.cs:126:52:126:55 | Message | SignatureExample.cs:126:58:126:67 | SignatureInput | From 6763f18f875a29c8c3f4a22da4e445aaac7f0dc7 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Thu, 26 Jun 2025 16:37:28 +0100 Subject: [PATCH 28/33] quantum-c#: Add hash tests --- .../quantum/dotnet/hashes/HashExample.cs | 69 +++++++++++++++++++ .../dotnet/hashes/hash_operations.expected | 7 ++ .../quantum/dotnet/hashes/hash_operations.ql | 6 ++ .../quantum/dotnet/hashes/options | 2 + 4 files changed, 84 insertions(+) create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/HashExample.cs create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/options diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/HashExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/HashExample.cs new file mode 100644 index 000000000000..8edd033abbb5 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/HashExample.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace QuantumExamples.Cryptography +{ + public class HashExample + { + public static void RunExample() + { + const string originalMessage = "This is a message to hash!"; + + // Demonstrate various hash algorithms + DemonstrateBasicHashing(originalMessage); + DemonstrateStreamHashing(originalMessage); + DemonstrateOneShotHashing(originalMessage); + } + + private static void DemonstrateBasicHashing(string message) + { + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + + using (SHA256 sha256 = SHA256.Create()) + { + byte[] hash = sha256.ComputeHash(messageBytes); + Console.WriteLine("SHA256 hash: {0}", Convert.ToBase64String(hash)); + } + + using (SHA1 sha1 = SHA1.Create()) + { + byte[] hash = sha1.ComputeHash(messageBytes); + Console.WriteLine("SHA1 hash: {0}", Convert.ToBase64String(hash)); + } + + using (MD5 md5 = MD5.Create()) + { + byte[] hash = md5.ComputeHash(messageBytes); + Console.WriteLine("MD5 hash: {0}", Convert.ToBase64String(hash)); + } + } + + private static void DemonstrateStreamHashing(string message) + { + using SHA256 sha256 = SHA256.Create(); + using MemoryStream stream = new MemoryStream(); + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + stream.Write(messageBytes, 0, messageBytes.Length); + stream.Position = 0; + + byte[] hash = sha256.ComputeHash(stream); + Console.WriteLine("Stream-based SHA256 hash: {0}", Convert.ToBase64String(hash)); + } + + private static void DemonstrateOneShotHashing(string message) + { + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + + byte[] sha256Hash = SHA256.HashData(messageBytes); + Console.WriteLine("One-shot SHA256 hash: {0}", Convert.ToBase64String(sha256Hash)); + + byte[] sha1Hash = SHA1.HashData(messageBytes); + Console.WriteLine("One-shot SHA1 hash: {0}", Convert.ToBase64String(sha1Hash)); + + byte[] md5Hash = MD5.HashData(messageBytes); + Console.WriteLine("One-shot MD5 hash: {0}", Convert.ToBase64String(md5Hash)); + } + } +} \ No newline at end of file diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.expected new file mode 100644 index 000000000000..7fdd4b1da01f --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.expected @@ -0,0 +1,7 @@ +| HashExample.cs:26:31:26:62 | HashOperation | HashExample.cs:26:31:26:62 | Digest | HashExample.cs:26:50:26:61 | Message | SHA2 | SHA256 | +| HashExample.cs:32:31:32:60 | HashOperation | HashExample.cs:32:31:32:60 | Digest | HashExample.cs:32:48:32:59 | Message | SHA1 | SHA1 | +| HashExample.cs:38:31:38:59 | HashOperation | HashExample.cs:38:31:38:59 | Digest | HashExample.cs:38:47:38:58 | Message | MD5 | MD5 | +| HashExample.cs:51:27:51:52 | HashOperation | HashExample.cs:51:27:51:52 | Digest | HashExample.cs:48:26:48:37 | Message | SHA2 | SHA256 | +| HashExample.cs:59:33:59:61 | HashOperation | HashExample.cs:59:33:59:61 | Digest | HashExample.cs:59:49:59:60 | Message | SHA2 | SHA256 | +| HashExample.cs:62:31:62:57 | HashOperation | HashExample.cs:62:31:62:57 | Digest | HashExample.cs:62:45:62:56 | Message | SHA1 | SHA1 | +| HashExample.cs:65:30:65:55 | HashOperation | HashExample.cs:65:30:65:55 | Digest | HashExample.cs:65:43:65:54 | Message | MD5 | MD5 | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.ql new file mode 100644 index 000000000000..0aa1a1c31c49 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/hash_operations.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::HashOperationNode n, Crypto::AlgorithmNode algo +where algo = n.getAKnownAlgorithm() +select n, n.getDigest(), n.getInputArtifact(), algo.getAlgorithmName(), algo.getRawAlgorithmName() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/options b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/options new file mode 100644 index 000000000000..f4586e95ef0c --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/hashes/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj From b41743614b4f52b3eb91f3dfd3d5150ac6d024d4 Mon Sep 17 00:00:00 2001 From: Filipe Casal Date: Fri, 27 Jun 2025 16:25:45 +0100 Subject: [PATCH 29/33] quantum-c#: Add support for HMACs --- .../quantum/dotnet/Cryptography.qll | 99 +++++++++++++- .../quantum/dotnet/FlowAnalysis.qll | 2 + .../quantum/dotnet/OperationInstances.qll | 27 +++- .../quantum/dotnet/macs/HMACExample.cs | 121 ++++++++++++++++++ .../dotnet/macs/mac_hashalgorithms.expected | 10 ++ .../quantum/dotnet/macs/mac_hashalgorithms.ql | 6 + .../dotnet/macs/mac_operations.expected | 10 ++ .../quantum/dotnet/macs/mac_operations.ql | 5 + .../library-tests/quantum/dotnet/macs/options | 2 + 9 files changed, 278 insertions(+), 4 deletions(-) create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/HMACExample.cs create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/options diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index 54a4fe9ea8b9..f950d01ff8be 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -177,7 +177,8 @@ private predicate hashAlgorithmToFamily( hashName = "SHA3_384" and hashFamily = Crypto::SHA3() and digestLength = 384 or hashName = "SHA3_512" and hashFamily = Crypto::SHA3() and digestLength = 512 - // TODO: is there an idiomatic way to add a default type here? + or + hashName = "RIPEMD160" and hashFamily = Crypto::RIPEMD160() and digestLength = 160 } class HashAlgorithmNameUser extends MethodCall { @@ -630,3 +631,99 @@ class CryptoStreamUse extends MethodCall { result = this.getArgument(0) } } + +class MacAlgorithmType extends CryptographyType { + MacAlgorithmType() { this.getName().matches(["HMAC%", "KeyedHashAlgorithm"]) } +} + +class HMACCreation extends ObjectCreation { + HMACCreation() { this.getObjectType() instanceof MacAlgorithmType } + + Expr getKeyArg() { if this.hasNoArguments() then result = this else result = this.getArgument(0) } + + string getRawAlgorithmName() { result = this.getObjectType().getName() } +} + +class MacUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall { + MacUse() { + this.getQualifier().getType() instanceof MacAlgorithmType and + this.getTarget().hasName(["ComputeHash", "ComputeHashAsync", "HashData", "HashDataAsync"]) + } + + predicate isIntermediate() { none() } + + Expr getOutput() { + not this.isIntermediate() and + // some functions receive the destination as a parameter + if + super.getTarget().getName() = ["HashData"] and super.getNumberOfArguments() = 3 + or + super.getTarget().getName() = ["HashDataAsync"] and super.getNumberOfArguments() = 4 + then result = super.getArgument(2) + else result = this + } + + private Expr getDataArg() { + // ComputeHash and ComputeHashAsync take the data as the first argument. + if super.getTarget().getName().matches("ComputeHash%") + then result = super.getArgument(0) + else result = super.getArgument(1) + } + + Expr getInputArg() { + result = this.getDataArg() and result.getType() instanceof ByteArrayOrReadOnlyByteSpanType + } + + Expr getStreamArg() { result = this.getDataArg() and result.getType() instanceof Stream } + + Expr getKeyArg() { + if not super.getTarget().getName().matches("ComputeHash%") + then result = super.getArgument(0) + else result = HMACFlow::getCreationFromUse(this, _, _).getKeyArg() + } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = super.getQualifier() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } + + Expr getQualifier() { result = super.getQualifier() } +} + +class HMACAlgorithmInstance extends Crypto::MACAlgorithmInstance instanceof Expr { + HMACAlgorithmInstance() { this = any(MacUse c).getQualifier() } + + override Crypto::TMACType getMACType() { result instanceof Crypto::THMAC } + + override string getRawMACAlgorithmName() { result = super.getType().getName() } +} + +class HMACAlgorithmQualifier extends Crypto::HMACAlgorithmInstance, Crypto::AlgorithmValueConsumer, + HMACAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof Expr +{ + override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() { result = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } + + override Crypto::THashType getHashFamily() { + result = getHashFamily(this.getRawHashAlgorithmName()) + } + + override string getRawHashAlgorithmName() { + if super.getType().hasName("KeyedHashAlgorithm") + then result = this.getOriginalRawHashAlgorithmName() + else result = super.getType().getName().replaceAll("HMAC", "") + } + + override int getFixedDigestLength() { + hashAlgorithmToFamily(this.getRawHashAlgorithmName(), _, result) + } + + private string getOriginalRawHashAlgorithmName() { + exists(MacUse use | + use.getQualifier() = this and + result = HMACFlow::getCreationFromUse(use, _, _).getRawAlgorithmName().replaceAll("HMAC", "") + ) + } +} diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 077721afad14..78a274584f7b 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -70,6 +70,8 @@ module CryptoStreamFlow = CreationToUseFlow; +module HMACFlow = CreationToUseFlow; + module SymmetricAlgorithmFlow = CreationToUseFlow; diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index f93671f275f5..75c88ae68756 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -99,7 +99,9 @@ class SymmetricAlgorithmOperationInstance extends Crypto::KeyOperationInstance i */ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanceof CryptoStreamCreation { - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = this.getCryptoTransform() } + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result = this.getCryptoTransform() + } override Crypto::KeyOperationSubtype getKeyOperationSubtype() { if this.getCryptoTransform().isEncryptor() @@ -117,7 +119,8 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc if exists(this.getCryptoTransform().getKeyArg()) then result.asExpr() = this.getCryptoTransform().getKeyArg() else ( - result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and + result.asExpr() = + SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and result.asExpr().(SymmetricAlgorithmUse).isKeyConsumer() ) } @@ -129,7 +132,8 @@ class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanc if exists(this.getCryptoTransform().getIvArg()) then result.asExpr() = this.getCryptoTransform().getIvArg() else ( - result.asExpr() = SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and + result.asExpr() = + SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and result.asExpr().(SymmetricAlgorithmUse).isIvConsumer() ) } @@ -205,3 +209,20 @@ class AeadOperationInstance extends Crypto::KeyOperationInstance instanceof Aead result.asExpr() = super.getOutputArg() } } + +class HMACOperationInstance extends Crypto::MACOperationInstance instanceof MacUse { + HMACOperationInstance() { not super.isIntermediate() } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result = super.getQualifier() + } + + override Crypto::ConsumerInputDataFlowNode getMessageConsumer() { + result.asExpr() = super.getInputArg() or + result.asExpr() = StreamFlow::getEarlierUse(super.getStreamArg(), _, _).getInputArg() + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + result.asExpr() = super.getKeyArg() + } +} diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/HMACExample.cs b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/HMACExample.cs new file mode 100644 index 000000000000..cecc0482542e --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/HMACExample.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace QuantumExamples.Cryptography +{ + public class HMACExample + { + public static void RunExample() + { + const string originalMessage = "This is a message to authenticate!"; + + // Demonstrate various MAC approaches + DemonstrateHMACMethods(originalMessage); + DemonstrateKeyedHashAlgorithm(originalMessage); + DemonstrateOneShotMAC(originalMessage); + DemonstrateStreamBasedMAC(originalMessage); + } + + private static void DemonstrateHMACMethods(string message) + { + Console.WriteLine("=== HMAC Methods ==="); + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + byte[] key = GenerateKey(32); // 256-bit key + + Console.WriteLine($"Original message: {message}"); + Console.WriteLine($"Key: {Convert.ToBase64String(key)}"); + + // HMAC-SHA256 using HMACSHA256 class + using (HMACSHA256 hmacSha256 = new HMACSHA256(key)) + { + byte[] hash = hmacSha256.ComputeHash(messageBytes); + Console.WriteLine($"HMAC-SHA256: {Convert.ToBase64String(hash)}"); + } + + // HMAC-SHA1 using HMACSHA1 class + using (HMACSHA1 hmacSha1 = new HMACSHA1(key)) + { + byte[] hash = hmacSha1.ComputeHash(messageBytes); + Console.WriteLine($"HMAC-SHA1: {Convert.ToBase64String(hash)}"); + } + + // HMAC-SHA384 using HMACSHA384 class + using (HMACSHA384 hmacSha384 = new HMACSHA384(key)) + { + byte[] hash = hmacSha384.ComputeHash(messageBytes); + Console.WriteLine($"HMAC-SHA384: {Convert.ToBase64String(hash)}"); + } + + // HMAC-SHA512 using HMACSHA512 class + using (HMACSHA512 hmacSha512 = new HMACSHA512(key)) + { + byte[] hash = hmacSha512.ComputeHash(messageBytes); + Console.WriteLine($"HMAC-SHA512: {Convert.ToBase64String(hash)}"); + } + } + + private static void DemonstrateKeyedHashAlgorithm(string message) + { + Console.WriteLine("\n=== KeyedHashAlgorithm Base Class ==="); + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + byte[] key = GenerateKey(32); + + // Using KeyedHashAlgorithm base class reference + using (KeyedHashAlgorithm keyedHash = new HMACSHA256(key)) + { + byte[] hash = keyedHash.ComputeHash(messageBytes); + Console.WriteLine($"KeyedHashAlgorithm (HMAC-SHA256): {Convert.ToBase64String(hash)}"); + Console.WriteLine($"Algorithm name: {keyedHash.GetType().Name}"); + Console.WriteLine($"Hash size: {keyedHash.HashSize} bits"); + } + } + + private static void DemonstrateOneShotMAC(string message) + { + Console.WriteLine("\n=== One-Shot MAC Methods ==="); + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + byte[] key = GenerateKey(32); + + byte[] hmacSha256OneShot = HMACSHA256.HashData(key, messageBytes); + Console.WriteLine($"One-shot HMAC-SHA256: {Convert.ToBase64String(hmacSha256OneShot)}"); + + byte[] hmacSha1OneShot = HMACSHA1.HashData(key, messageBytes); + Console.WriteLine($"One-shot HMAC-SHA1: {Convert.ToBase64String(hmacSha1OneShot)}"); + + byte[] hmacSha384OneShot = HMACSHA384.HashData(key, messageBytes); + Console.WriteLine($"One-shot HMAC-SHA384: {Convert.ToBase64String(hmacSha384OneShot)}"); + + byte[] hmacSha512OneShot = HMACSHA512.HashData(key, messageBytes); + Console.WriteLine($"One-shot HMAC-SHA512: {Convert.ToBase64String(hmacSha512OneShot)}"); + } + + private static void DemonstrateStreamBasedMAC(string message) + { + Console.WriteLine("\n=== Stream-Based MAC ==="); + byte[] key = GenerateKey(32); + + using (HMACSHA256 hmac = new HMACSHA256(key)) + using (MemoryStream stream = new MemoryStream()) + { + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + stream.Write(messageBytes, 0, messageBytes.Length); + stream.Position = 0; + + byte[] hash = hmac.ComputeHash(stream); + Console.WriteLine($"Stream-based HMAC-SHA256: {Convert.ToBase64String(hash)}"); + } + } + + private static byte[] GenerateKey(int sizeInBytes) + { + byte[] key = new byte[sizeInBytes]; + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(key); + } + return key; + } + } +} \ No newline at end of file diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.expected new file mode 100644 index 000000000000..a54d5393f5cb --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.expected @@ -0,0 +1,10 @@ +| HMACExample.cs:33:31:33:66 | MACOperation | HMACSHA256 | HMAC | +| HMACExample.cs:40:31:40:64 | MACOperation | HMACSHA1 | HMAC | +| HMACExample.cs:47:31:47:66 | MACOperation | HMACSHA384 | HMAC | +| HMACExample.cs:54:31:54:66 | MACOperation | HMACSHA512 | HMAC | +| HMACExample.cs:68:31:68:65 | MACOperation | KeyedHashAlgorithm | HMAC | +| HMACExample.cs:81:40:81:77 | MACOperation | HMACSHA256 | HMAC | +| HMACExample.cs:84:38:84:73 | MACOperation | HMACSHA1 | HMAC | +| HMACExample.cs:87:40:87:77 | MACOperation | HMACSHA384 | HMAC | +| HMACExample.cs:90:40:90:77 | MACOperation | HMACSHA512 | HMAC | +| HMACExample.cs:106:31:106:54 | MACOperation | HMACSHA256 | HMAC | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.ql new file mode 100644 index 000000000000..6a94a3deb029 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_hashalgorithms.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::MACOperationNode n, Crypto::AlgorithmNode algo +where n.getAKnownAlgorithm() = algo +select n, algo.getRawAlgorithmName(), algo.getAlgorithmName() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.expected new file mode 100644 index 000000000000..b3ef87fef1db --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.expected @@ -0,0 +1,10 @@ +| HMACExample.cs:33:31:33:66 | MACOperation | HMACExample.cs:31:59:31:61 | Key | HMACExample.cs:33:54:33:65 | Message | +| HMACExample.cs:40:31:40:64 | MACOperation | HMACExample.cs:38:53:38:55 | Key | HMACExample.cs:40:52:40:63 | Message | +| HMACExample.cs:47:31:47:66 | MACOperation | HMACExample.cs:45:59:45:61 | Key | HMACExample.cs:47:54:47:65 | Message | +| HMACExample.cs:54:31:54:66 | MACOperation | HMACExample.cs:52:59:52:61 | Key | HMACExample.cs:54:54:54:65 | Message | +| HMACExample.cs:68:31:68:65 | MACOperation | HMACExample.cs:66:66:66:68 | Key | HMACExample.cs:68:53:68:64 | Message | +| HMACExample.cs:81:40:81:77 | MACOperation | HMACExample.cs:81:60:81:62 | Key | HMACExample.cs:81:65:81:76 | Message | +| HMACExample.cs:84:38:84:73 | MACOperation | HMACExample.cs:84:56:84:58 | Key | HMACExample.cs:84:61:84:72 | Message | +| HMACExample.cs:87:40:87:77 | MACOperation | HMACExample.cs:87:60:87:62 | Key | HMACExample.cs:87:65:87:76 | Message | +| HMACExample.cs:90:40:90:77 | MACOperation | HMACExample.cs:90:60:90:62 | Key | HMACExample.cs:90:65:90:76 | Message | +| HMACExample.cs:106:31:106:54 | MACOperation | HMACExample.cs:99:53:99:55 | Key | HMACExample.cs:103:30:103:41 | Message | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.ql new file mode 100644 index 000000000000..a72d575230a9 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/mac_operations.ql @@ -0,0 +1,5 @@ +import csharp +import experimental.quantum.Language + +from Crypto::MACOperationNode n +select n, n.getAKey(), n.getAMessage() \ No newline at end of file diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/options b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/options new file mode 100644 index 000000000000..f4586e95ef0c --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/macs/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj From e0193f8a2f4e6f334392d2a124ff601bb559ab58 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Fri, 4 Jul 2025 11:08:29 +0200 Subject: [PATCH 30/33] Fixed spelling --- csharp/ql/lib/experimental/quantum/Language.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll index 031a31c7b8f9..29f7fba7ccaf 100644 --- a/csharp/ql/lib/experimental/quantum/Language.qll +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -145,8 +145,8 @@ class InsecureRandomnessSource extends RandomnessSource { } /** - * An instance of random number generation, modelled as the expression - * tied to an output node (i.e., the RNG output) + * An instance of random number generation, modeled as the expression tied to an + * output node (i.e., the RNG output) */ abstract class RandomnessInstance extends Crypto::RandomNumberGenerationInstance { override DataFlow::Node getOutputNode() { result.asExpr() = this } From 0fc2427bab4099358c2c61dbd4d10e20badcac04 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Fri, 4 Jul 2025 13:19:54 +0200 Subject: [PATCH 31/33] Fixed missing symmetric algorithm type --- .../lib/experimental/quantum/dotnet/AlgorithmInstances.qll | 4 +++- csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll | 7 +------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 4c5735d09528..0d42209beedf 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -213,8 +213,10 @@ class AeadAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance, override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } } +bindingset[algorithmName] private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) { - algorithmName = "Aes%" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) + algorithmName.matches("Aes%") and + result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) or algorithmName = "DES" and result = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::DES()) or diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index f950d01ff8be..a2385febbc70 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -467,18 +467,13 @@ class SymmetricAlgorithmUse extends QualifiableExpr { this instanceof PropertyWrite and this.getQualifiedDeclaration().getName() = "Mode" } - predicate isCreationCall() { - // TODO: Matching using `hasName` does not work here for some reason. - this.getQualifiedDeclaration().getName().matches("Create%") - } + predicate isCreationCall() { this.getQualifiedDeclaration().getName().matches("Create%") } predicate isEncryptionCall() { - // TODO: Matching using `hasName` does not work here for some reason. this.getQualifiedDeclaration().getName().matches(["Encrypt%", "TryEncrypt%"]) } predicate isDecryptionCall() { - // TODO: Matching using `hasName` does not work here for some reason. this.getQualifiedDeclaration().getName().matches(["Decrypt%", "TryDecrypt%"]) } From a4523506fafa6aa1ce2c01239fc6372a06877d69 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Fri, 4 Jul 2025 13:25:55 +0200 Subject: [PATCH 32/33] Added CBOM graph-based unit tests for .NET --- .../ciphers/cipher_input_sources.expected | 3 - .../dotnet/ciphers/cipher_input_sources.ql | 6 -- .../ciphers/cipher_key_sources.expected | 6 -- .../dotnet/ciphers/cipher_key_sources.ql | 6 -- .../ciphers/cipher_nonce_sources.expected | 6 -- .../dotnet/ciphers/cipher_nonce_sources.ql | 6 -- .../dotnet/ciphers/cipher_operations.expected | 6 -- .../dotnet/ciphers/cipher_operations.ql | 6 -- .../dotnet/ciphers/node_edges.expected | 60 +++++++++++++++++++ .../quantum/dotnet/ciphers/node_edges.ql | 5 ++ .../dotnet/ciphers/node_properties.expected | 44 ++++++++++++++ .../quantum/dotnet/ciphers/node_properties.ql | 6 ++ .../quantum/dotnet/ciphers/nodes.expected | 52 ++++++++++++++++ .../quantum/dotnet/ciphers/nodes.ql | 5 ++ 14 files changed, 172 insertions(+), 45 deletions(-) delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected delete mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.ql create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.expected create mode 100644 csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.ql diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected deleted file mode 100644 index 9f8f26c388a3..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.expected +++ /dev/null @@ -1,3 +0,0 @@ -| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:61:60:61:69 | Message | AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | -| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | -| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:36:42:45 | Message | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql deleted file mode 100644 index 5ce237d6af22..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_input_sources.ql +++ /dev/null @@ -1,6 +0,0 @@ -import csharp -import experimental.quantum.Language - -from Crypto::CipherOperationNode n, Crypto::MessageArtifactNode m -where n.getAnInputArtifact() = m -select n, m, m.getSourceNode() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected deleted file mode 100644 index bc68e7a2d1ea..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.expected +++ /dev/null @@ -1,6 +0,0 @@ -| AesCbcExample.cs:38:28:38:80 | EncryptOperation | AesCbcExample.cs:36:21:36:27 | Key | AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | -| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:60:21:60:27 | Key | AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | -| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | -| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | -| AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:26:41:26:43 | Key | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | -| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:39:41:39:43 | Key | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql deleted file mode 100644 index a9d041efc0d0..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_key_sources.ql +++ /dev/null @@ -1,6 +0,0 @@ -import csharp -import experimental.quantum.Language - -from Crypto::CipherOperationNode op, Crypto::KeyArtifactNode k -where op.getAKey() = k -select op, k, k.getSourceNode() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected deleted file mode 100644 index 432e31dd2b35..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.expected +++ /dev/null @@ -1,6 +0,0 @@ -| AesCbcExample.cs:38:28:38:80 | EncryptOperation | AesCbcExample.cs:38:59:38:60 | Nonce | AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | -| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:61:72:61:73 | Nonce | AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | -| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | -| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | -| AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:31:29:31:33 | Nonce | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | -| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:29:42:33 | Nonce | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql deleted file mode 100644 index 4729bdcd5664..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_nonce_sources.ql +++ /dev/null @@ -1,6 +0,0 @@ -import csharp -import experimental.quantum.Language - -from Crypto::CipherOperationNode op, Crypto::NonceArtifactNode n -where op.getANonce() = n -select op, n, n.getSourceNode() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected deleted file mode 100644 index feaeca5966be..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.expected +++ /dev/null @@ -1,6 +0,0 @@ -| AesCbcExample.cs:38:28:38:80 | EncryptOperation | AesCbcExample.cs:38:43:38:56 | Message | AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | AesCbcExample.cs:36:21:36:27 | Key | AesCbcExample.cs:38:59:38:60 | Nonce | AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | Encrypt | -| AesCbcExample.cs:61:45:61:93 | DecryptOperation | AesCbcExample.cs:61:60:61:69 | Message | AesCbcExample.cs:61:45:61:93 | KeyOperationOutput | AesCbcExample.cs:60:21:60:27 | Key | AesCbcExample.cs:61:72:61:73 | Nonce | AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | Decrypt | -| AesCfbExample.cs:42:53:42:114 | EncryptOperation | AesCfbExample.cs:44:41:44:50 | Message | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | AesCfbExample.cs:31:17:31:23 | Key | AesCfbExample.cs:32:17:32:22 | Nonce | AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | Encrypt | -| AesCfbExample.cs:68:53:68:113 | DecryptOperation | AesCfbExample.cs:66:66:66:75 | Message | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | AesCfbExample.cs:63:66:63:68 | Key | AesCfbExample.cs:63:71:63:72 | Nonce | AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | Decrypt | -| AesGcmExample.cs:31:17:31:67 | EncryptOperation | AesGcmExample.cs:31:36:31:49 | Message | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | AesGcmExample.cs:26:41:26:43 | Key | AesGcmExample.cs:31:29:31:33 | Nonce | AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | Encrypt | -| AesGcmExample.cs:42:17:42:67 | DecryptOperation | AesGcmExample.cs:42:36:42:45 | Message | AesGcmExample.cs:42:53:42:66 | KeyOperationOutput | AesGcmExample.cs:39:41:39:43 | Key | AesGcmExample.cs:42:29:42:33 | Nonce | AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | Decrypt | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql deleted file mode 100644 index 1206a2361480..000000000000 --- a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/cipher_operations.ql +++ /dev/null @@ -1,6 +0,0 @@ -import csharp -import experimental.quantum.Language - -from Crypto::CipherOperationNode n -select n, n.getAnInputArtifact(), n.getAnOutputArtifact(), n.getAKey(), n.getANonce(), - n.getAnAlgorithmOrGenericSource(), n.getKeyOperationSubtype() diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.expected new file mode 100644 index 000000000000..a31125579a0f --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.expected @@ -0,0 +1,60 @@ +| AesCbcExample.cs:36:21:36:27 | Key | Source | AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | Algorithm | AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | Input | AesCbcExample.cs:38:43:38:56 | Message | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | Key | AesCbcExample.cs:36:21:36:27 | Key | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | Nonce | AesCbcExample.cs:38:59:38:60 | Nonce | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | Output | AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | +| AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | Mode | AesCbcExample.cs:38:28:38:80 | ModeOfOperation | +| AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | Padding | AesCbcExample.cs:38:63:38:79 | PaddingAlgorithm | +| AesCbcExample.cs:38:43:38:56 | Message | Source | AesCbcExample.cs:38:43:38:56 | Message | +| AesCbcExample.cs:38:59:38:60 | Nonce | Source | AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | +| AesCbcExample.cs:60:21:60:27 | Key | Source | AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | Algorithm | AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | Input | AesCbcExample.cs:61:60:61:69 | Message | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | Key | AesCbcExample.cs:60:21:60:27 | Key | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | Nonce | AesCbcExample.cs:61:72:61:73 | Nonce | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | Output | AesCbcExample.cs:61:45:61:93 | KeyOperationOutput | +| AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | Mode | AesCbcExample.cs:61:45:61:93 | ModeOfOperation | +| AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | Padding | AesCbcExample.cs:61:76:61:92 | PaddingAlgorithm | +| AesCbcExample.cs:61:60:61:69 | Message | Source | AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | +| AesCbcExample.cs:61:72:61:73 | Nonce | Source | AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | +| AesCfbExample.cs:31:17:31:23 | Key | Source | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | +| AesCfbExample.cs:32:17:32:22 | Nonce | Source | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | +| AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | Mode | AesCfbExample.cs:33:28:33:41 | ModeOfOperation | +| AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | Padding | AesCfbExample.cs:34:31:34:46 | PaddingAlgorithm | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | Algorithm | AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | Input | AesCfbExample.cs:44:41:44:50 | Message | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | Key | AesCfbExample.cs:31:17:31:23 | Key | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | Nonce | AesCfbExample.cs:32:17:32:22 | Nonce | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | Output | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | +| AesCfbExample.cs:44:41:44:50 | Message | Source | AesCfbExample.cs:44:41:44:50 | Message | +| AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | Mode | AesCfbExample.cs:59:28:59:41 | ModeOfOperation | +| AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | Padding | AesCfbExample.cs:60:31:60:46 | PaddingAlgorithm | +| AesCfbExample.cs:63:66:63:68 | Key | Source | AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | +| AesCfbExample.cs:63:71:63:72 | Nonce | Source | AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | +| AesCfbExample.cs:66:66:66:75 | Message | Source | AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | Algorithm | AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | Input | AesCfbExample.cs:66:66:66:75 | Message | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | Key | AesCfbExample.cs:63:66:63:68 | Key | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | Nonce | AesCfbExample.cs:63:71:63:72 | Nonce | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | Output | AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | +| AesGcmExample.cs:26:41:26:43 | Key | Source | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | Algorithm | AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | Input | AesGcmExample.cs:31:36:31:49 | Message | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | Key | AesGcmExample.cs:26:41:26:43 | Key | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | Nonce | AesGcmExample.cs:31:29:31:33 | Nonce | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | Output | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | +| AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | Mode | AesGcmExample.cs:31:17:31:67 | ModeOfOperation | +| AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | Padding | AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | +| AesGcmExample.cs:31:29:31:33 | Nonce | Source | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | +| AesGcmExample.cs:31:36:31:49 | Message | Source | AesGcmExample.cs:31:36:31:49 | Message | +| AesGcmExample.cs:39:41:39:43 | Key | Source | AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | Algorithm | AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | Input | AesGcmExample.cs:42:36:42:45 | Message | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | Key | AesGcmExample.cs:39:41:39:43 | Key | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | Nonce | AesGcmExample.cs:42:29:42:33 | Nonce | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | Output | AesGcmExample.cs:42:53:42:66 | KeyOperationOutput | +| AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | Mode | AesGcmExample.cs:42:17:42:67 | ModeOfOperation | +| AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | Padding | AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | +| AesGcmExample.cs:42:29:42:33 | Nonce | Source | AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | +| AesGcmExample.cs:42:36:42:45 | Message | Source | AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.ql new file mode 100644 index 000000000000..b793c5b7480f --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_edges.ql @@ -0,0 +1,5 @@ +import csharp +import experimental.quantum.Language + +from Crypto::NodeBase n, string key +select n, key, n.getChild(key) diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.expected new file mode 100644 index 000000000000..64c923b7ad98 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.expected @@ -0,0 +1,44 @@ +| AesCbcExample.cs:36:21:36:27 | Key | KeyType | Unknown | AesCbcExample.cs:36:21:36:27 | AesCbcExample.cs:36:21:36:27 | +| AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | Name | AES | AesCbcExample.cs:38:28:38:80 | AesCbcExample.cs:38:28:38:80 | +| AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | RawName | Aes | AesCbcExample.cs:38:28:38:80 | AesCbcExample.cs:38:28:38:80 | +| AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | Structure | Block | AesCbcExample.cs:38:28:38:80 | AesCbcExample.cs:38:28:38:80 | +| AesCbcExample.cs:38:28:38:80 | ModeOfOperation | Name | CBC | AesCbcExample.cs:38:28:38:80 | AesCbcExample.cs:38:28:38:80 | +| AesCbcExample.cs:38:28:38:80 | ModeOfOperation | RawName | Cbc | AesCbcExample.cs:38:28:38:80 | AesCbcExample.cs:38:28:38:80 | +| AesCbcExample.cs:38:63:38:79 | PaddingAlgorithm | Name | PKCS7 | AesCbcExample.cs:38:63:38:79 | AesCbcExample.cs:38:63:38:79 | +| AesCbcExample.cs:38:63:38:79 | PaddingAlgorithm | RawName | PKCS7 | AesCbcExample.cs:38:63:38:79 | AesCbcExample.cs:38:63:38:79 | +| AesCbcExample.cs:60:21:60:27 | Key | KeyType | Unknown | AesCbcExample.cs:60:21:60:27 | AesCbcExample.cs:60:21:60:27 | +| AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | Name | AES | AesCbcExample.cs:61:45:61:93 | AesCbcExample.cs:61:45:61:93 | +| AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | RawName | Aes | AesCbcExample.cs:61:45:61:93 | AesCbcExample.cs:61:45:61:93 | +| AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | Structure | Block | AesCbcExample.cs:61:45:61:93 | AesCbcExample.cs:61:45:61:93 | +| AesCbcExample.cs:61:45:61:93 | ModeOfOperation | Name | CBC | AesCbcExample.cs:61:45:61:93 | AesCbcExample.cs:61:45:61:93 | +| AesCbcExample.cs:61:45:61:93 | ModeOfOperation | RawName | Cbc | AesCbcExample.cs:61:45:61:93 | AesCbcExample.cs:61:45:61:93 | +| AesCbcExample.cs:61:76:61:92 | PaddingAlgorithm | Name | PKCS7 | AesCbcExample.cs:61:76:61:92 | AesCbcExample.cs:61:76:61:92 | +| AesCbcExample.cs:61:76:61:92 | PaddingAlgorithm | RawName | PKCS7 | AesCbcExample.cs:61:76:61:92 | AesCbcExample.cs:61:76:61:92 | +| AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | Description | RandomNumberGenerator | AesCbcExample.cs:76:30:76:32 | AesCbcExample.cs:76:30:76:32 | +| AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | Description | RandomNumberGenerator | AesCbcExample.cs:86:30:86:31 | AesCbcExample.cs:86:30:86:31 | +| AesCfbExample.cs:31:17:31:23 | Key | KeyType | Unknown | AesCfbExample.cs:31:17:31:23 | AesCfbExample.cs:31:17:31:23 | +| AesCfbExample.cs:33:28:33:41 | ModeOfOperation | Name | CFB | AesCfbExample.cs:33:28:33:41 | AesCfbExample.cs:33:28:33:41 | +| AesCfbExample.cs:33:28:33:41 | ModeOfOperation | RawName | CFB | AesCfbExample.cs:33:28:33:41 | AesCfbExample.cs:33:28:33:41 | +| AesCfbExample.cs:34:31:34:46 | PaddingAlgorithm | Name | NoPadding | AesCfbExample.cs:34:31:34:46 | AesCfbExample.cs:34:31:34:46 | +| AesCfbExample.cs:34:31:34:46 | PaddingAlgorithm | RawName | None | AesCfbExample.cs:34:31:34:46 | AesCfbExample.cs:34:31:34:46 | +| AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | Name | AES | AesCfbExample.cs:36:46:36:66 | AesCfbExample.cs:36:46:36:66 | +| AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | RawName | Aes | AesCfbExample.cs:36:46:36:66 | AesCfbExample.cs:36:46:36:66 | +| AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | Structure | Block | AesCfbExample.cs:36:46:36:66 | AesCfbExample.cs:36:46:36:66 | +| AesCfbExample.cs:59:28:59:41 | ModeOfOperation | Name | CFB | AesCfbExample.cs:59:28:59:41 | AesCfbExample.cs:59:28:59:41 | +| AesCfbExample.cs:59:28:59:41 | ModeOfOperation | RawName | CFB | AesCfbExample.cs:59:28:59:41 | AesCfbExample.cs:59:28:59:41 | +| AesCfbExample.cs:60:31:60:46 | PaddingAlgorithm | Name | NoPadding | AesCfbExample.cs:60:31:60:46 | AesCfbExample.cs:60:31:60:46 | +| AesCfbExample.cs:60:31:60:46 | PaddingAlgorithm | RawName | None | AesCfbExample.cs:60:31:60:46 | AesCfbExample.cs:60:31:60:46 | +| AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | Name | AES | AesCfbExample.cs:63:46:63:73 | AesCfbExample.cs:63:46:63:73 | +| AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | RawName | Aes | AesCfbExample.cs:63:46:63:73 | AesCfbExample.cs:63:46:63:73 | +| AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | Structure | Block | AesCfbExample.cs:63:46:63:73 | AesCfbExample.cs:63:46:63:73 | +| AesCfbExample.cs:63:66:63:68 | Key | KeyType | Unknown | AesCfbExample.cs:63:66:63:68 | AesCfbExample.cs:63:66:63:68 | +| AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | Description | RandomNumberGenerator | AesCfbExample.cs:87:30:87:32 | AesCfbExample.cs:87:30:87:32 | +| AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | Description | RandomNumberGenerator | AesCfbExample.cs:97:30:97:31 | AesCfbExample.cs:97:30:97:31 | +| AesGcmExample.cs:26:41:26:43 | Key | KeyType | Unknown | AesGcmExample.cs:26:41:26:43 | AesGcmExample.cs:26:41:26:43 | +| AesGcmExample.cs:31:17:31:67 | ModeOfOperation | Name | GCM | AesGcmExample.cs:31:17:31:67 | AesGcmExample.cs:31:17:31:67 | +| AesGcmExample.cs:31:17:31:67 | ModeOfOperation | RawName | Gcm | AesGcmExample.cs:31:17:31:67 | AesGcmExample.cs:31:17:31:67 | +| AesGcmExample.cs:39:41:39:43 | Key | KeyType | Unknown | AesGcmExample.cs:39:41:39:43 | AesGcmExample.cs:39:41:39:43 | +| AesGcmExample.cs:42:17:42:67 | ModeOfOperation | Name | GCM | AesGcmExample.cs:42:17:42:67 | AesGcmExample.cs:42:17:42:67 | +| AesGcmExample.cs:42:17:42:67 | ModeOfOperation | RawName | Gcm | AesGcmExample.cs:42:17:42:67 | AesGcmExample.cs:42:17:42:67 | +| AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | Description | RandomNumberGenerator | AesGcmExample.cs:53:30:53:32 | AesGcmExample.cs:53:30:53:32 | +| AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | Description | RandomNumberGenerator | AesGcmExample.cs:63:30:63:34 | AesGcmExample.cs:63:30:63:34 | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.ql new file mode 100644 index 000000000000..322758d018be --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/node_properties.ql @@ -0,0 +1,6 @@ +import csharp +import experimental.quantum.Language + +from Crypto::NodeBase n, string key, string value, Location location +where n.properties(key, value, location) +select n, key, value, location diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.expected b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.expected new file mode 100644 index 000000000000..1be4aa229b28 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.expected @@ -0,0 +1,52 @@ +| AesCbcExample.cs:36:21:36:27 | Key | +| AesCbcExample.cs:38:28:38:80 | EncryptOperation | +| AesCbcExample.cs:38:28:38:80 | KeyOperationAlgorithm | +| AesCbcExample.cs:38:28:38:80 | KeyOperationOutput | +| AesCbcExample.cs:38:28:38:80 | ModeOfOperation | +| AesCbcExample.cs:38:43:38:56 | Message | +| AesCbcExample.cs:38:59:38:60 | Nonce | +| AesCbcExample.cs:38:63:38:79 | PaddingAlgorithm | +| AesCbcExample.cs:60:21:60:27 | Key | +| AesCbcExample.cs:61:45:61:93 | DecryptOperation | +| AesCbcExample.cs:61:45:61:93 | KeyOperationAlgorithm | +| AesCbcExample.cs:61:45:61:93 | KeyOperationOutput | +| AesCbcExample.cs:61:45:61:93 | ModeOfOperation | +| AesCbcExample.cs:61:60:61:69 | Message | +| AesCbcExample.cs:61:72:61:73 | Nonce | +| AesCbcExample.cs:61:76:61:92 | PaddingAlgorithm | +| AesCbcExample.cs:76:30:76:32 | RandomNumberGeneration | +| AesCbcExample.cs:86:30:86:31 | RandomNumberGeneration | +| AesCfbExample.cs:31:17:31:23 | Key | +| AesCfbExample.cs:32:17:32:22 | Nonce | +| AesCfbExample.cs:33:28:33:41 | ModeOfOperation | +| AesCfbExample.cs:34:31:34:46 | PaddingAlgorithm | +| AesCfbExample.cs:36:46:36:66 | KeyOperationAlgorithm | +| AesCfbExample.cs:42:53:42:114 | EncryptOperation | +| AesCfbExample.cs:44:41:44:50 | Message | +| AesCfbExample.cs:47:33:47:51 | KeyOperationOutput | +| AesCfbExample.cs:59:28:59:41 | ModeOfOperation | +| AesCfbExample.cs:60:31:60:46 | PaddingAlgorithm | +| AesCfbExample.cs:63:46:63:73 | KeyOperationAlgorithm | +| AesCfbExample.cs:63:66:63:68 | Key | +| AesCfbExample.cs:63:71:63:72 | Nonce | +| AesCfbExample.cs:66:66:66:75 | Message | +| AesCfbExample.cs:68:53:68:113 | DecryptOperation | +| AesCfbExample.cs:73:49:73:65 | KeyOperationOutput | +| AesCfbExample.cs:87:30:87:32 | RandomNumberGeneration | +| AesCfbExample.cs:97:30:97:31 | RandomNumberGeneration | +| AesGcmExample.cs:26:41:26:43 | Key | +| AesGcmExample.cs:31:17:31:67 | EncryptOperation | +| AesGcmExample.cs:31:17:31:67 | KeyOperationAlgorithm | +| AesGcmExample.cs:31:17:31:67 | ModeOfOperation | +| AesGcmExample.cs:31:29:31:33 | Nonce | +| AesGcmExample.cs:31:36:31:49 | Message | +| AesGcmExample.cs:31:52:31:61 | KeyOperationOutput | +| AesGcmExample.cs:39:41:39:43 | Key | +| AesGcmExample.cs:42:17:42:67 | DecryptOperation | +| AesGcmExample.cs:42:17:42:67 | KeyOperationAlgorithm | +| AesGcmExample.cs:42:17:42:67 | ModeOfOperation | +| AesGcmExample.cs:42:29:42:33 | Nonce | +| AesGcmExample.cs:42:36:42:45 | Message | +| AesGcmExample.cs:42:53:42:66 | KeyOperationOutput | +| AesGcmExample.cs:53:30:53:32 | RandomNumberGeneration | +| AesGcmExample.cs:63:30:63:34 | RandomNumberGeneration | diff --git a/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.ql b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.ql new file mode 100644 index 000000000000..d17b30ab08cd --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/ciphers/nodes.ql @@ -0,0 +1,5 @@ +import csharp +import experimental.quantum.Language + +from Crypto::NodeBase n +select n From 81f06d997de237a5bdadbdd67153a9e25cc40638 Mon Sep 17 00:00:00 2001 From: Fredrik Dahlgren Date: Fri, 4 Jul 2025 13:55:18 +0200 Subject: [PATCH 33/33] Fixed QL for QL code scanning results for .NET --- .../quantum/dotnet/AlgorithmInstances.qll | 2 - .../quantum/dotnet/Cryptography.qll | 64 +++++++++---------- .../quantum/dotnet/FlowAnalysis.qll | 23 ++++--- .../quantum/dotnet/OperationInstances.qll | 4 +- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll index 0d42209beedf..fbee2bb4205d 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -7,8 +7,6 @@ private import FlowAnalysis class NamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof NamedCurvePropertyAccess { - NamedCurveAlgorithmInstance() { this instanceof NamedCurvePropertyAccess } - override string getRawEllipticCurveName() { result = super.getCurveName() } override Crypto::TEllipticCurveType getEllipticCurveType() { diff --git a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll index a2385febbc70..1647a26dc93f 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -6,16 +6,16 @@ class CryptographyType extends Type { CryptographyType() { this.hasFullyQualifiedName("System.Security.Cryptography", _) } } -class ECParameters extends CryptographyType { - ECParameters() { this.hasName("ECParameters") } +class EcParameters extends CryptographyType { + EcParameters() { this.hasName("ECParameters") } } -class RSAParameters extends CryptographyType { - RSAParameters() { this.hasName("RSAParameters") } +class RsaParameters extends CryptographyType { + RsaParameters() { this.hasName("RSAParameters") } } -class ECCurve extends CryptographyType { - ECCurve() { this.hasName("ECCurve") } +class EcCurve extends CryptographyType { + EcCurve() { this.hasName("ECCurve") } } class HashAlgorithmType extends CryptographyType { @@ -71,12 +71,12 @@ class EcdsaCreateCall extends CryptographyCreateCall { } // This class is used to model the `ECDsa.Create(ECParameters)` call -class ECDsaCreateCallWithParameters extends EcdsaCreateCall { - ECDsaCreateCallWithParameters() { this.getArgument(0).getType() instanceof ECParameters } +class EcdsaCreateCallWithParameters extends EcdsaCreateCall { + EcdsaCreateCallWithParameters() { this.getArgument(0).getType() instanceof EcParameters } } -class ECDsaCreateCallWithECCurve extends EcdsaCreateCall { - ECDsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof ECCurve } +class EcdsaCreateCallWithECCurve extends EcdsaCreateCall { + EcdsaCreateCallWithECCurve() { this.getArgument(0).getType() instanceof EcCurve } } class RsaCreateCall extends CryptographyCreateCall { @@ -127,7 +127,7 @@ class NamedCurvePropertyAccess extends PropertyAccess { NamedCurvePropertyAccess() { super.getType().getName() = "ECCurve" and - eccurveNameMapping(super.getProperty().toString().toUpperCase(), curveName) + ecCurveNameMapping(super.getProperty().toString().toUpperCase(), curveName) } string getCurveName() { result = curveName } @@ -196,7 +196,7 @@ class HashAlgorithmNameUser extends MethodCall { * Private predicate mapping NIST names to SEC names and leaving all others the same. */ bindingset[nist] -private predicate eccurveNameMapping(string nist, string secp) { +private predicate ecCurveNameMapping(string nist, string secp) { if nist.matches("NIST%") then nist = "NISTP256" and secp = "secp256r1" @@ -208,28 +208,28 @@ private predicate eccurveNameMapping(string nist, string secp) { } // OPERATION INSTANCES -private class ECDsaClass extends CryptographyType { - ECDsaClass() { this.hasName("ECDsa") } +private class EcdsaClass extends CryptographyType { + EcdsaClass() { this.hasName("ECDsa") } } -private class RSAClass extends CryptographyType { - RSAClass() { this.hasName("RSA") } +private class RsaClass extends CryptographyType { + RsaClass() { this.hasName("RSA") } } -private class RSAPKCS1SignatureFormatter extends CryptographyType { - RSAPKCS1SignatureFormatter() { this.hasName("RSAPKCS1SignatureFormatter") } +private class RsaPkcs1SignatureFormatter extends CryptographyType { + RsaPkcs1SignatureFormatter() { this.hasName("RSAPKCS1SignatureFormatter") } } -private class RSAPKCS1SignatureDeformatter extends CryptographyType { - RSAPKCS1SignatureDeformatter() { this.hasName("RSAPKCS1SignatureDeformatter") } +private class RsaPkcs1SignatureDeformatter extends CryptographyType { + RsaPkcs1SignatureDeformatter() { this.hasName("RSAPKCS1SignatureDeformatter") } } private class SignerType extends Type { SignerType() { - this instanceof ECDsaClass or - this instanceof RSAClass or - this instanceof RSAPKCS1SignatureFormatter or - this instanceof RSAPKCS1SignatureDeformatter + this instanceof EcdsaClass or + this instanceof RsaClass or + this instanceof RsaPkcs1SignatureFormatter or + this instanceof RsaPkcs1SignatureDeformatter } } @@ -631,8 +631,8 @@ class MacAlgorithmType extends CryptographyType { MacAlgorithmType() { this.getName().matches(["HMAC%", "KeyedHashAlgorithm"]) } } -class HMACCreation extends ObjectCreation { - HMACCreation() { this.getObjectType() instanceof MacAlgorithmType } +class HmacCreation extends ObjectCreation { + HmacCreation() { this.getObjectType() instanceof MacAlgorithmType } Expr getKeyArg() { if this.hasNoArguments() then result = this else result = this.getArgument(0) } @@ -674,7 +674,7 @@ class MacUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall { Expr getKeyArg() { if not super.getTarget().getName().matches("ComputeHash%") then result = super.getArgument(0) - else result = HMACFlow::getCreationFromUse(this, _, _).getKeyArg() + else result = HmacFlow::getCreationFromUse(this, _, _).getKeyArg() } override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = super.getQualifier() } @@ -684,16 +684,16 @@ class MacUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall { Expr getQualifier() { result = super.getQualifier() } } -class HMACAlgorithmInstance extends Crypto::MACAlgorithmInstance instanceof Expr { - HMACAlgorithmInstance() { this = any(MacUse c).getQualifier() } +class HmacAlgorithmInstance extends Crypto::MACAlgorithmInstance instanceof Expr { + HmacAlgorithmInstance() { this = any(MacUse c).getQualifier() } override Crypto::TMACType getMACType() { result instanceof Crypto::THMAC } override string getRawMACAlgorithmName() { result = super.getType().getName() } } -class HMACAlgorithmQualifier extends Crypto::HMACAlgorithmInstance, Crypto::AlgorithmValueConsumer, - HMACAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof Expr +class HmacAlgorithmQualifier extends Crypto::HMACAlgorithmInstance, Crypto::AlgorithmValueConsumer, + HmacAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof Expr { override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() { result = this } @@ -718,7 +718,7 @@ class HMACAlgorithmQualifier extends Crypto::HMACAlgorithmInstance, Crypto::Algo private string getOriginalRawHashAlgorithmName() { exists(MacUse use | use.getQualifier() = this and - result = HMACFlow::getCreationFromUse(use, _, _).getRawAlgorithmName().replaceAll("HMAC", "") + result = HmacFlow::getCreationFromUse(use, _, _).getRawAlgorithmName().replaceAll("HMAC", "") ) } } diff --git a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll index 78a274584f7b..a0c3a30994cd 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -70,7 +70,7 @@ module CryptoStreamFlow = CreationToUseFlow; -module HMACFlow = CreationToUseFlow; +module HmacFlow = CreationToUseFlow; module SymmetricAlgorithmFlow = CreationToUseFlow; @@ -263,17 +263,16 @@ module SigningCreateToUseFlow { sink.asExpr() = any(SignerUse use).(QualifiableExpr).getQualifier() } - /** - * An additional flow step across new object creations that use the original objects. - * - * Example: - * ``` - * RSA rsa = RSA.Create() - * RSAPKCS1SignatureFormatter rsaFormatter = new(rsa); - * rsaFormatter.SetHashAlgorithm(nameof(SHA256)); - * signedHash = rsaFormatter.CreateSignature(hash); - * ``` - */ + // Holds if the incoming node is an argument of the constructor call + // represented by the outgoing node. + // + // Example: + // ``` + // RSA rsa = RSA.Create() + // RSAPKCS1SignatureFormatter rsaFormatter = new(rsa); + // rsaFormatter.SetHashAlgorithm(nameof(SHA256)); + // signedHash = rsaFormatter.CreateSignature(hash); + // ``` predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { exists(ObjectCreation create | node2.asExpr() = create and node1.asExpr() = create.getAnArgument() diff --git a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll index 75c88ae68756..1a424f57a937 100644 --- a/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -210,8 +210,8 @@ class AeadOperationInstance extends Crypto::KeyOperationInstance instanceof Aead } } -class HMACOperationInstance extends Crypto::MACOperationInstance instanceof MacUse { - HMACOperationInstance() { not super.isIntermediate() } +class HmacOperationInstance extends Crypto::MACOperationInstance instanceof MacUse { + HmacOperationInstance() { not super.isIntermediate() } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = super.getQualifier()