diff --git a/cpp/ql/lib/experimental/Quantum/Language.qll b/cpp/ql/lib/experimental/Quantum/Language.qll index 2dc17f5e267a..0d0caf7f49b5 100644 --- a/cpp/ql/lib/experimental/Quantum/Language.qll +++ b/cpp/ql/lib/experimental/Quantum/Language.qll @@ -112,7 +112,7 @@ module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig { module ArtifactUniversalFlow = DataFlow::Global; -abstract class CipherOutputArtifact extends Crypto::CipherOutputArtifactInstance { +abstract class CipherOutputArtifact extends Crypto::KeyOperationOutputArtifactInstance { override predicate flowsTo(Crypto::FlowAwareElement other) { ArtifactUniversalFlow::flow(this.getOutputNode(), other.getInputNode()) } diff --git a/java/ql/lib/experimental/Quantum/JCA.qll b/java/ql/lib/experimental/Quantum/JCA.qll index 2bce7fa68438..6589cbbc9d5d 100644 --- a/java/ql/lib/experimental/Quantum/JCA.qll +++ b/java/ql/lib/experimental/Quantum/JCA.qll @@ -5,10 +5,15 @@ import semmle.code.java.controlflow.Dominance module JCAModel { import Language + import Crypto::KeyOpAlg as KeyOpAlg - abstract class JCAAlgorithmInstance extends Crypto::AlgorithmInstance { - abstract Crypto::AlgorithmValueConsumer getConsumer(); - } + abstract class CipherAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { } + + abstract class EllipticCurveAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { } + + abstract class HashAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { } + + abstract class KeyAgreementAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { } // TODO: Verify that the PBEWith% case works correctly bindingset[algo] @@ -54,7 +59,48 @@ module JCAModel { } bindingset[name] - predicate elliptic_curve_names(string name) { Crypto::isEllipticCurveAlgorithmName(name) } + predicate elliptic_curve_names(string name) { + // Note: as a one-off exception, we use the internal Crypto module implementation of `isEllipticCurveAlgorithmName` + Crypto::isEllipticCurveAlgorithmName(name) + } + + /* + * TODO: + * + * MAC Algorithms possible (JCA Default + BouncyCastle Extensions) + * + * Name Type Description + * --------------------------------------------------------------------------- + * "HmacMD5" HMAC HMAC with MD5 (not recommended) + * "HmacSHA1" HMAC HMAC with SHA-1 (not recommended) + * "HmacSHA224" HMAC HMAC with SHA-224 + * "HmacSHA256" HMAC HMAC with SHA-256 + * "HmacSHA384" HMAC HMAC with SHA-384 + * "HmacSHA512" HMAC HMAC with SHA-512 + * + * (BouncyCastle and Other Provider Extensions) + * "AESCMAC" CMAC Cipher-based MAC using AES + * "DESCMAC" CMAC CMAC with DES (legacy) + * "GMAC" GCM-based MAC Authenticates AAD only (GCM-style) + * "Poly1305" AEAD-style MAC Used with ChaCha20 + * "SipHash" Hash-based MAC Fast MAC for short inputs + * "BLAKE2BMAC" HMAC-style BLAKE2b MAC (cryptographic hash) + * "HmacRIPEMD160" HMAC HMAC with RIPEMD160 hash + */ + + bindingset[name] + predicate mac_names(string name) { + name.toUpperCase() + .matches([ + "HMAC%", "AESCMAC", "DESCMAC", "GMAC", "Poly1305", "SipHash", "BLAKE2BMAC", + "HMACRIPEMD160" + ].toUpperCase()) + } + + bindingset[name] + predicate key_agreement_names(string name) { + name.toUpperCase().matches(["DH", "EDH", "ECDH", "X25519", "X448"].toUpperCase()) + } bindingset[name] Crypto::TKeyDerivationType kdf_name_to_kdf_type(string name, string withSubstring) { @@ -65,10 +111,11 @@ module JCAModel { name.matches("PBEWith%") and result instanceof Crypto::PBES and withSubstring = name.regexpCapture("PBEWith(.*)", 1) + // TODO: add additional } bindingset[name] - Crypto::THashType hash_name_to_hash_type(string name, int digestLength) { + Crypto::THashType hash_name_to_type_known(string name, int digestLength) { name = "SHA-1" and result instanceof Crypto::SHA1 and digestLength = 160 or name = ["SHA-256", "SHA-384", "SHA-512"] and @@ -104,6 +151,77 @@ module JCAModel { digestLength = 512 // TODO: verify } + bindingset[name] + private predicate mode_name_to_type_known( + Crypto::TBlockCipherModeOfOperationType type, string name + ) { + type = Crypto::ECB() and name = "ECB" + or + type = Crypto::CBC() and name = "CBC" + or + type = Crypto::GCM() and name = "GCM" + or + type = Crypto::CTR() and name = "CTR" + or + type = Crypto::XTS() and name = "XTS" + or + type = Crypto::CCM() and name = "CCM" + or + type = Crypto::SIV() and name = "SIV" + or + type = Crypto::OCB() and name = "OCB" + } + + bindingset[name] + private predicate cipher_name_to_type_known(KeyOpAlg::TAlgorithm type, string name) { + exists(string upper | upper = name.toUpperCase() | + upper.matches("AES%") and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::AES()) + or + upper = "DES" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DES()) + or + upper = "TRIPLEDES" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::TripleDES()) + or + upper = "IDEA" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::IDEA()) + or + upper = "CAST5" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAST5()) + or + upper = "CHACHA20" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CHACHA20()) + or + upper = "RC4" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC4()) + or + upper = "RC5" and + type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC5()) + or + upper = "RSA" and + type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA()) + ) + } + + bindingset[name] + predicate mac_name_to_mac_type_known(Crypto::TMACType type, string name) { + type = Crypto::THMAC() and + name.toUpperCase().matches("HMAC%") + } + + bindingset[name] + predicate key_agreement_name_to_type_known(Crypto::TKeyAgreementType type, string name) { + type = Crypto::DH() and + name.toUpperCase() = "DH" + or + type = Crypto::EDH() and + name.toUpperCase() = "EDH" + or + type = Crypto::ECDH() and + name.toUpperCase() in ["ECDH", "X25519", "X448"] + } + /** * A `StringLiteral` in the `"ALG/MODE/PADDING"` or `"ALG"` format */ @@ -117,13 +235,7 @@ module JCAModel { string getPadding() { result = this.getValue().splitAt("/", 2) } } - class EllipticCurveStringLiteral extends StringLiteral { - EllipticCurveStringLiteral() { elliptic_curve_names(this.getValue()) } - - string getStandardEllipticCurveName() { result = this.getValue() } - } - - class CipherGetInstanceCall extends Call { + class CipherGetInstanceCall extends MethodCall { CipherGetInstanceCall() { this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "getInstance") } @@ -133,17 +245,21 @@ module JCAModel { Expr getProviderArg() { result = this.getArgument(1) } } + // TODO: handle key artifact produced by unwrap private class CipherOperationCall extends MethodCall { CipherOperationCall() { - exists(string s | s in ["doFinal", "wrap", "unwrap"] | - this.getMethod().hasQualifiedName("javax.crypto", "Cipher", s) - ) + this.getMethod() + .hasQualifiedName("javax.crypto", "Cipher", ["update", "doFinal", "wrap", "unwrap"]) } + predicate isIntermediate() { this.getMethod().getName() = "update" } + Expr getInput() { result = this.getArgument(0) } Expr getOutput() { - result = this.getArgument(3) + exists(int outputIndex | this.getMethod().getParameter(outputIndex).getName() = "output" | + result = this.getArgument(outputIndex) + ) or this.getMethod().getReturnType().hasName("byte[]") and result = this } @@ -152,32 +268,18 @@ module JCAModel { } /** - * Data-flow configuration modelling flow from a cipher string literal to a value consumer argument. + * Data-flow configuration modelling flow from a cipher string literal to a cipher algorithm consumer. */ - private module CipherAlgorithmStringToFetchConfig implements DataFlow::ConfigSig { + private module CipherAlgorithmStringToCipherConsumerConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CipherStringLiteral } predicate isSink(DataFlow::Node sink) { - exists(Crypto::AlgorithmValueConsumer consumer | sink = consumer.getInputNode()) + sink = any(CipherAlgorithmValueConsumer call).getInputNode() } } module CipherAlgorithmStringToFetchFlow = - TaintTracking::Global; - - /** - * Data-flow configuration modelling flow from a cipher string literal to a value consumer argument. - */ - private module EllipticCurveAlgorithmStringToFetchConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof EllipticCurveStringLiteral } - - predicate isSink(DataFlow::Node sink) { - exists(Crypto::AlgorithmValueConsumer consumer | sink = consumer.getInputNode()) - } - } - - module EllipticCurveAlgorithmStringToFetchFlow = - TaintTracking::Global; + TaintTracking::Global; /** * Note: padding and a mode of operation will only exist when the padding / mode (*and its type*) are determinable. @@ -188,8 +290,8 @@ module JCAModel { * * TODO: Model the case of relying on a provider default, but alert on it as a bad practice. */ - class CipherStringLiteralPaddingAlgorithmInstance extends JCAAlgorithmInstance, - CipherStringLiteralAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof CipherStringLiteral + class CipherStringLiteralPaddingAlgorithmInstance extends CipherStringLiteralAlgorithmInstance, + Crypto::PaddingAlgorithmInstance instanceof CipherStringLiteral { CipherStringLiteralPaddingAlgorithmInstance() { exists(super.getPadding()) } // TODO: provider defaults @@ -211,174 +313,29 @@ module JCAModel { } } - class CipherStringLiteralModeAlgorithmInstance extends JCAAlgorithmInstance, - CipherStringLiteralPaddingAlgorithmInstance, Crypto::ModeOfOperationAlgorithmInstance instanceof CipherStringLiteral + class CipherStringLiteralModeAlgorithmInstance extends CipherStringLiteralPaddingAlgorithmInstance, + Crypto::ModeOfOperationAlgorithmInstance instanceof CipherStringLiteral { CipherStringLiteralModeAlgorithmInstance() { exists(super.getMode()) } // TODO: provider defaults override string getRawModeAlgorithmName() { result = super.getMode() } - bindingset[name] - private predicate modeToNameMappingKnown(Crypto::TBlockCipherModeOperationType type, string name) { - type instanceof Crypto::ECB and name = "ECB" - or - type instanceof Crypto::CBC and name = "CBC" - or - type instanceof Crypto::GCM and name = "GCM" - or - type instanceof Crypto::CTR and name = "CTR" - or - type instanceof Crypto::XTS and name = "XTS" - or - type instanceof Crypto::CCM and name = "CCM" - or - type instanceof Crypto::SIV and name = "SIV" - or - type instanceof Crypto::OCB and name = "OCB" - } - - override Crypto::TBlockCipherModeOperationType getModeType() { - if this.modeToNameMappingKnown(_, super.getMode()) - then this.modeToNameMappingKnown(result, super.getMode()) + override Crypto::TBlockCipherModeOfOperationType getModeType() { + if mode_name_to_type_known(_, super.getMode()) + then mode_name_to_type_known(result, super.getMode()) else result instanceof Crypto::OtherMode } } - class KeyGeneratorGetInstanceCall extends MethodCall { - KeyGeneratorGetInstanceCall() { - this.getCallee().hasQualifiedName("javax.crypto", "KeyGenerator", "getInstance") - or - this.getCallee().hasQualifiedName("java.security", "KeyPairGenerator", "getInstance") - } - - Expr getAlgorithmArg() { result = super.getArgument(0) } - } - - // For general elliptic curves, getInstance("EC") is used - // and java.security.spec.ECGenParameterSpec("") is what sets the specific curve. - // The result of ECGenParameterSpec is passed to KeyPairGenerator.initialize - // If the curve is not specified, the default is used. - // We would trace the use of this inside a KeyPairGenerator.initialize - class ECGenParameterSpecCall extends ClassInstanceExpr { - ECGenParameterSpecCall() { - this.(ClassInstanceExpr) - .getConstructedType() - .hasQualifiedName("java.security.spec", "ECGenParameterSpec") - } - - Expr getAlgorithmArg() { result = super.getArgument(0) } - - KeyPairGeneratorInitializeCall getInitializeConsumerCall() { - exists(DataFlow::Node sink | - ECGenParameterSpecCallToInitializeFlow::flow(DataFlow::exprNode(this), sink) and - result.getAnArgument() = sink.asExpr() - ) - } - } - - abstract class KeyGenAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { - // abstract predicate flowsToKeyGenerateCallQualifier(KeyGeneratorGenerateCall sink); - abstract DataFlow::Node getResultNode(); - } - - class KeyPairGeneratorInitializeCall extends MethodCall { - KeyPairGeneratorInitializeCall() { - this.getCallee().hasQualifiedName("java.security", "KeyPairGenerator", "initialize") - } - - Expr getKeyArg() { - result = this.getArgument(0) and - result.getType() instanceof IntegralType - } - } - - private module ECGenParameterSpecCallToInitializeConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof ECGenParameterSpecCall } - - predicate isSink(DataFlow::Node sink) { - exists(KeyPairGeneratorInitializeCall c | c.getAnArgument() = sink.asExpr()) - } - } - - module ECGenParameterSpecCallToInitializeFlow = - DataFlow::Global; - - class ECGenParameterSpecAlgorithmValueConsumer extends KeyGenAlgorithmValueConsumer { - ECGenParameterSpecCall call; - - ECGenParameterSpecAlgorithmValueConsumer() { this = call.getAlgorithmArg() } - - override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } - - override DataFlow::Node getResultNode() { - // Traversing to the initialilzer directly and calling this the 'result' - // to simplify the trace. In theory you would trace from the call - // through the initializer, but we already have a trace to the initializer - // so using this instead of altering/creating data flow configs. - call.getInitializeConsumerCall().getQualifier() = result.asExpr() - } - - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - result.(JCAAlgorithmInstance).getConsumer() = this - } - } - - class KeyGeneratorGetInstanceAlgorithmValueConsumer extends KeyGenAlgorithmValueConsumer { - KeyGeneratorGetInstanceCall call; - - KeyGeneratorGetInstanceAlgorithmValueConsumer() { this = call.getAlgorithmArg() } - - override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } - - override DataFlow::Node getResultNode() { result.asExpr() = call } - - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - // The source is any instance whose consumer is this - result.(JCAAlgorithmInstance).getConsumer() = this - } - } - - class EllipticCurveStringLiteralAlgorithmInstance extends JCAAlgorithmInstance, - Crypto::EllipticCurveAlgorithmInstance instanceof StringLiteral - { - Crypto::AlgorithmValueConsumer consumer; - - EllipticCurveStringLiteralAlgorithmInstance() { - // Trace a known elliptic curve algorithm string literal to a key gen consumer - EllipticCurveAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), - consumer.getInputNode()) - } - - override Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } - - override string getRawEllipticCurveAlgorithmName() { result = super.getValue() } - - override string getStandardCurveName() { result = this.getRawEllipticCurveAlgorithmName() } - - override Crypto::TEllipticCurveType getEllipticCurveFamily() { - Crypto::isEllipticCurveAlgorithm(this.getRawEllipticCurveAlgorithmName(), _, result) - } - - override string getKeySize() { - exists(int keySize | - Crypto::isEllipticCurveAlgorithm(this.getRawEllipticCurveAlgorithmName(), keySize, _) and - result = keySize.toString() - ) - } - } - - class CipherStringLiteralAlgorithmInstance extends JCAAlgorithmInstance, - Crypto::CipherAlgorithmInstance instanceof CipherStringLiteral + class CipherStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof CipherStringLiteral { - // NOTE: this consumer is generic, but cipher algorithms can be consumed - // by getInstance as well as key generation - Crypto::AlgorithmValueConsumer consumer; + CipherAlgorithmValueConsumer consumer; CipherStringLiteralAlgorithmInstance() { CipherAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) } - override Crypto::AlgorithmValueConsumer getConsumer() { result = consumer } + CipherAlgorithmValueConsumer getConsumer() { result = consumer } override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { result = this // TODO: provider defaults @@ -388,43 +345,19 @@ module JCAModel { result = this // TODO: provider defaults } - override string getRawCipherAlgorithmName() { result = super.getValue() } + override string getRawAlgorithmName() { result = super.getValue() } - override Crypto::TCipherType getCipherFamily() { - if this.cipherNameMappingKnown(_, super.getAlgorithmName()) - then this.cipherNameMappingKnown(result, super.getAlgorithmName()) - else result instanceof Crypto::OtherCipherType + override KeyOpAlg::Algorithm getAlgorithmType() { + if cipher_name_to_type_known(_, super.getAlgorithmName()) + then cipher_name_to_type_known(result, super.getAlgorithmName()) + else result instanceof KeyOpAlg::TUnknownKeyOperationAlgorithmType } - bindingset[name] - private predicate cipherNameMappingKnown(Crypto::TCipherType type, string name) { - name = "AES" and - type instanceof Crypto::AES - or - name = "DES" and - type instanceof Crypto::DES - or - name = "TripleDES" and - type instanceof Crypto::TripleDES - or - name = "IDEA" and - type instanceof Crypto::IDEA - or - name = "CAST5" and - type instanceof Crypto::CAST5 - or - name = "ChaCha20" and - type instanceof Crypto::CHACHA20 - or - name = "RC4" and - type instanceof Crypto::RC4 - or - name = "RC5" and - type instanceof Crypto::RC5 - or - name = "RSA" and - type instanceof Crypto::RSA + override string getKeySizeFixed() { + none() // TODO: implement to handle variants such as AES-128 } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } } bindingset[input] @@ -451,13 +384,13 @@ module JCAModel { override string getRawHashAlgorithmName() { result = super.getPadding() } - override Crypto::THashType getHashFamily() { result = hash_name_to_hash_type(hashName, _) } + override Crypto::THashType getHashFamily() { result = hash_name_to_type_known(hashName, _) } - override int getDigestLength() { exists(hash_name_to_hash_type(hashName, result)) } + override int getDigestLength() { exists(hash_name_to_type_known(hashName, result)) } } - class OAEPPaddingAlgorithmInstance extends JCAAlgorithmInstance, - Crypto::OAEPPaddingAlgorithmInstance, CipherStringLiteralPaddingAlgorithmInstance + class OAEPPaddingAlgorithmInstance extends Crypto::OAEPPaddingAlgorithmInstance, + CipherStringLiteralPaddingAlgorithmInstance { override Crypto::HashAlgorithmInstance getOAEPEncodingHashAlgorithm() { result = this } @@ -469,19 +402,13 @@ module JCAModel { * * For example, in `Cipher.getInstance(algorithm)`, this class represents `algorithm`. */ - class CipherGetInstanceAlgorithmArg extends Crypto::AlgorithmValueConsumer instanceof Expr { + class CipherGetInstanceAlgorithmArg extends CipherAlgorithmValueConsumer instanceof Expr { CipherGetInstanceCall call; CipherGetInstanceAlgorithmArg() { this = call.getAlgorithmArg() } override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } - CipherStringLiteral getOrigin(string value) { - CipherAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(result), - DataFlow::exprNode(this.(Expr).getAChildExpr*())) and - value = result.getValue() - } - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this } @@ -507,133 +434,214 @@ module JCAModel { } } - private newtype TCipherModeFlowState = - TUninitializedCipherModeFlowState() or - TInitializedCipherModeFlowState(CipherInitCall call) or - TUsedCipherModeFlowState(CipherInitCall init) + signature class GetInstanceCallSig instanceof MethodCall; - abstract private class CipherModeFlowState extends TCipherModeFlowState { - string toString() { - this = TUninitializedCipherModeFlowState() and result = "uninitialized" - or - this = TInitializedCipherModeFlowState(_) and result = "initialized" - } + signature class InitCallSig instanceof MethodCall; - abstract Crypto::CipherOperationSubtype getCipherOperationMode(); + signature class UseCallSig instanceof MethodCall { + /** + * Holds if the use is not a final use, such as an `update()` call before `doFinal()` + */ + predicate isIntermediate(); } - private class UninitializedCipherModeFlowState extends CipherModeFlowState, - TUninitializedCipherModeFlowState + /** + * An generic analysis module for analyzing the `getInstance` to `initialize` to `doOperation` pattern in the JCA. + * + * For example: + * ``` + * kpg = KeyPairGenerator.getInstance(); + * kpg.initialize(...); + * kpg.generate(...); + * ``` + */ + module GetInstanceInitUseFlowAnalysis< + GetInstanceCallSig GetInstance, InitCallSig Init, UseCallSig Use> { - override Crypto::CipherOperationSubtype getCipherOperationMode() { - result instanceof Crypto::UnknownCipherOperationSubtype + newtype TFlowState = + TUninitialized() or + TInitialized(Init call) or + TIntermediateUse(Use call) + + abstract class InitFlowState extends TFlowState { + string toString() { + this = TUninitialized() and result = "Uninitialized" + or + this = TInitialized(_) and result = "Initialized" + // TODO: add intermediate use + } } - } - private class InitializedCipherModeFlowState extends CipherModeFlowState, - TInitializedCipherModeFlowState - { - CipherInitCall call; - DataFlow::Node node1; - DataFlow::Node node2; - Crypto::CipherOperationSubtype mode; + class UninitializedFlowState extends InitFlowState, TUninitialized { } - InitializedCipherModeFlowState() { - this = TInitializedCipherModeFlowState(call) and - DataFlow::localFlowStep(node1, node2) and - node2.asExpr() = call.getQualifier() and - // TODO: does this make this predicate inefficient as it binds with anything? - not node1.asExpr() = call.getQualifier() and - mode = call.getCipherOperationModeType() - } + class InitializedFlowState extends InitFlowState, TInitialized { + Init call; + DataFlow::Node node1; + DataFlow::Node node2; - CipherInitCall getInitCall() { result = call } + InitializedFlowState() { + this = TInitialized(call) and + node2.asExpr() = call.(MethodCall).getQualifier() and + DataFlow::localFlowStep(node1, node2) and + node1 != node2 + } - DataFlow::Node getFstNode() { result = node1 } + Init getInitCall() { result = call } - /** - * Returns the node *to* which the state-changing step occurs - */ - DataFlow::Node getSndNode() { result = node2 } + DataFlow::Node getFstNode() { result = node1 } - override Crypto::CipherOperationSubtype getCipherOperationMode() { result = mode } - } + DataFlow::Node getSndNode() { result = node2 } + } - /** - * Trace from cipher initialization to a cryptographic operation, - * specifically `Cipher.doFinal()`, `Cipher.wrap()`, or `Cipher.unwrap()`. - * - * TODO: handle `Cipher.update()` - */ - private module CipherGetInstanceToCipherOperationConfig implements DataFlow::StateConfigSig { - class FlowState = TCipherModeFlowState; + class IntermediateUseState extends InitFlowState, TIntermediateUse { + Use call; + DataFlow::Node node1; + DataFlow::Node node2; + + IntermediateUseState() { + this = TIntermediateUse(call) and + call.isIntermediate() and + node1.asExpr() = call.(MethodCall).getQualifier() and + node2 = node1 + } + + Use getUseCall() { result = call } + + DataFlow::Node getFstNode() { result = node1 } - predicate isSource(DataFlow::Node src, FlowState state) { - state instanceof UninitializedCipherModeFlowState and - src.asExpr() instanceof CipherGetInstanceCall + DataFlow::Node getSndNode() { result = node2 } } - predicate isSink(DataFlow::Node sink, FlowState state) { none() } // TODO: document this, but this is intentional (avoid cross products?) + module GetInstanceToInitToUseConfig implements DataFlow::StateConfigSig { + class FlowState = InitFlowState; - predicate isSink(DataFlow::Node sink) { - exists(CipherOperationCall c | c.getQualifier() = sink.asExpr()) + predicate isSource(DataFlow::Node src, FlowState state) { + state instanceof UninitializedFlowState and + src.asExpr() instanceof GetInstance + or + src = state.(InitializedFlowState).getSndNode() + or + src = state.(IntermediateUseState).getSndNode() + } + + // TODO: document this, but this is intentional (avoid cross products?) + predicate isSink(DataFlow::Node sink, FlowState state) { none() } + + predicate isSink(DataFlow::Node sink) { + exists(Init c | c.(MethodCall).getQualifier() = sink.asExpr()) + or + exists(Use c | not c.isIntermediate() and c.(MethodCall).getQualifier() = sink.asExpr()) + } + + predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + ) { + state1 = state1 and + ( + node1 = state2.(InitializedFlowState).getFstNode() and + node2 = state2.(InitializedFlowState).getSndNode() + or + node1 = state2.(IntermediateUseState).getFstNode() and + node2 = state2.(IntermediateUseState).getSndNode() + ) + } + + predicate isBarrier(DataFlow::Node node, FlowState state) { + exists(CipherInitCall call | node.asExpr() = call.getQualifier() | + state instanceof UninitializedFlowState + or + state.(InitializedFlowState).getInitCall() != call + ) + } + } + + module GetInstanceToInitToUseFlow = DataFlow::GlobalWithState; + + GetInstance getInstantiationFromUse( + Use use, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink + ) { + src.getNode().asExpr() = result and + sink.getNode().asExpr() = use.(MethodCall).getQualifier() and + GetInstanceToInitToUseFlow::flowPath(src, sink) } - predicate isAdditionalFlowStep( - DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + GetInstance getInstantiationFromInit( + Init init, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink ) { - state1 = state1 and - node1 = state2.(InitializedCipherModeFlowState).getFstNode() and - node2 = state2.(InitializedCipherModeFlowState).getSndNode() + src.getNode().asExpr() = result and + sink.getNode().asExpr() = init.(MethodCall).getQualifier() and + GetInstanceToInitToUseFlow::flowPath(src, sink) } - predicate isBarrier(DataFlow::Node node, FlowState state) { - exists(CipherInitCall call | node.asExpr() = call.getQualifier() | - state instanceof UninitializedCipherModeFlowState - or - state.(InitializedCipherModeFlowState).getInitCall() != call - ) + Init getInitFromUse( + Use use, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink + ) { + src.getNode().asExpr() = result.(MethodCall).getQualifier() and + sink.getNode().asExpr() = use.(MethodCall).getQualifier() and + GetInstanceToInitToUseFlow::flowPath(src, sink) + } + + predicate hasInit(Use use) { exists(getInitFromUse(use, _, _)) } + + Use getAnIntermediateUseFromFinalUse( + Use final, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink + ) { + not final.isIntermediate() and + result.isIntermediate() and + src.getNode().asExpr() = result.(MethodCall).getQualifier() and + sink.getNode().asExpr() = final.(MethodCall).getQualifier() and + GetInstanceToInitToUseFlow::flowPath(src, sink) } } - module CipherGetInstanceToCipherOperationFlow = - DataFlow::GlobalWithState; + module CipherFlowAnalysisImpl = + GetInstanceInitUseFlowAnalysis; - class CipherOperationInstance extends Crypto::CipherOperationInstance instanceof Call { - Crypto::CipherOperationSubtype mode; - CipherGetInstanceToCipherOperationFlow::PathNode sink; - CipherOperationCall doFinalize; - CipherGetInstanceAlgorithmArg consumer; + module CipherFlow = CipherFlowAnalysisImpl::GetInstanceToInitToUseFlow; - CipherOperationInstance() { - exists(CipherGetInstanceToCipherOperationFlow::PathNode src, CipherGetInstanceCall getCipher | - CipherGetInstanceToCipherOperationFlow::flowPath(src, sink) and - src.getNode().asExpr() = getCipher and - sink.getNode().asExpr() = doFinalize.getQualifier() and - sink.getState().(CipherModeFlowState).getCipherOperationMode() = mode and - this = doFinalize and - consumer = getCipher.getAlgorithmArg() - ) + Crypto::KeyOperationSubtype getKeyOperationSubtypeFromState( + CipherFlowAnalysisImpl::InitFlowState state + ) { + state instanceof CipherFlowAnalysisImpl::UninitializedFlowState and + result = Crypto::TUnknownKeyOperationMode() + or + exists(CipherInitCall call | state = CipherFlowAnalysisImpl::TInitialized(call) | + result = call.getCipherOperationModeType() + ) + } + + class CipherOperationInstance extends Crypto::KeyOperationInstance instanceof CipherOperationCall { + override Crypto::KeyOperationSubtype getKeyOperationSubtype() { + if CipherFlowAnalysisImpl::hasInit(this) + then result = CipherFlowAnalysisImpl::getInitFromUse(this, _, _).getCipherOperationModeType() + else result = Crypto::TUnknownKeyOperationMode() } - override Crypto::CipherOperationSubtype getCipherOperationSubtype() { result = mode } + CipherGetInstanceCall getInstantiationCall() { + result = CipherFlowAnalysisImpl::getInstantiationFromUse(this, _, _) + } + + CipherInitCall getInitCall() { result = CipherFlowAnalysisImpl::getInitFromUse(this, _, _) } override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { - result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg() + result.asExpr() = this.getInitCall().getNonceArg() } override Crypto::ConsumerInputDataFlowNode getInputConsumer() { - result = doFinalize.getMessageArg() + result = super.getMessageArg() or + result = CipherFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getMessageArg() } override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { - result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getKeyArg() + result.asExpr() = this.getInitCall().getKeyArg() } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = consumer } + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result = this.getInstantiationCall().getAlgorithmArg() + } - override Crypto::CipherOutputArtifactInstance getOutputArtifact() { - result = doFinalize.getOutput() + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = super.getOutput() } } @@ -677,7 +685,6 @@ module JCAModel { } } - // e.g., getPublic or getPrivate class KeyAdditionalFlowSteps extends MethodCall { KeyAdditionalFlowSteps() { this.getCallee().hasQualifiedName("java.security", "KeyPair", "getPublic") @@ -734,15 +741,15 @@ module JCAModel { module JavaxCipherModeAccessToInitFlow = DataFlow::Global; private predicate cipher_mode_str_to_cipher_mode_known( - string mode, Crypto::CipherOperationSubtype cipher_mode + string mode, Crypto::KeyOperationSubtype cipher_mode ) { - mode = "ENCRYPT_MODE" and cipher_mode instanceof Crypto::EncryptionSubtype + mode = "ENCRYPT_MODE" and cipher_mode = Crypto::TEncryptMode() or - mode = "WRAP_MODE" and cipher_mode instanceof Crypto::WrapSubtype + mode = "WRAP_MODE" and cipher_mode = Crypto::TWrapMode() or - mode = "DECRYPT_MODE" and cipher_mode instanceof Crypto::DecryptionSubtype + mode = "DECRYPT_MODE" and cipher_mode = Crypto::TDecryptMode() or - mode = "UNWRAP_MODE" and cipher_mode instanceof Crypto::UnwrapSubtype + mode = "UNWRAP_MODE" and cipher_mode = Crypto::TUnwrapMode() } class CipherInitCall extends MethodCall { @@ -765,10 +772,10 @@ module JCAModel { ) } - Crypto::CipherOperationSubtype getCipherOperationModeType() { + Crypto::KeyOperationSubtype getCipherOperationModeType() { if cipher_mode_str_to_cipher_mode_known(this.getModeOrigin().getField().getName(), _) then cipher_mode_str_to_cipher_mode_known(this.getModeOrigin().getField().getName(), result) - else result instanceof Crypto::UnknownCipherOperationSubtype + else result = Crypto::TUnknownKeyOperationMode() } Expr getKeyArg() { @@ -787,27 +794,26 @@ module JCAModel { override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } } - class CipherOperationCallOutput extends Crypto::CipherOutputArtifactInstance { - CipherOperationCallOutput() { this = any(CipherOperationCall call).getOutput() } - - override DataFlow::Node getOutputNode() { result.asExpr() = this } - } + /* + * Hash Functions + */ - // flow config from a known hash algorithm literal to MessageDigest.getInstance + /** + * Flow from a known hash algorithm name to a `MessageDigest.getInstance(sink)` call. + */ module KnownHashAlgorithmLiteralToMessageDigestConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { hash_names(src.asExpr().(StringLiteral).getValue()) } predicate isSink(DataFlow::Node sink) { - exists(MessageDigestGetInstanceCall call | sink.asExpr() = call.getAlgorithmArg()) + exists(HashAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) } } module KnownHashAlgorithmLiteralToMessageDigestFlow = DataFlow::Global; - class KnownHashAlgorithm extends JCAAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof StringLiteral - { - MessageDigestAlgorithmValueConsumer consumer; + class KnownHashAlgorithm extends Crypto::HashAlgorithmInstance instanceof StringLiteral { + HashAlgorithmValueConsumer consumer; KnownHashAlgorithm() { hash_names(this.getValue()) and @@ -815,23 +821,28 @@ module JCAModel { consumer.getInputNode()) } - override MessageDigestAlgorithmValueConsumer getConsumer() { result = consumer } + HashAlgorithmValueConsumer getConsumer() { result = consumer } override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() } override Crypto::THashType getHashFamily() { - result = hash_name_to_hash_type(this.getRawHashAlgorithmName(), _) + result = hash_name_to_type_known(this.getRawHashAlgorithmName(), _) } override int getDigestLength() { - exists(hash_name_to_hash_type(this.getRawHashAlgorithmName(), result)) + exists(hash_name_to_type_known(this.getRawHashAlgorithmName(), result)) } } - class MessageDigestAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { + module MessageDigestFlowAnalysisImpl = + GetInstanceInitUseFlowAnalysis; + + class MessageDigestGetInstanceAlgorithmValueConsumer extends HashAlgorithmValueConsumer { MessageDigestGetInstanceCall call; - MessageDigestAlgorithmValueConsumer() { this = call.getAlgorithmArg() } + MessageDigestGetInstanceAlgorithmValueConsumer() { this = call.getAlgorithmArg() } + + MessageDigestGetInstanceCall getInstantiationCall() { result = call } override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } @@ -846,17 +857,18 @@ module JCAModel { } Expr getAlgorithmArg() { result = this.getArgument(0) } - - DigestHashOperation getDigestCall() { - DigestGetInstanceToDigestFlow::flow(DataFlow::exprNode(this), - DataFlow::exprNode(result.(DigestCall).getQualifier())) - } } class DigestCall extends MethodCall { - DigestCall() { this.getCallee().hasQualifiedName("java.security", "MessageDigest", "digest") } + DigestCall() { + this.getCallee().hasQualifiedName("java.security", "MessageDigest", ["update", "digest"]) + } Expr getDigestArtifactOutput() { result = this } + + Expr getInputArg() { result = this.getArgument(0) } + + predicate isIntermediate() { this.getMethod().getName() = "update" } } // flow config from MessageDigest.getInstance to MessageDigest.digest @@ -870,255 +882,280 @@ module JCAModel { module DigestGetInstanceToDigestFlow = DataFlow::Global; - class DigestArtifact extends Crypto::DigestArtifactInstance { - DigestArtifact() { this = any(DigestCall call).getDigestArtifactOutput() } - - override DataFlow::Node getOutputNode() { result.asExpr() = this } - } - class DigestHashOperation extends Crypto::HashOperationInstance instanceof DigestCall { - override Crypto::DigestArtifactInstance getDigestArtifact() { - result = this.(DigestCall).getDigestArtifactOutput() + DigestHashOperation() { not super.isIntermediate() } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { + result.asExpr() = super.getDigestArtifactOutput() } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - exists(MessageDigestGetInstanceCall call | - call.getDigestCall() = this and result = call.getAlgorithmArg() - ) + MessageDigestFlowAnalysisImpl::getInstantiationFromUse(this, _, _) = + result.(MessageDigestGetInstanceAlgorithmValueConsumer).getInstantiationCall() } - } - // flow from instance created by getInstance to generateKey - module KeyGeneratorAlgValueConsumerToGenerateConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { - exists(KeyGenAlgorithmValueConsumer consumer | consumer.getResultNode() = src) + override Crypto::ConsumerInputDataFlowNode getInputConsumer() { + result.asExpr() = super.getInputArg() or + result.asExpr() = + MessageDigestFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getInputArg() } + } - predicate isSink(DataFlow::Node sink) { - exists(KeyGeneratorGenerateCall call | sink.asExpr() = call.(MethodCall).getQualifier()) - } - } - - module KeyGeneratorAlgValueConsumerToGenerateFlow = - DataFlow::Global; - - class KeyGeneratorGenerateCall extends Crypto::KeyGenerationOperationInstance instanceof MethodCall - { - Crypto::KeyArtifactType type; - - KeyGeneratorGenerateCall() { - this.getCallee().hasQualifiedName("javax.crypto", "KeyGenerator", "generateKey") and - type instanceof Crypto::TSymmetricKeyType - or - this.getCallee().hasQualifiedName("java.security", "KeyPairGenerator", "generateKeyPair") and - type instanceof Crypto::TAsymmetricKeyType - } + /* + * Key Generation + */ - override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result.asExpr() = this } + module KeyGeneratorFlowAnalysisImpl = + GetInstanceInitUseFlowAnalysis; - override Crypto::KeyArtifactType getOutputKeyType() { result = type } + module KeyGeneratorFlow = KeyGeneratorFlowAnalysisImpl::GetInstanceToInitToUseFlow; - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - exists(KeyGenAlgorithmValueConsumer consumer | - KeyGeneratorAlgValueConsumerToGenerateFlow::flow(consumer.getResultNode(), - DataFlow::exprNode(this.(Call).getQualifier())) and - result = consumer + abstract class KeyGeneratorParameterSpecClassInstanceExpr extends ClassInstanceExpr { + KeyGeneratorInitCall getAnInitCallUse() { + exists(DataFlow::Node sink | + KeyGeneratorParameterSpecToInitializeFlow::flow(DataFlow::exprNode(this), sink) and + result.getAlgorithmParameterSpecArg() = sink.asExpr() ) } + } - Crypto::AlgorithmInstance getAKnownAlgorithm() { - result = this.getAnAlgorithmValueConsumer().getAKnownAlgorithmSource() + class DHGenParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr { + DHGenParameterSpecInstance() { + this.(ClassInstanceExpr) + .getConstructedType() + .hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec") } - override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() } + Expr getPrimeSizeArg() { result = this.getArgument(0) } - override string getKeySizeFixed() { none() } + Expr getExponentSizeArg() { result = this.getArgument(1) } } - /* - * TODO: - * - * MAC Algorithms possible (JCA Default + BouncyCastle Extensions) - * - * Name Type Description - * --------------------------------------------------------------------------- - * "HmacMD5" HMAC HMAC with MD5 (not recommended) - * "HmacSHA1" HMAC HMAC with SHA-1 (not recommended) - * "HmacSHA224" HMAC HMAC with SHA-224 - * "HmacSHA256" HMAC HMAC with SHA-256 - * "HmacSHA384" HMAC HMAC with SHA-384 - * "HmacSHA512" HMAC HMAC with SHA-512 - * - * (BouncyCastle and Other Provider Extensions) - * "AESCMAC" CMAC Cipher-based MAC using AES - * "DESCMAC" CMAC CMAC with DES (legacy) - * "GMAC" GCM-based MAC Authenticates AAD only (GCM-style) - * "Poly1305" AEAD-style MAC Used with ChaCha20 - * "SipHash" Hash-based MAC Fast MAC for short inputs - * "BLAKE2BMAC" HMAC-style BLAKE2b MAC (cryptographic hash) - * "HmacRIPEMD160" HMAC HMAC with RIPEMD160 hash - */ + class DSAParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr { + DSAParameterSpecInstance() { + this.(ClassInstanceExpr) + .getConstructedType() + .hasQualifiedName("java.security.spec", "DSAParameterSpec") + } - bindingset[name] - predicate mac_names(string name) { - name.toUpperCase() - .matches([ - "HMAC%", "AESCMAC", "DESCMAC", "GMAC", "Poly1305", "SipHash", "BLAKE2BMAC", - "HMACRIPEMD160" - ].toUpperCase()) - } + Expr getPArg() { result = this.getArgument(0) } - bindingset[name] - predicate mac_name_to_mac_type_known(Crypto::TMACType type, string name) { - type instanceof Crypto::THMAC and - name.toUpperCase().matches("HMAC%") - } + Expr getQArg() { result = this.getArgument(1) } - module MACKnownAlgorithmToConsumerConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { mac_names(src.asExpr().(StringLiteral).getValue()) } + Expr getSeedLenArg() { result = this.getArgument(2) } + } - predicate isSink(DataFlow::Node sink) { - exists(MACGetInstanceCall call | sink.asExpr() = call.getAlgorithmArg()) + class ECGenParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr { + ECGenParameterSpecInstance() { + this.(ClassInstanceExpr) + .getConstructedType() + .hasQualifiedName("java.security.spec", "ECGenParameterSpec") } - } - module MACKnownAlgorithmToConsumerFlow = DataFlow::Global; + Expr getCurveNameArg() { result = this.getArgument(0) } - module MACGetInstanceToMACOperationFlowConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { src.asExpr() instanceof MACGetInstanceCall } + Expr getRandomNumberGeneratorArg() { result = this.getArgument(1) } + } - predicate isSink(DataFlow::Node sink) { - exists(MACOperationCall call | sink.asExpr() = call.(MethodCall).getQualifier()) or - exists(MACInitCall call | sink.asExpr() = call.(MethodCall).getQualifier()) + class RSAGenParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr { + RSAGenParameterSpecInstance() { + this.(ClassInstanceExpr) + .getConstructedType() + .hasQualifiedName("java.security.spec", "RSAGenParameterSpec") } - } - module MACGetInstanceToMACOperationFlow = - DataFlow::Global; + Expr getKeySizeArg() { result = this.getArgument(0) } + } - module MACInitCallToMACOperationFlowConfig implements DataFlow::ConfigSig { - // TODO: use flow state with one config + private module KeyGeneratorParameterSpecToInitializeConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { - exists(MACInitCall init | src.asExpr() = init.getQualifier()) + src.asExpr() instanceof KeyGeneratorParameterSpecClassInstanceExpr } predicate isSink(DataFlow::Node sink) { - exists(MACOperationCall call | sink.asExpr() = call.(MethodCall).getQualifier()) + exists(KeyGeneratorInitCall c | + c.getKeyType() = Crypto::TAsymmetricKeyType() and + c.getArgument(0) = sink.asExpr() + ) } } - module MACInitCallToMACOperationFlow = DataFlow::Global; + module KeyGeneratorParameterSpecToInitializeFlow = + DataFlow::Global; - class KnownMACAlgorithm extends JCAAlgorithmInstance, Crypto::MACAlgorithmInstance instanceof StringLiteral + class ECGenParameterSpecClassInstanceExpr extends KeyGeneratorParameterSpecClassInstanceExpr { + ECGenParameterSpecClassInstanceExpr() { + this.(ClassInstanceExpr) + .getConstructedType() + .hasQualifiedName("java.security.spec", "ECGenParameterSpec") + } + + Expr getAlgorithmArg() { result = this.getArgument(0) } + } + + class KeyGenerationAlgorithmValueConsumer extends CipherAlgorithmValueConsumer, + KeyAgreementAlgorithmValueConsumer, EllipticCurveAlgorithmValueConsumer instanceof Expr { - MACGetInstanceAlgorithmValueConsumer consumer; + KeyGeneratorGetInstanceCall instantiationCall; - KnownMACAlgorithm() { - mac_names(this.getValue()) and - MACKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) + KeyGenerationAlgorithmValueConsumer() { + // This is only an algorithm value consumer if it accepts a spec rather than a key size (integral) + this = instantiationCall.getAlgorithmArg() and not super.getType() instanceof IntegralType + or + // However, for general elliptic curves, getInstance("EC") is used + // and java.security.spec.ECGenParameterSpec("") is what sets the specific curve. + // If init is not specified, the default (P-) + // The result of ECGenParameterSpec is passed to KeyPairGenerator.initialize + // If the curve is not specified, the default is used. + // We would trace the use of this inside a KeyPairGenerator.initialize + exists(KeyGeneratorInitCall initCall, ECGenParameterSpecClassInstanceExpr spec | + KeyGeneratorFlow::flow(DataFlow::exprNode(instantiationCall), + DataFlow::exprNode(initCall.getQualifier())) and + spec.getAnInitCallUse() = initCall and + spec.getAlgorithmArg() = this + ) } - override MACGetInstanceAlgorithmValueConsumer getConsumer() { result = consumer } + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } - override string getRawMACAlgorithmName() { result = super.getValue() } + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this or + result.(KeyAgreementStringLiteralAlgorithmInstance).getConsumer() = this or + result.(EllipticCurveStringLiteralInstance).getConsumer() = this + } - override Crypto::TMACType getMACType() { - if mac_name_to_mac_type_known(_, super.getValue()) - then mac_name_to_mac_type_known(result, super.getValue()) - else result instanceof Crypto::TOtherMACType + KeyGeneratorGetInstanceCall getInstantiationCall() { result = instantiationCall } + } + + // TODO: Link getAlgorithm from KeyPairGenerator to algorithm instances or AVCs? High priority. + class KeyGeneratorGetInstanceCall extends MethodCall { + KeyGeneratorGetInstanceCall() { + this.getCallee().hasQualifiedName("javax.crypto", "KeyGenerator", "getInstance") + or + this.getCallee().hasQualifiedName("java.security", "KeyPairGenerator", "getInstance") } + + Expr getAlgorithmArg() { result = super.getArgument(0) } } - class MACGetInstanceCall extends MethodCall { - MACGetInstanceCall() { this.getCallee().hasQualifiedName("javax.crypto", "Mac", "getInstance") } + class KeyGeneratorInitCall extends MethodCall { + Crypto::TKeyArtifactType keyType; - Expr getAlgorithmArg() { result = this.getArgument(0) } + KeyGeneratorInitCall() { + this.getCallee().hasQualifiedName("java.security", "KeyPairGenerator", "initialize") and + keyType = Crypto::TAsymmetricKeyType() + or + this.getCallee().hasQualifiedName("javax.crypto", "KeyGenerator", ["init", "initialize"]) and + keyType = Crypto::TSymmetricKeyType() + } - MACOperationCall getOperation() { - MACGetInstanceToMACOperationFlow::flow(DataFlow::exprNode(this), - DataFlow::exprNode(result.(MethodCall).getQualifier())) + Crypto::TKeyArtifactType getKeyType() { result = keyType } + + Expr getAlgorithmParameterSpecArg() { + result = this.getArgument(0) and + this.getMethod().getParameterType(0).hasName("AlgorithmParameterSpec") } - MACInitCall getInitCall() { - MACGetInstanceToMACOperationFlow::flow(DataFlow::exprNode(this), - DataFlow::exprNode(result.getQualifier())) + Expr getKeySizeArg() { + result = this.getArgument(0) and + this.getMethod().getParameterType(0) instanceof IntegralType + } + + Expr getRandomnessSourceArg() { + exists(int index | + this.getMethod().getParameterType(index).hasName("SecureRandom") and + result = this.getArgument(index) + ) } } - class MACInitCall extends MethodCall { - MACInitCall() { this.getCallee().hasQualifiedName("javax.crypto", "Mac", "init") } + class KeyGeneratorGenerateCall extends Crypto::KeyGenerationOperationInstance instanceof MethodCall + { + Crypto::KeyArtifactType type; - Expr getKeyArg() { - result = this.getArgument(0) and this.getMethod().getParameterType(0).hasName("Key") + KeyGeneratorGenerateCall() { + this.getCallee().hasQualifiedName("javax.crypto", "KeyGenerator", "generateKey") and + type instanceof Crypto::TSymmetricKeyType + or + this.getCallee() + .hasQualifiedName("java.security", "KeyPairGenerator", ["generateKeyPair", "genKeyPair"]) and + type instanceof Crypto::TAsymmetricKeyType } - MACOperationCall getOperation() { - MACInitCallToMACOperationFlow::flow(DataFlow::exprNode(this.getQualifier()), - DataFlow::exprNode(result.(MethodCall).getQualifier())) - } - } + predicate isIntermediate() { none() } - class MACGetInstanceAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { - MACGetInstanceCall call; + override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result.asExpr() = this } - MACGetInstanceAlgorithmValueConsumer() { this = call.getAlgorithmArg() } + override Crypto::KeyArtifactType getOutputKeyType() { result = type } - override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + KeyGeneratorFlowAnalysisImpl::getInstantiationFromUse(this, _, _) = + result.(KeyGenerationAlgorithmValueConsumer).getInstantiationCall() + } - override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { - exists(KnownMACAlgorithm l | l.getConsumer() = this and result = l) + Crypto::AlgorithmInstance getAKnownAlgorithm() { + result = this.getAnAlgorithmValueConsumer().getAKnownAlgorithmSource() } + + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { + KeyGeneratorFlowAnalysisImpl::getInitFromUse(this, _, _).getKeySizeArg() = result.asExpr() + } + + override string getKeySizeFixed() { none() } } - class MACOperationCall extends Crypto::MACOperationInstance instanceof MethodCall { - Expr output; + class KeyGeneratorCipherAlgorithm extends CipherStringLiteralAlgorithmInstance { + KeyGeneratorCipherAlgorithm() { consumer instanceof KeyGenerationAlgorithmValueConsumer } - MACOperationCall() { - super.getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Mac") and - ( - super.getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])"]) and this = output - or - super.getMethod().hasStringSignature("doFinal(byte[], int)") and - this.getArgument(0) = output + override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { + exists(KeyGeneratorGetInstanceCall getInstance, KeyGeneratorInitCall init | + getInstance = + this.getConsumer().(KeyGenerationAlgorithmValueConsumer).getInstantiationCall() and + getInstance = KeyGeneratorFlowAnalysisImpl::getInstantiationFromInit(init, _, _) and + init.getKeySizeArg() = result.asExpr() ) } - override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - exists(MACGetInstanceCall instantiation | - instantiation.getOperation() = this and result = instantiation.getAlgorithmArg() + predicate isOnlyConsumedByKeyGen() { + forall(Crypto::AlgorithmValueConsumer c | + c = this.getConsumer() and + c instanceof KeyGenerationAlgorithmValueConsumer ) } - override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { - exists(MACGetInstanceCall instantiation, MACInitCall initCall | - instantiation.getOperation() = this and - initCall.getOperation() = this and - instantiation.getInitCall() = initCall and - result.asExpr() = initCall.getKeyArg() - ) - } + override predicate shouldHaveModeOfOperation() { this.isOnlyConsumedByKeyGen() } - override Crypto::ConsumerInputDataFlowNode getMessageConsumer() { - result.asExpr() = super.getArgument(0) and - super.getMethod().getParameterType(0).hasName("byte[]") - } + override predicate shouldHavePaddingScheme() { this.isOnlyConsumedByKeyGen() } } - module SecretKeyFactoryGetInstanceToGenerateSecretFlowConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node src) { - exists(SecretKeyFactoryGetInstanceCall call | src.asExpr() = call) - } + /* + * Key Derivation Functions (KDFs) + */ - predicate isSink(DataFlow::Node sink) { - exists(SecretKeyFactoryGenerateSecretCall call | - sink.asExpr() = call.(MethodCall).getQualifier() - ) + class KeySpecInstantiation extends ClassInstanceExpr { + KeySpecInstantiation() { + this.getConstructedType() + .hasQualifiedName("javax.crypto.spec", + ["PBEKeySpec", "SecretKeySpec", "PBEKeySpec", "DESedeKeySpec"]) } + + Expr getPasswordArg() { result = this.getArgument(0) } } - module PBEKeySpecInstantiationToGenerateSecretFlowConfig implements DataFlow::ConfigSig { + class PBEKeySpecInstantiation extends KeySpecInstantiation { + PBEKeySpecInstantiation() { this.getConstructedType().hasName("PBEKeySpec") } + + Expr getSaltArg() { result = this.getArgument(1) } + + Expr getIterationCountArg() { result = this.getArgument(2) } + + Expr getKeyLengthArg() { result = this.getArgument(3) } + } + + module KeySpecInstantiationToGenerateSecretFlowConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node src) { - exists(PBEKeySpecInstantiation call | src.asExpr() = call) + exists(KeySpecInstantiation call | src.asExpr() = call) } predicate isSink(DataFlow::Node sink) { @@ -1134,28 +1171,21 @@ module JCAModel { } } - module SecretKeyFactoryGetInstanceToGenerateSecretFlow = - DataFlow::Global; - - module PBEKeySpecInstantiationToGenerateSecretFlow = - DataFlow::Global; + module KeySpecInstantiationToGenerateSecretFlow = + DataFlow::Global; module KDFAlgorithmStringToGetInstanceFlow = DataFlow::Global; - class PBEKeySpecInstantiation extends ClassInstanceExpr { - PBEKeySpecInstantiation() { - this.getConstructedType().hasQualifiedName("javax.crypto.spec", "PBEKeySpec") - } - - Expr getPasswordArg() { result = this.getArgument(0) } - - Expr getSaltArg() { result = this.getArgument(1) } + class DUMMY_UNUSED_METHODCALL extends MethodCall { + DUMMY_UNUSED_METHODCALL() { none() } + } - Expr getIterationCountArg() { result = this.getArgument(2) } + module SecretKeyFactoryFlowAnalysisImpl = + GetInstanceInitUseFlowAnalysis; - Expr getKeyLengthArg() { result = this.getArgument(3) } - } + module SecretKeyFactoryFlow = SecretKeyFactoryFlowAnalysisImpl::GetInstanceToInitToUseFlow; class SecretKeyFactoryGetInstanceCall extends MethodCall { SecretKeyFactoryGetInstanceCall() { @@ -1163,15 +1193,19 @@ module JCAModel { } Expr getAlgorithmArg() { result = this.getArgument(0) } + } - SecretKeyFactoryGenerateSecretCall getOperation() { - SecretKeyFactoryGetInstanceToGenerateSecretFlow::flow(DataFlow::exprNode(this), - DataFlow::exprNode(result.(MethodCall).getQualifier())) + class SecretKeyFactoryGenerateSecretCall extends MethodCall { + SecretKeyFactoryGenerateSecretCall() { + this.getCallee().hasQualifiedName("javax.crypto", "SecretKeyFactory", "generateSecret") } + + Expr getKeySpecArg() { result = this.getArgument(0) } + + predicate isIntermediate() { none() } } - class KDFAlgorithmStringLiteral extends JCAAlgorithmInstance, - Crypto::KeyDerivationAlgorithmInstance instanceof StringLiteral + class KDFAlgorithmStringLiteral extends Crypto::KeyDerivationAlgorithmInstance instanceof StringLiteral { SecretKeyFactoryKDFAlgorithmValueConsumer consumer; @@ -1186,10 +1220,10 @@ module JCAModel { result = kdf_name_to_kdf_type(super.getValue(), _) } - override SecretKeyFactoryKDFAlgorithmValueConsumer getConsumer() { result = consumer } + SecretKeyFactoryKDFAlgorithmValueConsumer getConsumer() { result = consumer } } - class PBKDF2AlgorithmStringLiteral extends JCAAlgorithmInstance, KDFAlgorithmStringLiteral, + class PBKDF2AlgorithmStringLiteral extends KDFAlgorithmStringLiteral, Crypto::PBKDF2AlgorithmInstance, Crypto::HMACAlgorithmInstance, Crypto::HashAlgorithmInstance, Crypto::AlgorithmValueConsumer { @@ -1200,11 +1234,11 @@ module JCAModel { override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this } override Crypto::THashType getHashFamily() { - result = hash_name_to_hash_type(this.getRawHashAlgorithmName(), _) + result = hash_name_to_type_known(this.getRawHashAlgorithmName(), _) } override int getDigestLength() { - exists(hash_name_to_hash_type(this.getRawHashAlgorithmName(), result)) + exists(hash_name_to_type_known(this.getRawHashAlgorithmName(), result)) } override string getRawMACAlgorithmName() { @@ -1237,51 +1271,41 @@ module JCAModel { SecretKeyFactoryGetInstanceCall getInstantiation() { result = call } } - class SecretKeyFactoryGenerateSecretCall extends Crypto::KeyDerivationOperationInstance instanceof MethodCall + class KeyDerivationOperationCall extends Crypto::KeyDerivationOperationInstance instanceof SecretKeyFactoryGenerateSecretCall { - SecretKeyFactoryGenerateSecretCall() { - super.getCallee().hasQualifiedName("javax.crypto", "SecretKeyFactory", "generateSecret") - } - - Expr getKeySpecArg() { - result = super.getArgument(0) and - super.getMethod().getParameterType(0).hasName("KeySpec") - } + KeyDerivationOperationCall() { not super.isIntermediate() } - PBEKeySpecInstantiation getInstantiation() { - PBEKeySpecInstantiationToGenerateSecretFlow::flow(DataFlow::exprNode(result), - DataFlow::exprNode(this.getKeySpecArg())) + KeySpecInstantiation getKeySpecInstantiation() { + KeySpecInstantiationToGenerateSecretFlow::flow(DataFlow::exprNode(result), + DataFlow::exprNode(super.getKeySpecArg())) } override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { - exists(SecretKeyFactoryGetInstanceCall instantiation | - instantiation.getOperation() = this and result = instantiation.getAlgorithmArg() - ) + result.(SecretKeyFactoryKDFAlgorithmValueConsumer).getInstantiation() = + SecretKeyFactoryFlowAnalysisImpl::getInstantiationFromUse(this, _, _) } override Crypto::ConsumerInputDataFlowNode getSaltConsumer() { - result.asExpr() = this.getInstantiation().getSaltArg() + result.asExpr() = this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getSaltArg() } override Crypto::ConsumerInputDataFlowNode getInputConsumer() { - result.asExpr() = this.getInstantiation().getPasswordArg() + result.asExpr() = this.getKeySpecInstantiation().getPasswordArg() } override Crypto::ConsumerInputDataFlowNode getIterationCountConsumer() { - result.asExpr() = this.getInstantiation().getIterationCountArg() + result.asExpr() = + this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getIterationCountArg() } - override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { - result.asExpr() = this and - super.getMethod().getReturnType().hasName("SecretKey") - } + override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result.asExpr() = this } override Crypto::ConsumerInputDataFlowNode getOutputKeySizeConsumer() { - result.asExpr() = this.getInstantiation().getKeyLengthArg() + result.asExpr() = this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getKeyLengthArg() } override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { - result.asExpr() = this.getInstantiation().getKeyLengthArg() + result.asExpr() = this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getKeyLengthArg() } override string getKeySizeFixed() { none() } @@ -1290,5 +1314,305 @@ module JCAModel { override string getIterationCountFixed() { none() } } - // TODO: flow the GCGenParametersSpecCall to an init, and the init to the operations + + /* + * Key agreement + */ + + module KeyAgreementFlowAnalysisImpl = + GetInstanceInitUseFlowAnalysis; + + class KeyAgreementStringLiteral extends StringLiteral { + KeyAgreementStringLiteral() { key_agreement_names(this.getValue()) } + } + + /** + * Data-flow configuration modelling flow from a key agreement string literal to a key agreement algorithm consumer. + */ + private module KeyAgreementAlgorithmStringToConsumerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof KeyAgreementStringLiteral } + + predicate isSink(DataFlow::Node sink) { + sink = any(KeyAgreementAlgorithmValueConsumer consumer).getInputNode() + } + } + + module KeyAgreementAlgorithmStringToConsumerFlow = + TaintTracking::Global; + + class KeyAgreementInitCall extends MethodCall { + KeyAgreementInitCall() { + this.getCallee().hasQualifiedName("javax.crypto", "KeyAgreement", "init") + } + + Expr getServerKeyArg() { result = this.getArgument(0) } + } + + class KeyAgreementGetInstanceCall extends MethodCall { + KeyAgreementGetInstanceCall() { + this.getCallee().hasQualifiedName("javax.crypto", "KeyAgreement", "getInstance") + } + + Expr getAlgorithmArg() { result = super.getArgument(0) } + } + + private class KeyAgreementGetInstanceAlgorithmArgValueConsumer extends KeyAgreementAlgorithmValueConsumer + { + KeyAgreementGetInstanceCall call; + + KeyAgreementGetInstanceAlgorithmArgValueConsumer() { this = call.getAlgorithmArg() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + result.(KeyAgreementStringLiteralAlgorithmInstance).getConsumer() = this + } + + KeyAgreementGetInstanceCall getInstantiationCall() { result = call } + } + + class KeyAgreementStringLiteralAlgorithmInstance extends Crypto::KeyAgreementAlgorithmInstance instanceof KeyAgreementStringLiteral + { + KeyAgreementAlgorithmValueConsumer consumer; + + KeyAgreementStringLiteralAlgorithmInstance() { + KeyAgreementAlgorithmStringToConsumerFlow::flow(DataFlow::exprNode(this), + consumer.getInputNode()) + } + + override string getRawKeyAgreementAlgorithmName() { result = super.getValue() } + + override Crypto::TKeyAgreementType getKeyAgreementType() { + if key_agreement_name_to_type_known(_, super.getValue()) + then key_agreement_name_to_type_known(result, super.getValue()) + else result = Crypto::UnknownKeyAgreementType() + } + + KeyAgreementAlgorithmValueConsumer getConsumer() { result = consumer } + } + + class KeyAgreementCall extends MethodCall { + KeyAgreementCall() { + this.getCallee() + .hasQualifiedName("javax.crypto", "KeyAgreement", ["generateSecret", "doPhase"]) + } + + predicate isIntermediate() { this.getCallee().getName() = "doPhase" } + + DataFlow::Node getOutputNode() { + result.asExpr() = this and + not this.isIntermediate() + } + + Expr getPeerKeyArg() { + this.isIntermediate() and + result = this.getArgument(0) and + this.getCallee().getName() = "doPhase" + } + } + + class KeyAgreementSecretGenerationOperationInstance extends Crypto::KeyAgreementSecretGenerationOperationInstance instanceof KeyAgreementCall + { + KeyAgreementSecretGenerationOperationInstance() { + // exclude doPhase (only include generateSecret) + not super.isIntermediate() + } + + override Crypto::ConsumerInputDataFlowNode getServerKeyConsumer() { + result.asExpr() = KeyAgreementFlowAnalysisImpl::getInitFromUse(this, _, _).getServerKeyArg() + } + + override Crypto::ConsumerInputDataFlowNode getPeerKeyConsumer() { + result.asExpr() = + KeyAgreementFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getPeerKeyArg() + } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + result.(KeyAgreementGetInstanceAlgorithmArgValueConsumer).getInstantiationCall() = + KeyAgreementFlowAnalysisImpl::getInstantiationFromUse(this, _, _) + } + + override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result.asExpr() = this } + } + + /* + * MACs + */ + + module MACKnownAlgorithmToConsumerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { mac_names(src.asExpr().(StringLiteral).getValue()) } + + predicate isSink(DataFlow::Node sink) { + exists(MACGetInstanceCall call | sink.asExpr() = call.getAlgorithmArg()) + } + } + + module MACKnownAlgorithmToConsumerFlow = DataFlow::Global; + + module MACGetInstanceToMACOperationFlowConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof MACGetInstanceCall } + + predicate isSink(DataFlow::Node sink) { + exists(MACOperationCall call | sink.asExpr() = call.(MethodCall).getQualifier()) or + exists(MACInitCall call | sink.asExpr() = call.(MethodCall).getQualifier()) + } + } + + module MACGetInstanceToMACOperationFlow = + DataFlow::Global; + + module MACInitCallToMACOperationFlowConfig implements DataFlow::ConfigSig { + // TODO: use flow state with one config + predicate isSource(DataFlow::Node src) { + exists(MACInitCall init | src.asExpr() = init.getQualifier()) + } + + predicate isSink(DataFlow::Node sink) { + exists(MACOperationCall call | sink.asExpr() = call.(MethodCall).getQualifier()) + } + } + + module MACInitCallToMACOperationFlow = DataFlow::Global; + + class KnownMACAlgorithm extends Crypto::MACAlgorithmInstance instanceof StringLiteral { + MACGetInstanceAlgorithmValueConsumer consumer; + + KnownMACAlgorithm() { + mac_names(this.getValue()) and + MACKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) + } + + MACGetInstanceAlgorithmValueConsumer getConsumer() { result = consumer } + + override string getRawMACAlgorithmName() { result = super.getValue() } + + override Crypto::TMACType getMACType() { + if mac_name_to_mac_type_known(_, super.getValue()) + then mac_name_to_mac_type_known(result, super.getValue()) + else result instanceof Crypto::TOtherMACType + } + } + + class MACGetInstanceCall extends MethodCall { + MACGetInstanceCall() { this.getCallee().hasQualifiedName("javax.crypto", "Mac", "getInstance") } + + Expr getAlgorithmArg() { result = this.getArgument(0) } + + MACOperationCall getOperation() { + MACGetInstanceToMACOperationFlow::flow(DataFlow::exprNode(this), + DataFlow::exprNode(result.(MethodCall).getQualifier())) + } + + MACInitCall getInitCall() { + MACGetInstanceToMACOperationFlow::flow(DataFlow::exprNode(this), + DataFlow::exprNode(result.getQualifier())) + } + } + + class MACInitCall extends MethodCall { + MACInitCall() { this.getCallee().hasQualifiedName("javax.crypto", "Mac", "init") } + + Expr getKeyArg() { + result = this.getArgument(0) and this.getMethod().getParameterType(0).hasName("Key") + } + + MACOperationCall getOperation() { + MACInitCallToMACOperationFlow::flow(DataFlow::exprNode(this.getQualifier()), + DataFlow::exprNode(result.(MethodCall).getQualifier())) + } + } + + class MACGetInstanceAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { + MACGetInstanceCall call; + + MACGetInstanceAlgorithmValueConsumer() { this = call.getAlgorithmArg() } + + override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this } + + override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { + exists(KnownMACAlgorithm l | l.getConsumer() = this and result = l) + } + } + + class MACOperationCall extends Crypto::MACOperationInstance instanceof MethodCall { + Expr output; + + MACOperationCall() { + super.getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Mac") and + ( + super.getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])"]) and this = output + or + super.getMethod().hasStringSignature("doFinal(byte[], int)") and + this.getArgument(0) = output + ) + } + + override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { + exists(MACGetInstanceCall instantiation | + instantiation.getOperation() = this and result = instantiation.getAlgorithmArg() + ) + } + + override Crypto::ConsumerInputDataFlowNode getKeyConsumer() { + exists(MACGetInstanceCall instantiation, MACInitCall initCall | + instantiation.getOperation() = this and + initCall.getOperation() = this and + instantiation.getInitCall() = initCall and + result.asExpr() = initCall.getKeyArg() + ) + } + + override Crypto::ConsumerInputDataFlowNode getMessageConsumer() { + result.asExpr() = super.getArgument(0) and + super.getMethod().getParameterType(0).hasName("byte[]") + } + } + + /* + * Elliptic Curves (EC) + */ + + module EllipticCurveStringToConsumerConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { src.asExpr() instanceof EllipticCurveStringLiteral } + + predicate isSink(DataFlow::Node sink) { + exists(EllipticCurveAlgorithmValueConsumer consumer | sink = consumer.getInputNode()) + } + } + + module EllipticCurveStringToConsumerFlow = DataFlow::Global; + + class EllipticCurveStringLiteral extends StringLiteral { + EllipticCurveStringLiteral() { elliptic_curve_names(this.getValue()) } + } + + class EllipticCurveStringLiteralInstance extends Crypto::EllipticCurveInstance instanceof EllipticCurveStringLiteral + { + EllipticCurveAlgorithmValueConsumer consumer; + + EllipticCurveStringLiteralInstance() { + EllipticCurveStringToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode()) + } + + override string getRawEllipticCurveName() { result = super.getValue() } + + override Crypto::TEllipticCurveType getEllipticCurveType() { + if Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _, _) + then + Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _, result) + else result = Crypto::OtherEllipticCurveType() + } + + override string getKeySize() { + exists(int keySize | + Crypto::ellipticCurveNameToKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), keySize, + _) + | + result = keySize.toString() + ) + } + + EllipticCurveAlgorithmValueConsumer getConsumer() { result = consumer } + } } diff --git a/java/ql/lib/experimental/Quantum/Language.qll b/java/ql/lib/experimental/Quantum/Language.qll index 8027ef33df7b..6f7dc88157b2 100644 --- a/java/ql/lib/experimental/Quantum/Language.qll +++ b/java/ql/lib/experimental/Quantum/Language.qll @@ -38,7 +38,7 @@ module CryptoInput implements InputSig { predicate artifactOutputFlowsToGenericInput( DataFlow::Node artifactOutput, DataFlow::Node otherInput ) { - ArtifactUniversalFlow::flow(artifactOutput, otherInput) + ArtifactFlow::flow(artifactOutput, otherInput) } } @@ -60,7 +60,7 @@ class GenericUnreferencedParameterSource extends Crypto::GenericUnreferencedPara } override predicate flowsTo(Crypto::FlowAwareElement other) { - GenericDataSourceUniversalFlow::flow(this.getOutputNode(), other.getInputNode()) + GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode()) } override DataFlow::Node getOutputNode() { result.asParameter() = this } @@ -76,7 +76,7 @@ class GenericLocalDataSource extends Crypto::GenericLocalDataSource { override DataFlow::Node getOutputNode() { result.asExpr() = this } override predicate flowsTo(Crypto::FlowAwareElement other) { - GenericDataSourceUniversalFlow::flow(this.getOutputNode(), other.getInputNode()) + GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode()) } override string getAdditionalDescription() { result = this.toString() } @@ -88,7 +88,7 @@ class GenericRemoteDataSource extends Crypto::GenericRemoteDataSource { override DataFlow::Node getOutputNode() { result.asExpr() = this } override predicate flowsTo(Crypto::FlowAwareElement other) { - GenericDataSourceUniversalFlow::flow(this.getOutputNode(), other.getInputNode()) + GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode()) } override string getAdditionalDescription() { result = this.toString() } @@ -100,14 +100,14 @@ class ConstantDataSource extends Crypto::GenericConstantSourceInstance instanceo // where typical algorithms are specified, but EC specifically means set up a // default curve container, that will later be specified explicitly (or if not a default) // curve is used. - this = any(Literal l | l.getValue() != "EC") + this.getValue() != "EC" } override DataFlow::Node getOutputNode() { result.asExpr() = this } override predicate flowsTo(Crypto::FlowAwareElement other) { // TODO: separate config to avoid blowing up data-flow analysis - GenericDataSourceUniversalFlow::flow(this.getOutputNode(), other.getInputNode()) + GenericDataSourceFlow::flow(this.getOutputNode(), other.getInputNode()) } override string getAdditionalDescription() { result = this.toString() } @@ -122,15 +122,24 @@ abstract class RandomnessInstance extends Crypto::RandomNumberGenerationInstance } class SecureRandomnessInstance extends RandomnessInstance { + RandomDataSource source; + SecureRandomnessInstance() { - exists(RandomDataSource s | this = s.getOutput() | - s.getSourceOfRandomness() instanceof SecureRandomNumberGenerator - ) + this = source.getOutput() and + source.getSourceOfRandomness() instanceof SecureRandomNumberGenerator } + + override string getGeneratorName() { result = source.getSourceOfRandomness().getQualifiedName() } } class InsecureRandomnessInstance extends RandomnessInstance { - InsecureRandomnessInstance() { exists(InsecureRandomnessSource node | this = node.asExpr()) } + RandomDataSource source; + + InsecureRandomnessInstance() { + any(InsecureRandomnessSource src).asExpr() = this and source.getOutput() = this + } + + override string getGeneratorName() { result = source.getSourceOfRandomness().getQualifiedName() } } /** @@ -142,12 +151,12 @@ abstract class AdditionalFlowInputStep extends DataFlow::Node { final DataFlow::Node getInput() { result = this } } -module ArtifactUniversalFlow = DataFlow::Global; +module ArtifactFlow = DataFlow::Global; /** * Generic data source to node input configuration */ -module GenericDataSourceUniversalFlowConfig implements DataFlow::ConfigSig { +module GenericDataSourceFlowConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source = any(Crypto::GenericSourceInstance i).getOutputNode() } @@ -175,7 +184,7 @@ module GenericDataSourceUniversalFlowConfig implements DataFlow::ConfigSig { } } -module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig { +module ArtifactFlowConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source = any(Crypto::ArtifactInstance artifact).getOutputNode() } @@ -194,10 +203,16 @@ module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig { predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { node1.(AdditionalFlowInputStep).getOutput() = node2 + or + exists(MethodCall m | + m.getMethod().hasQualifiedName("java.lang", "String", "getBytes") and + node1.asExpr() = m.getQualifier() and + node2.asExpr() = m + ) } } -module GenericDataSourceUniversalFlow = TaintTracking::Global; +module GenericDataSourceFlow = TaintTracking::Global; // Import library-specific modeling import JCA diff --git a/java/ql/src/experimental/Quantum/InsecureNonceSource.ql b/java/ql/src/experimental/Quantum/InsecureNonceSource.ql new file mode 100644 index 000000000000..9c06884328bb --- /dev/null +++ b/java/ql/src/experimental/Quantum/InsecureNonceSource.ql @@ -0,0 +1,20 @@ +/** + * @name Insecure nonce at a cipher operation + * @id java/insecure-nonce + * @kind problem + * @problem.severity error + * @precision high + * @description A nonce is generated from a source that is not secure. This can lead to + * vulnerabilities such as replay attacks or key recovery. + */ + +import experimental.Quantum.Language + +predicate isInsecureNonceSource(Crypto::NonceArtifactNode n, Crypto::NodeBase src) { + src = n.getSourceNode() and + not src.asElement() instanceof SecureRandomnessInstance +} + +from Crypto::KeyOperationNode op, Crypto::NodeBase src +where isInsecureNonceSource(op.getANonce(), src) +select op, "Operation uses insecure nonce source $@", src, src.toString() diff --git a/java/ql/src/experimental/Quantum/InsecureOrUnknownNonceAtOperation.ql b/java/ql/src/experimental/Quantum/InsecureOrUnknownNonceAtOperation.ql index 7fea3515b7dd..a9eb70076a0a 100644 --- a/java/ql/src/experimental/Quantum/InsecureOrUnknownNonceAtOperation.ql +++ b/java/ql/src/experimental/Quantum/InsecureOrUnknownNonceAtOperation.ql @@ -7,18 +7,17 @@ import experimental.Quantum.Language from - Crypto::NonceArtifactNode n, Crypto::CipherOperationNode op, Crypto::FlowAwareElement src, - string msg + Crypto::NonceArtifactNode n, Crypto::KeyOperationNode op, Crypto::FlowAwareElement src, string msg where op.getANonce() = n and // Only encryption mode is relevant for insecure nonces, consder any 'unknown' subtype // as possibly encryption. ( - op.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype + op.getKeyOperationSubtype() instanceof Crypto::EncryptionSubtype or - op.getCipherOperationSubtype() instanceof Crypto::WrapSubtype + op.getKeyOperationSubtype() instanceof Crypto::WrapSubtype or - op.getCipherOperationSubtype() instanceof Crypto::UnwrapSubtype + op.getKeyOperationSubtype() instanceof Crypto::UnwrapSubtype ) and ( // Known sources cases that are not secure diff --git a/java/ql/src/experimental/Quantum/InventoryFilters/ArtifactReuse.qll b/java/ql/src/experimental/Quantum/InventoryFilters/ArtifactReuse.qll new file mode 100644 index 000000000000..de283f89775e --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventoryFilters/ArtifactReuse.qll @@ -0,0 +1,70 @@ +import java +import semmle.code.java.dataflow.DataFlow +import experimental.Quantum.Language + +/** + * Flow from any function that appears to return a value + * to an artifact node. + * NOTE: TODO: need to handle call by refernece for now. Need to re-evaluate (see notes below) + * Such functions may be 'wrappers' for some derived value. + */ +private module WrapperConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + exists(Call c | + c = source.asExpr() + // not handling references yet, I think we want to flat say references are only ok + // if I know the source, otherwise, it has to be through an additional flow step, which + // we filter as a source, i.e., references are only allowed as sources only, + // no inferrece? Not sure if that would work + //or + // source.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = c.getAnArgument() + ) and + // Filter out sources that are known additional flow steps, as these are likely not the + // kind of wrapper source we are looking for. + not exists(AdditionalFlowInputStep s | s.getOutput() = source) + } + + // Flow through additional flow steps + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + node1.(AdditionalFlowInputStep).getOutput() = node2 + } + + predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(Crypto::ArtifactNode i).asElement() } +} + +module WrapperFlow = DataFlow::Global; + +/** + * Using a set approach to determine if reuse of an artifact exists. + * This predicate produces a set of 'wrappers' that flow to the artifact node. + * This set can be compared with the set to another artifact node to determine if they are the same. + */ +private DataFlow::Node getWrapperSet(Crypto::NonceArtifactNode a) { + WrapperFlow::flow(result, DataFlow::exprNode(a.asElement())) + or + result.asExpr() = a.getSourceElement() +} + +/** + * Two different artifact nodes are considered reuse if any of the following conditions are met: + * 1. The source for artifact `a` and artifact `b` are the same and the source is a literal. + * 2. The source for artifact `a` and artifact `b` are not the same and the source is a literal of the same value. + * 3. For all 'wrappers' that return the source of artifact `a`, and that wrapper also exists for artifact `b`. + * 4. For all 'wrappers' that return the source of artifact `b`, and that wrapper also exists for artifact `a`. + */ +predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) { + a != b and + ( + a.getSourceElement() = b.getSourceElement() and a.getSourceElement() instanceof Literal + or + a.getSourceElement().(Literal).getValue() = b.getSourceElement().(Literal).getValue() + or + forex(DataFlow::Node e | e = getWrapperSet(a) | + exists(DataFlow::Node e2 | e2 = getWrapperSet(b) | e = e2) + ) + or + forex(DataFlow::Node e | e = getWrapperSet(b) | + exists(DataFlow::Node e2 | e2 = getWrapperSet(a) | e = e2) + ) + ) +} diff --git a/java/ql/src/experimental/Quantum/InventoryFilters/KnownWeakKDFIterationCount.ql b/java/ql/src/experimental/Quantum/InventoryFilters/KnownWeakKDFIterationCount.ql new file mode 100644 index 000000000000..439295f74e36 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventoryFilters/KnownWeakKDFIterationCount.ql @@ -0,0 +1,15 @@ +/** + * @name Detects known weak KDf iteration counts (less than 100k and the count is statically known) + * @id java/crypto_inventory_filters/known_weak_kdf_iteration_count + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::KeyDerivationOperationNode op, Literal l +where + op.getIterationCount().asElement() = l and + l.getValue().toInt() < 100000 +select op, "Key derivation operation configures iteration count below 100k: $@", l, + l.getValue().toString() diff --git a/java/ql/src/experimental/Quantum/InventoryFilters/ReusedNonce.ql b/java/ql/src/experimental/Quantum/InventoryFilters/ReusedNonce.ql new file mode 100644 index 000000000000..d8adccace557 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventoryFilters/ReusedNonce.ql @@ -0,0 +1,12 @@ +/** + * @name Detects reuse of the same nonce in multiple operations + * @id java/crypto_inventory_filter/nonce_reuse + * @kind problem + */ + +import java +import ArtifactReuse + +from Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2 +where isArtifactReuse(nonce1, nonce2) +select nonce1, "Reuse with nonce $@", nonce2, nonce2.toString() diff --git a/java/ql/src/experimental/Quantum/InventoryFilters/UnknownKDFIterationCount.ql b/java/ql/src/experimental/Quantum/InventoryFilters/UnknownKDFIterationCount.ql new file mode 100644 index 000000000000..0c91e66d52bc --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventoryFilters/UnknownKDFIterationCount.ql @@ -0,0 +1,19 @@ +/** + * @name Detects unknown KDf iteration counts + * @id java/crypto_inventory_filters/unknown_kdf_iteration_count + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::KeyDerivationOperationNode op, Element e, string msg +where + e = op.getIterationCount().asElement() and + not e instanceof Literal and + msg = "Key derivation operation with unknown iteration: $@" + or + not exists(op.getIterationCount()) and + e = op.asElement() and + msg = "Key derivation operation with no iteration configuration." +select op, msg, e, e.toString() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricAlgorithm.ql new file mode 100644 index 000000000000..1e5a7c5bced1 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricAlgorithm.ql @@ -0,0 +1,12 @@ +/** + * @name Detects known asymmetric algorithms + * @id java/crypto_inventory_slices/known_asymmetric_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::AlgorithmNode a +where Crypto::isKnownAsymmetricAlgorithm(a) +select a, "Instance of asymmetric algorithm " + a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricCipherAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricCipherAlgorithm.ql new file mode 100644 index 000000000000..962a6b720159 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricCipherAlgorithm.ql @@ -0,0 +1,12 @@ +/** + * @name Detects known asymmetric cipher algorithms + * @id java/crypto_inventory_slices/known_symmetric_cipher_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::KeyOperationAlgorithmNode a +where a.getAlgorithmType() instanceof Crypto::KeyOpAlg::AsymmetricCipherAlgorithm +select a, "Instance of asymmetric cipher algorithm " + a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricOperationAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricOperationAlgorithm.ql new file mode 100644 index 000000000000..0900401b80d7 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownAsymmetricOperationAlgorithm.ql @@ -0,0 +1,12 @@ +/** + * @name Detects operations where the algorithm applied is a known asymmetric algorithms + * @id java/crypto_inventory_slices/known_asymmetric_operation_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::OperationNode op, Crypto::AlgorithmNode a +where a = op.getAKnownAlgorithm() and Crypto::isKnownAsymmetricAlgorithm(a) +select op, "Operation using asymmetric algorithm $@", a, a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownCipherAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownCipherAlgorithm.ql new file mode 100644 index 000000000000..4096fe16d29c --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownCipherAlgorithm.ql @@ -0,0 +1,15 @@ +/** + * @name Detects known cipher algorithms + * @id java/crypto_inventory_slices/known_cipher_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +// TODO: should there be a cipher algorithm node? +from Crypto::KeyOperationAlgorithmNode a +where + a.getAlgorithmType() instanceof Crypto::KeyOpAlg::AsymmetricCipherAlgorithm or + a.getAlgorithmType() instanceof Crypto::KeyOpAlg::SymmetricCipherAlgorithm +select a, "Instance of cipher algorithm " + a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownEllipticCurveAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownEllipticCurveAlgorithm.ql new file mode 100644 index 000000000000..048bcd8182ce --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownEllipticCurveAlgorithm.ql @@ -0,0 +1,11 @@ +/** + * @name Detects known elliptic curve algorithms + * @id java/crypto_inventory_slices/known_elliptic_curve_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::EllipticCurveNode a +select a, "Instance of elliptic curve algorithm " + a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingAlgorithm.ql new file mode 100644 index 000000000000..632872725e71 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingAlgorithm.ql @@ -0,0 +1,11 @@ +/** + * @name Detects algorithms that are known hashing algorithms + * @id java/crypto_inventory_slices/known_hashing_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::HashAlgorithmNode a +select a, "Instance of hashing algorithm " + a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingOperation.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingOperation.ql new file mode 100644 index 000000000000..b3556393173d --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingOperation.ql @@ -0,0 +1,11 @@ +/** + * @name Detects uses of hashing operations (operations exlicitly for hashing only, irrespective of the algorithm used) + * @id java/crypto_inventory_slices/known_hashing_operation + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::HashOperationNode op +select op, "Known hashing operation" diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingOperationAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingOperationAlgorithm.ql new file mode 100644 index 000000000000..ce0f4d37d4d1 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownHashingOperationAlgorithm.ql @@ -0,0 +1,12 @@ +/** + * @name Detects operations where the algorithm applied is a known hashing algorithm + * @id java/crypto_inventory_slices/operation_with_known_hashing_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::OperationNode op, Crypto::HashAlgorithmNode a +where a = op.getAKnownAlgorithm() +select op, "Operation using hashing algorithm $@", a, a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationAlgorithm.ql new file mode 100644 index 000000000000..584ffef0bbfa --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationAlgorithm.ql @@ -0,0 +1,11 @@ +/** + * @name Detects known key derivation algorithms + * @id java/crypto_inventory_slices/known_key_derivation_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::KeyDerivationAlgorithmNode alg +select alg, "Known key derivation algorithm " + alg.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationOperation.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationOperation.ql new file mode 100644 index 000000000000..8c77b2aa984d --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationOperation.ql @@ -0,0 +1,11 @@ +/** + * @name Detects uses of key derivation operations (operations exlicitly for key derivation only, irrespective of the algorithm used) + * @id java/crypto_inventory_slices/known_key_derivation_operation + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::KeyDerivationOperationNode op +select op, "Known key derivation operation" diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationOperationAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationOperationAlgorithm.ql new file mode 100644 index 000000000000..cf9a4e96f4da --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/KnownKeyDerivationOperationAlgorithm.ql @@ -0,0 +1,12 @@ +/** + * @name Detects operations where the algorithm applied is a known key derivation algorithm + * @id java/crypto_inventory_slices/operation_with_known_key_derivation_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Crypto::OperationNode op, Crypto::KeyDerivationAlgorithmNode a +where a = op.getAKnownAlgorithm() +select op, "Operation using key derivation algorithm $@", a, a.getAlgorithmName() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/KnownSymmetricCipherAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/KnownSymmetricCipherAlgorithm.ql new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/ql/src/experimental/Quantum/InventorySlices/LikelyCryptoAPIFunction.ql b/java/ql/src/experimental/Quantum/InventorySlices/LikelyCryptoAPIFunction.ql new file mode 100644 index 000000000000..0076c478dec9 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/LikelyCryptoAPIFunction.ql @@ -0,0 +1,16 @@ +/** + * @name Detects functions that take in crypto configuration parameters but calls are not detected in source. + * @id java/crypto_inventory_slices/likely_crypto_api_function + * @kind problem + */ + +import java +import experimental.Quantum.Language + +from Callable f, Parameter p, Crypto::OperationNode op +where + op.asElement().(Expr).getEnclosingCallable() = f and + op.getAnAlgorithmOrGenericSource().asElement() = p +select f, + "Likely crypto API function: Operation $@ configured by parameter $@ with no known configuring call", + op, op.toString(), p, p.toString() diff --git a/java/ql/src/experimental/Quantum/InventorySlices/UnknownOperationAlgorithm.ql b/java/ql/src/experimental/Quantum/InventorySlices/UnknownOperationAlgorithm.ql new file mode 100644 index 000000000000..61a27c75dac2 --- /dev/null +++ b/java/ql/src/experimental/Quantum/InventorySlices/UnknownOperationAlgorithm.ql @@ -0,0 +1,22 @@ +/** + * @name Detects operations where the algorithm applied is unknown + * @id java/crypto_inventory_slices/unknown_operation_algorithm + * @kind problem + */ + +import java +import experimental.Quantum.Language + +//TODO: can we have an unknown node concept? +from Crypto::OperationNode op, Element e, string msg +where + not exists(op.getAnAlgorithmOrGenericSource()) and + e = op.asElement() and + msg = "Operation with unconfigured algorithm (no known sources)." + or + exists(Crypto::GenericSourceNode n | + n = op.getAnAlgorithmOrGenericSource() and + e = n.asElement() + ) and + msg = "Operation with unknown algorithm source: $@" +select op, msg, e, e.toString() diff --git a/java/ql/src/experimental/Quantum/PossibleReusedNonce.ql b/java/ql/src/experimental/Quantum/PossibleReusedNonce.ql deleted file mode 100644 index f9fc7a873db1..000000000000 --- a/java/ql/src/experimental/Quantum/PossibleReusedNonce.ql +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @name Possible Nonce Reuse: Produces false positives if reuse occurs in a source that is a re-entry point. - * @id java/possible-nonce-reuse - * @kind problem - */ - -import experimental.Quantum.Language -import semmle.code.java.dataflow.DataFlow - -from - Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2, - Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2, Crypto::FlowAwareElement src1, - Crypto::FlowAwareElement src2 -where - // NOTE: not looking at value of the nonce, if we knew value, it would be insecure (hard coded) - // Instead trying to find nonce sources that trace to multiple operations. - // Only looking for encryption operations, presumably if reuse for decryption either wouldn't be observable - // (the encryption happened else where) or we are able to see the encryption and decryption operation and - // reuse for encryption is the concern) - ( - op1.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype or - op1.getCipherOperationSubtype() instanceof Crypto::WrapSubtype or - op1.getCipherOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype - ) and - ( - op2.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype or - op2.getCipherOperationSubtype() instanceof Crypto::WrapSubtype or - op2.getCipherOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype - ) and - nonce1 = op1.getANonce() and - nonce2 = op2.getANonce() and - op1 != op2 and - nonce1.getSourceElement() = src1 and - nonce2.getSourceElement() = src2 and - src1 = src2 -// TODO: need to clarify that a reuse in a non-finalize is ok, need to check if 'finalize' through a modeled predicate -select op1, "Operation has a possible reused nonce with source $@", src1, src1.toString() diff --git a/java/ql/src/experimental/Quantum/TestAESGCMNonce.ql b/java/ql/src/experimental/Quantum/TestAESGCMNonce.ql new file mode 100644 index 000000000000..985527318ffa --- /dev/null +++ b/java/ql/src/experimental/Quantum/TestAESGCMNonce.ql @@ -0,0 +1,16 @@ +/** + * @name "PQC Test" + */ + +import experimental.Quantum.Language + +class AESGCMAlgorithmNode extends Crypto::KeyOperationAlgorithmNode { + AESGCMAlgorithmNode() { + this.getAlgorithmType() = Crypto::KeyOpAlg::TSymmetricCipher(Crypto::KeyOpAlg::AES()) and + this.getModeOfOperation().getModeType() = Crypto::GCM() + } +} + +from Crypto::KeyOperationNode op, Crypto::NonceArtifactNode nonce +where op.getAKnownAlgorithm() instanceof AESGCMAlgorithmNode and nonce = op.getANonce() +select op, nonce.getSourceNode() diff --git a/java/ql/src/experimental/Quantum/TestCipher.ql b/java/ql/src/experimental/Quantum/TestCipher.ql index 8743ef3aa19b..503d60039229 100644 --- a/java/ql/src/experimental/Quantum/TestCipher.ql +++ b/java/ql/src/experimental/Quantum/TestCipher.ql @@ -1,18 +1,18 @@ /** - * @name "PQC Test" + * @name "Key operation slice table demo query" */ import experimental.Quantum.Language from - Crypto::CipherOperationNode op, Crypto::CipherAlgorithmNode a, + Crypto::KeyOperationNode op, Crypto::KeyOperationAlgorithmNode a, Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p, Crypto::NonceArtifactNode nonce, Crypto::KeyArtifactNode k where - a = op.getAKnownCipherAlgorithm() and + a = op.getAKnownAlgorithm() and m = a.getModeOfOperation() and p = a.getPaddingAlgorithm() and nonce = op.getANonce() and k = op.getAKey() -select op, op.getCipherOperationSubtype(), a, a.getRawAlgorithmName(), m, m.getRawAlgorithmName(), - p, p.getRawAlgorithmName(), nonce, k, k.getSourceElement() +select op, op.getKeyOperationSubtype(), a, a.getRawAlgorithmName(), m, m.getRawAlgorithmName(), p, + p.getRawAlgorithmName(), nonce, k diff --git a/java/ql/src/experimental/Quantum/TestCipherKey.ql b/java/ql/src/experimental/Quantum/TestCipherKey.ql index 1ae0cdfd89f0..c489320528d0 100644 --- a/java/ql/src/experimental/Quantum/TestCipherKey.ql +++ b/java/ql/src/experimental/Quantum/TestCipherKey.ql @@ -4,11 +4,11 @@ import experimental.Quantum.Language -from Crypto::CipherOperationNode op, Crypto::CipherAlgorithmNode a, Crypto::KeyArtifactNode k +from Crypto::KeyOperationNode op, Crypto::CipherAlgorithmNode a, Crypto::KeyArtifactNode k where a = op.getAKnownCipherAlgorithm() and k = op.getAKey() -select op, op.getCipherOperationSubtype(), a, a.getRawAlgorithmName(), k, k.getSourceNode() +select op, op.getKeyOperationSubtype(), a, a.getRawAlgorithmName(), k, k.getSourceNode() /* * from Crypto::CipherOperationNode op * where op.getLocation().getFile().getBaseName() = "AsymmetricEncryptionMacHybridCryptosystem.java" diff --git a/java/ql/src/experimental/Quantum/TestHash.ql b/java/ql/src/experimental/Quantum/TestHash.ql index 96f99193cba4..76ef6951a7e8 100644 --- a/java/ql/src/experimental/Quantum/TestHash.ql +++ b/java/ql/src/experimental/Quantum/TestHash.ql @@ -1,9 +1,9 @@ /** - * @name TestHashOperations + * @name "Hash operation slice table demo query" */ import experimental.Quantum.Language from Crypto::HashOperationNode op, Crypto::HashAlgorithmNode alg -where alg = op.getAKnownHashAlgorithm() +where alg = op.getAKnownAlgorithm() select op, op.getDigest(), alg, alg.getRawAlgorithmName() diff --git a/misc/scripts/cryptography/generate_cbom.py b/misc/scripts/cryptography/generate_cbom.py index fa4c2cb1d023..6bd0d19712c5 100644 --- a/misc/scripts/cryptography/generate_cbom.py +++ b/misc/scripts/cryptography/generate_cbom.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +import re import sys import argparse import subprocess @@ -86,6 +87,7 @@ def main(): parser.add_argument("-c", "--codeql", required=True, help="Path to CodeQL CLI executable.") parser.add_argument("-d", "--database", required=True, help="Path to the CodeQL database.") parser.add_argument("-q", "--query", required=True, help="Path to the .ql query file.") + parser.add_argument("--queryid", required=True, help="Query ID for the analysis.") parser.add_argument("-o", "--output", required=True, help="Output directory for analysis results.") args = parser.parse_args() @@ -94,7 +96,13 @@ def main(): run_codeql_analysis(args.codeql, args.database, args.query, args.output) # Locate DGML file - dgml_file = os.path.join(args.output, "java", "print-cbom-graph.dgml") + ALLOWED_QUERY_ID = re.compile(r'^[a-zA-Z0-9_\-]+$') + + if not ALLOWED_QUERY_ID.match(args.queryid): + print("Invalid query_id provided: '%s'. Allowed characters: letters, digits, '_', and '-'.", args.queryid) + sys.exit(1) + + dgml_file = os.path.join(args.output, "java", '{}.dgml'.format(args.queryid)) dot_file = dgml_file.replace(".dgml", ".dot") if os.path.exists(dgml_file): diff --git a/shared/cryptography/codeql/cryptography/Model.qll b/shared/cryptography/codeql/cryptography/Model.qll index 09f67f156c2a..a7938b1d1ce7 100644 --- a/shared/cryptography/codeql/cryptography/Model.qll +++ b/shared/cryptography/codeql/cryptography/Model.qll @@ -3,7 +3,6 @@ */ import codeql.util.Location -import codeql.util.Option import codeql.util.Either signature module InputSig { @@ -334,7 +333,6 @@ module CryptographyBase Input> { */ abstract class ArtifactConsumer extends ConsumerElement { /** - * DO NOT USE: * Use `getAKnownArtifactSource() instead. The behaviour of these two predicates is equivalent. */ final override KnownElement getAKnownSource() { result = this.getAKnownArtifactSource() } @@ -348,26 +346,22 @@ module CryptographyBase Input> { * For example: * A `NonceArtifactConsumer` is always the `NonceArtifactInstance` itself, since data only becomes (i.e., is determined to be) * a `NonceArtifactInstance` when it is consumed in a context that expects a nonce (e.g., an argument expecting nonce data). - * In this case, the artifact (nonce) is fully defined by the context in which it is consumed, and the consumer embodies + * + * In this case, the artifact (nonce) is fully defined by the context in which it is consumed, and the consumer embodies * that identity without the need for additional differentiation. Without the context a consumer provides, that data could * otherwise be any other type of artifact or even simply random data. * - * TODO: what if a Nonce from hypothetical func `generateNonce()` flows to this instance which is also a Nonce? - * TODO: potential solution is creating another artifact type called NonceData or treating it as a generic source. - * - * TODO: An alternative is simply having a predicate DataFlowNode getNonceInputNode() on (for example) operations. - * Under the hood, in Model.qll, we would create the instance for the modeller, thus avoiding the need for the modeller - * to create a separate consumer class / instance themselves using this class. + * This class is used to create synthetic nodes for the artifact at any place where it is consumed. */ abstract private class ArtifactConsumerAndInstance extends ArtifactConsumer, ArtifactInstance { - override predicate isConsumerArtifact() { any() } + final override predicate isConsumerArtifact() { any() } } final private class NonceArtifactConsumer extends ArtifactConsumerAndInstance { ConsumerInputDataFlowNode inputNode; NonceArtifactConsumer() { - exists(CipherOperationInstance op | inputNode = op.getNonceConsumer()) and + exists(KeyOperationInstance op | inputNode = op.getNonceConsumer()) and this = Input::dfn_to_element(inputNode) } @@ -379,11 +373,13 @@ module CryptographyBase Input> { MessageArtifactConsumer() { ( - exists(CipherOperationInstance op | inputNode = op.getInputConsumer()) + exists(KeyOperationInstance op | inputNode = op.getInputConsumer()) or exists(KeyDerivationOperationInstance op | inputNode = op.getInputConsumer()) or exists(MACOperationInstance op | inputNode = op.getMessageConsumer()) + or + exists(HashOperationInstance op | inputNode = op.getInputConsumer()) ) and this = Input::dfn_to_element(inputNode) } @@ -402,7 +398,9 @@ module CryptographyBase Input> { final override ConsumerInputDataFlowNode getInputNode() { result = inputNode } } - // Output artifacts are determined solely by the element that produces them. + /** + * An artifact that is produced by an operation, representing a concrete artifact instance rather than a synthetic consumer artifact. + */ abstract class OutputArtifactInstance extends ArtifactInstance { override predicate isConsumerArtifact() { none() } @@ -413,13 +411,52 @@ module CryptographyBase Input> { } } - abstract class DigestArtifactInstance extends OutputArtifactInstance { } - + /** + * An artifact representing a random number generator's output. + */ abstract class RandomNumberGenerationInstance extends OutputArtifactInstance { - // TODO: input seed? + abstract string getGeneratorName(); } - abstract class CipherOutputArtifactInstance extends OutputArtifactInstance { } + /** + * An artifact representing a key operation's output, e.g.: + * 1. Encryption/decryption output (ciphertext or plaintext) + * 1. Signing output (signature) + * 1. Key encapsulation output (wrapped or unwrapped key) + */ + final class KeyOperationOutputArtifactInstance extends OutputArtifactInstance { + KeyOperationInstance creator; + + KeyOperationOutputArtifactInstance() { + Input::dfn_to_element(creator.getOutputArtifact()) = this + } + + override DataFlowNode getOutputNode() { result = creator.getOutputArtifact() } + } + + /** + * An artifact representing the message digest output of a hash operation. + */ + final class HashOutputArtifactInstance extends OutputArtifactInstance { + HashOperationInstance creator; + + HashOutputArtifactInstance() { Input::dfn_to_element(creator.getOutputArtifact()) = this } + + override DataFlowNode getOutputNode() { result = creator.getOutputArtifact() } + } + + /** + * An artifact representing the shared secret generated by key agreement operations. + */ + final class KeyAgreementSharedSecretOutputArtifactInstance extends OutputArtifactInstance { + KeyAgreementSecretGenerationOperationInstance creator; + + KeyAgreementSharedSecretOutputArtifactInstance() { + Input::dfn_to_element(creator.getOutputArtifact()) = this + } + + override DataFlowNode getOutputNode() { result = creator.getOutputArtifact() } + } // Artifacts that may be outputs or inputs newtype TKeyArtifactType = @@ -459,8 +496,14 @@ module CryptographyBase Input> { // TODO: key type hint? e.g. hint: private || public KeyArtifactConsumer() { ( - exists(CipherOperationInstance op | inputNode = op.getKeyConsumer()) or + exists(KeyOperationInstance op | inputNode = op.getKeyConsumer()) + or exists(MACOperationInstance op | inputNode = op.getKeyConsumer()) + or + exists(KeyAgreementSecretGenerationOperationInstance op | + inputNode = op.getServerKeyConsumer() or + inputNode = op.getPeerKeyConsumer() + ) ) and this = Input::dfn_to_element(inputNode) } @@ -471,68 +514,314 @@ module CryptographyBase Input> { } /** - * A cipher operation instance, such as encryption or decryption. + * The `KeyOpAlg` module defines key operation algorithms types (e.g., symmetric ciphers, signatures, etc.) + * and provides mapping of those types to string names and structural properties. */ - abstract class CipherOperationInstance extends OperationInstance { + module KeyOpAlg { /** - * Gets the subtype of this cipher operation, distinguishing encryption, decryption, key wrapping, and key unwrapping. + * An algorithm used in key operations. */ - abstract CipherOperationSubtype getCipherOperationSubtype(); + newtype TAlgorithm = + TSymmetricCipher(TSymmetricCipherType t) or + TAsymmetricCipher(TAsymmetricCipherType t) or + TSignature(TSignatureAlgorithmType t) or + TKeyEncapsulation(TKEMAlgorithmType t) or + TUnknownKeyOperationAlgorithmType() + + // Parameterized algorithm types + newtype TSymmetricCipherType = + AES() or + ARIA() or + BLOWFISH() or + CAMELLIA() or + CAST5() or + CHACHA20() or + DES() or + DESX() or + GOST() or + IDEA() or + KUZNYECHIK() or + MAGMA() or + TripleDES() or + DoubleDES() or + RC2() or + RC4() or + RC5() or + SEED() or + SM4() or + OtherSymmetricCipherType() + + newtype TAsymmetricCipherType = + RSA() or + OtherAsymmetricCipherType() + + newtype TSignatureAlgorithmType = + DSA() or + ECDSA() or + Ed25519() or + Ed448() or + OtherSignatureAlgorithmType() + + newtype TKEMAlgorithmType = + Kyber() or + FrodoKEM() or + OtherKEMAlgorithmType() + + newtype TCipherStructureType = + Block() or + Stream() or + UnknownCipherStructureType() + + class CipherStructureType extends TCipherStructureType { + string toString() { + result = "Block" and this = Block() + or + result = "Stream" and this = Stream() + or + result = "Unknown" and this = UnknownCipherStructureType() + } + } + + bindingset[type] + predicate symmetric_cipher_to_name_and_structure( + TSymmetricCipherType type, string name, CipherStructureType s + ) { + type = AES() and name = "AES" and s = Block() + or + type = ARIA() and name = "ARIA" and s = Block() + or + type = BLOWFISH() and name = "Blowfish" and s = Block() + or + type = CAMELLIA() and name = "Camellia" and s = Block() + or + type = CAST5() and name = "CAST5" and s = Block() + or + type = CHACHA20() and name = "ChaCha20" and s = Stream() + or + type = DES() and name = "DES" and s = Block() + or + type = DESX() and name = "DESX" and s = Block() + or + type = GOST() and name = "GOST" and s = Block() + or + type = IDEA() and name = "IDEA" and s = Block() + or + type = KUZNYECHIK() and name = "Kuznyechik" and s = Block() + or + type = MAGMA() and name = "Magma" and s = Block() + or + type = TripleDES() and name = "TripleDES" and s = Block() + or + type = DoubleDES() and name = "DoubleDES" and s = Block() + or + type = RC2() and name = "RC2" and s = Block() + or + type = RC4() and name = "RC4" and s = Stream() + or + type = RC5() and name = "RC5" and s = Block() + or + type = SEED() and name = "SEED" and s = Block() + or + type = SM4() and name = "SM4" and s = Block() + or + type = OtherSymmetricCipherType() and + name = "UnknownSymmetricCipher" and + s = UnknownCipherStructureType() + } + + bindingset[type] + predicate type_to_name(Algorithm type, string name) { + // Symmetric cipher algorithm + symmetric_cipher_to_name_and_structure(type.(SymmetricCipherAlgorithm).getType(), name, _) + or + // Asymmetric cipher algorithms + type = TAsymmetricCipher(RSA()) and name = "RSA" + or + type = TAsymmetricCipher(OtherAsymmetricCipherType()) and name = "UnknownAsymmetricCipher" + or + // Signature algorithms + type = TSignature(DSA()) and name = "DSA" + or + type = TSignature(ECDSA()) and name = "ECDSA" + or + type = TSignature(Ed25519()) and name = "Ed25519" + or + type = TSignature(Ed448()) and name = "Ed448" + or + type = TSignature(OtherSignatureAlgorithmType()) and name = "UnknownSignature" + or + // Key Encapsulation Mechanisms + type = TKeyEncapsulation(Kyber()) and name = "Kyber" + or + type = TKeyEncapsulation(FrodoKEM()) and name = "FrodoKEM" + or + type = TKeyEncapsulation(OtherKEMAlgorithmType()) and name = "UnknownKEM" + or + // Unknown + type = TUnknownKeyOperationAlgorithmType() and name = "Unknown" + } + + class Algorithm extends TAlgorithm { + string toString() { type_to_name(this, result) } + } + + class SymmetricCipherAlgorithm extends Algorithm, TSymmetricCipher { + TSymmetricCipherType type; + + SymmetricCipherAlgorithm() { this = TSymmetricCipher(type) } + + TSymmetricCipherType getType() { result = type } + } + + class AsymmetricCipherAlgorithm extends Algorithm, TAsymmetricCipher { + TAsymmetricCipherType type; + + AsymmetricCipherAlgorithm() { this = TAsymmetricCipher(type) } + + TAsymmetricCipherType getType() { result = type } + } + } + + /** + * A key-based cryptographic operation instance, encompassing: + * 1. **Ciphers**: Encryption and decryption, both symmetric and asymmetric + * 1. **Signing**: Signing and verifying, **NOT** including MACs (see `MACOperationInstance`) + * 1. **Key encapsulation**: Key wrapping and unwrapping + * + * This class represents a generic key operation that transforms input data + * using a cryptographic key, producing an output artifact such as ciphertext, + * plaintext, a signature, or an (un-)wrapped key. + */ + abstract class KeyOperationInstance extends OperationInstance { + final KeyOperationOutputArtifactInstance getOutputArtifactInstance() { + result.getOutputNode() = this.getOutputArtifact() + } /** - * Gets the consumer of nonces/IVs associated with this cipher operation. + * Gets the subtype of this key operation, distinguishing operations such as + * encryption, decryption, signing, verification, key wrapping, and key unwrapping. */ - abstract ConsumerInputDataFlowNode getNonceConsumer(); + abstract KeyOperationSubtype getKeyOperationSubtype(); /** - * Gets the consumer of plaintext or ciphertext input associated with this cipher operation. + * Gets the consumer of the cryptographic key used in this key operation. + * The key may be symmetric or asymmetric, depending on the operation subtype. */ - abstract ConsumerInputDataFlowNode getInputConsumer(); + abstract ConsumerInputDataFlowNode getKeyConsumer(); /** - * Gets the consumer of a key. + * Gets the consumer of nonces or initialization vectors (IVs) associated with this key operation. + * These are typically required for encryption, AEAD, or wrap modes. + * + * If the operation does not require a nonce, this predicate should be implemented as `none()`. */ - abstract ConsumerInputDataFlowNode getKeyConsumer(); + abstract ConsumerInputDataFlowNode getNonceConsumer(); /** - * Gets the output artifact of this cipher operation. + * Gets the consumer of the primary message input for this key operation. + * For example: plaintext (for encryption), ciphertext (for decryption), + * message to be signed, or wrapped key to be unwrapped. + */ + abstract ConsumerInputDataFlowNode getInputConsumer(); + + /** + * Gets the output artifact produced by this key operation. + * This may represent ciphertext, a digital signature, a wrapped key, or any + * other data resulting from the operation. * * Implementation guidelines: - * 1. Each unique output target should have an artifact. - * 1. Discarded outputs from intermittent calls should not be artifacts. + * 1. Each semantically meaningful output should result in an artifact. + * 2. Discarded or transient intermediate values should not be artifacts. */ - abstract CipherOutputArtifactInstance getOutputArtifact(); + abstract ArtifactOutputDataFlowNode getOutputArtifact(); } - abstract class CipherAlgorithmInstance extends AlgorithmInstance { + /** + * A key-based algorithm instance used in cryptographic operations such as encryption, decryption, + * signing, verification, and key wrapping. + */ + abstract class KeyOperationAlgorithmInstance extends AlgorithmInstance { /** - * Gets the raw name as it appears in source, e.g., "AES/CBC/PKCS7Padding". - * This name is not parsed or formatted. + * Gets the raw algorithm name as provided in source, e.g., "AES/CBC/PKCS7Padding". + * This name is not parsed or normalized. */ - abstract string getRawCipherAlgorithmName(); + abstract string getRawAlgorithmName(); /** - * Gets the type of this cipher, e.g., "AES" or "ChaCha20". + * Gets the key operation algorithm type, e.g., `TSignature(Ed25519())` or `TSymmetricCipher(AES())`. + * + * If the category of algorithm is known, but the precise algorithm is not, the following type hints should be used: + * - `TSymmetricCipher(OtherSymmetricCipherType())` + * - `TAsymmetricCipher(OtherAsymmetricCipherType())` + * - `TSignature(OtherSignatureAlgorithmType())` + * - `TKeyEncapsulation(OtherKEMAlgorithmType())` + * + * If the category of algorithm is not known, the following type should be used: + * - `TUnknownKeyOperationAlgorithmType()` + * + * This predicate should always hold. */ - abstract TCipherType getCipherFamily(); + abstract KeyOpAlg::Algorithm getAlgorithmType(); /** - * Gets the mode of operation of this cipher, e.g., "GCM" or "CBC". + * Gets the mode of operation, such as "CBC", "GCM", or "ECB". + * + * Edge-cases and modeling guidance: + * - Mode of operation not identifiable: result is `none()`. + * - No mode possible (e.g., RSA, DSA, or ChaCha20): result is `none()`. + * - Mode of operation explicitly specified as none: result is `ModeOfOperationAlgorithmInstance`. * - * IMPLEMENTATION NOTE: as a tradeoff, this is not a consumer but always either an instance or unknown. - * A mode of operation is therefore assumed to always be part of the cipher algorithm itself. + * IMPLEMENTATION NOTE: This is treated as part of the algorithm identity and + * not modeled as a separate algorithm value consumer. */ abstract ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm(); /** - * Gets the padding scheme of this cipher, e.g., "PKCS7" or "NoPadding". + * Gets the padding scheme, such as "PKCS7", "OAEP", or "NoPadding". * - * IMPLEMENTATION NOTE: as a tradeoff, this is not a consumer but always either an instance or unknown. - * A padding algorithm is therefore assumed to always be defined as part of the cipher algorithm itself. + * See the modeling guidance for `getModeOfOperationAlgorithm` for modeling guidance. */ abstract PaddingAlgorithmInstance getPaddingAlgorithm(); + + /** + * Gets the key size in bits specified for this algorithm variant, for example, "128" for "AES-128". This predicate is only + * necessary to specify if there are multiple variants of the algorithm defined by key size and a specific key size is known. + * + * If a specific key size is unknown, this predicate should be implemented as `none()`. + * + * If the algorithm accepts a range of key sizes without a particular one specified, this predicate should be implemented as `none()`. + */ + abstract string getKeySizeFixed(); + + /** + * Gets a consumer for the key size in bits specified for this algorithm variant. + */ + abstract ConsumerInputDataFlowNode getKeySizeConsumer(); + + /** + * Holds if this algorithm is expected to have a mode specified. + */ + predicate shouldHaveModeOfOperation() { any() } + + /** + * Holds if this algorithm is expected to have a padding scheme specified. + */ + predicate shouldHavePaddingScheme() { any() } } + newtype TBlockCipherModeOfOperationType = + ECB() or // Not secure, widely used + CBC() or // Vulnerable to padding oracle attacks + CFB() or + GCM() or // Widely used AEAD mode (TLS 1.3, SSH, IPsec) + CTR() or // Fast stream-like encryption (SSH, disk encryption) + XTS() or // Standard for full-disk encryption (BitLocker, LUKS, FileVault) + CCM() or // Used in lightweight cryptography (IoT, WPA2) + SIV() or // Misuse-resistant encryption, used in secure storage + OCB() or // Efficient AEAD mode + OFB() or + OtherMode() + abstract class ModeOfOperationAlgorithmInstance extends AlgorithmInstance { /** * Gets the type of this mode of operation, e.g., "ECB" or "CBC". @@ -541,7 +830,7 @@ module CryptographyBase Input> { * * If a type cannot be determined, the result is `OtherMode`. */ - abstract TBlockCipherModeOperationType getModeType(); + abstract TBlockCipherModeOfOperationType getModeType(); /** * Gets the isolated name as it appears in source, e.g., "CBC" in "AES/CBC/PKCS7Padding". @@ -570,7 +859,7 @@ module CryptographyBase Input> { } abstract class OAEPPaddingAlgorithmInstance extends PaddingAlgorithmInstance { - OAEPPaddingAlgorithmInstance() { this.getPaddingType() = OAEP() } + OAEPPaddingAlgorithmInstance() { this.getPaddingType() instanceof OAEP } /** * Gets the hash algorithm used in this padding scheme. @@ -585,6 +874,7 @@ module CryptographyBase Input> { newtype TMACType = THMAC() or + TCMAC() or TOtherMACType() abstract class MACAlgorithmInstance extends AlgorithmInstance { @@ -622,37 +912,37 @@ module CryptographyBase Input> { abstract AlgorithmValueConsumer getHashAlgorithmValueConsumer(); } - abstract class KeyEncapsulationOperationInstance extends OperationInstance { } - - abstract class KeyEncapsulationAlgorithmInstance extends AlgorithmInstance { } - - abstract class EllipticCurveAlgorithmInstance extends AlgorithmInstance { + abstract class EllipticCurveInstance extends AlgorithmInstance { /** * Gets the isolated name as it appears in source * * This name should not be parsed or formatted beyond isolating the raw name if necessary. */ - abstract string getRawEllipticCurveAlgorithmName(); + abstract string getRawEllipticCurveName(); + + abstract TEllipticCurveType getEllipticCurveType(); + + abstract string getKeySize(); /** - * The 'standard' curve name, e.g., "P-256" or "secp256r1". - * meaning the full name of the curve, including the family, key size, and other - * typical parameters found on the name. In many cases this will - * be equivalent to `getRawEllipticCurveAlgorithmName()`, but not always - * (e.g., if the curve is specified through a raw NID). + * The 'parsed' curve name, e.g., "P-256" or "secp256r1" + * The parsed name is full name of the curve, including the family, key size, and other + * typical parameters found on the name. + * + * In many cases this will be equivalent to `getRawEllipticCurveAlgorithmName()`, + * but not always (e.g., if the curve is specified through a raw NID). + * * In cases like an NID, we want the standardized name so users can quickly * understand what the curve is, while also parsing out the family and key size * separately. */ - abstract string getStandardCurveName(); - - abstract TEllipticCurveType getEllipticCurveFamily(); - - abstract string getKeySize(); + string getParsedEllipticCurveName() { result = this.getRawEllipticCurveName() } } abstract class HashOperationInstance extends OperationInstance { - abstract DigestArtifactInstance getDigestArtifact(); + abstract ArtifactOutputDataFlowNode getOutputArtifact(); + + abstract ConsumerInputDataFlowNode getInputConsumer(); } abstract class HashAlgorithmInstance extends AlgorithmInstance { @@ -686,7 +976,7 @@ module CryptographyBase Input> { abstract KeyArtifactType getOutputKeyType(); // Defaults or fixed values - abstract string getKeySizeFixed(); + string getKeySizeFixed() { none() } // Consumer input nodes abstract ConsumerInputDataFlowNode getKeySizeConsumer(); @@ -702,9 +992,9 @@ module CryptographyBase Input> { final override string getKeyCreationTypeDescription() { result = "KeyDerivation" } // Defaults or fixed values - abstract string getIterationCountFixed(); + string getIterationCountFixed() { none() } - abstract string getOutputKeySizeFixed(); + string getOutputKeySizeFixed() { none() } // Generic consumer input nodes abstract ConsumerInputDataFlowNode getIterationCountConsumer(); @@ -722,6 +1012,7 @@ module CryptographyBase Input> { PBES() or HKDF() or ARGON2() or + SCRYPT() or OtherKeyDerivationType() abstract class KeyDerivationAlgorithmInstance extends AlgorithmInstance { @@ -748,6 +1039,18 @@ module CryptographyBase Input> { abstract AlgorithmValueConsumer getHMACAlgorithmValueConsumer(); } + abstract class ScryptAlgorithmInstance extends KeyDerivationAlgorithmInstance { + ScryptAlgorithmInstance() { this.getKDFType() instanceof SCRYPT } + + /** + * Gets the HMAC algorithm used by this PBKDF2 algorithm. + * + * Note: Other PRFs are not supported, as most cryptographic libraries + * only support HMAC for PBKDF2's PRF input. + */ + abstract AlgorithmValueConsumer getHMACAlgorithmValueConsumer(); + } + abstract class KeyGenerationOperationInstance extends KeyCreationOperationInstance { final override string getKeyCreationTypeDescription() { result = "KeyGeneration" } } @@ -756,10 +1059,62 @@ module CryptographyBase Input> { final override string getKeyCreationTypeDescription() { result = "KeyLoad" } } + // Key agreement algorithms + newtype TKeyAgreementType = + DH() or // Diffie-Hellman + EDH() or // Ephemeral Diffie-Hellman + ECDH() or // Elliptic Curve Diffie-Hellman + // Note: x25519 and x448 are applications of ECDH + UnknownKeyAgreementType() + + abstract class KeyAgreementAlgorithmInstance extends AlgorithmInstance { + abstract TKeyAgreementType getKeyAgreementType(); + + abstract string getRawKeyAgreementAlgorithmName(); + } + + abstract class ECDHKeyAgreementAlgorithmInstance extends KeyAgreementAlgorithmInstance { + ECDHKeyAgreementAlgorithmInstance() { this.getKeyAgreementType() instanceof ECDH } + + /** + * Gets the consumer for the elliptic curve used in the key agreement operation. + */ + abstract AlgorithmValueConsumer getEllipticCurveAlgorithmValueConsumer(); + } + + abstract class KeyAgreementSecretGenerationOperationInstance extends OperationInstance { + /** + * The private key used in the key agreement operation. + * This key represents the local party in the key agreement. + */ + abstract ConsumerInputDataFlowNode getServerKeyConsumer(); + + /** + * The public key used in the key agreement operation, coming + * from the peer (the other party in the key agreement). + */ + abstract ConsumerInputDataFlowNode getPeerKeyConsumer(); + + /** + * The output artifact produced by the key agreement operation. + * This artifact represents the shared secret generated during + * the key agreement process. + */ + abstract ArtifactOutputDataFlowNode getOutputArtifact(); + } + private signature class AlgorithmInstanceType instanceof AlgorithmInstance; private signature predicate isCandidateAVCSig(AlgorithmValueConsumer avc); + /** + * An element that is either an `AlgorithmInstance` or an `AlgorithmValueConsumer` with no known sources. + * + * This concept is used to model consumers that have no known source as an algorithm node. + * + * The `isCandidateAVCSig` predicate is used to restrict the set of consumers that expect inputs of `AlgorithmInstanceType`. + * These "total unknown" algorithm nodes would otherwise not exist if not modelled as a consumer node. + */ module AlgorithmInstanceOrValueConsumer< AlgorithmInstanceType Alg, isCandidateAVCSig/1 isCandidateAVC> { @@ -769,14 +1124,17 @@ module CryptographyBase Input> { this instanceof Alg or // Or an AlgorithmValueConsumer with unknown sources and no known sources + not this instanceof Alg and isCandidateAVC(this) and not exists(this.(AlgorithmValueConsumer).getAKnownAlgorithmSource()) and - exists(this.(AlgorithmValueConsumer).getAGenericSource()) + exists(GenericSourceInstance src | + src = this.(AlgorithmValueConsumer).getAGenericSource() and src != this + ) } Alg asAlg() { result = this } - AlgorithmValueConsumer asAVC() { result = this } + AlgorithmValueConsumer asAVC() { result = this and not this instanceof Alg } } } @@ -785,8 +1143,8 @@ module CryptographyBase Input> { exists(HMACAlgorithmInstance alg | avc = alg.getAConsumer()) } - private predicate isCipherAVC(AlgorithmValueConsumer avc) { - exists(CipherOperationInstance op | op.getAnAlgorithmValueConsumer() = avc) + private predicate isKeyOperationAlgorithmAVC(AlgorithmValueConsumer avc) { + exists(KeyOperationInstance op | op.getAnAlgorithmValueConsumer() = avc) } private predicate isMACAVC(AlgorithmValueConsumer avc) { @@ -798,8 +1156,21 @@ module CryptographyBase Input> { exists(KeyDerivationOperationInstance op | op.getAnAlgorithmValueConsumer() = avc) } - final private class CipherAlgorithmInstanceOrValueConsumer = - AlgorithmInstanceOrValueConsumer::Union; + private predicate isEllipticCurveAVC(AlgorithmValueConsumer avc) { + exists(ECDHKeyAgreementAlgorithmInstance alg | + avc = alg.getEllipticCurveAlgorithmValueConsumer() + ) or + exists(KeyGenerationOperationInstance op | op.getAnAlgorithmValueConsumer() = avc) + } + + private predicate isKeyAgreementAVC(AlgorithmValueConsumer avc) { + exists(KeyAgreementSecretGenerationOperationInstance op | + op.getAnAlgorithmValueConsumer() = avc + ) + } + + final private class KeyOperationAlgorithmInstanceOrValueConsumer = + AlgorithmInstanceOrValueConsumer::Union; final private class HashAlgorithmInstanceOrValueConsumer = AlgorithmInstanceOrValueConsumer::Union; @@ -810,50 +1181,52 @@ module CryptographyBase Input> { final private class KeyDerivationAlgorithmInstanceOrValueConsumer = AlgorithmInstanceOrValueConsumer::Union; - final private class EllipticCurveAlgorithmInstanceOrValueConsumer = - AlgorithmInstanceOrValueConsumer::Union; + final private class EllipticCurveInstanceOrValueConsumer = + AlgorithmInstanceOrValueConsumer::Union; + + final private class KeyAgreementAlgorithmInstanceOrValueConsumer = + AlgorithmInstanceOrValueConsumer::Union; private newtype TNode = - // Artifacts (data that is not an operation or algorithm, e.g., a key) - TDigest(DigestArtifactInstance e) or + // Output artifacts (data that is not an operation or algorithm, e.g., a key) + TDigest(HashOutputArtifactInstance e) or TKey(KeyArtifactInstance e) or - TCipherOutput(CipherOutputArtifactInstance e) or - // Input artifact nodes (synthetic, used to differentiate input as entities) + TSharedSecret(KeyAgreementSharedSecretOutputArtifactInstance e) or + // Input artifacts (synthetic nodes, used to differentiate input as entities) TNonceInput(NonceArtifactConsumer e) or TMessageInput(MessageArtifactConsumer e) or TSaltInput(SaltArtifactConsumer e) or TRandomNumberGeneration(RandomNumberGenerationInstance e) { e.flowsTo(_) } or - // Operations (e.g., hashing, encryption) + // Key Creation Operation union type (e.g., key generation, key load) + TKeyCreationOperation(KeyCreationOperationInstance e) or + // Key operations, algorithms, and artifacts + // These types are union types of encryption, signing, encapsulation and their algorithms/artifacts. + // The artifacts are the outputs, e.g., ciphertext, signature, wrapped key. + TKeyOperation(KeyOperationInstance e) or + TKeyOperationAlgorithm(KeyOperationAlgorithmInstanceOrValueConsumer e) or + TKeyOperationOutput(KeyOperationOutputArtifactInstance e) or + // Non-Standalone Algorithms (e.g., Mode, Padding) + // These algorithms are always tied to a key operation algorithm + TModeOfOperationAlgorithm(ModeOfOperationAlgorithmInstance e) or + TPaddingAlgorithm(PaddingAlgorithmInstance e) or + // All other operations THashOperation(HashOperationInstance e) or - TCipherOperation(CipherOperationInstance e) or - TKeyEncapsulationOperation(KeyEncapsulationOperationInstance e) or TMACOperation(MACOperationInstance e) or - // Key Creation Operations - TKeyCreationOperation(KeyCreationOperationInstance e) or - // Algorithms (e.g., SHA-256, AES) - TCipherAlgorithm(CipherAlgorithmInstanceOrValueConsumer e) or - TEllipticCurveAlgorithm(EllipticCurveAlgorithmInstanceOrValueConsumer e) or + TKeyAgreementOperation(KeyAgreementSecretGenerationOperationInstance e) or + // All other algorithms + TEllipticCurve(EllipticCurveInstanceOrValueConsumer e) or THashAlgorithm(HashAlgorithmInstanceOrValueConsumer e) or TKeyDerivationAlgorithm(KeyDerivationAlgorithmInstanceOrValueConsumer e) or - TKeyEncapsulationAlgorithm(KeyEncapsulationAlgorithmInstance e) or TMACAlgorithm(MACAlgorithmInstanceOrValueConsumer e) or - // Non-standalone Algorithms (e.g., Mode, Padding) - // TODO: need to rename this, as "mode" is getting reused in different contexts, be precise - TModeOfOperationAlgorithm(ModeOfOperationAlgorithmInstance e) or - TPaddingAlgorithm(PaddingAlgorithmInstance e) or - // Composite and hybrid cryptosystems (e.g., RSA-OAEP used with AES, post-quantum hybrid cryptosystems) - // These nodes are always parent nodes and are not modeled but rather defined via library-agnostic patterns. - TKemDemHybridCryptosystem(CipherAlgorithmNode dem) or // TODO, change this relation and the below ones - TKeyAgreementHybridCryptosystem(CipherAlgorithmInstance ka) or - TAsymmetricEncryptionMacHybridCryptosystem(CipherAlgorithmInstance enc) or - TPostQuantumHybridCryptosystem(CipherAlgorithmInstance enc) or - // Generic source nodes + TKeyAgreementAlgorithm(KeyAgreementAlgorithmInstanceOrValueConsumer e) or + // Generic source nodes, i.e., sources of data that are not resolvable to a specific known asset. TGenericSourceNode(GenericSourceInstance e) { // An element modelled as a `GenericSourceInstance` can also be modelled as a `KnownElement` // For example, a string literal "AES" could be a generic constant but also an algorithm instance. - // Only create generic nodes tied to instances which are not also a `KnownElement`. + // + // Therefore, only create generic nodes tied to instances which are not also a `KnownElement`... not e instanceof KnownElement and - // Only create nodes for generic sources which flow to other elements + // ... and that flow to other elements e.flowsTo(_) } @@ -862,25 +1235,8 @@ module CryptographyBase Input> { * * Each `NodeBase` is a node in a graph of cryptographic operations, where the edges are the relationships between the nodes. * - * A node, as opposed to a property, is a construct that can reference or be referenced by more than one node. - * For example: a key size is a single value configuring a cipher algorithm, but a single mode of operation algorithm - * can be referenced by multiple disjoint cipher algorithms. For example, even if the same key size value is reused - * for multiple cipher algorithms, the key size holds no information when devolved to that simple value, and it is - * therefore not a "construct" or "element" being reused by multiple nodes. - * - * As a rule of thumb, a node is an algorithm or the use of an algorithm (an operation), as well as structured data - * consumed by or produced by an operation or algorithm (an artifact) that represents a construct beyond its data. - * - * _Example 1_: A seed of a random number generation algorithm has meaning beyond its value, as its reuse in multiple - * random number generation algorithms is more relevant than its underlying value. In contrast, a key size is only - * relevant to analysis in terms of its underlying value. Therefore, an RNG seed is a node; a key size is not. However, - * the key size might have a `GenericSourceNode` source, even if it itself is not a node. - * - * _Example 2_: A salt for a key derivation function *is* an `ArtifactNode`. - * - * _Example 3_: The iteration count of a key derivation function is *not* a node, but it may link to a generic node. - * - * _Example 4_: A nonce for a cipher operation *is* an `ArtifactNode`. + * As a rule of thumb, a node is an algorithm or the use of an algorithm (an operation), as well as structured data (an artifact) + * consumed by or produced by an operation or algorithm. */ abstract class NodeBase extends TNode { /** @@ -931,7 +1287,7 @@ module CryptographyBase Input> { /** * A generic source node is a source of data that is not resolvable to a specific asset. */ - private class GenericSourceNode extends NodeBase, TGenericSourceNode { + final class GenericSourceNode extends NodeBase, TGenericSourceNode { GenericSourceInstance instance; GenericSourceNode() { this = TGenericSourceNode(instance) } @@ -1056,7 +1412,7 @@ module CryptographyBase Input> { } /** - * A nonce or initialization vector input + * A nonce or initialization vector input. */ final class NonceArtifactNode extends ArtifactNode, TNonceInput { NonceArtifactConsumer instance; @@ -1069,7 +1425,7 @@ module CryptographyBase Input> { } /** - * A message or plaintext/ciphertext input + * A message or plaintext/ciphertext input. */ final class MessageArtifactNode extends ArtifactNode, TMessageInput { MessageArtifactConsumer instance; @@ -1082,7 +1438,7 @@ module CryptographyBase Input> { } /** - * A salt input + * A salt input. */ final class SaltArtifactNode extends ArtifactNode, TSaltInput { SaltArtifactConsumer instance; @@ -1095,14 +1451,16 @@ module CryptographyBase Input> { } /** - * Output text from a cipher operation + * The base class for output nodes from key operations. + * + * This class represents the output of key generation, key derivation, encryption, decryption, signing, and verification. */ - final class CipherOutputNode extends ArtifactNode, TCipherOutput { - CipherOutputArtifactInstance instance; + class KeyOperationOutputNode extends ArtifactNode, TKeyOperationOutput { + KeyOperationOutputArtifactInstance instance; - CipherOutputNode() { this = TCipherOutput(instance) } + KeyOperationOutputNode() { this = TKeyOperationOutput(instance) } - final override string getInternalType() { result = "CipherOutput" } + final override string getInternalType() { result = "KeyOperationOutput" } override LocatableElement asElement() { result = instance } @@ -1110,7 +1468,7 @@ module CryptographyBase Input> { } /** - * A source of random number generation + * A source of random number generation. */ final class RandomNumberGenerationNode extends ArtifactNode, TRandomNumberGeneration { RandomNumberGenerationInstance instance; @@ -1122,6 +1480,31 @@ module CryptographyBase Input> { override LocatableElement asElement() { result = instance } override string getSourceNodeRelationship() { none() } // TODO: seed? + + override predicate properties(string key, string value, Location location) { + super.properties(key, value, location) + or + // [ONLY_KNOWN] + key = "Description" and + value = instance.getGeneratorName() and + location = this.getLocation() + } + } + + /** + * A union type of all algorithm types that can be used in key creation operations. + */ + class TKeyCreationCandidateAlgorithm = + TKeyOperationAlgorithm or TEllipticCurve or TKeyAgreementAlgorithm or TKeyDerivationAlgorithm; + + /** + * A candidate algorithm node for key creation. + * + * Note: This is not an independent node type, but a subset of `AlgorithmNode` that is of type `TKeyCreationCandidateAlgorithm`. + */ + private class KeyCreationCandidateAlgorithmNode extends TKeyCreationCandidateAlgorithm instanceof AlgorithmNode + { + string toString() { result = super.getAlgorithmName() } } /** @@ -1145,7 +1528,7 @@ module CryptographyBase Input> { .getAGenericSourceNode() } - CipherAlgorithmNode getAKnownAlgorithm() { + KeyCreationCandidateAlgorithmNode getAKnownAlgorithm() { result = instance.(KeyCreationOperationInstance).getAnAlgorithmValueConsumer().getAKnownSourceNode() } @@ -1179,10 +1562,10 @@ module CryptographyBase Input> { } /** - * A digest produced by a hash operation. + * A digest artifact produced by a hash operation. */ final class DigestArtifactNode extends ArtifactNode, TDigest { - DigestArtifactInstance instance; + HashOutputArtifactInstance instance; DigestArtifactNode() { this = TDigest(instance) } @@ -1191,6 +1574,19 @@ module CryptographyBase Input> { override LocatableElement asElement() { result = instance } } + /** + * A shared secret artifact produced by a key agreement operation. + */ + final class SharedSecretArtifactNode extends ArtifactNode, TSharedSecret { + KeyAgreementSharedSecretOutputArtifactInstance instance; + + SharedSecretArtifactNode() { this = TSharedSecret(instance) } + + final override string getInternalType() { result = "SharedSecret" } + + override LocatableElement asElement() { result = instance } + } + abstract class KeyCreationOperationNode extends OperationNode, TKeyCreationOperation { KeyCreationOperationInstance instance; @@ -1298,13 +1694,71 @@ module CryptographyBase Input> { } } + class KeyAgreementOperationNode extends OperationNode, TKeyAgreementOperation { + KeyAgreementSecretGenerationOperationInstance instance; + + KeyAgreementOperationNode() { this = TKeyAgreementOperation(instance) } + + final override string getInternalType() { result = "KeyAgreementOperation" } + + override LocatableElement asElement() { result = instance } + + override predicate isCandidateAlgorithmNode(AlgorithmNode node) { + node instanceof KeyAgreementAlgorithmNode + } + + SharedSecretArtifactNode getOutput() { + result.asElement() = instance.getOutputArtifact().getArtifact() + } + + KeyArtifactNode getServerKey() { + result.asElement() = instance.getServerKeyConsumer().getConsumer() + } + + KeyArtifactNode getPeerKey() { + result.asElement() = instance.getPeerKeyConsumer().getConsumer() + } + + override NodeBase getChild(string key) { + result = super.getChild(key) + or + // [ALWAYS_KNOWN] + key = "Output" and + result = this.getOutput() + or + // [KNOWN_OR_UNKOWN] + key = "ServerKey" and + if exists(this.getServerKey()) then result = this.getServerKey() else result = this + or + // [KNOWN_OR_UNKOWN] + key = "PeerKey" and + if exists(this.getPeerKey()) then result = this.getPeerKey() else result = this + } + } + + class KeyAgreementAlgorithmNode extends AlgorithmNode, TKeyAgreementAlgorithm { + KeyAgreementAlgorithmInstanceOrValueConsumer instance; + + KeyAgreementAlgorithmNode() { this = TKeyAgreementAlgorithm(instance) } + + final override string getInternalType() { result = "KeyAgreementAlgorithm" } + + override LocatableElement asElement() { result = instance } + + final override string getRawAlgorithmName() { + result = instance.asAlg().getRawKeyAgreementAlgorithmName() + } + + override string getAlgorithmName() { result = this.getRawAlgorithmName() } // TODO: standardize? + } + class KeyGenerationOperationNode extends KeyCreationOperationNode { KeyGenerationOperationInstance keyGenInstance; KeyGenerationOperationNode() { keyGenInstance = instance } override predicate isCandidateAlgorithmNode(AlgorithmNode node) { - node instanceof CipherAlgorithmNode + node instanceof KeyCreationCandidateAlgorithmNode } override NodeBase getChild(string key) { @@ -1389,7 +1843,7 @@ module CryptographyBase Input> { result = instance.asAlg().getRawKDFAlgorithmName() } - final override string getAlgorithmName() { result = this.getRawAlgorithmName() } + override string getAlgorithmName() { result = this.getRawAlgorithmName() } // TODO: standardize? } /** @@ -1413,195 +1867,97 @@ module CryptographyBase Input> { } } - // /** - // * PKCS12KDF key derivation function - // */ - // abstract class PKCS12KDF extends KeyDerivationWithDigestParameterNode { - // override string getAlgorithmName() { result = "PKCS12KDF" } - // /** - // * Gets the iteration count of this key derivation algorithm. - // */ - // abstract string getIterationCount(Location location); - // /** - // * Gets the raw ID argument specifying the intended use of the derived key. - // * - // * The intended use is defined in RFC 7292, appendix B.3, as follows: - // * - // * This standard specifies 3 different values for the ID byte mentioned above: - // * - // * 1. If ID=1, then the pseudorandom bits being produced are to be used - // * as key material for performing encryption or decryption. - // * - // * 2. If ID=2, then the pseudorandom bits being produced are to be used - // * as an IV (Initial Value) for encryption or decryption. - // * - // * 3. If ID=3, then the pseudorandom bits being produced are to be used - // * as an integrity key for MACing. - // */ - // abstract string getIDByte(Location location); - // override predicate properties(string key, string value, Location location) { - // super.properties(key, value, location) - // or - // ( - // // [KNOWN_OR_UNKNOWN] - // key = "Iterations" and - // if exists(this.getIterationCount(location)) - // then value = this.getIterationCount(location) - // else ( - // value instanceof UnknownPropertyValue and location instanceof UnknownLocation - // ) - // ) - // or - // ( - // // [KNOWN_OR_UNKNOWN] - // key = "IdByte" and - // if exists(this.getIDByte(location)) - // then value = this.getIDByte(location) - // else ( - // value instanceof UnknownPropertyValue and location instanceof UnknownLocation - // ) - // ) - // } - // } - // /** - // * scrypt key derivation function - // */ - // abstract class SCRYPT extends KeyDerivationAlgorithmNode { - // final override string getAlgorithmName() { result = "scrypt" } - // /** - // * Gets the iteration count (`N`) argument - // */ - // abstract string get_N(Location location); - // /** - // * Gets the block size (`r`) argument - // */ - // abstract string get_r(Location location); - // /** - // * Gets the parallelization factor (`p`) argument - // */ - // abstract string get_p(Location location); - // /** - // * Gets the derived key length argument - // */ - // abstract string getDerivedKeyLength(Location location); - // override predicate properties(string key, string value, Location location) { - // super.properties(key, value, location) - // or - // ( - // // [KNOWN_OR_UNKNOWN] - // key = "N" and - // if exists(this.get_N(location)) - // then value = this.get_N(location) - // else ( - // value instanceof UnknownPropertyValue and location instanceof UnknownLocation - // ) - // ) - // or - // ( - // // [KNOWN_OR_UNKNOWN] - // key = "r" and - // if exists(this.get_r(location)) - // then value = this.get_r(location) - // else ( - // value instanceof UnknownPropertyValue and location instanceof UnknownLocation - // ) - // ) - // or - // ( - // // [KNOWN_OR_UNKNOWN] - // key = "p" and - // if exists(this.get_p(location)) - // then value = this.get_p(location) - // else ( - // value instanceof UnknownPropertyValue and location instanceof UnknownLocation - // ) - // ) - // or - // ( - // // [KNOWN_OR_UNKNOWN] - // key = "KeyLength" and - // if exists(this.getDerivedKeyLength(location)) - // then value = this.getDerivedKeyLength(location) - // else ( - // value instanceof UnknownPropertyValue and location instanceof UnknownLocation - // ) - // ) - // } - // } - /* - * TODO: - * - * Rule: No newtype representing a type of algorithm should be modelled with multiple interfaces - * - * Example 1: HKDF and PKCS12KDF are both key derivation algorithms. - * However, PKCS12KDF also has a property: the iteration count. - * - * If we have HKDF and PKCS12KDF under TKeyDerivationType, - * someone modelling a library might try to make a generic identification of both of those algorithms. - * - * They will therefore not use the specialized type for PKCS12KDF, - * meaning "from PKCS12KDF algo select algo" will have no results. - * - * Example 2: Each type below represents a common family of elliptic curves, with a shared interface, i.e., - * predicates for library modellers to implement as well as the properties and edges reported. + /** + * scrypt key derivation function */ + class ScryptAlgorithmNode extends KeyDerivationAlgorithmNode { + ScryptAlgorithmInstance scryptInstance; - newtype TCipherOperationSubtype = - TEncryptionMode() or - TDecryptionMode() or - TWrapMode() or - TUnwrapMode() or - TSignatureMode() or - TUnknownCipherOperationMode() - - abstract class CipherOperationSubtype extends TCipherOperationSubtype { - abstract string toString(); - } + ScryptAlgorithmNode() { scryptInstance = instance.asAlg() } - class EncryptionSubtype extends CipherOperationSubtype, TEncryptionMode { - override string toString() { result = "Encrypt" } - } - - class DecryptionSubtype extends CipherOperationSubtype, TDecryptionMode { - override string toString() { result = "Decrypt" } - } + /** + * Gets the iteration count (`N`) argument + */ + GenericSourceNode get_N() { none() } // TODO - class WrapSubtype extends CipherOperationSubtype, TWrapMode { - override string toString() { result = "Wrap" } - } + /** + * Gets the block size (`r`) argument + */ + GenericSourceNode get_r() { none() } // TODO - class UnwrapSubtype extends CipherOperationSubtype, TUnwrapMode { - override string toString() { result = "Unwrap" } + /** + * Gets the parallelization factor (`p`) argument + */ + GenericSourceNode get_p() { none() } // TODO } - class SignatureSubtype extends CipherOperationSubtype, TSignatureMode { - override string toString() { result = "Sign" } - } + /** + * A type defining the subtype type of a key operation. + */ + newtype TKeyOperationSubtype = + TEncryptMode() or + TDecryptMode() or + TWrapMode() or + TUnwrapMode() or + TSignMode() or + TVerifyMode() or + TUnknownKeyOperationMode() - class UnknownCipherOperationSubtype extends CipherOperationSubtype, TUnknownCipherOperationMode { - override string toString() { result = "Unknown" } + /** + * A class defining the subtype of a key operation. + */ + class KeyOperationSubtype extends TKeyOperationSubtype { + string toString() { + result = "Encrypt" and this = TEncryptMode() + or + result = "Decrypt" and this = TDecryptMode() + or + result = "Wrap" and this = TWrapMode() + or + result = "Unwrap" and this = TUnwrapMode() + or + result = "Sign" and this = TSignMode() + or + result = "Verify" and this = TVerifyMode() + or + result = "Unknown" and this = TUnknownKeyOperationMode() + } } /** - * An encryption operation that processes plaintext to generate a ciphertext. - * This operation takes an input message (plaintext) of arbitrary content and length - * and produces a ciphertext as the output using a specified encryption algorithm (with a mode and padding). + * A key-based cryptographic transformation that operates on data using either a symmetric or asymmetric cryptographic key. + * + * This operation class covers operations based on symmetric ciphers or broader asymmetric algorithms, including: + * + * - **Encryption / Decryption**: + * Symmetric (e.g., AES-GCM) or asymmetric (e.g., RSA-OAEP, ECIES) encryption of plaintext to ciphertext or vice-versa. + * + * - **Key Wrapping / Unwrapping**: + * Encapsulation of symmetric keys using algorithms such as Kyber, AES-KW, RSA-KEM, RSA-OAEP, etc. + * + * - **Signing / Verifying**: + * Digital signatures using private/public keypairs (e.g., Ed25519, RSA-PSS, ECDSA) + * + * Each sub-operation is represented by a `CipherOperationSubtype`, such as `Encrypt`, `Sign`, `Wrap`, etc. + * + * Note: This class does _not_ include symmetric message authentication operations (MACs) like HMAC or CMAC. + * These are handled separately in the `MacOperationNode` class. */ - final class CipherOperationNode extends OperationNode, TCipherOperation { - CipherOperationInstance instance; + class KeyOperationNode extends OperationNode, TKeyOperation { + KeyOperationInstance instance; - CipherOperationNode() { this = TCipherOperation(instance) } + KeyOperationNode() { this = TKeyOperation(instance) } + + final KeyOperationSubtype getKeyOperationSubtype() { + result = instance.getKeyOperationSubtype() + } override LocatableElement asElement() { result = instance } - override string getInternalType() { result = "CipherOperation" } + override string getInternalType() { result = "KeyOperation" } override predicate isCandidateAlgorithmNode(AlgorithmNode node) { - node instanceof CipherAlgorithmNode - } - - CipherOperationSubtype getCipherOperationSubtype() { - result = instance.getCipherOperationSubtype() + node instanceof KeyOperationAlgorithmNode } NonceArtifactNode getANonce() { result.asElement() = instance.getNonceConsumer().getConsumer() } @@ -1610,25 +1966,29 @@ module CryptographyBase Input> { result.asElement() = instance.getInputConsumer().getConsumer() } - CipherOutputNode getAnOutputArtifact() { result.asElement() = instance.getOutputArtifact() } + KeyOperationOutputNode getAnOutputArtifact() { + result.asElement() = instance.getOutputArtifactInstance() + } KeyArtifactNode getAKey() { result.asElement() = instance.getKeyConsumer().getConsumer() } override NodeBase getChild(string key) { result = super.getChild(key) or - // [KNOWN_OR_UNKNOWN] + // [KNOWN_OR_UNKNOWN] - but only if not sign/verify + not this instanceof SignatureOperationNode and key = "Nonce" and if exists(this.getANonce()) then result = this.getANonce() else result = this or // [KNOWN_OR_UNKNOWN] - key = "InputText" and + key = "Input" and if exists(this.getAnInputArtifact()) then result = this.getAnInputArtifact() else result = this or - // [KNOWN_OR_UNKNOWN] - key = "OutputText" and + // [KNOWN_OR_UNKNOWN] - but only if not verify + not this.getKeyOperationSubtype() instanceof TVerifyMode and + key = "Output" and if exists(this.getAnOutputArtifact()) then result = this.getAnOutputArtifact() else result = this @@ -1637,33 +1997,47 @@ module CryptographyBase Input> { key = "Key" and if exists(this.getAKey()) then result = this.getAKey() else result = this } + } - override predicate properties(string key, string value, Location location) { - super.properties(key, value, location) + class CipherOperationNode extends KeyOperationNode { + string nodeName; + + CipherOperationNode() { + this.getKeyOperationSubtype() = TEncryptMode() and nodeName = "EncryptOperation" or - // [ALWAYS_KNOWN] - Unknown is handled in getCipherOperationMode() - key = "Operation" and - value = this.getCipherOperationSubtype().toString() and - location = this.getLocation() + this.getKeyOperationSubtype() = TDecryptMode() and nodeName = "DecryptOperation" + } + + override string getInternalType() { result = nodeName } + } + + class KeyEncapsulationOperationNode extends KeyOperationNode { + string nodeName; + + KeyEncapsulationOperationNode() { + this.getKeyOperationSubtype() = TWrapMode() and nodeName = "WrapOperation" + or + this.getKeyOperationSubtype() = TUnwrapMode() and nodeName = "UnwrapOperation" + } + + override string getInternalType() { result = nodeName } + } + + class SignatureOperationNode extends KeyOperationNode { + string nodeName; + + SignatureOperationNode() { + this.getKeyOperationSubtype() = TSignMode() and nodeName = "SignOperation" + or + this.getKeyOperationSubtype() = TVerifyMode() and nodeName = "VerifyOperation" } + + override string getInternalType() { result = nodeName } } /** * Block cipher modes of operation algorithms */ - newtype TBlockCipherModeOperationType = - ECB() or // Not secure, widely used - CBC() or // Vulnerable to padding oracle attacks - CFB() or - GCM() or // Widely used AEAD mode (TLS 1.3, SSH, IPsec) - CTR() or // Fast stream-like encryption (SSH, disk encryption) - XTS() or // Standard for full-disk encryption (BitLocker, LUKS, FileVault) - CCM() or // Used in lightweight cryptography (IoT, WPA2) - SIV() or // Misuse-resistant encryption, used in secure storage - OCB() or // Efficient AEAD mode - OFB() or - OtherMode() - class ModeOfOperationAlgorithmNode extends AlgorithmNode, TModeOfOperationAlgorithm { ModeOfOperationAlgorithmInstance instance; @@ -1682,29 +2056,29 @@ module CryptographyBase Input> { * * If a type cannot be determined, the result is `OtherMode`. */ - TBlockCipherModeOperationType getModeType() { result = instance.getModeType() } + TBlockCipherModeOfOperationType getModeType() { result = instance.getModeType() } bindingset[type] - final private predicate modeToNameMapping(TBlockCipherModeOperationType type, string name) { - type instanceof ECB and name = "ECB" + final private predicate modeToNameMapping(TBlockCipherModeOfOperationType type, string name) { + type = ECB() and name = "ECB" or - type instanceof CBC and name = "CBC" + type = CBC() and name = "CBC" or - type instanceof GCM and name = "GCM" + type = GCM() and name = "GCM" or - type instanceof CTR and name = "CTR" + type = CTR() and name = "CTR" or - type instanceof XTS and name = "XTS" + type = XTS() and name = "XTS" or - type instanceof CCM and name = "CCM" + type = CCM() and name = "CCM" or - type instanceof SIV and name = "SIV" + type = SIV() and name = "SIV" or - type instanceof OCB and name = "OCB" + type = OCB() and name = "OCB" or - type instanceof CFB and name = "CFB" + type = CFB() and name = "CFB" or - type instanceof OFB and name = "OFB" + type = OFB() and name = "OFB" } override string getAlgorithmName() { this.modeToNameMapping(this.getModeType(), result) } @@ -1732,17 +2106,17 @@ module CryptographyBase Input> { bindingset[type] final private predicate paddingToNameMapping(TPaddingType type, string name) { - type instanceof PKCS1_v1_5 and name = "PKCS1_v1_5" + type = ANSI_X9_23() and name = "ANSI_X9_23" or - type instanceof PSS and name = "PSS" + type = NoPadding() and name = "NoPadding" or - type instanceof PKCS7 and name = "PKCS7" + type = OAEP() and name = "OAEP" or - type instanceof ANSI_X9_23 and name = "ANSI_X9_23" + type = PKCS1_v1_5() and name = "PKCS1_v1_5" or - type instanceof NoPadding and name = "NoPadding" + type = PKCS7() and name = "PKCS7" or - type instanceof OAEP and name = "OAEP" + type = PSS() and name = "PSS" } override string getAlgorithmName() { this.paddingToNameMapping(this.getPaddingType(), result) } @@ -1780,81 +2154,49 @@ module CryptographyBase Input> { } } - /** - * A helper type for distinguishing between block and stream ciphers. - */ - newtype TCipherStructureType = - Block() or - Stream() or - Asymmetric() or - UnknownCipherStructureType() - - private string getCipherStructureTypeString(TCipherStructureType type) { - type instanceof Block and result = "Block" - or - type instanceof Stream and result = "Stream" - or - type instanceof Asymmetric and result = "Asymmetric" - or - type instanceof UnknownCipherStructureType and result instanceof UnknownPropertyValue - } + class KeyOperationAlgorithmNode extends AlgorithmNode, TKeyOperationAlgorithm { + KeyOperationAlgorithmInstanceOrValueConsumer instance; - /** - * Symmetric algorithms - */ - newtype TCipherType = - AES() or - ARIA() or - BLOWFISH() or - CAMELLIA() or - CAST5() or - CHACHA20() or - DES() or - DESX() or - GOST() or - IDEA() or - KUZNYECHIK() or - MAGMA() or - TripleDES() or - DoubleDES() or - RC2() or - RC4() or - RC5() or - RSA() or - SEED() or - SM4() or - OtherCipherType() - - final class CipherAlgorithmNode extends AlgorithmNode, TCipherAlgorithm { - CipherAlgorithmInstanceOrValueConsumer instance; - - CipherAlgorithmNode() { this = TCipherAlgorithm(instance) } + KeyOperationAlgorithmNode() { this = TKeyOperationAlgorithm(instance) } override LocatableElement asElement() { result = instance } - override string getInternalType() { result = "CipherAlgorithm" } + override string getInternalType() { result = "KeyOperationAlgorithm" } - final TCipherStructureType getCipherStructure() { - this.cipherFamilyToNameAndStructure(this.getCipherFamily(), _, result) + final KeyOpAlg::CipherStructureType getSymmetricCipherStructure() { + KeyOpAlg::symmetric_cipher_to_name_and_structure(this.getAlgorithmType() + .(KeyOpAlg::SymmetricCipherAlgorithm) + .getType(), _, result) } final override string getAlgorithmName() { - this.cipherFamilyToNameAndStructure(this.getCipherFamily(), result, _) + KeyOpAlg::type_to_name(this.getAlgorithmType(), result) } - final override string getRawAlgorithmName() { - result = instance.asAlg().getRawCipherAlgorithmName() - } + final override string getRawAlgorithmName() { result = instance.asAlg().getRawAlgorithmName() } /** - * Gets the key size of this cipher, e.g., "128" or "256". + * Gets the key size variant of this algorithm in bits, e.g., 128 for "AES-128". */ - string getKeySize(Location location) { none() } // TODO + string getKeySizeFixed() { result = instance.asAlg().getKeySizeFixed() } // TODO: key sizes for known algorithms /** - * Gets the type of this cipher, e.g., "AES" or "ChaCha20". + * Gets the key size generic source node. */ - TCipherType getCipherFamily() { result = instance.asAlg().getCipherFamily() } + GenericSourceNode getKeySize() { + result = instance.asAlg().getKeySizeConsumer().getConsumer().getAGenericSourceNode() + } + + /** + * Gets the type of this key operation algorithm, e.g., "SymmetricEncryption(_)" or "" + */ + KeyOpAlg::Algorithm getAlgorithmType() { result = instance.asAlg().getAlgorithmType() } + + predicate isAsymmetric() { + this.getAlgorithmType() instanceof KeyOpAlg::TAsymmetricCipher + or + this.getAlgorithmType() instanceof KeyOpAlg::TSignature + } /** * Gets the mode of operation of this cipher, e.g., "GCM" or "CBC". @@ -1870,87 +2212,43 @@ module CryptographyBase Input> { result.asElement() = instance.asAlg().getPaddingAlgorithm() } - bindingset[type] - final private predicate cipherFamilyToNameAndStructure( - TCipherType type, string name, TCipherStructureType s - ) { - type instanceof AES and name = "AES" and s = Block() - or - type instanceof ARIA and name = "ARIA" and s = Block() - or - type instanceof BLOWFISH and name = "Blowfish" and s = Block() - or - type instanceof CAMELLIA and name = "Camellia" and s = Block() - or - type instanceof CAST5 and name = "CAST5" and s = Block() - or - type instanceof CHACHA20 and name = "ChaCha20" and s = Stream() - or - type instanceof DES and name = "DES" and s = Block() - or - type instanceof DESX and name = "DESX" and s = Block() - or - type instanceof GOST and name = "GOST" and s = Block() - or - type instanceof IDEA and name = "IDEA" and s = Block() - or - type instanceof KUZNYECHIK and name = "Kuznyechik" and s = Block() - or - type instanceof MAGMA and name = "Magma" and s = Block() - or - type instanceof TripleDES and name = "TripleDES" and s = Block() - or - type instanceof DoubleDES and name = "DoubleDES" and s = Block() - or - type instanceof RC2 and name = "RC2" and s = Block() - or - type instanceof RC4 and name = "RC4" and s = Stream() - or - type instanceof RC5 and name = "RC5" and s = Block() - or - type instanceof RSA and name = "RSA" and s = Asymmetric() - or - type instanceof SEED and name = "SEED" and s = Block() - or - type instanceof SM4 and name = "SM4" and s = Block() - or - type instanceof OtherCipherType and - name instanceof UnknownPropertyValue and // TODO: get rid of this hack to bind structure and type - s = UnknownCipherStructureType() - } - override NodeBase getChild(string edgeName) { result = super.getChild(edgeName) or - // [KNOWN_OR_UNKNOWN] + // [KNOWN_OR_UNKNOWN] - but only if not suppressed edgeName = "Mode" and - if exists(this.getModeOfOperation()) - then result = this.getModeOfOperation() - else result = this + ( + if exists(this.getModeOfOperation()) + then result = this.getModeOfOperation() + else result = this + ) and + instance.asAlg().shouldHaveModeOfOperation() or - // [KNOWN_OR_UNKNOWN] + // [KNOWN_OR_UNKNOWN] - but only if not suppressed edgeName = "Padding" and - if exists(this.getPaddingAlgorithm()) - then result = this.getPaddingAlgorithm() - else result = this + ( + if exists(this.getPaddingAlgorithm()) + then result = this.getPaddingAlgorithm() + else result = this + ) and + instance.asAlg().shouldHavePaddingScheme() } override predicate properties(string key, string value, Location location) { super.properties(key, value, location) or - // [ALWAYS_KNOWN] - unknown case is handled in `getCipherStructureTypeString` + // [ONLY_KNOWN] - only if symmetric, unknown case is handled in `toString` key = "Structure" and - getCipherStructureTypeString(this.getCipherStructure()) = value and - location instanceof UnknownLocation + this.getSymmetricCipherStructure().toString() = value and + location = this.getLocation() or + // [ONLY_KNOWN] + key = "KeySize" and ( - // [KNOWN_OR_UNKNOWN] - key = "KeySize" and - if exists(this.getKeySize(location)) - then value = this.getKeySize(location) - else ( - value instanceof UnknownPropertyValue and location instanceof UnknownLocation - ) + value = this.getKeySizeFixed() and + location = this.getLocation() + or + node_as_property(this.getKeySize(), value, location) ) } } @@ -1974,10 +2272,16 @@ module CryptographyBase Input> { node instanceof HashAlgorithmNode } + MessageArtifactNode getInputArtifact() { + result.asElement() = instance.getInputConsumer().getConsumer() + } + /** * Gets the output digest node */ - DigestArtifactNode getDigest() { result.asElement() = instance.getDigestArtifact() } + DigestArtifactNode getDigest() { + result.asElement() = instance.getOutputArtifact().getArtifact() + } override NodeBase getChild(string key) { result = super.getChild(key) @@ -1985,6 +2289,10 @@ module CryptographyBase Input> { // [KNOWN_OR_UNKNOWN] key = "Digest" and if exists(this.getDigest()) then result = this.getDigest() else result = this + or + // [KNOWN_OR_UNKNOWN] + key = "Message" and + if exists(this.getInputArtifact()) then result = this.getInputArtifact() else result = this } } @@ -2020,32 +2328,32 @@ module CryptographyBase Input> { override string getRawAlgorithmName() { result = instance.asAlg().getRawHashAlgorithmName() } - final predicate hashTypeToNameMapping(THashType type, string name) { - type instanceof BLAKE2B and name = "BLAKE2B" + final private predicate hashTypeToNameMapping(THashType type, string name) { + type = BLAKE2B() and name = "BLAKE2B" or - type instanceof BLAKE2S and name = "BLAKE2S" + type = BLAKE2S() and name = "BLAKE2S" or - type instanceof RIPEMD160 and name = "RIPEMD160" + type = RIPEMD160() and name = "RIPEMD160" or - type instanceof MD2 and name = "MD2" + type = MD2() and name = "MD2" or - type instanceof MD4 and name = "MD4" + type = MD4() and name = "MD4" or - type instanceof MD5 and name = "MD5" + type = MD5() and name = "MD5" or - type instanceof POLY1305 and name = "POLY1305" + type = POLY1305() and name = "POLY1305" or - type instanceof SHA1 and name = "SHA1" + type = SHA1() and name = "SHA1" or - type instanceof SHA2 and name = "SHA2" + type = SHA2() and name = "SHA2" or - type instanceof SHA3 and name = "SHA3" + type = SHA3() and name = "SHA3" or - type instanceof SHAKE and name = "SHAKE" + type = SHAKE() and name = "SHAKE" or - type instanceof SM3 and name = "SM3" + type = SM3() and name = "SM3" or - type instanceof WHIRLPOOL and name = "WHIRLPOOL" + type = WHIRLPOOL() and name = "WHIRLPOOL" } /** @@ -2128,19 +2436,32 @@ module CryptographyBase Input> { private predicate isNumsCurve(string curveName, int keySize) { // ALL NUMS CURVES keySize in [256, 384, 512] and - exists(string suff | suff in ["T1"] | curveName = "NUMSP" + keySize.toString() + suff) + exists(string suff | suff = "T1" | curveName = "NUMSP" + keySize.toString() + suff) } + /** + * Holds if `name` corresponds to a known elliptic curve. + * + * Note: As an exception, this predicate may be used for library modelling, as curve names are largely standardized. + * + * When modelling, verify that this predicate offers sufficient coverage for the library and handle edge-cases. + */ bindingset[curveName] predicate isEllipticCurveAlgorithmName(string curveName) { - isEllipticCurveAlgorithm(curveName, _, _) + ellipticCurveNameToKeySizeAndFamilyMapping(curveName, _, _) } /** - * Holds if `name` corresponds to a known elliptic curve. + * Relates elliptic curve names to their key size and family. + * + * Note: As an exception, this predicate may be used for library modelling, as curve names are largely standardized. + * + * When modelling, verify that this predicate offers sufficient coverage for the library and handle edge-cases. */ bindingset[rawName] - predicate isEllipticCurveAlgorithm(string rawName, int keySize, TEllipticCurveType curveFamily) { + predicate ellipticCurveNameToKeySizeAndFamilyMapping( + string rawName, int keySize, TEllipticCurveType curveFamily + ) { exists(string curveName | curveName = rawName.toUpperCase() | isSecCurve(curveName, keySize) and curveFamily = SEC() or @@ -2156,37 +2477,47 @@ module CryptographyBase Input> { or curveName = "CURVE25519" and keySize = 255 and curveFamily = CURVE25519() or - curveName = "X25519" and keySize = 255 and curveFamily = CURVE25519() - or - curveName = "ED25519" and keySize = 255 and curveFamily = CURVE25519() - or - curveName = "CURVE448" and keySize = 448 and curveFamily = CURVE448() - or - curveName = "ED448" and keySize = 448 and curveFamily = CURVE448() - or - curveName = "X448" and keySize = 448 and curveFamily = CURVE448() - or + // TODO: separate these into key agreement logic or sign/verify (ECDSA / ECDH) + // or + // curveName = "X25519" and keySize = 255 and curveFamily = CURVE25519() + // or + // curveName = "ED25519" and keySize = 255 and curveFamily = CURVE25519() + // or + // curveName = "ED448" and keySize = 448 and curveFamily = CURVE448() + // curveName = "CURVE448" and keySize = 448 and curveFamily = CURVE448() + // or + // or + // curveName = "X448" and keySize = 448 and curveFamily = CURVE448() curveName = "SM2" and keySize in [256, 512] and curveFamily = SM2() ) } - final class EllipticCurveNode extends AlgorithmNode, TEllipticCurveAlgorithm { - EllipticCurveAlgorithmInstanceOrValueConsumer instance; + final class EllipticCurveNode extends AlgorithmNode, TEllipticCurve { + EllipticCurveInstanceOrValueConsumer instance; - EllipticCurveNode() { this = TEllipticCurveAlgorithm(instance) } + EllipticCurveNode() { this = TEllipticCurve(instance) } - override string getInternalType() { result = "EllipticCurveAlgorithm" } + override string getInternalType() { result = "EllipticCurve" } + + override LocatableElement asElement() { result = instance } final override string getRawAlgorithmName() { - result = instance.asAlg().getRawEllipticCurveAlgorithmName() + result = instance.asAlg().getRawEllipticCurveName() } - // NICK QUESTION: do I repeat the key size and curve family predicates here as wrappers of the instance? - override LocatableElement asElement() { result = instance } + /* + * Mandating that for Elliptic Curves specifically, users are *only* responsible + * for providing as the 'raw' name within source code. + * + * Rationale: elliptic curve names can have a lot of variation in their components + * (e.g., "secp256r1" vs "P-256"), trying to produce generalized set of properties + * is possible to capture all cases, but such modeling is likely not necessary. + * if all properties need to be captured, we can reassess how names are generated. + */ - TEllipticCurveType getEllipticCurveFamily() { - result = instance.asAlg().getEllipticCurveFamily() - } + override string getAlgorithmName() { result = this.getRawAlgorithmName() } + + TEllipticCurveType getEllipticCurveType() { result = instance.asAlg().getEllipticCurveType() } override predicate properties(string key, string value, Location location) { super.properties(key, value, location) @@ -2196,28 +2527,16 @@ module CryptographyBase Input> { value = instance.asAlg().getKeySize() and location = this.getLocation() or - key = "StdCurveName" and - value = instance.asAlg().getStandardCurveName().toUpperCase() and + // [KNOWN_OR_UNKNOWN] + key = "ParsedName" and + value = instance.asAlg().getParsedEllipticCurveName() and location = this.getLocation() } + } - override string getAlgorithmName() { result = this.getRawAlgorithmName().toUpperCase() } - // /** - // * Mandating that for Elliptic Curves specifically, users are responsible - // * for providing as the 'raw' name, the official name of the algorithm. - // * - // * Casing doesn't matter, we will enforce further naming restrictions on - // * `getAlgorithmName` by default. - // * - // * Rationale: elliptic curve names can have a lot of variation in their components - // * (e.g., "secp256r1" vs "P-256"), trying to produce generalized set of properties - // * is possible to capture all cases, but such modeling is likely not necessary. - // * if all properties need to be captured, we can reassess how names are generated. - // */ - // abstract override string getRawAlgorithmName(); - } - - abstract class KEMAlgorithm extends TKeyEncapsulationAlgorithm, AlgorithmNode { - final override string getInternalType() { result = "KeyEncapsulationAlgorithm" } + predicate isKnownAsymmetricAlgorithm(AlgorithmNode node) { + node instanceof EllipticCurveNode + or + node instanceof KeyOperationAlgorithmNode and node.(KeyOperationAlgorithmNode).isAsymmetric() } }