Adding a new means for determining if there is nonce reuse.

This commit is contained in:
REDMOND\brodes
2025-04-28 11:34:16 -04:00
parent 28ccc83346
commit fdd09a4dbf
3 changed files with 82 additions and 36 deletions

View File

@@ -0,0 +1,70 @@
import java
import semmle.code.java.dataflow.DataFlow
import experimental.Quantum.Language
/**
* Flow from any function that appears to return a value
* to an artifact node.
* NOTE: TODO: need to handle call by refernece for now. Need to re-evaluate (see notes below)
* Such functions may be 'wrappers' for some derived value.
*/
private module WrapperConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(Call c |
c = source.asExpr()
// not handling references yet, I think we want to flat say references are only ok
// if I know the source, otherwise, it has to be through an additional flow step, which
// we filter as a source, i.e., references are only allowed as sources only,
// no inferrece? Not sure if that would work
//or
// source.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() = c.getAnArgument()
) and
// Filter out sources that are known additional flow steps, as these are likely not the
// kind of wrapper source we are looking for.
not exists(AdditionalFlowInputStep s | s.getOutput() = source)
}
// Flow through additional flow steps
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
node1.(AdditionalFlowInputStep).getOutput() = node2
}
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(Crypto::ArtifactNode i).asElement() }
}
module WrapperFlow = DataFlow::Global<WrapperConfig>;
/**
* Using a set approach to determine if reuse of an artifact exists.
* This predicate produces a set of 'wrappers' that flow to the artifact node.
* This set can be compared with the set to another artifact node to determine if they are the same.
*/
private DataFlow::Node getWrapperSet(Crypto::NonceArtifactNode a) {
WrapperFlow::flow(result, DataFlow::exprNode(a.asElement()))
or
result.asExpr() = a.getSourceElement()
}
/**
* Two different artifact nodes are considered reuse if any of the following conditions are met:
* 1. The source for artifact `a` and artifact `b` are the same and the source is a literal.
* 2. The source for artifact `a` and artifact `b` are not the same and the source is a literal of the same value.
* 3. For all 'wrappers' that return the source of artifact `a`, and that wrapper also exists for artifact `b`.
* 4. For all 'wrappers' that return the source of artifact `b`, and that wrapper also exists for artifact `a`.
*/
predicate isArtifactReuse(Crypto::ArtifactNode a, Crypto::ArtifactNode b) {
a != b and
(
a.getSourceElement() = b.getSourceElement() and a.getSourceElement() instanceof Literal
or
a.getSourceElement().(Literal).getValue() = b.getSourceElement().(Literal).getValue()
or
forex(DataFlow::Node e | e = getWrapperSet(a) |
exists(DataFlow::Node e2 | e2 = getWrapperSet(b) | e = e2)
)
or
forex(DataFlow::Node e | e = getWrapperSet(b) |
exists(DataFlow::Node e2 | e2 = getWrapperSet(a) | e = e2)
)
)
}

View File

@@ -0,0 +1,12 @@
/**
* @name Detects reuse of the same nonce in multiple operations
* @id java/crypto_inventory_filter/nonce_reuse
* @kind problem
*/
import java
import ArtifactReuse
from Crypto::NonceArtifactNode nonce1, Crypto::NonceArtifactNode nonce2
where isArtifactReuse(nonce1, nonce2)
select nonce1, "Reuse with nonce $@", nonce2, nonce2.toString()

View File

@@ -1,36 +0,0 @@
/**
* @name Possible Nonce Reuse: Produces false positives if reuse occurs in a source that is a re-entry point.
* @id java/possible-nonce-reuse
* @kind problem
*/
import experimental.Quantum.Language
import semmle.code.java.dataflow.DataFlow
from
Crypto::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.
// Only looking for encryption operations, presumably if reuse for decryption either wouldn't be observable
// (the encryption happened else where) or we are able to see the encryption and decryption operation and
// reuse for encryption is the concern)
(
op1.getKeyOperationSubtype() instanceof Crypto::EncryptionSubtype or
op1.getKeyOperationSubtype() instanceof Crypto::WrapSubtype or
op1.getKeyOperationSubtype() instanceof Crypto::UnknownCipherOperationSubtype
) and
(
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
op1 != op2 and
nonce1.getSourceElement() = src1 and
nonce2.getSourceElement() = src2 and
src1 = src2
// TODO: need to clarify that a reuse in a non-finalize is ok, need to check if 'finalize' through a modeled predicate
select op1, "Operation has a possible reused nonce with source $@", src1, src1.toString()