diff --git a/csharp/ql/lib/experimental/quantum/Language.qll b/csharp/ql/lib/experimental/quantum/Language.qll new file mode 100644 index 000000000000..29f7fba7ccaf --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/Language.qll @@ -0,0 +1,183 @@ +private import csharp as Language +private import semmle.code.csharp.dataflow.DataFlow +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 = Language::DataFlow::Node; + + class LocatableElement = Language::Element; + + class UnknownLocation = UnknownDefaultLocation; + + string locationToFileBaseNameAndLineNumberString(Language::Location location) { + result = location.getFile().getBaseName() + ":" + location.getStartLine() + } + + LocatableElement dfn_to_element(Language::DataFlow::Node node) { + result = node.asExpr() or + result = node.asParameter() + } + + predicate artifactOutputFlowsToGenericInput( + Language::DataFlow::Node artifactOutput, Language::DataFlow::Node otherInput + ) { + ArtifactFlow::flow(artifactOutput, otherInput) + } +} + +// Instantiate the `CryptographyBase` module +module Crypto = CryptographyBase; + +/** + * 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(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 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, 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 } +} + +/** + * 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() } +} + +/** + * 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.qll b/csharp/ql/lib/experimental/quantum/dotnet.qll new file mode 100644 index 000000000000..463f00f1ed18 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet.qll @@ -0,0 +1,3 @@ +import dotnet.AlgorithmValueConsumers +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 new file mode 100644 index 000000000000..fbee2bb4205d --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmInstances.qll @@ -0,0 +1,246 @@ +private import csharp +private import experimental.quantum.Language +private import AlgorithmValueConsumers +private import OperationInstances +private import Cryptography +private import FlowAnalysis + +class NamedCurveAlgorithmInstance extends Crypto::EllipticCurveInstance instanceof NamedCurvePropertyAccess +{ + override string getRawEllipticCurveName() { result = super.getCurveName() } + + override Crypto::TEllipticCurveType getEllipticCurveType() { + Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _, result) + } + + override int getKeySize() { + Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), result, _) + } +} + +abstract class SigningAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance { + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() } + + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + + override int getKeySizeFixed() { none() } +} + +class HashAlgorithmNameInstance extends Crypto::HashAlgorithmInstance instanceof HashAlgorithmName { + HashAlgorithmNameConsumer consumer; + + HashAlgorithmNameInstance() { + HashAlgorithmNameToUse::flow(DataFlow::exprNode(this), consumer.getInputNode()) + } + + 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 } +} + +/** + * 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 +{ + SymmetricAlgorithmInstance() { + this.isEncryptionCall() or this.isDecryptionCall() or this.isCreationCall() + } + + override string getRawAlgorithmName() { + result = super.getSymmetricAlgorithm().getType().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() { + super.isCreationCall() and + result.(CipherModeLiteralInstance).getConsumer() = this.getCipherModeAlgorithmValueConsumer() + or + (super.isEncryptionCall() or super.isDecryptionCall()) and + result = this + } + + 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() { + 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::getIntermediateUseFromUse(this, _, _) and + result instanceof CipherModePropertyWrite + } + + override int getKeySizeFixed() { none() } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } +} + +/** + * 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() + } +} + +/** + * 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 = ModeLiteralFlow::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 } +} + +/** + * A cipher mode literal, such as `CipherMode.CBC`. + */ +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 } +} + +/** + * A call to either `Encrypt` or `Decrypt` on an `AesGcm`, `AesCcm`, or + * `ChaCha20Poly1305` instance. The algorithm is defined implicitly by this AST + * node. + */ +class AeadAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance, + Crypto::ModeOfOperationAlgorithmInstance instanceof AeadUse +{ + override string getRawAlgorithmName() { + super.getQualifier().getType().hasName("Aes%") and result = "Aes" + or + super.getQualifier().getType().hasName("ChaCha20%") and result = "ChaCha20" + } + + override string getRawModeAlgorithmName() { + super.getQualifier().getType().getName() = "AesGcm" and result = "Gcm" + or + 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.getRawModeAlgorithmName() = "Gcm" and result = Crypto::GCM() + or + this.getRawModeAlgorithmName() = "Ccm" and result = Crypto::CCM() + } + + override int getKeySizeFixed() { none() } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + + override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { result = this } + + override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() } +} + +bindingset[algorithmName] +private Crypto::KeyOpAlg::Algorithm symmetricAlgorithmNameToType(string algorithmName) { + algorithmName.matches("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() +} + +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 new file mode 100644 index 000000000000..0464d184aaab --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/AlgorithmValueConsumers.qll @@ -0,0 +1,94 @@ +private import csharp +private import experimental.quantum.Language +private import AlgorithmInstances +private import OperationInstances +private import Cryptography + +class HashAlgorithmNameConsumer extends Crypto::AlgorithmValueConsumer { + HashAlgorithmNameUser call; + + HashAlgorithmNameConsumer() { this = call.getHashAlgorithmNameUser() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + 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 + } +} + +/** + * 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 padding mode argument passed to a symmetric algorithm method call. + */ +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 = this } +} + +/** + * A call to either `Encrypt` or `Decrypt` on an `AesGcm`, `AesCcm` or + * `ChaCha20Poly1305` instance. The algorithm is defined implicitly by this AST + * node. + */ +class AeadAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof AeadUse { + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } + + 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 new file mode 100644 index 000000000000..1647a26dc93f --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/Cryptography.qll @@ -0,0 +1,724 @@ +private import csharp +private import experimental.quantum.Language +private import FlowAnalysis + +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") } +} + +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 CryptographyCreateCall extends MethodCall { + CryptographyCreateCall() { + this.getTarget().hasName("Create") and + this.getQualifier().getType() instanceof CryptographyType + } + + Expr getAlgorithmArg() { + if this.getArgument(0).getType().getName().matches("%Parameters") + then result = this.getArgument(0) + else result = this + } + + Expr getKeyConsumer() { + if this.getArgument(0).getType().getName().matches("%Parameters") + then result = this.getArgument(0) + else result = this + } +} + +class EcdsaType extends CryptographyType { + EcdsaType() { this.hasName("ECDsa") } +} + +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() instanceof EcdsaType } +} + +// 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 RsaCreateCall extends CryptographyCreateCall { + RsaCreateCall() { this.getQualifier().getType().hasName("RSA") } +} + +class SigningCreateCall extends CryptographyCreateCall { + SigningCreateCall() { + this instanceof EcdsaCreateCall or + this instanceof RsaCreateCall + } +} + +/** + * 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::AlgorithmValueConsumer, Crypto::HashAlgorithmInstance instanceof Expr +{ + HashAlgorithmQualifier() { this = any(HashUse c).getQualifier() } + + override Crypto::THashType getHashFamily() { + result = getHashFamily(this.getRawHashAlgorithmName()) + } + + override string getRawHashAlgorithmName() { result = super.getType().getName() } + + override int getFixedDigestLength() { + hashAlgorithmToFamily(this.getRawHashAlgorithmName(), _, result) + } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { none() } +} + +class NamedCurvePropertyAccess extends PropertyAccess { + string curveName; + + NamedCurvePropertyAccess() { + super.getType().getName() = "ECCurve" and + ecCurveNameMapping(super.getProperty().toString().toUpperCase(), curveName) + } + + 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 } + + 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 +) { + 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 + or + hashName = "RIPEMD160" and hashFamily = Crypto::RIPEMD160() and digestLength = 160 +} + +class HashAlgorithmNameUser extends MethodCall { + Expr arg; + + HashAlgorithmNameUser() { + arg = this.getAnArgument() and + arg.getType() instanceof HashAlgorithmNameType + } + + Expr getHashAlgorithmNameUser() { result = arg } +} + +/** + * 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 CryptographyType { + EcdsaClass() { this.hasName("ECDsa") } +} + +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 or + this instanceof RsaPkcs1SignatureFormatter or + this instanceof RsaPkcs1SignatureDeformatter + } +} + +class ByteArrayType extends Type { + ByteArrayType() { this.getName() = "Byte[]" } +} + +class ReadOnlyByteSpanType extends Type { + ReadOnlyByteSpanType() { this.getName() = "ReadOnlySpan" } +} + +class ByteArrayOrReadOnlyByteSpanType extends Type { + ByteArrayOrReadOnlyByteSpanType() { + this instanceof ByteArrayType or + this instanceof ReadOnlyByteSpanType + } +} + +class HashUse extends Crypto::AlgorithmValueConsumer instanceof MethodCall { + HashUse() { + this.getQualifier().getType() instanceof HashAlgorithmType and + this.getTarget() + .hasName([ + "ComputeHash", "ComputeHashAsync", "HashCore", "HashData", "HashDataAsync", + "TransformBlock", "TransformFinalBlock", "TryComputeHash", "TryHashData", + "TryHashFinal", "HashFinal" + ]) + } + + predicate isIntermediate() { super.getTarget().hasName("HashCore") } + + Expr getOutput() { + not this.isIntermediate() and + // some functions receive the destination as a parameter + if + super.getTarget().getName() = ["TryComputeHash", "TryHashFinal", "TryHashData"] + or + super.getTarget().getName() = ["HashData"] and super.getNumberOfArguments() = 2 + or + super.getTarget().getName() = ["HashDataAsync"] and super.getNumberOfArguments() = 3 + then result = super.getArgument(1) + else result = this + } + + Expr getInputArg() { + result = super.getArgument(0) and result.getType() instanceof ByteArrayOrReadOnlyByteSpanType + } + + Expr getStreamArg() { + result = super.getAnArgument() and + result.getType() instanceof Stream + } + + 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 or super.getType() instanceof RsaPkcs1Type + } + + override string getRawAlgorithmName() { result = super.getType().getName() } + + override Crypto::KeyOpAlg::Algorithm getAlgorithmType() { + result = Crypto::KeyOpAlg::TSignature(Crypto::KeyOpAlg::OtherSignatureAlgorithmType()) + } +} + +class SignerUse extends MethodCall { + SignerUse() { + this.getTarget().getName().matches(["Verify%", "Sign%", "CreateSignature"]) and + this.getQualifier().getType() instanceof SignerType + } + + 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 + ( + result = this.getArgument([1, 3]) and + result.getType() instanceof ByteArrayOrReadOnlyByteSpanType + ) + } + + predicate isIntermediate() { none() } + + Expr getSignatureOutput() { + this.isSigner() and + 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%", "CreateSignature"]) } + + predicate isVerifier() { this.getTarget().getName().matches("Verify%") } +} + +/** + * An AEAD class, such as `AesGcm`, `AesCcm`, or `ChaCha20Poly1305`. + */ +class Aead extends Class { + Aead() { + this.hasFullyQualifiedName("System.Security.Cryptography", + ["AesGcm", "AesCcm", "ChaCha20Poly1305"]) + } +} + +class AeadCreation extends ObjectCreation { + AeadCreation() { this.getObjectType() instanceof Aead } + + Expr getKeyArg() { result = this.getArgument(0) } +} + +class AeadUse extends MethodCall { + AeadUse() { + this.getQualifier().getType() instanceof Aead 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 + } +} + +class SymmetricAlgorithmUse extends QualifiableExpr { + SymmetricAlgorithmUse() { + this.getQualifier().getType() instanceof SymmetricAlgorithm and + this.getQualifiedDeclaration() + .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() { + this.getQualifiedDeclaration().hasName(["Key", "IV", "Padding", "Mode"]) + } + + // 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" + } + + predicate isCreationCall() { this.getQualifiedDeclaration().getName().matches("Create%") } + + predicate isEncryptionCall() { + this.getQualifiedDeclaration().getName().matches(["Encrypt%", "TryEncrypt%"]) + } + + predicate isDecryptionCall() { + 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 + } +} + +/** + * 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) + } +} + +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 new file mode 100644 index 000000000000..a0c3a30994cd --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/FlowAnalysis.qll @@ -0,0 +1,292 @@ +private import csharp +private import semmle.code.csharp.dataflow.DataFlow +private import experimental.quantum.Language +private import OperationInstances +private import AlgorithmValueConsumers +private import Cryptography + +signature class CreationCallSig instanceof Call; + +signature class UseCallSig instanceof QualifiableExpr { + predicate isIntermediate(); +} + +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.(QualifiableExpr).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.(QualifiableExpr).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) + } +} + +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 HashCreateToUseFlow = CreationToUseFlow; + +module CryptoStreamFlow = CreationToUseFlow; + +module AeadFlow = CreationToUseFlow; + +module HmacFlow = 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 + * 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, or from a `CipherMode` member access + * (e.g. `CipherMode.CBC`) to a `Mode` property write on a `SymmetricAlgorithm` + * instance. + * + * Example: + * ``` + * Aes aes = Aes.Create(); + * aes.Padding = PaddingMode.PKCS7; + * ... + * ``` + */ +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 or + sink.asExpr() instanceof PaddingModeArgument or + sink.asExpr() instanceof CipherModePropertyWrite + } + + // 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 ModeLiteralFlow = DataFlow::Global; + + Expr getConsumer(Expr mode, ModeLiteralFlow::PathNode source, ModeLiteralFlow::PathNode sink) { + source.getNode().asExpr() = mode and + sink.getNode().asExpr() = result and + ModeLiteralFlow::flowPath(source, sink) + } +} + +/** + * 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. + * + * 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 StreamFlow { + private module StreamConfig implements DataFlow::ConfigSig { + 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) { + // 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 StreamFlow = DataFlow::Global; + + StreamCreation getWrappedStreamCreation( + StreamCreation stream, StreamFlow::PathNode source, StreamFlow::PathNode sink + ) { + source.getNode().asExpr() = result and + sink.getNode().asExpr() = stream and + StreamFlow::flowPath(source, sink) + } + + 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) + } +} + +/** + * 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() } +} + +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() + } + + // 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() + ) + } + } + + 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/lib/experimental/quantum/dotnet/OperationInstances.qll b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll new file mode 100644 index 000000000000..1a424f57a937 --- /dev/null +++ b/csharp/ql/lib/experimental/quantum/dotnet/OperationInstances.qll @@ -0,0 +1,228 @@ +private import csharp +private import DataFlow +private import experimental.quantum.Language +private import AlgorithmValueConsumers +private import FlowAnalysis +private import Cryptography + +class SigningOperationInstance extends Crypto::SignatureOperationInstance instanceof SignerUse { + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result = super.getQualifier() + } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if super.isSigner() + then result = Crypto::TSignMode() + else + if super.isVerifier() + then result = Crypto::TVerifyMode() + else result = Crypto::TUnknownKeyOperationMode() + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + result.asExpr() = SigningCreateToUseFlow::getCreationFromUse(this, _, _).getKeyConsumer() + } + + 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() + } +} + +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() { + result = super.getQualifier() + } +} + +/** + * 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 + * call to `Aes.CreateEncryptor()`) + */ +class CryptoStreamOperationInstance extends Crypto::KeyOperationInstance instanceof CryptoStreamCreation +{ + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result = this.getCryptoTransform() + } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if this.getCryptoTransform().isEncryptor() + then result = Crypto::TEncryptMode() + else + if this.getCryptoTransform().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(this.getCryptoTransform().getKeyArg()) + then result.asExpr() = this.getCryptoTransform().getKeyArg() + else ( + result.asExpr() = + SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) 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(this.getCryptoTransform().getIvArg()) + then result.asExpr() = this.getCryptoTransform().getIvArg() + else ( + result.asExpr() = + SymmetricAlgorithmFlow::getIntermediateUseFromUse(this.getCryptoTransform(), _, _) and + result.asExpr().(SymmetricAlgorithmUse).isIvConsumer() + ) + } + + // 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() = this.getWrappedStreamCreation().getInputArg() + or + result.asExpr() = this.getEarlierWrappedStreamUse().getInputArg() + or + result.asExpr() = CryptoStreamFlow::getUseFromCreation(this, _, _).getInputArg() + } + + // 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() = 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, _, _) + } + + StreamUse getEarlierWrappedStreamUse() { + result = StreamFlow::getEarlierUse(this.getWrappedStreamCreation().getStreamArg(), _, _) + } + + StreamUse getLaterWrappedStreamUse() { + result = StreamFlow::getLaterUse(this.getWrappedStreamCreation().getStreamArg(), _, _) + } +} + +/** + * A call to either `Encrypt` or `Decrypt` on an `AesGcm`, `AesCcm`, or + * `ChaCha20Poly1305` instance. + */ +class AeadOperationInstance extends Crypto::KeyOperationInstance instanceof AeadUse { + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + // See `AeadModeAlgorithmValueConsumer` for the algorithm value consumer. + result = this + } + + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + result = this.(AeadUse).getKeyOperationSubtype() + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + result.asExpr() = AeadFlow::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() + } +} + +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/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} 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" +} 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/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/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/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 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 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 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 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..a9f1e91e8376 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/SignatureExample.cs @@ -0,0 +1,137 @@ +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 + 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..7f1714137805 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations.expected @@ -0,0 +1,15 @@ +| 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.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..0df68b3a2297 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_algorithm.expected @@ -0,0 +1,15 @@ +| 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_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..4658309c9c41 --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_keys.expected @@ -0,0 +1,15 @@ +| 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_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..d95bfcd87baf --- /dev/null +++ b/csharp/ql/test/experimental/library-tests/quantum/dotnet/signatures/sign_operations_signatures.expected @@ -0,0 +1,8 @@ +| 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 | 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()