Move CipherOperation into KeyOperation, refactor

- KeyOperation and Algorithm now encompasses encryption, decryption, wrapping, unwrapping, signing, and verifying.
- Removed elliptic curve implementation from JCA.qll pending rewrite
- Removed JCAAlgorithmInstance abstraction from JCA.qll
- Cleaned up and removed JCA-specific naming from Model.qll
- Added and clarified documentation
This commit is contained in:
Nicolas Will
2025-04-09 21:19:00 +02:00
parent a2fe19af38
commit b9d0abda63
7 changed files with 986 additions and 1016 deletions

View File

@@ -5,10 +5,7 @@ import semmle.code.java.controlflow.Dominance
module JCAModel {
import Language
abstract class JCAAlgorithmInstance extends Crypto::AlgorithmInstance {
abstract Crypto::AlgorithmValueConsumer getConsumer();
}
import Crypto::KeyOpAlg as KeyOpAlg
// TODO: Verify that the PBEWith% case works correctly
bindingset[algo]
@@ -54,10 +51,48 @@ module JCAModel {
}
bindingset[name]
predicate elliptic_curve_names(string name) { Crypto::isEllipticCurveAlgorithmName(name) }
predicate elliptic_curve_names(string name) {
// Note: as a one-off exception, we use the internal Crypto module implementation of `isEllipticCurveAlgorithmName`
Crypto::isEllipticCurveAlgorithmName(name)
}
/*
* TODO:
*
* MAC Algorithms possible (JCA Default + BouncyCastle Extensions)
*
* Name Type Description
* ---------------------------------------------------------------------------
* "HmacMD5" HMAC HMAC with MD5 (not recommended)
* "HmacSHA1" HMAC HMAC with SHA-1 (not recommended)
* "HmacSHA224" HMAC HMAC with SHA-224
* "HmacSHA256" HMAC HMAC with SHA-256
* "HmacSHA384" HMAC HMAC with SHA-384
* "HmacSHA512" HMAC HMAC with SHA-512
*
* (BouncyCastle and Other Provider Extensions)
* "AESCMAC" CMAC Cipher-based MAC using AES
* "DESCMAC" CMAC CMAC with DES (legacy)
* "GMAC" GCM-based MAC Authenticates AAD only (GCM-style)
* "Poly1305" AEAD-style MAC Used with ChaCha20
* "SipHash" Hash-based MAC Fast MAC for short inputs
* "BLAKE2BMAC" HMAC-style BLAKE2b MAC (cryptographic hash)
* "HmacRIPEMD160" HMAC HMAC with RIPEMD160 hash
*/
bindingset[name]
predicate key_agreement_names(string name) { Crypto::isKeyAgreementAlgorithmName(name) }
predicate mac_names(string name) {
name.toUpperCase()
.matches([
"HMAC%", "AESCMAC", "DESCMAC", "GMAC", "Poly1305", "SipHash", "BLAKE2BMAC",
"HMACRIPEMD160"
].toUpperCase())
}
bindingset[name]
predicate key_agreement_names(string name) {
name.toUpperCase().matches(["DH", "EDH", "ECDH", "X25519", "X448"].toUpperCase())
}
bindingset[name]
Crypto::TKeyDerivationType kdf_name_to_kdf_type(string name, string withSubstring) {
@@ -107,6 +142,77 @@ module JCAModel {
digestLength = 512 // TODO: verify
}
bindingset[name]
private predicate mode_name_to_type_known(
Crypto::TBlockCipherModeOfOperationType type, string name
) {
type = Crypto::ECB() and name = "ECB"
or
type = Crypto::CBC() and name = "CBC"
or
type = Crypto::GCM() and name = "GCM"
or
type = Crypto::CTR() and name = "CTR"
or
type = Crypto::XTS() and name = "XTS"
or
type = Crypto::CCM() and name = "CCM"
or
type = Crypto::SIV() and name = "SIV"
or
type = Crypto::OCB() and name = "OCB"
}
bindingset[name]
private predicate cipher_name_to_type_known(KeyOpAlg::TAlgorithm type, string name) {
exists(string upper | upper = name.toUpperCase() |
upper.matches("AES%") and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::AES())
or
upper = "DES" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::DES())
or
upper = "TRIPLEDES" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::TripleDES())
or
upper = "IDEA" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::IDEA())
or
upper = "CAST5" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CAST5())
or
upper = "CHACHA20" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::CHACHA20())
or
upper = "RC4" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC4())
or
upper = "RC5" and
type = KeyOpAlg::TSymmetricCipher(KeyOpAlg::RC5())
or
upper = "RSA" and
type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
)
}
bindingset[name]
predicate mac_name_to_mac_type_known(Crypto::TMACType type, string name) {
type = Crypto::THMAC() and
name.toUpperCase().matches("HMAC%")
}
bindingset[name]
predicate key_agreement_name_to_type_known(Crypto::TKeyAgreementType type, string name) {
type = Crypto::DH() and
name.toUpperCase() = "DH"
or
type = Crypto::EDH() and
name.toUpperCase() = "EDH"
or
type = Crypto::ECDH() and
name.toUpperCase() in ["ECDH", "X25519", "X448"]
}
/**
* A `StringLiteral` in the `"ALG/MODE/PADDING"` or `"ALG"` format
*/
@@ -120,16 +226,6 @@ module JCAModel {
string getPadding() { result = this.getValue().splitAt("/", 2) }
}
class EllipticCurveStringLiteral extends StringLiteral {
EllipticCurveStringLiteral() { elliptic_curve_names(this.getValue()) }
string getStandardEllipticCurveName() { result = this.getValue() }
}
class KeyAgreementStringLiteral extends StringLiteral {
KeyAgreementStringLiteral() { key_agreement_names(this.getValue()) }
}
class CipherGetInstanceCall extends Call {
CipherGetInstanceCall() {
this.getCallee().hasQualifiedName("javax.crypto", "Cipher", "getInstance")
@@ -159,9 +255,9 @@ module JCAModel {
}
/**
* Data-flow configuration modelling flow from a cipher string literal to a value consumer argument.
* Data-flow configuration modelling flow from a cipher string literal to a `CipherGetInstanceCall` argument.
*/
private module CipherAlgorithmStringToFetchConfig implements DataFlow::ConfigSig {
private module AlgorithmStringToFetchConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof CipherStringLiteral }
predicate isSink(DataFlow::Node sink) {
@@ -169,22 +265,7 @@ module JCAModel {
}
}
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>;
module AlgorithmStringToFetchFlow = TaintTracking::Global<AlgorithmStringToFetchConfig>;
/**
* Note: padding and a mode of operation will only exist when the padding / mode (*and its type*) are determinable.
@@ -195,8 +276,8 @@ module JCAModel {
*
* TODO: Model the case of relying on a provider default, but alert on it as a bad practice.
*/
class CipherStringLiteralPaddingAlgorithmInstance extends JCAAlgorithmInstance,
CipherStringLiteralAlgorithmInstance, Crypto::PaddingAlgorithmInstance instanceof CipherStringLiteral
class CipherStringLiteralPaddingAlgorithmInstance extends CipherStringLiteralAlgorithmInstance,
Crypto::PaddingAlgorithmInstance instanceof CipherStringLiteral
{
CipherStringLiteralPaddingAlgorithmInstance() { exists(super.getPadding()) } // TODO: provider defaults
@@ -218,298 +299,29 @@ module JCAModel {
}
}
/**
* 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
class CipherStringLiteralModeAlgorithmInstance extends CipherStringLiteralPaddingAlgorithmInstance,
Crypto::ModeOfOperationAlgorithmInstance instanceof CipherStringLiteral
{
CipherStringLiteralModeAlgorithmInstance() { exists(super.getMode()) } // TODO: provider defaults
override string getRawModeAlgorithmName() { result = super.getMode() }
bindingset[name]
private predicate modeToNameMappingKnown(Crypto::TBlockCipherModeOperationType type, string name) {
type instanceof Crypto::ECB and name = "ECB"
or
type instanceof Crypto::CBC and name = "CBC"
or
type instanceof Crypto::GCM and name = "GCM"
or
type instanceof Crypto::CTR and name = "CTR"
or
type instanceof Crypto::XTS and name = "XTS"
or
type instanceof Crypto::CCM and name = "CCM"
or
type instanceof Crypto::SIV and name = "SIV"
or
type instanceof Crypto::OCB and name = "OCB"
}
override Crypto::TBlockCipherModeOperationType getModeType() {
if this.modeToNameMappingKnown(_, super.getMode())
then this.modeToNameMappingKnown(result, super.getMode())
override Crypto::TBlockCipherModeOfOperationType getModeType() {
if mode_name_to_type_known(_, super.getMode())
then mode_name_to_type_known(result, super.getMode())
else result instanceof Crypto::OtherMode
}
}
class KeyGeneratorGetInstanceCall extends MethodCall {
KeyGeneratorGetInstanceCall() {
this.getCallee().hasQualifiedName("javax.crypto", "KeyGenerator", "getInstance")
or
this.getCallee().hasQualifiedName("java.security", "KeyPairGenerator", "getInstance")
}
Expr getAlgorithmArg() { result = super.getArgument(0) }
}
// For general elliptic curves, getInstance("EC") is used
// and java.security.spec.ECGenParameterSpec("<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
class CipherStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof CipherStringLiteral
{
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())
AlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
override Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() {
result = this // TODO: provider defaults
@@ -519,42 +331,16 @@ module JCAModel {
result = this // TODO: provider defaults
}
override string getRawCipherAlgorithmName() { result = super.getValue() }
override string getRawAlgorithmName() { result = super.getValue() }
override Crypto::TCipherType getCipherFamily() {
if this.cipherNameMappingKnown(_, super.getAlgorithmName())
then this.cipherNameMappingKnown(result, super.getAlgorithmName())
else result instanceof Crypto::OtherCipherType
override KeyOpAlg::Algorithm getAlgorithmType() {
if cipher_name_to_type_known(_, super.getAlgorithmName())
then cipher_name_to_type_known(result, super.getAlgorithmName())
else result instanceof KeyOpAlg::TUnknownKeyOperationAlgorithmType
}
bindingset[name]
private predicate cipherNameMappingKnown(Crypto::TCipherType type, string name) {
name = "AES" and
type instanceof Crypto::AES
or
name = "DES" and
type instanceof Crypto::DES
or
name = "TripleDES" and
type instanceof Crypto::TripleDES
or
name = "IDEA" and
type instanceof Crypto::IDEA
or
name = "CAST5" and
type instanceof Crypto::CAST5
or
name = "ChaCha20" and
type instanceof Crypto::CHACHA20
or
name = "RC4" and
type instanceof Crypto::RC4
or
name = "RC5" and
type instanceof Crypto::RC5
or
name = "RSA" and
type instanceof Crypto::RSA
override string getKeySize() {
none() // TODO: implement to handle variants such as AES-128
}
}
@@ -587,12 +373,14 @@ module JCAModel {
override int getDigestLength() { exists(hash_name_to_hash_type(hashName, result)) }
}
class OAEPPaddingAlgorithmInstance extends JCAAlgorithmInstance,
Crypto::OAEPPaddingAlgorithmInstance, CipherStringLiteralPaddingAlgorithmInstance
class OAEPPaddingAlgorithmInstance extends Crypto::OAEPPaddingAlgorithmInstance,
CipherStringLiteralPaddingAlgorithmInstance
{
override Crypto::HashAlgorithmInstance getOAEPEncodingHashAlgorithm() { result = this }
override Crypto::HashAlgorithmInstance getMGF1HashAlgorithm() { none() } // TODO
override string getKeySize() { none() }
}
/**
@@ -608,7 +396,7 @@ module JCAModel {
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
CipherStringLiteral getOrigin(string value) {
CipherAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(result),
AlgorithmStringToFetchFlow::flow(DataFlow::exprNode(result),
DataFlow::exprNode(this.(Expr).getAChildExpr*())) and
value = result.getValue()
}
@@ -650,14 +438,14 @@ module JCAModel {
this = TInitializedCipherModeFlowState(_) and result = "initialized"
}
abstract Crypto::CipherOperationSubtype getCipherOperationMode();
abstract Crypto::KeyOperationSubtype getKeyOperationMode();
}
private class UninitializedCipherModeFlowState extends CipherModeFlowState,
TUninitializedCipherModeFlowState
{
override Crypto::CipherOperationSubtype getCipherOperationMode() {
result instanceof Crypto::UnknownCipherOperationSubtype
override Crypto::KeyOperationSubtype getKeyOperationMode() {
result instanceof Crypto::TUnknownKeyOperationMode
}
}
@@ -667,7 +455,7 @@ module JCAModel {
CipherInitCall call;
DataFlow::Node node1;
DataFlow::Node node2;
Crypto::CipherOperationSubtype mode;
Crypto::KeyOperationSubtype mode;
InitializedCipherModeFlowState() {
this = TInitializedCipherModeFlowState(call) and
@@ -687,7 +475,7 @@ module JCAModel {
*/
DataFlow::Node getSndNode() { result = node2 }
override Crypto::CipherOperationSubtype getCipherOperationMode() { result = mode }
override Crypto::KeyOperationSubtype getKeyOperationMode() { result = mode }
}
/**
@@ -730,8 +518,8 @@ module JCAModel {
module CipherGetInstanceToCipherOperationFlow =
DataFlow::GlobalWithState<CipherGetInstanceToCipherOperationConfig>;
class CipherOperationInstance extends Crypto::CipherOperationInstance instanceof Call {
Crypto::CipherOperationSubtype mode;
class CipherOperationInstance extends Crypto::KeyOperationInstance instanceof Call {
Crypto::KeyOperationSubtype mode;
CipherGetInstanceToCipherOperationFlow::PathNode sink;
CipherOperationCall doFinalize;
CipherGetInstanceAlgorithmArg consumer;
@@ -741,13 +529,13 @@ module JCAModel {
CipherGetInstanceToCipherOperationFlow::flowPath(src, sink) and
src.getNode().asExpr() = getCipher and
sink.getNode().asExpr() = doFinalize.getQualifier() and
sink.getState().(CipherModeFlowState).getCipherOperationMode() = mode and
sink.getState().(CipherModeFlowState).getKeyOperationMode() = mode and
this = doFinalize and
consumer = getCipher.getAlgorithmArg()
)
}
override Crypto::CipherOperationSubtype getCipherOperationSubtype() { result = mode }
override Crypto::KeyOperationSubtype getKeyOperationSubtype() { result = mode }
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() {
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg()
@@ -763,8 +551,8 @@ module JCAModel {
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = consumer }
override Crypto::CipherOutputArtifactInstance getOutputArtifact() {
result = doFinalize.getOutput()
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
result.asExpr() = doFinalize.getOutput()
}
}
@@ -865,15 +653,15 @@ module JCAModel {
module JavaxCipherModeAccessToInitFlow = DataFlow::Global<JavaxCipherModeAccessToInitConfig>;
private predicate cipher_mode_str_to_cipher_mode_known(
string mode, Crypto::CipherOperationSubtype cipher_mode
string mode, Crypto::KeyOperationSubtype cipher_mode
) {
mode = "ENCRYPT_MODE" and cipher_mode instanceof Crypto::EncryptionSubtype
mode = "ENCRYPT_MODE" and cipher_mode = Crypto::TEncryptMode()
or
mode = "WRAP_MODE" and cipher_mode instanceof Crypto::WrapSubtype
mode = "WRAP_MODE" and cipher_mode = Crypto::TWrapMode()
or
mode = "DECRYPT_MODE" and cipher_mode instanceof Crypto::DecryptionSubtype
mode = "DECRYPT_MODE" and cipher_mode = Crypto::TDecryptMode()
or
mode = "UNWRAP_MODE" and cipher_mode instanceof Crypto::UnwrapSubtype
mode = "UNWRAP_MODE" and cipher_mode = Crypto::TUnwrapMode()
}
class CipherInitCall extends MethodCall {
@@ -896,10 +684,10 @@ module JCAModel {
)
}
Crypto::CipherOperationSubtype getCipherOperationModeType() {
Crypto::KeyOperationSubtype getCipherOperationModeType() {
if cipher_mode_str_to_cipher_mode_known(this.getModeOrigin().getField().getName(), _)
then cipher_mode_str_to_cipher_mode_known(this.getModeOrigin().getField().getName(), result)
else result instanceof Crypto::UnknownCipherOperationSubtype
else result = Crypto::TUnknownKeyOperationMode()
}
Expr getKeyArg() {
@@ -918,12 +706,6 @@ module JCAModel {
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
}
class CipherOperationCallOutput extends Crypto::CipherOutputArtifactInstance {
CipherOperationCallOutput() { this = any(CipherOperationCall call).getOutput() }
override DataFlow::Node getOutputNode() { result.asExpr() = this }
}
// 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()) }
@@ -936,8 +718,7 @@ module JCAModel {
module KnownHashAlgorithmLiteralToMessageDigestFlow =
DataFlow::Global<KnownHashAlgorithmLiteralToMessageDigestConfig>;
class KnownHashAlgorithm extends JCAAlgorithmInstance, Crypto::HashAlgorithmInstance instanceof StringLiteral
{
class KnownHashAlgorithm extends Crypto::HashAlgorithmInstance instanceof StringLiteral {
MessageDigestAlgorithmValueConsumer consumer;
KnownHashAlgorithm() {
@@ -946,7 +727,7 @@ module JCAModel {
consumer.getInputNode())
}
override MessageDigestAlgorithmValueConsumer getConsumer() { result = consumer }
MessageDigestAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
@@ -1019,10 +800,22 @@ module JCAModel {
}
}
class KeyGeneratorCallAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
KeyGeneratorGetInstanceCall call;
KeyGeneratorCallAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this
}
}
// flow from instance created by getInstance to generateKey
module KeyGeneratorAlgValueConsumerToGenerateConfig implements DataFlow::ConfigSig {
module KeyGeneratorGetInstanceToGenerateConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
exists(KeyGenAlgorithmValueConsumer consumer | consumer.getResultNode() = src)
exists(KeyGeneratorGetInstanceCall call | src.asExpr() = call)
}
predicate isSink(DataFlow::Node sink) {
@@ -1030,8 +823,23 @@ module JCAModel {
}
}
module KeyGeneratorAlgValueConsumerToGenerateFlow =
DataFlow::Global<KeyGeneratorAlgValueConsumerToGenerateConfig>;
module KeyGeneratorGetInstanceToGenerateFlow =
DataFlow::Global<KeyGeneratorGetInstanceToGenerateConfig>;
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) }
predicate flowsToKeyGenerateCallQualifier(KeyGeneratorGenerateCall sink) {
KeyGeneratorGetInstanceToGenerateFlow::flow(DataFlow::exprNode(this),
DataFlow::exprNode(sink.(MethodCall).getQualifier()))
}
}
class KeyGeneratorGenerateCall extends Crypto::KeyGenerationOperationInstance instanceof MethodCall
{
@@ -1050,10 +858,8 @@ module JCAModel {
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
exists(KeyGeneratorGetInstanceCall getInstance |
getInstance.flowsToKeyGenerateCallQualifier(this) and result = getInstance.getAlgorithmArg()
)
}
@@ -1066,45 +872,6 @@ module JCAModel {
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()) }
@@ -1140,8 +907,7 @@ module JCAModel {
module MACInitCallToMACOperationFlow = DataFlow::Global<MACInitCallToMACOperationFlowConfig>;
class KnownMACAlgorithm extends JCAAlgorithmInstance, Crypto::MACAlgorithmInstance instanceof StringLiteral
{
class KnownMACAlgorithm extends Crypto::MACAlgorithmInstance instanceof StringLiteral {
MACGetInstanceAlgorithmValueConsumer consumer;
KnownMACAlgorithm() {
@@ -1149,7 +915,7 @@ module JCAModel {
MACKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
override MACGetInstanceAlgorithmValueConsumer getConsumer() { result = consumer }
MACGetInstanceAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawMACAlgorithmName() { result = super.getValue() }
@@ -1301,8 +1067,7 @@ module JCAModel {
}
}
class KDFAlgorithmStringLiteral extends JCAAlgorithmInstance,
Crypto::KeyDerivationAlgorithmInstance instanceof StringLiteral
class KDFAlgorithmStringLiteral extends Crypto::KeyDerivationAlgorithmInstance instanceof StringLiteral
{
SecretKeyFactoryKDFAlgorithmValueConsumer consumer;
@@ -1317,10 +1082,10 @@ module JCAModel {
result = kdf_name_to_kdf_type(super.getValue(), _)
}
override SecretKeyFactoryKDFAlgorithmValueConsumer getConsumer() { result = consumer }
SecretKeyFactoryKDFAlgorithmValueConsumer getConsumer() { result = consumer }
}
class PBKDF2AlgorithmStringLiteral extends JCAAlgorithmInstance, KDFAlgorithmStringLiteral,
class PBKDF2AlgorithmStringLiteral extends KDFAlgorithmStringLiteral,
Crypto::PBKDF2AlgorithmInstance, Crypto::HMACAlgorithmInstance, Crypto::HashAlgorithmInstance,
Crypto::AlgorithmValueConsumer
{
@@ -1421,5 +1186,132 @@ module JCAModel {
override string getIterationCountFixed() { none() }
}
// TODO: flow the GCGenParametersSpecCall to an init, and the init to the operations
class KeyAgreementStringLiteral extends StringLiteral {
KeyAgreementStringLiteral() { key_agreement_names(this.getValue()) }
}
/**
* 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 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 Crypto::KeyAgreementAlgorithmInstance instanceof KeyAgreementStringLiteral
{
Crypto::AlgorithmValueConsumer consumer;
KeyAgreementStringLiteralAlgorithmInstance() {
KeyAgreementAlgorithmStringToFetchFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
Crypto::AlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawKeyAgreementAlgorithmName() { result = super.getValue() }
override Crypto::TKeyAgreementType getKeyAgreementType() {
if key_agreement_name_to_type_known(_, super.getValue())
then key_agreement_name_to_type_known(result, super.getValue())
else result = Crypto::UnknownKeyAgreementType()
}
}
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 its 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?
}
}
}

View File

@@ -7,18 +7,17 @@
import experimental.Quantum.Language
from
Crypto::NonceArtifactNode n, Crypto::CipherOperationNode op, Crypto::FlowAwareElement src,
string msg
Crypto::NonceArtifactNode n, Crypto::KeyOperationNode op, Crypto::FlowAwareElement src, string msg
where
op.getANonce() = n and
// Only encryption mode is relevant for insecure nonces, consder any 'unknown' subtype
// as possibly encryption.
(
op.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype
op.getKeyOperationSubtype() instanceof Crypto::EncryptionSubtype
or
op.getCipherOperationSubtype() instanceof Crypto::WrapSubtype
op.getKeyOperationSubtype() instanceof Crypto::WrapSubtype
or
op.getCipherOperationSubtype() instanceof Crypto::UnwrapSubtype
op.getKeyOperationSubtype() instanceof Crypto::UnwrapSubtype
) and
(
// Known sources cases that are not secure

View File

@@ -8,9 +8,8 @@ import experimental.Quantum.Language
import semmle.code.java.dataflow.DataFlow
from
Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2,
Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2, Crypto::FlowAwareElement src1,
Crypto::FlowAwareElement src2
Crypto::KeyOperationNode op1, Crypto::KeyOperationNode op2, Crypto::NonceArtifactNode nonce1,
Crypto::NonceArtifactNode nonce2, Crypto::FlowAwareElement src1, Crypto::FlowAwareElement src2
where
// NOTE: not looking at value of the nonce, if we knew value, it would be insecure (hard coded)
// Instead trying to find nonce sources that trace to multiple operations.
@@ -18,14 +17,14 @@ where
// (the encryption happened else where) or we are able to see the encryption and decryption operation and
// reuse for encryption is the concern)
(
op1.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype or
op1.getCipherOperationSubtype() instanceof Crypto::WrapSubtype or
op1.getCipherOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype
op1.getKeyOperationSubtype() instanceof Crypto::EncryptionSubtype or
op1.getKeyOperationSubtype() instanceof Crypto::WrapSubtype or
op1.getKeyOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype
) and
(
op2.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype or
op2.getCipherOperationSubtype() instanceof Crypto::WrapSubtype or
op2.getCipherOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype
op2.getKeyOperationSubtype() instanceof Crypto::EncryptionSubtype or
op2.getKeyOperationSubtype() instanceof Crypto::WrapSubtype or
op2.getKeyOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype
) and
nonce1 = op1.getANonce() and
nonce2 = op2.getANonce() and

View File

@@ -5,14 +5,14 @@
import experimental.Quantum.Language
from
Crypto::CipherOperationNode op, Crypto::CipherAlgorithmNode a,
Crypto::CipherOperationNode op, Crypto::KeyOperationAlgorithmNode a,
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p,
Crypto::NonceArtifactNode nonce, Crypto::KeyArtifactNode k
where
a = op.getAKnownCipherAlgorithm() and
a = op.getAKnownAlgorithm() and
m = a.getModeOfOperation() and
p = a.getPaddingAlgorithm() and
nonce = op.getANonce() and
k = op.getAKey()
select op, op.getCipherOperationSubtype(), a, a.getRawAlgorithmName(), m, m.getRawAlgorithmName(),
p, p.getRawAlgorithmName(), nonce, k, k.getSourceElement()
select op, op.getKeyOperationSubtype(), a, a.getRawAlgorithmName(), m, m.getRawAlgorithmName(), p,
p.getRawAlgorithmName(), nonce, k, k.getSourceElement()

View File

@@ -4,11 +4,11 @@
import experimental.Quantum.Language
from Crypto::CipherOperationNode op, Crypto::CipherAlgorithmNode a, Crypto::KeyArtifactNode k
from Crypto::KeyOperationNode op, Crypto::CipherAlgorithmNode a, Crypto::KeyArtifactNode k
where
a = op.getAKnownCipherAlgorithm() and
k = op.getAKey()
select op, op.getCipherOperationSubtype(), a, a.getRawAlgorithmName(), k, k.getSourceNode()
select op, op.getKeyOperationSubtype(), a, a.getRawAlgorithmName(), k, k.getSourceNode()
/*
* from Crypto::CipherOperationNode op
* where op.getLocation().getFile().getBaseName() = "AsymmetricEncryptionMacHybridCryptosystem.java"