Crypto: Add modeling for JCA signatures. Make consistent use of "unknown" or "other" for unrecognized types.

This commit is contained in:
REDMOND\brodes
2025-10-03 12:07:37 -04:00
parent a46bd4c4ca
commit f1eb6511a7
2 changed files with 223 additions and 6 deletions

View File

@@ -18,6 +18,8 @@ module JCAModel {
abstract class KeyAgreementAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
abstract class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
// TODO: Verify that the PBEWith% case works correctly
bindingset[algo]
predicate cipher_names(string algo) {
@@ -100,6 +102,12 @@ module JCAModel {
].toUpperCase())
}
bindingset[name]
predicate signature_names(string name) {
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches(["RSA", "ECDSA", "DSA"])
// note RSASSA-PSS is RSA with PSS where the digest is set through PSSParameterSpec
}
bindingset[name]
predicate key_agreement_names(string name) {
name.toUpperCase().matches(["DH", "EDH", "ECDH", "X25519", "X448"].toUpperCase())
@@ -217,6 +225,25 @@ module JCAModel {
name.toUpperCase() in ["ECDH", "X25519", "X448"]
}
bindingset[name]
predicate signature_name_to_type_known(Crypto::KeyOpAlg::TAlgorithm type, string name) {
name.toUpperCase().splitAt("with".toUpperCase(), 1) = "RSA" and
type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
or
name.toUpperCase().splitAt("with".toUpperCase(), 1) = "ECDSA" and
type = KeyOpAlg::TSignature(KeyOpAlg::ECDSA())
or
name.toUpperCase().splitAt("with".toUpperCase(), 1) = "DSA" and
type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
or
name.toUpperCase().matches("RSASSA-PSS") and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
}
bindingset[name]
Crypto::HashType signature_name_to_hash_type_known(string name, int digestLength) {
result = hash_name_to_type_known(name.splitAt("with", 0), digestLength)
}
/**
* A `StringLiteral` in the `"ALG/MODE/PADDING"` or `"ALG"` format
*/
@@ -345,7 +372,7 @@ module JCAModel {
override KeyOpAlg::AlgorithmType getAlgorithmType() {
if cipher_name_to_type_known(_, super.getAlgorithmName())
then cipher_name_to_type_known(result, super.getAlgorithmName())
else result instanceof KeyOpAlg::TUnknownKeyOperationAlgorithmType
else result instanceof KeyOpAlg::TOtherKeyOperationAlgorithmType
}
override int getKeySizeFixed() {
@@ -1639,6 +1666,196 @@ module JCAModel {
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
}
/**
* Signatures
*/
module SignatureKnownAlgorithmToConsumerConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SignatureStringLiteral }
predicate isSink(DataFlow::Node sink) {
sink = any(SignatureAlgorithmValueConsumer call).getInputNode()
}
}
module SignatureKnownAlgorithmToConsumerFlow =
TaintTracking::Global<SignatureKnownAlgorithmToConsumerConfig>;
class SignatureGetInstanceCall extends MethodCall {
SignatureGetInstanceCall() {
this.getCallee().hasQualifiedName("java.security", "Signature", "getInstance")
}
Expr getAlgorithmArg() { result = this.getArgument(0) }
}
class SignatureGetInstanceAlgorithmValueConsumer extends SignatureAlgorithmValueConsumer instanceof Expr
{
SignatureGetInstanceAlgorithmValueConsumer() {
this = any(SignatureGetInstanceCall c).getAlgorithmArg()
}
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
}
}
class SignatureStringLiteral extends StringLiteral {
SignatureStringLiteral() { signature_names(this.getValue()) }
}
class SignatureStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SignatureStringLiteral
{
SignatureAlgorithmValueConsumer consumer;
SignatureStringLiteralAlgorithmInstance() {
SignatureKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
}
SignatureAlgorithmValueConsumer getConsumer() { result = consumer }
override string getRawAlgorithmName() { result = super.getValue() }
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
if signature_name_to_type_known(_, super.getValue())
then signature_name_to_type_known(result, super.getValue())
else result = Crypto::KeyOpAlg::TOtherKeyOperationAlgorithmType()
}
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
// TODO: trace to any key size initializer?
none()
}
override int getKeySizeFixed() {
// TODO: are there known fixed key sizes to consider?
none()
}
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
}
class SignatureHashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof SignatureStringLiteralAlgorithmInstance
{
Crypto::THashType hashType;
int digestLength;
SignatureHashAlgorithmInstance() {
hashType = signature_name_to_hash_type_known(this.(StringLiteral).getValue(), digestLength)
}
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
override Crypto::THashType getHashFamily() { result = hashType }
override int getFixedDigestLength() { result = digestLength }
}
class SignatureInitCall extends MethodCall {
SignatureInitCall() {
this.getCallee().hasQualifiedName("java.security", "Signature", ["initSign", "initVerify"])
}
Expr getKeyArg() {
result = this.getArgument(0)
// TODO: verify can take in a certificate too?
}
}
private class SignatureOperationCall extends MethodCall {
SignatureOperationCall() {
this.getMethod().hasQualifiedName("java.security", "Signature", ["update", "sign", "verify"])
}
predicate isIntermediate() { super.getMethod().getName() = "update" }
Expr getMsgInput() { result = this.getArgument(0) and this.getMethod().getName() = "update" }
Expr getSignatureOutput() {
// no args, the signature is returned
result = this and this.getMethod().getName() = "sign" and not exists(this.getArgument(0))
or
// with args, the signature is written to the arg
result = this.getArgument(0) and this.getMethod().getName() = "sign"
}
Expr getSignatureInput() {
result = this.getArgument(0) and this.getMethod().getName() = "verify"
}
Crypto::KeyOperationSubtype getSubtype() {
result instanceof Crypto::TSignMode and this.getMethod().getName() = "sign"
or
result instanceof Crypto::TVerifyMode and this.getMethod().getName() = "verify"
}
}
class SignatureOperationinstance extends Crypto::SignatureOperationInstance instanceof SignatureOperationCall
{
SignatureOperationinstance() {
// exclude update (only include sign and verify)
not super.isIntermediate()
}
SignatureGetInstanceCall getInstantiationCall() {
result = SignatureFlowAnalysisImpl::getInstantiationFromUse(this, _, _)
}
SignatureInitCall getInitCall() {
result = SignatureFlowAnalysisImpl::getInitFromUse(this, _, _)
}
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
result.asExpr() = super.getMsgInput() or
result.asExpr() =
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getMsgInput()
}
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
result.asExpr() = this.getInitCall().getKeyArg()
}
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
result = this.getInstantiationCall().getAlgorithmArg()
}
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
result.asExpr() = super.getSignatureOutput() or
result.asExpr() =
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureOutput()
}
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
// TODO: RSASSA-PSS literal sets hashes differently, through a ParameterSpec
result = this.getInstantiationCall().getAlgorithmArg()
}
override predicate hasHashAlgorithmConsumer() {
// All jca signature algorithms specify a hash unless explicitly set as "NONEwith..."
exists(SignatureStringLiteralAlgorithmInstance i |
i.getConsumer() = this.getAnAlgorithmValueConsumer() and
not i.getRawAlgorithmName().toUpperCase().matches("NONEwith%".toUpperCase())
)
}
override Crypto::KeyOperationSubtype getKeyOperationSubtype() { result = super.getSubtype() }
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() {
result.asExpr() = super.getSignatureInput() or
result.asExpr() =
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureInput()
}
}
module SignatureFlowAnalysisImpl =
GetInstanceInitUseFlowAnalysis<SignatureGetInstanceCall, SignatureInitCall,
SignatureOperationCall>;
/*
* Elliptic Curves (EC)
*/

View File

@@ -16,7 +16,7 @@ module Types {
TSignature(TSignatureAlgorithmType t) or
TMac(TMacAlgorithmType t) or
TKeyEncapsulation(TKemAlgorithmType t) or
TUnknownKeyOperationAlgorithmType()
TOtherKeyOperationAlgorithmType()
// Parameterized algorithm types
newtype TSymmetricCipherType =
@@ -64,7 +64,7 @@ module Types {
newtype TCipherStructureType =
Block() or
Stream() or
UnknownCipherStructureType()
OtherCipherStructureType()
class CipherStructureType extends TCipherStructureType {
string toString() {
@@ -72,7 +72,7 @@ module Types {
or
result = "Stream" and this = Stream()
or
result = "Unknown" and this = UnknownCipherStructureType()
result = "Unknown" and this = OtherCipherStructureType()
}
}
@@ -119,7 +119,7 @@ module Types {
or
type = OtherSymmetricCipherType() and
name = "UnknownSymmetricCipher" and
s = UnknownCipherStructureType()
s = OtherCipherStructureType()
}
class AlgorithmType extends TAlgorithm {
@@ -157,7 +157,7 @@ module Types {
this = TMac(OtherMacAlgorithmType()) and result = "UnknownMac"
or
// Unknown
this = TUnknownKeyOperationAlgorithmType() and result = "Unknown"
this = TOtherKeyOperationAlgorithmType() and result = "Unknown"
}
int getImplicitKeySize() {