Refactor instances and consumers + add JCA hashes

This commit is contained in:
Nicolas Will
2025-03-18 22:05:00 +01:00
parent 8a7671dc2a
commit 95607c5f31
10 changed files with 475 additions and 167 deletions

View File

@@ -205,7 +205,7 @@ module JCAModel {
*
* For example, in `Cipher.getInstance(algorithm)`, this class represents `algorithm`.
*/
class CipherGetInstanceAlgorithmArg extends Crypto::AlgorithmConsumer instanceof Expr {
class CipherGetInstanceAlgorithmArg extends Crypto::AlgorithmValueConsumer instanceof Expr {
CipherGetInstanceCall call;
CipherGetInstanceAlgorithmArg() { this = call.getAlgorithmArg() }
@@ -218,7 +218,7 @@ module JCAModel {
value = result.getValue()
}
override Crypto::AlgorithmElement getAKnownAlgorithmSource() {
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this
}
}
@@ -354,15 +354,17 @@ module JCAModel {
override Crypto::CipherOperationSubtype getCipherOperationSubtype() { result = mode }
override Crypto::NonceArtifactConsumer getNonceConsumer() {
result = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg()
override DataFlow::Node getNonceConsumer() {
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getNonceArg()
}
override Crypto::CipherInputConsumer getInputConsumer() {
result = doFinalize.getMessageArg().asExpr()
override DataFlow::Node getInputConsumer() { result = doFinalize.getMessageArg() }
override DataFlow::Node getKeyConsumer() {
result.asExpr() = sink.getState().(InitializedCipherModeFlowState).getInitCall().getKeyArg()
}
override Crypto::AlgorithmConsumer getAlgorithmConsumer() { result = consumer }
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() { result = consumer }
override Crypto::CipherOutputArtifactInstance getOutputArtifact() {
result = doFinalize.getOutput()
@@ -493,27 +495,114 @@ module JCAModel {
}
}
class CipherInitCallNonceArgConsumer extends Crypto::NonceArtifactConsumer instanceof Expr {
CipherInitCallNonceArgConsumer() { this = any(CipherInitCall call).getNonceArg() }
override DataFlow::Node getInputNode() { result.asExpr() = this }
}
class CipherInitCallKeyConsumer extends Crypto::ArtifactConsumer {
CipherInitCallKeyConsumer() { this = any(CipherInitCall call).getKeyArg() }
override DataFlow::Node getInputNode() { result.asExpr() = this }
}
class CipherMessageInputConsumer extends Crypto::CipherInputConsumer {
CipherMessageInputConsumer() { this = any(CipherOperationCall call).getMessageArg().asExpr() }
override DataFlow::Node getInputNode() { result.asExpr() = this }
}
class CipherOperationCallOutput extends CipherOutputArtifact {
CipherOperationCallOutput() { this = any(CipherOperationCall call).getOutput() }
override DataFlow::Node getOutputNode() { result.asExpr() = this }
}
bindingset[hash]
predicate hash_names(string hash) {
hash.toUpperCase()
.matches([
"SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-224", "SHA3-256", "SHA3-384",
"SHA3-512", "BLAKE2b", "BLAKE2s"
].toUpperCase())
}
// 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 Crypto::HashAlgorithmInstance instanceof StringLiteral {
MessageDigestAlgorithmValueConsumer consumer;
KnownHashAlgorithm() {
hash_names(this.getValue()) and
KnownHashAlgorithmLiteralToMessageDigestFlow::flow(DataFlow::exprNode(this),
consumer.getInputNode())
}
MessageDigestAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawAlgorithmName() { result = this.(StringLiteral).getValue() }
override Crypto::THashType getHashFamily() {
result = Crypto::OtherHashType() // TODO
}
}
class MessageDigestAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer {
MessageDigestGetInstanceCall call;
MessageDigestAlgorithmValueConsumer() { this = call.getAlgorithmArg() }
override DataFlow::Node 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 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()
)
}
}
}

View File

@@ -25,6 +25,11 @@ module CryptoInput implements InputSig<Language::Location> {
class LocatableElement = Language::Element;
class UnknownLocation = UnknownDefaultLocation;
LocatableElement dfn_to_element(DataFlow::Node node) {
result = node.asExpr() or
result = node.asParameter()
}
}
/**
@@ -100,6 +105,17 @@ class InsecureRandomnessInstance extends RandomnessInstance {
InsecureRandomnessInstance() { exists(InsecureRandomnessSource node | this = node.asExpr()) }
}
/**
* Output artifact flow logic
*/
abstract class DigestArtifactInstance extends Crypto::DigestArtifactInstance {
override predicate flowsTo(Crypto::FlowAwareElement other) {
ArtifactUniversalFlow::flow(this.getOutputNode(), other.getInputNode())
}
override predicate isConsumerArtifact() { none() }
}
/**
* Artifact output to node input configuration
*/
@@ -115,6 +131,8 @@ abstract class CipherOutputArtifact extends Crypto::CipherOutputArtifactInstance
override predicate flowsTo(Crypto::FlowAwareElement other) {
ArtifactUniversalFlow::flow(this.getOutputNode(), other.getInputNode())
}
override predicate isConsumerArtifact() { none() }
}
/**
@@ -144,7 +162,7 @@ module GenericDataSourceUniversalFlowConfig implements DataFlow::ConfigSig {
module ArtifactUniversalFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source = any(Crypto::ArtifactElement artifact).getOutputNode()
source = any(Crypto::ArtifactInstance artifact).getOutputNode()
}
predicate isSink(DataFlow::Node sink) {

View File

@@ -6,17 +6,19 @@
import experimental.Quantum.Language
from Crypto::NonceNode n, Crypto::CipherOperationNode op, Crypto::FlowAwareElement src, string msg
from
Crypto::NonceArtifactNode n, Crypto::CipherOperationNode 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.
// as possibly encryption.
(
op.getCipherOperationSubtype() instanceof Crypto::EncryptionSubtype
or
op.getCipherOperationSubtype() instanceof Crypto::WrapSubtype
or
op.getCipherOperationSubtype() instanceof Crypto::UnwrapSubtype
op.getCipherOperationSubtype() instanceof Crypto::UnwrapSubtype
) and
(
// Known sources cases that are not secure
@@ -30,7 +32,6 @@ where
src = n.asElement()
)
select n, msg, src, src.toString()
// variant using instances, does not yield the same results
// from Crypto::NonceArtifactConsumer n, Crypto::CipherOperationInstance op, Crypto::FlowAwareElement src, string msg
// where

View File

@@ -1,14 +1,16 @@
/**
* @name Possible Nonce Reuse: Produces false positives if reuse occurs in a source that is a re-entry point.
* @id java/possible-nonce-reuse
* @kind problem
*/
import experimental.Quantum.Language
import semmle.code.java.dataflow.DataFlow
from
Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2, Crypto::NonceNode nonce1,
Crypto::NonceNode nonce2, Crypto::FlowAwareElement src1, Crypto::FlowAwareElement src2
Crypto::CipherOperationNode op1, Crypto::CipherOperationNode op2,
Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2, Crypto::FlowAwareElement src1,
Crypto::FlowAwareElement src2
where
// NOTE: not looking at value of the nonce, if we knew value, it would be insecure (hard coded)
// Instead trying to find nonce sources that trace to multiple operations.

View File

@@ -6,7 +6,8 @@ import experimental.Quantum.Language
from
Crypto::CipherOperationNode op, Crypto::CipherAlgorithmNode a,
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p, Crypto::NonceNode nonce
Crypto::ModeOfOperationAlgorithmNode m, Crypto::PaddingAlgorithmNode p,
Crypto::NonceArtifactNode nonce
where
a = op.getAKnownCipherAlgorithm() and
m = a.getModeOfOperation() and

View File

@@ -0,0 +1,9 @@
/**
* @name TestHashOperations
*/
import experimental.Quantum.Language
from Crypto::HashOperationNode op, Crypto::HashAlgorithmNode alg
where alg = op.getAKnownHashAlgorithm()
select op, op.getDigest(), alg, alg.getRawAlgorithmName()