mirror of
https://github.com/github/codeql.git
synced 2026-04-24 16:25:15 +02:00
1426 lines
51 KiB
Plaintext
1426 lines
51 KiB
Plaintext
import java
|
|
import semmle.code.java.dataflow.DataFlow
|
|
import semmle.code.java.dataflow.TaintTracking
|
|
import semmle.code.java.controlflow.Dominance
|
|
|
|
module JCAModel {
|
|
import Language
|
|
|
|
abstract class JCAAlgorithmInstance extends Crypto::AlgorithmInstance {
|
|
abstract Crypto::AlgorithmValueConsumer getConsumer();
|
|
}
|
|
|
|
// 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) { Crypto::isEllipticCurveAlgorithmName(name) }
|
|
|
|
bindingset[name]
|
|
predicate key_agreement_names(string name) { Crypto::isKeyAgreementAlgorithmName(name) }
|
|
|
|
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)
|
|
}
|
|
|
|
bindingset[name]
|
|
Crypto::THashType hash_name_to_hash_type(string name, int digestLength) {
|
|
name = "SHA-1" and result instanceof Crypto::SHA1 and digestLength = 160
|
|
or
|
|
name = ["SHA-256", "SHA-384", "SHA-512"] and
|
|
result instanceof Crypto::SHA2 and
|
|
digestLength = name.splitAt("-", 1).toInt()
|
|
or
|
|
name = ["SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"] and
|
|
result instanceof Crypto::SHA3 and
|
|
digestLength = name.splitAt("-", 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
|
|
}
|
|
|
|
/**
|
|
* 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 EllipticCurveStringLiteral extends StringLiteral {
|
|
EllipticCurveStringLiteral() { elliptic_curve_names(this.getValue()) }
|
|
|
|
string getStandardEllipticCurveName() { result = this.getValue() }
|
|
}
|
|
|
|
class KeyAgreementStringLiteral extends StringLiteral {
|
|
KeyAgreementStringLiteral() { key_agreement_names(this.getValue()) }
|
|
}
|
|
|
|
class CipherGetInstanceCall extends Call {
|
|
CipherGetInstanceCall() {
|
|
this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "getInstance")
|
|
}
|
|
|
|
Expr getAlgorithmArg() { result = this.getArgument(0) }
|
|
|
|
Expr getProviderArg() { result = this.getArgument(1) }
|
|
}
|
|
|
|
private class CipherOperationCall extends MethodCall {
|
|
CipherOperationCall() {
|
|
exists(string s | s in ["doFinal", "wrap", "unwrap"] |
|
|
this.getMethod().hasQualifiedName("javax.crypto", "Cipher", s)
|
|
)
|
|
}
|
|
|
|
Expr getInput() { result = this.getArgument(0) }
|
|
|
|
Expr getOutput() {
|
|
result = this.getArgument(3)
|
|
or
|
|
this.getMethod().getReturnType().hasName("byte[]") and result = this
|
|
}
|
|
|
|
DataFlow::Node getMessageArg() { result.asExpr() = this.getInput() }
|
|
}
|
|
|
|
/**
|
|
* Data-flow configuration modelling flow from a cipher string literal to a value consumer argument.
|
|
*/
|
|
private module CipherAlgorithmStringToFetchConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CipherStringLiteral }
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(Crypto::AlgorithmValueConsumer consumer | sink = consumer.getInputNode())
|
|
}
|
|
}
|
|
|
|
module CipherAlgorithmStringToFetchFlow =
|
|
TaintTracking::Global<CipherAlgorithmStringToFetchConfig>;
|
|
|
|
/**
|
|
* Data-flow configuration modelling flow from a elliptic curve 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<EllipticCurveAlgorithmStringToFetchConfig>;
|
|
|
|
/**
|
|
* 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 JCAAlgorithmInstance,
|
|
CipherStringLiteralAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof CipherStringLiteral
|
|
{
|
|
CipherStringLiteralPaddingAlgorithmInstance() { exists(super.getPadding()) } // TODO: provider defaults
|
|
|
|
override string getRawPaddingAlgorithmName() { result = super.getPadding() }
|
|
|
|
bindingset[name]
|
|
private predicate paddingToNameMappingKnown(Crypto::TPaddingType type, string name) {
|
|
type instanceof Crypto::NoPadding and name = "NOPADDING"
|
|
or
|
|
type instanceof Crypto::PKCS7 and name = ["PKCS5Padding", "PKCS7Padding"] // TODO: misnomer in the JCA?
|
|
or
|
|
type instanceof Crypto::OAEP and name.matches("OAEP%") // TODO: handle OAEPWith%
|
|
}
|
|
|
|
override Crypto::TPaddingType getPaddingType() {
|
|
if this.paddingToNameMappingKnown(_, super.getPadding())
|
|
then this.paddingToNameMappingKnown(result, super.getPadding())
|
|
else result instanceof Crypto::OtherPadding
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Data-flow configuration modelling flow from a key agreement literal to a value consumer argument.
|
|
*/
|
|
private module KeyAgreementAlgorithmStringToFetchConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof KeyAgreementStringLiteral }
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(Crypto::AlgorithmValueConsumer consumer | sink = consumer.getInputNode())
|
|
}
|
|
}
|
|
|
|
module KeyAgreementAlgorithmStringToFetchFlow =
|
|
TaintTracking::Global<KeyAgreementAlgorithmStringToFetchConfig>;
|
|
|
|
class KeyAgreementInitCall extends MethodCall {
|
|
KeyAgreementInitCall() {
|
|
this.getCallee().hasQualifiedName("javax.crypto", "KeyAgreement", "init")
|
|
}
|
|
|
|
Expr getServerKeyArg() { result = this.getArgument(0) }
|
|
}
|
|
|
|
private module KeyAgreementInitQualifierToSecretGenQualifierConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) {
|
|
exists(KeyAgreementInitCall init | src.asExpr() = init.getQualifier())
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(KeyAgreementGenerateSecretCall c | sink.asExpr() = c.getQualifier())
|
|
}
|
|
|
|
/**
|
|
* Barrier if we go into another init, assume the second init overwrites the first
|
|
*/
|
|
predicate isBarrierIn(DataFlow::Node node) { isSource(node) }
|
|
}
|
|
|
|
module KeyAgreementInitQualifierToSecretGenQualifierFlow =
|
|
DataFlow::Global<KeyAgreementInitQualifierToSecretGenQualifierConfig>;
|
|
|
|
class KeyAgreementGenerateSecretCall extends MethodCall {
|
|
KeyAgreementGenerateSecretCall() {
|
|
this.getCallee().hasQualifiedName("javax.crypto", "KeyAgreement", "generateSecret")
|
|
}
|
|
|
|
KeyAgreementInitCall getKeyAgreementInitCall() {
|
|
KeyAgreementInitQualifierToSecretGenQualifierFlow::flow(DataFlow::exprNode(result
|
|
.getQualifier()), DataFlow::exprNode(this.getQualifier()))
|
|
}
|
|
}
|
|
|
|
private module KeyAgreementAVCToInitQualifierConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) {
|
|
exists(KeyAgreementAlgorithmValueConsumer consumer | consumer.getResultNode() = src)
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(KeyAgreementInitCall init | sink.asExpr() = init.getQualifier())
|
|
}
|
|
}
|
|
|
|
module KeyAgreementAVCToInitQualifierFlow =
|
|
DataFlow::Global<KeyAgreementAVCToInitQualifierConfig>;
|
|
|
|
class KeyAgreementSecretGenerationOperationInstance extends Crypto::KeyAgreementSecretGenerationOperationInstance instanceof KeyAgreementGenerateSecretCall
|
|
{
|
|
override Crypto::ConsumerInputDataFlowNode getServerKeyConsumer() {
|
|
this.(KeyAgreementGenerateSecretCall).getKeyAgreementInitCall().getServerKeyArg() =
|
|
result.asExpr()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getPeerKeyConsumer() {
|
|
none() //TODO
|
|
}
|
|
|
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
|
none() // TODO: key agreeement has it's own algorithm consumer, separate from the key
|
|
// TODO: the char pred must trace from the consumer to here,
|
|
// in theory, along that path we would get the init and doPhase, but can I just get those
|
|
// separately avoiding a complicated config state for dataflow?
|
|
}
|
|
}
|
|
|
|
class CipherStringLiteralModeAlgorithmInstance extends JCAAlgorithmInstance,
|
|
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())
|
|
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("<CURVE NAME>") 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<ECGenParameterSpecCallToInitializeConfig>;
|
|
|
|
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 EllipticCurveStringLiteral
|
|
{
|
|
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 KeyAgreementGetInstanceCall extends MethodCall {
|
|
KeyAgreementGetInstanceCall() {
|
|
this.getCallee().hasQualifiedName("javax.crypto", "KeyAgreement", "getInstance")
|
|
}
|
|
|
|
Expr getAlgorithmArg() { result = super.getArgument(0) }
|
|
}
|
|
|
|
class KeyAgreementAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
|
|
KeyAgreementGetInstanceCall call;
|
|
|
|
KeyAgreementAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
|
|
|
|
DataFlow::Node getResultNode() { result.asExpr() = call }
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
|
|
|
|
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
|
result.(KeyAgreementStringLiteralAlgorithmInstance).getConsumer() = this
|
|
}
|
|
}
|
|
|
|
class KeyAgreementStringLiteralAlgorithmInstance extends JCAAlgorithmInstance,
|
|
Crypto::KeyAgreementAlgorithmInstance instanceof KeyAgreementStringLiteral
|
|
{
|
|
Crypto::AlgorithmValueConsumer consumer;
|
|
|
|
KeyAgreementStringLiteralAlgorithmInstance() {
|
|
KeyAgreementAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
|
|
}
|
|
|
|
override Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
|
|
// override Crypto::EllipticCurveAlgorithmInstance getEllipticCurveAlgorithm() {
|
|
// this.(KeyAgreementStringLiteral).getValue().toUpperCase() in ["X25519", "X448"] and
|
|
// // NOTE: this relies upon modeling the elliptic curve on 'this' separately
|
|
// result = this
|
|
// // TODO: or is ecdh and go find the curve
|
|
// // this.(KeyAgreementStringLiteral).toString().toUpperCase() = ["ECDH"]
|
|
// }
|
|
}
|
|
|
|
class CipherStringLiteralAlgorithmInstance extends JCAAlgorithmInstance,
|
|
Crypto::CipherAlgorithmInstance instanceof CipherStringLiteral
|
|
{
|
|
// NOTE: this consumer is generic, but cipher algorithms can be consumed
|
|
// by getInstance as well as key generation
|
|
Crypto::AlgorithmValueConsumer consumer;
|
|
|
|
CipherStringLiteralAlgorithmInstance() {
|
|
CipherAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
|
|
}
|
|
|
|
override Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
|
|
|
|
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
|
|
result = this // TODO: provider defaults
|
|
}
|
|
|
|
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
|
|
result = this // TODO: provider defaults
|
|
}
|
|
|
|
override string getRawCipherAlgorithmName() { result = super.getValue() }
|
|
|
|
override Crypto::TCipherType getCipherFamily() {
|
|
if this.cipherNameMappingKnown(_, super.getAlgorithmName())
|
|
then this.cipherNameMappingKnown(result, super.getAlgorithmName())
|
|
else result instanceof Crypto::OtherCipherType
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
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 getHashFamily() { result = hash_name_to_hash_type(hashName, _) }
|
|
|
|
override int getDigestLength() { exists(hash_name_to_hash_type(hashName, result)) }
|
|
}
|
|
|
|
class OAEPPaddingAlgorithmInstance extends JCAAlgorithmInstance,
|
|
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 Crypto::AlgorithmValueConsumer 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
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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"
|
|
]
|
|
}
|
|
}
|
|
|
|
private newtype TCipherModeFlowState =
|
|
TUninitializedCipherModeFlowState() or
|
|
TInitializedCipherModeFlowState(CipherInitCall call) or
|
|
TUsedCipherModeFlowState(CipherInitCall init)
|
|
|
|
abstract private class CipherModeFlowState extends TCipherModeFlowState {
|
|
string toString() {
|
|
this = TUninitializedCipherModeFlowState() and result = "uninitialized"
|
|
or
|
|
this = TInitializedCipherModeFlowState(_) and result = "initialized"
|
|
}
|
|
|
|
abstract Crypto::CipherOperationSubtype getCipherOperationMode();
|
|
}
|
|
|
|
private class UninitializedCipherModeFlowState extends CipherModeFlowState,
|
|
TUninitializedCipherModeFlowState
|
|
{
|
|
override Crypto::CipherOperationSubtype getCipherOperationMode() {
|
|
result instanceof Crypto::UnknownCipherOperationSubtype
|
|
}
|
|
}
|
|
|
|
private class InitializedCipherModeFlowState extends CipherModeFlowState,
|
|
TInitializedCipherModeFlowState
|
|
{
|
|
CipherInitCall call;
|
|
DataFlow::Node node1;
|
|
DataFlow::Node node2;
|
|
Crypto::CipherOperationSubtype mode;
|
|
|
|
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()
|
|
}
|
|
|
|
CipherInitCall getInitCall() { result = call }
|
|
|
|
DataFlow::Node getFstNode() { result = node1 }
|
|
|
|
/**
|
|
* Returns the node *to* which the state-changing step occurs
|
|
*/
|
|
DataFlow::Node getSndNode() { result = node2 }
|
|
|
|
override Crypto::CipherOperationSubtype getCipherOperationMode() { result = mode }
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
predicate isSource(DataFlow::Node src, FlowState state) {
|
|
state instanceof UninitializedCipherModeFlowState and
|
|
src.asExpr() instanceof CipherGetInstanceCall
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink, FlowState state) { none() } // TODO: document this, but this is intentional (avoid cross products?)
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(CipherOperationCall c | c.getQualifier() = sink.asExpr())
|
|
}
|
|
|
|
predicate isAdditionalFlowStep(
|
|
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
|
|
) {
|
|
state1 = state1 and
|
|
node1 = state2.(InitializedCipherModeFlowState).getFstNode() and
|
|
node2 = state2.(InitializedCipherModeFlowState).getSndNode()
|
|
}
|
|
|
|
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
|
exists(CipherInitCall call | node.asExpr() = call.getQualifier() |
|
|
state instanceof UninitializedCipherModeFlowState
|
|
or
|
|
state.(InitializedCipherModeFlowState).getInitCall() != call
|
|
)
|
|
}
|
|
}
|
|
|
|
module CipherGetInstanceToCipherOperationFlow =
|
|
DataFlow::GlobalWithState<CipherGetInstanceToCipherOperationConfig>;
|
|
|
|
class CipherOperationInstance extends Crypto::CipherOperationInstance instanceof Call {
|
|
Crypto::CipherOperationSubtype mode;
|
|
CipherGetInstanceToCipherOperationFlow::PathNode sink;
|
|
CipherOperationCall doFinalize;
|
|
CipherGetInstanceAlgorithmArg consumer;
|
|
|
|
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()
|
|
)
|
|
}
|
|
|
|
override Crypto::CipherOperationSubtype getCipherOperationSubtype() { result = mode }
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
|
|
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
|
result = doFinalize.getMessageArg()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
|
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getKeyArg()
|
|
}
|
|
|
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = consumer }
|
|
|
|
override Crypto::CipherOutputArtifactInstance getOutputArtifact() {
|
|
result = doFinalize.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() {
|
|
this.(ClassInstanceExpr)
|
|
.getConstructedType()
|
|
.hasQualifiedName("javax.crypto.spec", "IvParameterSpec")
|
|
}
|
|
|
|
override DataFlow::Node getInputNode() {
|
|
result.asExpr() = this.(ClassInstanceExpr).getArgument(0)
|
|
}
|
|
}
|
|
|
|
// TODO: this also specifies the tag length for GCM
|
|
class GCMParameterSpecInstance extends NonceParameterInstantiation {
|
|
GCMParameterSpecInstance() {
|
|
this.(ClassInstanceExpr)
|
|
.getConstructedType()
|
|
.hasQualifiedName("javax.crypto.spec", "GCMParameterSpec")
|
|
}
|
|
|
|
override DataFlow::Node getInputNode() {
|
|
result.asExpr() = this.(ClassInstanceExpr).getArgument(1)
|
|
}
|
|
}
|
|
|
|
class IvParameterSpecGetIvCall extends MethodCall {
|
|
IvParameterSpecGetIvCall() {
|
|
this.getMethod().hasQualifiedName("javax.crypto.spec", "IvParameterSpec", "getIV")
|
|
}
|
|
}
|
|
|
|
// e.g., getPublic or getPrivate
|
|
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::CipherOperationSubtype cipher_mode
|
|
) {
|
|
mode = "ENCRYPT_MODE" and cipher_mode instanceof Crypto::EncryptionSubtype
|
|
or
|
|
mode = "WRAP_MODE" and cipher_mode instanceof Crypto::WrapSubtype
|
|
or
|
|
mode = "DECRYPT_MODE" and cipher_mode instanceof Crypto::DecryptionSubtype
|
|
or
|
|
mode = "UNWRAP_MODE" and cipher_mode instanceof Crypto::UnwrapSubtype
|
|
}
|
|
|
|
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::CipherOperationSubtype 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
|
|
}
|
|
|
|
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 }
|
|
}
|
|
|
|
class CipherOperationCallOutput extends Crypto::CipherOutputArtifactInstance {
|
|
CipherOperationCallOutput() { this = any(CipherOperationCall call).getOutput() }
|
|
|
|
override DataFlow::Node getOutputNode() { result.asExpr() = this }
|
|
}
|
|
|
|
// flow config from a known hash algorithm literal to MessageDigest.getInstance
|
|
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())
|
|
}
|
|
}
|
|
|
|
module KnownHashAlgorithmLiteralToMessageDigestFlow =
|
|
DataFlow::Global<KnownHashAlgorithmLiteralToMessageDigestConfig>;
|
|
|
|
class KnownHashAlgorithm extends JCAAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof StringLiteral
|
|
{
|
|
MessageDigestAlgorithmValueConsumer consumer;
|
|
|
|
KnownHashAlgorithm() {
|
|
hash_names(this.getValue()) and
|
|
KnownHashAlgorithmLiteralToMessageDigestFlow::flow(DataFlow::exprNode(this),
|
|
consumer.getInputNode())
|
|
}
|
|
|
|
override MessageDigestAlgorithmValueConsumer getConsumer() { result = consumer }
|
|
|
|
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
|
|
|
|
override Crypto::THashType getHashFamily() {
|
|
result = hash_name_to_hash_type(this.getRawHashAlgorithmName(), _)
|
|
}
|
|
|
|
override int getDigestLength() {
|
|
exists(hash_name_to_hash_type(this.getRawHashAlgorithmName(), result))
|
|
}
|
|
}
|
|
|
|
class MessageDigestAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
|
|
MessageDigestGetInstanceCall call;
|
|
|
|
MessageDigestAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
|
|
|
|
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) }
|
|
|
|
DigestHashOperation getDigestCall() {
|
|
DigestGetInstanceToDigestFlow::flow(DataFlow::exprNode(this),
|
|
DataFlow::exprNode(result.(DigestCall).getQualifier()))
|
|
}
|
|
}
|
|
|
|
class DigestCall extends MethodCall {
|
|
DigestCall() { this.getCallee().hasQualifiedName("java.security", "MessageDigest", "digest") }
|
|
|
|
Expr getDigestArtifactOutput() { result = this }
|
|
}
|
|
|
|
// 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 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()
|
|
}
|
|
|
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
|
exists(MessageDigestGetInstanceCall call |
|
|
call.getDigestCall() = this and result = call.getAlgorithmArg()
|
|
)
|
|
}
|
|
}
|
|
|
|
// flow from instance created by getInstance to generateKey
|
|
module KeyGeneratorAlgValueConsumerToGenerateConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) {
|
|
exists(KeyGenAlgorithmValueConsumer consumer | consumer.getResultNode() = src)
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(KeyGeneratorGenerateCall call | sink.asExpr() = call.(MethodCall).getQualifier())
|
|
}
|
|
}
|
|
|
|
module KeyGeneratorAlgValueConsumerToGenerateFlow =
|
|
DataFlow::Global<KeyGeneratorAlgValueConsumerToGenerateConfig>;
|
|
|
|
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
|
|
}
|
|
|
|
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() { result.asExpr() = this }
|
|
|
|
override Crypto::KeyArtifactType getOutputKeyType() { result = type }
|
|
|
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
|
exists(KeyGenAlgorithmValueConsumer consumer |
|
|
KeyGeneratorAlgValueConsumerToGenerateFlow::flow(consumer.getResultNode(),
|
|
DataFlow::exprNode(this.(Call).getQualifier())) and
|
|
result = consumer
|
|
)
|
|
}
|
|
|
|
Crypto::AlgorithmInstance getAKnownAlgorithm() {
|
|
result = this.getAnAlgorithmValueConsumer().getAKnownAlgorithmSource()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() { none() }
|
|
|
|
override string getKeySizeFixed() { none() }
|
|
}
|
|
|
|
/*
|
|
* 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 mac_name_to_mac_type_known(Crypto::TMACType type, string name) {
|
|
type instanceof Crypto::THMAC and
|
|
name.toUpperCase().matches("HMAC%")
|
|
}
|
|
|
|
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 JCAAlgorithmInstance, Crypto::MACAlgorithmInstance instanceof StringLiteral
|
|
{
|
|
MACGetInstanceAlgorithmValueConsumer consumer;
|
|
|
|
KnownMACAlgorithm() {
|
|
mac_names(this.getValue()) and
|
|
MACKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
|
|
}
|
|
|
|
override 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[]")
|
|
}
|
|
}
|
|
|
|
module SecretKeyFactoryGetInstanceToGenerateSecretFlowConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) {
|
|
exists(SecretKeyFactoryGetInstanceCall call | src.asExpr() = call)
|
|
}
|
|
|
|
predicate isSink(DataFlow::Node sink) {
|
|
exists(SecretKeyFactoryGenerateSecretCall call |
|
|
sink.asExpr() = call.(MethodCall).getQualifier()
|
|
)
|
|
}
|
|
}
|
|
|
|
module PBEKeySpecInstantiationToGenerateSecretFlowConfig implements DataFlow::ConfigSig {
|
|
predicate isSource(DataFlow::Node src) {
|
|
exists(PBEKeySpecInstantiation call | src.asExpr() = call)
|
|
}
|
|
|
|
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 SecretKeyFactoryGetInstanceToGenerateSecretFlow =
|
|
DataFlow::Global<SecretKeyFactoryGetInstanceToGenerateSecretFlowConfig>;
|
|
|
|
module PBEKeySpecInstantiationToGenerateSecretFlow =
|
|
DataFlow::Global<PBEKeySpecInstantiationToGenerateSecretFlowConfig>;
|
|
|
|
module KDFAlgorithmStringToGetInstanceFlow =
|
|
DataFlow::Global<KDFAlgorithmStringToGetInstanceConfig>;
|
|
|
|
class PBEKeySpecInstantiation extends ClassInstanceExpr {
|
|
PBEKeySpecInstantiation() {
|
|
this.getConstructedType().hasQualifiedName("javax.crypto.spec", "PBEKeySpec")
|
|
}
|
|
|
|
Expr getPasswordArg() { result = this.getArgument(0) }
|
|
|
|
Expr getSaltArg() { result = this.getArgument(1) }
|
|
|
|
Expr getIterationCountArg() { result = this.getArgument(2) }
|
|
|
|
Expr getKeyLengthArg() { result = this.getArgument(3) }
|
|
}
|
|
|
|
class SecretKeyFactoryGetInstanceCall extends MethodCall {
|
|
SecretKeyFactoryGetInstanceCall() {
|
|
this.getCallee().hasQualifiedName("javax.crypto", "SecretKeyFactory", "getInstance")
|
|
}
|
|
|
|
Expr getAlgorithmArg() { result = this.getArgument(0) }
|
|
|
|
SecretKeyFactoryGenerateSecretCall getOperation() {
|
|
SecretKeyFactoryGetInstanceToGenerateSecretFlow::flow(DataFlow::exprNode(this),
|
|
DataFlow::exprNode(result.(MethodCall).getQualifier()))
|
|
}
|
|
}
|
|
|
|
class KDFAlgorithmStringLiteral extends JCAAlgorithmInstance,
|
|
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(), _)
|
|
}
|
|
|
|
override SecretKeyFactoryKDFAlgorithmValueConsumer getConsumer() { result = consumer }
|
|
}
|
|
|
|
class PBKDF2AlgorithmStringLiteral extends JCAAlgorithmInstance, KDFAlgorithmStringLiteral,
|
|
Crypto::PBKDF2AlgorithmInstance, Crypto::HMACAlgorithmInstance, Crypto::HashAlgorithmInstance,
|
|
Crypto::AlgorithmValueConsumer
|
|
{
|
|
PBKDF2AlgorithmStringLiteral() { super.getKDFType() instanceof Crypto::PBKDF2 }
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getInputNode() { none() }
|
|
|
|
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() { result = this }
|
|
|
|
override Crypto::THashType getHashFamily() {
|
|
result = hash_name_to_hash_type(this.getRawHashAlgorithmName(), _)
|
|
}
|
|
|
|
override int getDigestLength() {
|
|
exists(hash_name_to_hash_type(this.getRawHashAlgorithmName(), result))
|
|
}
|
|
|
|
override string getRawMACAlgorithmName() {
|
|
result = super.getRawKDFAlgorithmName().splitAt("PBKDF2With", 1)
|
|
}
|
|
|
|
override string getRawHashAlgorithmName() {
|
|
result = super.getRawKDFAlgorithmName().splitAt("WithHmac", 1)
|
|
}
|
|
|
|
override Crypto::TMACType getMACType() { result instanceof Crypto::THMAC }
|
|
|
|
override Crypto::AlgorithmValueConsumer getHMACAlgorithmValueConsumer() { result = this }
|
|
|
|
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() { result = this }
|
|
}
|
|
|
|
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 SecretKeyFactoryGenerateSecretCall extends Crypto::KeyDerivationOperationInstance instanceof MethodCall
|
|
{
|
|
SecretKeyFactoryGenerateSecretCall() {
|
|
super.getCallee().hasQualifiedName("javax.crypto", "SecretKeyFactory", "generateSecret")
|
|
}
|
|
|
|
Expr getKeySpecArg() {
|
|
result = super.getArgument(0) and
|
|
super.getMethod().getParameterType(0).hasName("KeySpec")
|
|
}
|
|
|
|
PBEKeySpecInstantiation getInstantiation() {
|
|
PBEKeySpecInstantiationToGenerateSecretFlow::flow(DataFlow::exprNode(result),
|
|
DataFlow::exprNode(this.getKeySpecArg()))
|
|
}
|
|
|
|
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
|
exists(SecretKeyFactoryGetInstanceCall instantiation |
|
|
instantiation.getOperation() = this and result = instantiation.getAlgorithmArg()
|
|
)
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getSaltConsumer() {
|
|
result.asExpr() = this.getInstantiation().getSaltArg()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
|
result.asExpr() = this.getInstantiation().getPasswordArg()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getIterationCountConsumer() {
|
|
result.asExpr() = this.getInstantiation().getIterationCountArg()
|
|
}
|
|
|
|
override Crypto::ArtifactOutputDataFlowNode getOutputKeyArtifact() {
|
|
result.asExpr() = this and
|
|
super.getMethod().getReturnType().hasName("SecretKey")
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getOutputKeySizeConsumer() {
|
|
result.asExpr() = this.getInstantiation().getKeyLengthArg()
|
|
}
|
|
|
|
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
|
result.asExpr() = this.getInstantiation().getKeyLengthArg()
|
|
}
|
|
|
|
override string getKeySizeFixed() { none() }
|
|
|
|
override string getOutputKeySizeFixed() { none() }
|
|
|
|
override string getIterationCountFixed() { none() }
|
|
}
|
|
// TODO: flow the GCGenParametersSpecCall to an init, and the init to the operations
|
|
}
|