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() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy