Files
codeql/java/ql/lib/experimental/quantum/JCA.qll

1910 lines
67 KiB
Plaintext

overlay[local?]
module;
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.controlflow.Dominance
module JCAModel {
import Language
import codeql.quantum.experimental.Standardization::Types::KeyOpAlg as KeyOpAlg
abstract class CipherAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
abstract class EllipticCurveAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
abstract class HashAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
abstract class KeyAgreementAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
abstract class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
// TODO: Verify that the PBEWith% case works correctly
bindingset[algo]
predicate cipher_names(string algo) {
algo.toUpperCase()
.matches([
"AES", "AESWrap", "AESWrapPad", "ARCFOUR", "Blowfish", "ChaCha20", "ChaCha20-Poly1305",
"DES", "DESede", "DESedeWrap", "ECIES", "PBEWith%", "RC2", "RC4", "RC5", "RSA"
].toUpperCase())
}
// TODO: Verify that the CFB% case works correctly
bindingset[mode]
predicate cipher_modes(string mode) {
mode.toUpperCase()
.matches([
"NONE", "CBC", "CCM", "CFB", "CFB%", "CTR", "CTS", "ECB", "GCM", "KW", "KWP", "OFB",
"OFB%", "PCBC"
].toUpperCase())
}
// TODO: Verify that the OAEPWith% case works correctly
bindingset[padding]
predicate cipher_padding(string padding) {
padding
.toUpperCase()
.matches([
"NoPadding", "ISO10126Padding", "OAEPPadding", "OAEPWith%", "PKCS1Padding",
"PKCS5Padding", "SSL3Padding"
].toUpperCase())
}
bindingset[hash]
predicate hash_names(string hash) {
hash.toUpperCase()
.matches(["SHA-%", "SHA3-%", "BLAKE2b%", "BLAKE2s%", "MD5", "RIPEMD160", "Whirlpool"]
.toUpperCase())
}
bindingset[kdf]
predicate kdf_names(string kdf) {
kdf.toUpperCase().matches(["PBKDF2With%", "PBEWith%"].toUpperCase())
}
bindingset[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())
}
/**
* Names that match known signature algorithms.
* https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
*/
bindingset[name]
predicate signature_names(string name) {
name.toUpperCase().splitAt("WITH", 1).matches(["RSA%", "ECDSA%", "DSA%"])
or
name.toUpperCase().matches(["RSASSA-PSS", "ED25519", "ED448", "EDDSA", "ML-DSA%", "HSS/LMS"])
}
bindingset[name]
predicate key_agreement_names(string name) {
name.toUpperCase()
.matches(["DH", "EDH", "ECDH", "X25519", "X448", "ML-KEM%", "XDH"].toUpperCase())
}
bindingset[name]
Crypto::TKeyDerivationType kdf_name_to_kdf_type(string name, string withSubstring) {
name.matches("PBKDF2With%") and
result instanceof Crypto::PBKDF2 and
withSubstring = name.regexpCapture("PBKDF2With(.*)", 1)
or
name.matches("PBEWith%") and
result instanceof Crypto::PBES and
withSubstring = name.regexpCapture("PBEWith(.*)", 1)
// TODO: add additional
}
bindingset[name]
Crypto::HashType hash_name_to_type_known(string name, int digestLength) {
name in ["SHA-1", "SHA1"] and result instanceof Crypto::SHA1 and digestLength = 160
or
name in ["SHA-256", "SHA-384", "SHA-512", "SHA256", "SHA384", "SHA512"] and
result instanceof Crypto::SHA2 and
digestLength = name.replaceAll("-", "").splitAt("SHA", 1).toInt()
or
name in ["SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512", "SHA3256", "SHA3384", "SHA3512"] and
result instanceof Crypto::SHA3 and
digestLength = name.replaceAll("-", "").splitAt("SHA3", 1).toInt()
or
(
name.matches("BLAKE2b%") and
result instanceof Crypto::BLAKE2B
or
name = "BLAKE2s" and result instanceof Crypto::BLAKE2S
) and
(
if exists(name.indexOf("-"))
then name.splitAt("-", 1).toInt() = digestLength
else digestLength = 512
)
or
name = "MD5" and
result instanceof Crypto::MD5 and
digestLength = 128
or
name = "RIPEMD160" and
result instanceof Crypto::RIPEMD160 and
digestLength = 160
or
name = "Whirlpool" and
result instanceof Crypto::WHIRLPOOL and
digestLength = 512 // TODO: verify
}
bindingset[name]
private predicate mode_name_to_type_known(KeyOpAlg::ModeOfOperationType type, string name) {
type = KeyOpAlg::ECB() and name = "ECB"
or
type = KeyOpAlg::CBC() and name = "CBC"
or
type = KeyOpAlg::GCM() and name = "GCM"
or
type = KeyOpAlg::CTR() and name = "CTR"
or
type = KeyOpAlg::XTS() and name = "XTS"
or
type = KeyOpAlg::CCM() and name = "CCM"
or
type = KeyOpAlg::SIV() and name = "SIV"
or
type = KeyOpAlg::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::TRIPLE_DES())
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 key_agreement_name_to_type_known(Crypto::TKeyAgreementType type, string name) {
type = Crypto::DH() and
name.toUpperCase() in ["DH", "XDH"]
or
type = Crypto::EDH() and
name.toUpperCase() = "EDH"
or
type = Crypto::ECDH() and
name.toUpperCase() in ["ECDH", "X25519", "X448"]
or
type = Crypto::OtherKeyAgreementType() and
name.toUpperCase().matches("ML-KEM%")
}
/**
* Maps a signature algorithm name to its type, if known.
* see https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
*/
bindingset[name]
predicate signature_name_to_type_known(Crypto::KeyOpAlg::TAlgorithm type, string name) {
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("RSA%") and
type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
or
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("ECDSA%") and
type = KeyOpAlg::TSignature(KeyOpAlg::ECDSA())
or
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("DSA%") and
type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
or
name.toUpperCase() = "RSASSA-PSS" and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
or
name.toUpperCase().matches(["EDDSA", "ED25519", "ED448"]) and
type = KeyOpAlg::TSignature(KeyOpAlg::EDDSA())
or
name.toUpperCase().matches("ML-DSA%") and type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
or
name.toUpperCase() = "HSS/LMS" and type = KeyOpAlg::TSignature(KeyOpAlg::HSS_LMS())
}
bindingset[name]
Crypto::HashType signature_name_to_hash_type_known(string name, int digestLength) {
result = hash_name_to_type_known(name.splitAt("with", 0), digestLength)
}
/**
* A `StringLiteral` in the `"ALG/MODE/PADDING"` or `"ALG"` format
*/
class CipherStringLiteral extends StringLiteral {
CipherStringLiteral() { cipher_names(this.getValue().splitAt("/")) }
string getAlgorithmName() { result = this.getValue().splitAt("/", 0) }
string getMode() { result = this.getValue().splitAt("/", 1) }
string getPadding() { result = this.getValue().splitAt("/", 2) }
}
class CipherGetInstanceCall extends MethodCall {
CipherGetInstanceCall() {
this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "getInstance")
}
Expr getAlgorithmArg() { result = this.getArgument(0) }
Expr getProviderArg() { result = this.getArgument(1) }
}
// TODO: handle key artifact produced by unwrap
private class CipherOperationCall extends MethodCall {
CipherOperationCall() {
this.getMethod()
.hasQualifiedName("javax.crypto", "Cipher", ["update", "doFinal", "wrap", "unwrap"])
}
predicate isIntermediate() { this.getMethod().getName() = "update" }
Expr getInput() { result = this.getArgument(0) }
Expr getOutput() {
exists(int outputIndex | this.getMethod().getParameter(outputIndex).getName() = "output" |
result = this.getArgument(outputIndex)
)
or
this.getMethod().getReturnType().hasName("byte[]") and result = this
}
DataFlow::Node getMessageArg() { result.asExpr() = this.getInput() }
}
/**
* Data-flow configuration modeling flow from a cipher string literal to a cipher algorithm consumer.
*/
private module CipherAlgorithmStringToCipherConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CipherStringLiteral }
predicate isSink(DataFlow::Node sink) {
sink = any(CipherAlgorithmValueConsumer call).getInputNode()
}
}
module CipherAlgorithmStringToFetchFlow =
TaintTracking::Global<CipherAlgorithmStringToCipherConsumerConfig>;
/**
* Note: padding and a mode of operation will only exist when the padding / mode (*and its type*) are determinable.
* This is because the mode will always be specified alongside the algorithm and never independently.
* Therefore, we can always assume that a determinable algorithm will have a determinable mode.
*
* In the case that only an algorithm is specified, e.g., "AES", the provider provides a default mode.
*
* TODO: Model the case of relying on a provider default, but alert on it as a bad practice.
*/
class CipherStringLiteralPaddingAlgorithmInstance extends CipherStringLiteralAlgorithmInstance,
Crypto::PaddingAlgorithmInstance instanceof CipherStringLiteral
{
CipherStringLiteralPaddingAlgorithmInstance() { exists(super.getPadding()) } // TODO: provider defaults
override string getRawPaddingAlgorithmName() { result = super.getPadding() }
bindingset[name]
private predicate paddingToNameMappingKnown(KeyOpAlg::PaddingSchemeType type, string name) {
type instanceof KeyOpAlg::NoPadding and name = "NOPADDING"
or
type instanceof KeyOpAlg::PKCS7 and name = ["PKCS5Padding", "PKCS7Padding"] // TODO: misnomer in the JCA?
or
type instanceof KeyOpAlg::OAEP and name.matches("OAEP%") // TODO: handle OAEPWith%
}
override KeyOpAlg::PaddingSchemeType getPaddingType() {
if this.paddingToNameMappingKnown(_, super.getPadding())
then this.paddingToNameMappingKnown(result, super.getPadding())
else result instanceof KeyOpAlg::OtherPadding
}
}
class CipherStringLiteralModeAlgorithmInstance extends CipherStringLiteralPaddingAlgorithmInstance,
Crypto::ModeOfOperationAlgorithmInstance instanceof CipherStringLiteral
{
CipherStringLiteralModeAlgorithmInstance() { exists(super.getMode()) } // TODO: provider defaults
override string getRawModeAlgorithmName() { result = super.getMode() }
override KeyOpAlg::ModeOfOperationType getModeType() {
if mode_name_to_type_known(_, super.getMode())
then mode_name_to_type_known(result, super.getMode())
else result instanceof KeyOpAlg::OtherMode
}
}
class CipherStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof CipherStringLiteral
{
CipherAlgorithmValueConsumer consumer;
CipherStringLiteralAlgorithmInstance() {
CipherAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
CipherAlgorithmValueConsumer getConsumer() { result = consumer }
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
result = this // TODO: provider defaults
}
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
result = this // TODO: provider defaults
}
override string getRawAlgorithmName() { result = super.getValue() }
override KeyOpAlg::AlgorithmType getAlgorithmType() {
if cipher_name_to_type_known(_, super.getAlgorithmName())
then cipher_name_to_type_known(result, super.getAlgorithmName())
else result instanceof KeyOpAlg::TOtherKeyOperationAlgorithmType
}
override int getKeySizeFixed() {
none() // TODO: implement to handle variants such as AES-128
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
}
bindingset[input]
predicate oaep_padding_string_components(string input, string hash, string mfg) {
exists(string regex |
regex = "OAEPWith(.*)And(.*)Padding" and
hash = input.regexpCapture(regex, 1) and
mfg = input.regexpCapture(regex, 2)
)
}
predicate oaep_padding_string_components_eval(string hash, string mfg) {
oaep_padding_string_components(any(CipherStringLiteral s).getPadding(), hash, mfg)
}
class OaepPaddingHashAlgorithmInstance extends OaepPaddingAlgorithmInstance,
Crypto::HashAlgorithmInstance instanceof CipherStringLiteral
{
string hashName;
OaepPaddingHashAlgorithmInstance() {
oaep_padding_string_components(super.getPadding(), hashName, _)
}
override string getRawHashAlgorithmName() { result = super.getPadding() }
override Crypto::THashType getHashType() { result = hash_name_to_type_known(hashName, _) }
override int getFixedDigestLength() { exists(hash_name_to_type_known(hashName, result)) }
}
class OaepPaddingAlgorithmInstance extends Crypto::OaepPaddingAlgorithmInstance,
CipherStringLiteralPaddingAlgorithmInstance
{
override Crypto::HashAlgorithmInstance getOaepEncodingHashAlgorithm() { result = this }
override Crypto::HashAlgorithmInstance getMgf1HashAlgorithm() { none() } // TODO
}
/**
* The cipher algorithm argument to a `CipherGetInstanceCall`.
*
* For example, in `Cipher.getInstance(algorithm)`, this class represents `algorithm`.
*/
class CipherGetInstanceAlgorithmArg extends CipherAlgorithmValueConsumer instanceof Expr {
CipherGetInstanceAlgorithmArg() { this = any(CipherGetInstanceCall call).getAlgorithmArg() }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this
}
}
/**
* An access to the `javax.crypto.Cipher` class.
*/
private class CipherAccess extends TypeAccess {
CipherAccess() { this.getType().(Class).hasQualifiedName("javax.crypto", "Cipher") }
}
/**
* An access to a cipher mode field of the `javax.crypto.Cipher` class,
* specifically `ENCRYPT_MODE`, `DECRYPT_MODE`, `WRAP_MODE`, or `UNWRAP_MODE`.
*/
private class JavaxCryptoCipherOperationModeAccess extends FieldAccess {
JavaxCryptoCipherOperationModeAccess() {
this.getQualifier() instanceof CipherAccess and
this.getField().getName().toUpperCase() in [
"ENCRYPT_MODE", "DECRYPT_MODE", "WRAP_MODE", "UNWRAP_MODE"
]
}
}
signature class GetInstanceCallSig instanceof MethodCall;
signature class InitCallSig instanceof MethodCall;
signature class UseCallSig instanceof MethodCall {
/**
* Holds if the use is not a final use, such as an `update()` call before `doFinal()`
*/
predicate isIntermediate();
}
/**
* 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>
{
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
}
}
class UninitializedFlowState extends InitFlowState, TUninitialized { }
class InitializedFlowState extends InitFlowState, TInitialized {
Init call;
DataFlow::Node node1;
DataFlow::Node node2;
InitializedFlowState() {
this = TInitialized(call) and
node2.asExpr() = call.(MethodCall).getQualifier() and
DataFlow::localFlowStep(node1, node2) and
node1 != node2
}
Init getInitCall() { result = call }
DataFlow::Node getFstNode() { result = node1 }
DataFlow::Node getSndNode() { result = node2 }
}
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 }
DataFlow::Node getSndNode() { result = node2 }
}
module GetInstanceToInitToUseConfig implements DataFlow::StateConfigSig {
class FlowState = InitFlowState;
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<GetInstanceToInitToUseConfig>;
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)
}
GetInstance getInstantiationFromInit(
Init init, GetInstanceToInitToUseFlow::PathNode src, GetInstanceToInitToUseFlow::PathNode sink
) {
src.getNode().asExpr() = result and
sink.getNode().asExpr() = init.(MethodCall).getQualifier() and
GetInstanceToInitToUseFlow::flowPath(src, sink)
}
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 CipherFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<CipherGetInstanceCall, CipherInitCall, CipherOperationCall>;
module CipherFlow = CipherFlowAnalysisImpl::GetInstanceToInitToUseFlow;
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 {
CipherOperationInstance() { not this.isIntermediate() }
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
if CipherFlowAnalysisImpl::hasInit(this)
then result = CipherFlowAnalysisImpl::getInitFromUse(this, _, _).getCipherOperationModeType()
else result = Crypto::TUnknownKeyOperationMode()
}
CipherGetInstanceCall getInstantiationCall() {
result = CipherFlowAnalysisImpl::getInstantiationFromUse(this, _, _)
}
CipherInitCall getInitCall() { result = CipherFlowAnalysisImpl::getInitFromUse(this, _, _) }
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
result.asExpr() = this.getInitCall().getNonceArg()
}
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
result = super.getMessageArg() or
result = CipherFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getMessageArg()
}
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
result.asExpr() = this.getInitCall().getKeyArg()
}
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
result = this.getInstantiationCall().getAlgorithmArg()
}
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
result.asExpr() = super.getOutput()
}
}
/**
* Initialization vectors and other nonce artifacts
*/
abstract class NonceParameterInstantiation extends ClassInstanceExpr {
DataFlow::Node getOutputNode() { result.asExpr() = this }
abstract DataFlow::Node getInputNode();
}
class IvParameterSpecInstance extends NonceParameterInstantiation {
IvParameterSpecInstance() {
super.getConstructedType().hasQualifiedName("javax.crypto.spec", "IvParameterSpec")
}
override DataFlow::Node getInputNode() { result.asExpr() = super.getArgument(0) }
}
// TODO: this also specifies the tag length for GCM
class GCMParameterSpecInstance extends NonceParameterInstantiation {
GCMParameterSpecInstance() {
super.getConstructedType().hasQualifiedName("javax.crypto.spec", "GCMParameterSpec")
}
override DataFlow::Node getInputNode() { result.asExpr() = super.getArgument(1) }
}
class IvParameterSpecGetIvCall extends MethodCall {
IvParameterSpecGetIvCall() {
this.getMethod().hasQualifiedName("javax.crypto.spec", "IvParameterSpec", "getIV")
}
}
class KeyAdditionalFlowSteps extends MethodCall {
KeyAdditionalFlowSteps() {
this.getCallee().hasQualifiedName("java.security", "KeyPair", "getPublic")
or
this.getCallee().hasQualifiedName("java.security", "KeyPair", "getPrivate")
or
this.getCallee().hasQualifiedName("java.security", "Key", "getEncoded")
}
DataFlow::Node getInputNode() { result.asExpr() = this.getQualifier() }
DataFlow::Node getOutputNode() { result.asExpr() = this }
}
predicate additionalFlowSteps(DataFlow::Node node1, DataFlow::Node node2) {
exists(IvParameterSpecGetIvCall m |
node1.asExpr() = m.getQualifier() and
node2.asExpr() = m
)
or
exists(NonceParameterInstantiation n |
node1 = n.getInputNode() and
node2 = n.getOutputNode()
)
or
exists(KeyAdditionalFlowSteps call |
node1 = call.getInputNode() and
node2 = call.getOutputNode()
)
}
class ArtifactAdditionalFlowStep extends AdditionalFlowInputStep {
DataFlow::Node output;
ArtifactAdditionalFlowStep() { additionalFlowSteps(this, output) }
override DataFlow::Node getOutput() { result = output }
}
/**
* A data-flow configuration to track flow from a mode field access to
* the mode argument of the `init` method of the `javax.crypto.Cipher` class.
*/
private module JavaxCipherModeAccessToInitConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof JavaxCryptoCipherOperationModeAccess
}
predicate isSink(DataFlow::Node sink) {
exists(CipherInitCall c | c.getModeArg() = sink.asExpr())
}
}
module JavaxCipherModeAccessToInitFlow = DataFlow::Global<JavaxCipherModeAccessToInitConfig>;
private predicate cipher_mode_str_to_cipher_mode_known(
string mode, Crypto::KeyOperationSubtype cipher_mode
) {
mode = "ENCRYPT_MODE" and cipher_mode = Crypto::TEncryptMode()
or
mode = "WRAP_MODE" and cipher_mode = Crypto::TWrapMode()
or
mode = "DECRYPT_MODE" and cipher_mode = Crypto::TDecryptMode()
or
mode = "UNWRAP_MODE" and cipher_mode = Crypto::TUnwrapMode()
}
class CipherInitCall extends MethodCall {
CipherInitCall() { this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "init") }
/**
* Returns the mode argument to the `init` method
* that is used to determine the cipher operation mode.
* Note this is the raw expr and not necessarily a direct access
* of a mode. Use `getModeOrigin()` to get the field access origin
* flowing to this argument, if one exists (is known).
*/
Expr getModeArg() { result = this.getArgument(0) }
JavaxCryptoCipherOperationModeAccess getModeOrigin() {
exists(DataFlow::Node src, DataFlow::Node sink |
JavaxCipherModeAccessToInitFlow::flow(src, sink) and
src.asExpr() = result and
this.getModeArg() = sink.asExpr()
)
}
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 = Crypto::TUnknownKeyOperationMode()
}
Expr getKeyArg() {
result = this.getArgument(1) and this.getMethod().getParameterType(1).hasName("Key")
}
Expr getNonceArg() {
result = this.getArgument(2) and
this.getMethod().getParameterType(2).hasName("AlgorithmParameterSpec")
}
}
class CipherInitCallKeyConsumer extends Crypto::ArtifactConsumer {
CipherInitCallKeyConsumer() { this = any(CipherInitCall call).getKeyArg() }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
}
/*
* Hash Functions
*/
/**
* 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(HashAlgorithmValueConsumer consumer | sink = consumer.getInputNode())
}
}
module KnownHashAlgorithmLiteralToMessageDigestFlow =
DataFlow::Global<KnownHashAlgorithmLiteralToMessageDigestConfig>;
class KnownHashAlgorithm extends Crypto::HashAlgorithmInstance instanceof StringLiteral {
HashAlgorithmValueConsumer consumer;
KnownHashAlgorithm() {
hash_names(super.getValue()) and
KnownHashAlgorithmLiteralToMessageDigestFlow::flow(DataFlow::exprNode(this),
consumer.getInputNode())
}
HashAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawHashAlgorithmName() { result = super.getValue() }
override Crypto::THashType getHashType() {
result = hash_name_to_type_known(this.getRawHashAlgorithmName(), _)
}
override int getFixedDigestLength() {
exists(hash_name_to_type_known(this.getRawHashAlgorithmName(), result))
}
}
module MessageDigestFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<MessageDigestGetInstanceCall, DUMMY_UNUSED_METHODCALL, DigestCall>;
class MessageDigestGetInstanceAlgorithmValueConsumer extends HashAlgorithmValueConsumer {
MessageDigestGetInstanceCall call;
MessageDigestGetInstanceAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
MessageDigestGetInstanceCall getInstantiationCall() { result = call }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
exists(KnownHashAlgorithm l | l.getConsumer() = this and result = l)
}
}
class MessageDigestGetInstanceCall extends MethodCall {
MessageDigestGetInstanceCall() {
this.getCallee().hasQualifiedName("java.security", "MessageDigest", "getInstance")
}
Expr getAlgorithmArg() { result = this.getArgument(0) }
}
class DigestCall extends MethodCall {
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
module DigestGetInstanceToDigestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof MessageDigestGetInstanceCall }
predicate isSink(DataFlow::Node sink) {
exists(DigestCall c | c.getQualifier() = sink.asExpr())
}
}
module DigestGetInstanceToDigestFlow = DataFlow::Global<DigestGetInstanceToDigestConfig>;
class DigestHashOperation extends Crypto::HashOperationInstance instanceof DigestCall {
DigestHashOperation() { not super.isIntermediate() }
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
result.asExpr() = super.getDigestArtifactOutput()
}
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
MessageDigestFlowAnalysisImpl::getInstantiationFromUse(this, _, _) =
result.(MessageDigestGetInstanceAlgorithmValueConsumer).getInstantiationCall()
}
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
result.asExpr() = super.getInputArg() or
result.asExpr() =
MessageDigestFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getInputArg()
}
}
/*
* Key Generation
*/
module KeyGeneratorFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<KeyGeneratorGetInstanceCall, KeyGeneratorInitCall,
KeyGeneratorGenerateCall>;
module KeyGeneratorFlow = KeyGeneratorFlowAnalysisImpl::GetInstanceToInitToUseFlow;
abstract class KeyGeneratorParameterSpecClassInstanceExpr extends ClassInstanceExpr {
KeyGeneratorInitCall getAnInitCallUse() {
exists(DataFlow::Node sink |
KeyGeneratorParameterSpecToInitializeFlow::flow(DataFlow::exprNode(this), sink) and
result.getAlgorithmParameterSpecArg() = sink.asExpr()
)
}
}
class DHGenParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr {
DHGenParameterSpecInstance() {
super.getConstructedType().hasQualifiedName("javax.crypto.spec", "DHGenParameterSpec")
}
Expr getPrimeSizeArg() { result = this.getArgument(0) }
Expr getExponentSizeArg() { result = this.getArgument(1) }
}
class DSAParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr {
DSAParameterSpecInstance() {
super.getConstructedType().hasQualifiedName("java.security.spec", "DSAParameterSpec")
}
Expr getPArg() { result = this.getArgument(0) }
Expr getQArg() { result = this.getArgument(1) }
Expr getSeedLenArg() { result = this.getArgument(2) }
}
class ECGenParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr {
ECGenParameterSpecInstance() {
super.getConstructedType().hasQualifiedName("java.security.spec", "ECGenParameterSpec")
}
Expr getCurveNameArg() { result = this.getArgument(0) }
Expr getRandomNumberGeneratorArg() { result = this.getArgument(1) }
}
class RSAGenParameterSpecInstance extends KeyGeneratorParameterSpecClassInstanceExpr {
RSAGenParameterSpecInstance() {
super.getConstructedType().hasQualifiedName("java.security.spec", "RSAGenParameterSpec")
}
Expr getKeySizeArg() { result = this.getArgument(0) }
}
private module KeyGeneratorParameterSpecToInitializeConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
src.asExpr() instanceof KeyGeneratorParameterSpecClassInstanceExpr
}
predicate isSink(DataFlow::Node sink) {
exists(KeyGeneratorInitCall c |
c.getKeyType() = Crypto::TAsymmetricKeyType() and
c.getArgument(0) = sink.asExpr()
)
}
}
module KeyGeneratorParameterSpecToInitializeFlow =
DataFlow::Global<KeyGeneratorParameterSpecToInitializeConfig>;
class ECGenParameterSpecClassInstanceExpr extends KeyGeneratorParameterSpecClassInstanceExpr {
ECGenParameterSpecClassInstanceExpr() {
super.getConstructedType().hasQualifiedName("java.security.spec", "ECGenParameterSpec")
}
Expr getAlgorithmArg() { result = this.getArgument(0) }
}
class KeyGenerationAlgorithmValueConsumer extends CipherAlgorithmValueConsumer,
KeyAgreementAlgorithmValueConsumer, EllipticCurveAlgorithmValueConsumer instanceof Expr
{
KeyGeneratorGetInstanceCall instantiationCall;
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("<CURVE NAME>") 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 Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this or
result.(KeyAgreementStringLiteralAlgorithmInstance).getConsumer() = this or
result.(EllipticCurveStringLiteralInstance).getConsumer() = this or
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
}
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 KeyGeneratorInitCall extends MethodCall {
Crypto::TKeyArtifactType keyType;
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()
}
Crypto::TKeyArtifactType getKeyType() { result = keyType }
Expr getAlgorithmParameterSpecArg() {
result = this.getArgument(0) and
this.getMethod().getParameterType(0).hasName("AlgorithmParameterSpec")
}
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 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", "genKeyPair"]) and
type instanceof Crypto::TAsymmetricKeyType
}
predicate isIntermediate() { none() }
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result.asExpr() = this }
override Crypto::KeyArtifactType getOutputKeyType() { result = type }
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
KeyGeneratorFlowAnalysisImpl::getInstantiationFromUse(this, _, _) =
result.(KeyGenerationAlgorithmValueConsumer).getInstantiationCall()
}
Crypto::AlgorithmInstance getAKnownAlgorithm() {
result = this.getAnAlgorithmValueConsumer().getAKnownAlgorithmSource()
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
KeyGeneratorFlowAnalysisImpl::getInitFromUse(this, _, _).getKeySizeArg() = result.asExpr()
}
override int getKeySizeFixed() { none() }
override Crypto::ConsumerInputDataFlowNode getKeyValueConsumer() { none() }
override predicate hasKeyValueConsumer() { none() }
}
class KeyGeneratorCipherAlgorithm extends CipherStringLiteralAlgorithmInstance {
KeyGeneratorCipherAlgorithm() { consumer instanceof KeyGenerationAlgorithmValueConsumer }
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
exists(KeyGeneratorGetInstanceCall getInstance, KeyGeneratorInitCall init |
getInstance =
this.getConsumer().(KeyGenerationAlgorithmValueConsumer).getInstantiationCall() and
getInstance = KeyGeneratorFlowAnalysisImpl::getInstantiationFromInit(init, _, _) and
init.getKeySizeArg() = result.asExpr()
)
}
predicate isOnlyConsumedByKeyGen() {
forall(Crypto::AlgorithmValueConsumer c |
c = this.getConsumer() and
c instanceof KeyGenerationAlgorithmValueConsumer
)
}
override predicate shouldHaveModeOfOperation() { this.isOnlyConsumedByKeyGen() }
override predicate shouldHavePaddingScheme() { this.isOnlyConsumedByKeyGen() }
}
/*
* Key Derivation Functions (KDFs)
*/
class KeySpecInstantiation extends ClassInstanceExpr {
KeySpecInstantiation() {
this.getConstructedType()
.hasQualifiedName("javax.crypto.spec",
["PBEKeySpec", "SecretKeySpec", "PBEKeySpec", "DESedeKeySpec"])
}
Expr getPasswordArg() { result = this.getArgument(0) }
}
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) { src.asExpr() instanceof KeySpecInstantiation }
predicate isSink(DataFlow::Node sink) {
exists(SecretKeyFactoryGenerateSecretCall call | sink.asExpr() = call.getKeySpecArg())
}
}
module KDFAlgorithmStringToGetInstanceConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { kdf_names(src.asExpr().(StringLiteral).getValue()) }
predicate isSink(DataFlow::Node sink) {
exists(SecretKeyFactoryGetInstanceCall call | sink.asExpr() = call.getAlgorithmArg())
}
}
module KeySpecInstantiationToGenerateSecretFlow =
DataFlow::Global<KeySpecInstantiationToGenerateSecretFlowConfig>;
module KDFAlgorithmStringToGetInstanceFlow =
DataFlow::Global<KDFAlgorithmStringToGetInstanceConfig>;
class DUMMY_UNUSED_METHODCALL extends MethodCall {
DUMMY_UNUSED_METHODCALL() { none() }
}
module SecretKeyFactoryFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<SecretKeyFactoryGetInstanceCall, DUMMY_UNUSED_METHODCALL,
SecretKeyFactoryGenerateSecretCall>;
module SecretKeyFactoryFlow = SecretKeyFactoryFlowAnalysisImpl::GetInstanceToInitToUseFlow;
class SecretKeyFactoryGetInstanceCall extends MethodCall {
SecretKeyFactoryGetInstanceCall() {
this.getCallee().hasQualifiedName("javax.crypto", "SecretKeyFactory", "getInstance")
}
Expr getAlgorithmArg() { result = this.getArgument(0) }
}
class SecretKeyFactoryGenerateSecretCall extends MethodCall {
SecretKeyFactoryGenerateSecretCall() {
this.getCallee().hasQualifiedName("javax.crypto", "SecretKeyFactory", "generateSecret")
}
Expr getKeySpecArg() { result = this.getArgument(0) }
predicate isIntermediate() { none() }
}
class KdfAlgorithmStringLiteral extends Crypto::KeyDerivationAlgorithmInstance instanceof StringLiteral
{
SecretKeyFactoryKDFAlgorithmValueConsumer consumer;
KdfAlgorithmStringLiteral() {
kdf_names(this.getValue()) and
KDFAlgorithmStringToGetInstanceFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
override string getRawKdfAlgorithmName() { result = super.getValue() }
override Crypto::TKeyDerivationType getKdfType() {
result = kdf_name_to_kdf_type(super.getValue(), _)
}
SecretKeyFactoryKDFAlgorithmValueConsumer getConsumer() { result = consumer }
}
class Pbkdf2WithHmac_KeyOperationAlgorithmStringLiteral extends Crypto::KeyOperationAlgorithmInstance instanceof KdfAlgorithmStringLiteral
{
Pbkdf2WithHmac_KeyOperationAlgorithmStringLiteral() {
this.(StringLiteral).getValue().toUpperCase().matches("PBKDF2WithHmac%".toUpperCase())
}
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
result = KeyOpAlg::TMac(KeyOpAlg::HMAC())
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
// TODO: trace to any key size initializer?
none()
}
override int getKeySizeFixed() {
// TODO: are there known fixed key sizes to consider?
none()
}
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
override string getRawAlgorithmName() { result = this.(StringLiteral).getValue() }
}
class Pbkdf2WithHmac_HashAlgorithmStringLiteral extends Crypto::HashAlgorithmInstance instanceof Pbkdf2WithHmac_KeyOperationAlgorithmStringLiteral
{
string hashName;
Pbkdf2WithHmac_HashAlgorithmStringLiteral() {
hashName = this.(StringLiteral).getValue().splitAt("WithHmac", 1)
}
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
override Crypto::THashType getHashType() { result = hash_name_to_type_known(hashName, _) }
override int getFixedDigestLength() { exists(hash_name_to_type_known(hashName, result)) }
}
//TODO: handle PBE "with" cases
class Pbkdf2WithHmac_Pbkdf2AlgorithmInstance extends Crypto::Pbkdf2AlgorithmInstance,
KdfAlgorithmStringLiteral, // this is a parent already, but extending to have immediate access to 'getConsumer()'
Pbkdf2WithHmac_KeyOperationAlgorithmStringLiteral
{
override Crypto::AlgorithmValueConsumer getHmacAlgorithmValueConsumer() {
result = this.getConsumer()
}
}
// NOTE: must use instanceof to avoid non-monotonic recursion
class Pbkdf2WithHmac_HmacAlgorithmInstance extends Crypto::HmacAlgorithmInstance instanceof Pbkdf2WithHmac_KeyOperationAlgorithmStringLiteral
{
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
result = this.(KdfAlgorithmStringLiteral).getConsumer()
}
override int getKeySizeFixed() {
// already defined by parent key operation algorithm, but extending an instance
// still requires we override this method
result = super.getKeySizeFixed()
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
// already defined by parent key operation algorithm, but extending an instance
// still requires we override this method
result = super.getKeySizeConsumer()
}
override string getRawAlgorithmName() {
// already defined by parent key operation algorithm, but extending an instance
// still requires we override this method
result = super.getRawAlgorithmName()
}
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
result = KeyOpAlg::TMac(KeyOpAlg::HMAC())
}
}
class SecretKeyFactoryKDFAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer instanceof Expr
{
SecretKeyFactoryGetInstanceCall call;
SecretKeyFactoryKDFAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
exists(KdfAlgorithmStringLiteral l | l.getConsumer() = this and result = l)
}
SecretKeyFactoryGetInstanceCall getInstantiation() { result = call }
}
class KeyDerivationOperationCall extends Crypto::KeyDerivationOperationInstance instanceof SecretKeyFactoryGenerateSecretCall
{
KeyDerivationOperationCall() { not super.isIntermediate() }
KeySpecInstantiation getKeySpecInstantiation() {
KeySpecInstantiationToGenerateSecretFlow::flow(DataFlow::exprNode(result),
DataFlow::exprNode(super.getKeySpecArg()))
}
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
result.(SecretKeyFactoryKDFAlgorithmValueConsumer).getInstantiation() =
SecretKeyFactoryFlowAnalysisImpl::getInstantiationFromUse(this, _, _)
}
override Crypto::ConsumerInputDataFlowNode getSaltConsumer() {
result.asExpr() = this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getSaltArg()
}
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
result.asExpr() = this.getKeySpecInstantiation().getPasswordArg()
}
override Crypto::ConsumerInputDataFlowNode getIterationCountConsumer() {
result.asExpr() =
this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getIterationCountArg()
}
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result.asExpr() = this }
override Crypto::ConsumerInputDataFlowNode getOutputKeySizeConsumer() {
result.asExpr() = this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getKeyLengthArg()
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
result.asExpr() = this.getKeySpecInstantiation().(PBEKeySpecInstantiation).getKeyLengthArg()
}
override int getKeySizeFixed() { none() }
override string getOutputKeySizeFixed() { none() }
override string getIterationCountFixed() { none() }
}
/*
* Key agreement
*/
module KeyAgreementFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<KeyAgreementGetInstanceCall, KeyAgreementInitCall,
KeyAgreementCall>;
class KeyAgreementStringLiteral extends StringLiteral {
KeyAgreementStringLiteral() { key_agreement_names(this.getValue()) }
}
/**
* Data-flow configuration modeling 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<KeyAgreementAlgorithmStringToConsumerConfig>;
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::OtherKeyAgreementType()
}
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<MacKnownAlgorithmToConsumerConfig>;
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<MacGetInstanceToMacOperationFlowConfig>;
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<MacInitCallToMacOperationFlowConfig>;
class KnownMacAlgorithm extends Crypto::KeyOperationAlgorithmInstance instanceof StringLiteral {
MacGetInstanceAlgorithmValueConsumer consumer;
KnownMacAlgorithm() {
mac_names(this.getValue()) and
MacKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
MacGetInstanceAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawAlgorithmName() { result = super.getValue() }
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
if super.getValue().toUpperCase().matches("HMAC%")
then result = KeyOpAlg::TMac(KeyOpAlg::HMAC())
else
if super.getValue().toUpperCase().matches("CMAC%")
then result = KeyOpAlg::TMac(KeyOpAlg::CMAC())
else result = KeyOpAlg::TMac(KeyOpAlg::OtherMacAlgorithmType())
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
// TODO: trace to any key size initializer?
none()
}
override int getKeySizeFixed() {
// TODO: are there known fixed key sizes to consider?
none()
}
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
}
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 {
MacGetInstanceAlgorithmValueConsumer() { this = any(MacGetInstanceCall c).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 getInputConsumer() {
result.asExpr() = super.getArgument(0) and
super.getMethod().getParameterType(0).hasName("byte[]")
}
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() { result.asExpr() = output }
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() { none() }
override predicate hasHashAlgorithmConsumer() { none() }
override Crypto::KeyOperationSubtype getKeyOperationSubtype() {
result instanceof Crypto::TMacMode
}
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
}
/**
* Signatures
*/
module SignatureKnownAlgorithmToConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SignatureStringLiteral }
predicate isSink(DataFlow::Node sink) {
sink = any(SignatureAlgorithmValueConsumer call).getInputNode()
}
}
module SignatureKnownAlgorithmToConsumerFlow =
TaintTracking::Global<SignatureKnownAlgorithmToConsumerConfig>;
class SignatureGetInstanceCall extends MethodCall {
SignatureGetInstanceCall() {
this.getCallee().hasQualifiedName("java.security", "Signature", "getInstance")
}
Expr getAlgorithmArg() { result = this.getArgument(0) }
}
class SignatureGetInstanceAlgorithmValueConsumer extends SignatureAlgorithmValueConsumer instanceof Expr
{
SignatureGetInstanceAlgorithmValueConsumer() {
this = any(SignatureGetInstanceCall c).getAlgorithmArg()
}
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
}
}
class SignatureStringLiteral extends StringLiteral {
SignatureStringLiteral() { signature_names(this.getValue()) }
}
class SignatureStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SignatureStringLiteral
{
SignatureAlgorithmValueConsumer consumer;
SignatureStringLiteralAlgorithmInstance() {
SignatureKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
SignatureAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawAlgorithmName() { result = super.getValue() }
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
if signature_name_to_type_known(_, super.getValue())
then signature_name_to_type_known(result, super.getValue())
else result = Crypto::KeyOpAlg::TOtherKeyOperationAlgorithmType()
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
// TODO: trace to any key size initializer?
none()
}
override int getKeySizeFixed() {
// TODO: are there known fixed key sizes to consider?
none()
}
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
}
class SignatureHashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof SignatureStringLiteralAlgorithmInstance
{
Crypto::THashType hashType;
int digestLength;
SignatureHashAlgorithmInstance() {
hashType = signature_name_to_hash_type_known(this.(StringLiteral).getValue(), digestLength)
}
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
override Crypto::THashType getHashType() { result = hashType }
override int getFixedDigestLength() { result = digestLength }
}
class SignatureInitCall extends MethodCall {
SignatureInitCall() {
this.getCallee().hasQualifiedName("java.security", "Signature", ["initSign", "initVerify"])
}
Expr getKeyArg() {
result = this.getArgument(0)
// TODO: verify can take in a certificate too?
}
}
private class SignatureOperationCall extends MethodCall {
SignatureOperationCall() {
this.getMethod().hasQualifiedName("java.security", "Signature", ["update", "sign", "verify"])
}
predicate isIntermediate() { super.getMethod().getName() = "update" }
Expr getMsgInput() { result = this.getArgument(0) and this.getMethod().getName() = "update" }
Expr getSignatureOutput() {
// no args, the signature is returned
result = this and this.getMethod().getName() = "sign" and not exists(this.getArgument(0))
or
// with args, the signature is written to the arg
result = this.getArgument(0) and this.getMethod().getName() = "sign"
}
Expr getSignatureInput() {
result = this.getArgument(0) and this.getMethod().getName() = "verify"
}
Crypto::KeyOperationSubtype getSubtype() {
result instanceof Crypto::TSignMode and this.getMethod().getName() = "sign"
or
result instanceof Crypto::TVerifyMode and this.getMethod().getName() = "verify"
}
}
class SignatureOperationInstance extends Crypto::SignatureOperationInstance instanceof SignatureOperationCall
{
SignatureOperationInstance() {
// exclude update (only include sign and verify)
not super.isIntermediate()
}
SignatureGetInstanceCall getInstantiationCall() {
result = SignatureFlowAnalysisImpl::getInstantiationFromUse(this, _, _)
}
SignatureInitCall getInitCall() {
result = SignatureFlowAnalysisImpl::getInitFromUse(this, _, _)
}
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
result.asExpr() = super.getMsgInput() or
result.asExpr() =
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getMsgInput()
}
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
result.asExpr() = this.getInitCall().getKeyArg()
}
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
result = this.getInstantiationCall().getAlgorithmArg()
}
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
result.asExpr() = super.getSignatureOutput() or
result.asExpr() =
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureOutput()
}
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
// TODO: RSASSA-PSS literal sets hashes differently, through a ParameterSpec
result = this.getInstantiationCall().getAlgorithmArg()
}
override predicate hasHashAlgorithmConsumer() {
// All jca signature algorithms specify a hash unless explicitly set as "NONEwith..."
exists(SignatureStringLiteralAlgorithmInstance i |
i.getConsumer() = this.getAnAlgorithmValueConsumer() and
not i.getRawAlgorithmName().toUpperCase().matches("NONEwith%".toUpperCase())
)
}
override Crypto::KeyOperationSubtype getKeyOperationSubtype() { result = super.getSubtype() }
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() {
result.asExpr() = super.getSignatureInput() or
result.asExpr() =
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureInput()
}
}
module SignatureFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<SignatureGetInstanceCall, SignatureInitCall,
SignatureOperationCall>;
/*
* 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<EllipticCurveStringToConsumerConfig>;
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::EllipticCurveType getEllipticCurveType() {
if
Crypto::ellipticCurveNameToKnownKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _, _)
then
Crypto::ellipticCurveNameToKnownKeySizeAndFamilyMapping(this.getRawEllipticCurveName(), _,
result)
else result = Crypto::OtherEllipticCurveType()
}
override int getKeySize() {
Crypto::ellipticCurveNameToKnownKeySizeAndFamilyMapping(this.getRawEllipticCurveName(),
result, _)
}
EllipticCurveAlgorithmValueConsumer getConsumer() { result = consumer }
}
}