Skip to content

Commit e2a8d58

Browse files
authored
Merge pull request #20583 from bdrodes/jca_signature_extensions
Crypto: Add JCA signatures, RNG, and unit tests
2 parents 9e278b9 + cb812b4 commit e2a8d58

33 files changed

+10600
-12
lines changed

cpp/ql/lib/experimental/quantum/OpenSSL/AlgorithmInstances/CipherAlgorithmInstance.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class KnownOpenSslCipherConstantAlgorithmInstance extends OpenSslAlgorithmInstan
119119
knownOpenSslConstantToCipherFamilyType(this, result)
120120
or
121121
not knownOpenSslConstantToCipherFamilyType(this, _) and
122-
result = Crypto::KeyOpAlg::TUnknownKeyOperationAlgorithmType()
122+
result = Crypto::KeyOpAlg::TOtherKeyOperationAlgorithmType()
123123
}
124124

125125
override OpenSslAlgorithmValueConsumer getAvc() { result = getterCall }

java/ql/lib/experimental/quantum/JCA.qll

Lines changed: 257 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ module JCAModel {
1818

1919
abstract class KeyAgreementAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
2020

21+
abstract class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
22+
2123
// TODO: Verify that the PBEWith% case works correctly
2224
bindingset[algo]
2325
predicate cipher_names(string algo) {
@@ -100,9 +102,21 @@ module JCAModel {
100102
].toUpperCase())
101103
}
102104

105+
/**
106+
* Names that match known signature algorithms.
107+
* https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
108+
*/
109+
bindingset[name]
110+
predicate signature_names(string name) {
111+
name.toUpperCase().splitAt("WITH", 1).matches(["RSA%", "ECDSA%", "DSA%"])
112+
or
113+
name.toUpperCase().matches(["RSASSA-PSS", "ED25519", "ED448", "EDDSA", "ML-DSA%", "HSS/LMS"])
114+
}
115+
103116
bindingset[name]
104117
predicate key_agreement_names(string name) {
105-
name.toUpperCase().matches(["DH", "EDH", "ECDH", "X25519", "X448"].toUpperCase())
118+
name.toUpperCase()
119+
.matches(["DH", "EDH", "ECDH", "X25519", "X448", "ML-KEM%", "XDH"].toUpperCase())
106120
}
107121

108122
bindingset[name]
@@ -208,13 +222,46 @@ module JCAModel {
208222
bindingset[name]
209223
predicate key_agreement_name_to_type_known(Crypto::TKeyAgreementType type, string name) {
210224
type = Crypto::DH() and
211-
name.toUpperCase() = "DH"
225+
name.toUpperCase() in ["DH", "XDH"]
212226
or
213227
type = Crypto::EDH() and
214228
name.toUpperCase() = "EDH"
215229
or
216230
type = Crypto::ECDH() and
217231
name.toUpperCase() in ["ECDH", "X25519", "X448"]
232+
or
233+
type = Crypto::OtherKeyAgreementType() and
234+
name.toUpperCase().matches("ML-KEM%")
235+
}
236+
237+
/**
238+
* Maps a signature algorithm name to its type, if known.
239+
* see https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
240+
*/
241+
bindingset[name]
242+
predicate signature_name_to_type_known(Crypto::KeyOpAlg::TAlgorithm type, string name) {
243+
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("RSA%") and
244+
type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
245+
or
246+
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("ECDSA%") and
247+
type = KeyOpAlg::TSignature(KeyOpAlg::ECDSA())
248+
or
249+
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("DSA%") and
250+
type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
251+
or
252+
name.toUpperCase() = "RSASSA-PSS" and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
253+
or
254+
name.toUpperCase().matches(["EDDSA", "ED25519", "ED448"]) and
255+
type = KeyOpAlg::TSignature(KeyOpAlg::EDDSA())
256+
or
257+
name.toUpperCase().matches("ML-DSA%") and type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
258+
or
259+
name.toUpperCase() = "HSS/LMS" and type = KeyOpAlg::TSignature(KeyOpAlg::HSS_LMS())
260+
}
261+
262+
bindingset[name]
263+
Crypto::HashType signature_name_to_hash_type_known(string name, int digestLength) {
264+
result = hash_name_to_type_known(name.splitAt("with", 0), digestLength)
218265
}
219266

220267
/**
@@ -345,7 +392,7 @@ module JCAModel {
345392
override KeyOpAlg::AlgorithmType getAlgorithmType() {
346393
if cipher_name_to_type_known(_, super.getAlgorithmName())
347394
then cipher_name_to_type_known(result, super.getAlgorithmName())
348-
else result instanceof KeyOpAlg::TUnknownKeyOperationAlgorithmType
395+
else result instanceof KeyOpAlg::TOtherKeyOperationAlgorithmType
349396
}
350397

351398
override int getKeySizeFixed() {
@@ -999,7 +1046,8 @@ module JCAModel {
9991046
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
10001047
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this or
10011048
result.(KeyAgreementStringLiteralAlgorithmInstance).getConsumer() = this or
1002-
result.(EllipticCurveStringLiteralInstance).getConsumer() = this
1049+
result.(EllipticCurveStringLiteralInstance).getConsumer() = this or
1050+
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
10031051
}
10041052

10051053
KeyGeneratorGetInstanceCall getInstantiationCall() { result = instantiationCall }
@@ -1047,6 +1095,21 @@ module JCAModel {
10471095
}
10481096
}
10491097

1098+
/**
1099+
* An instance of `java.security.SecureRandom.nextBytes(byte[])` call.
1100+
* This is already generally modeled for Java in CodeQL, but
1101+
* we model it again as part of the crypto API model to have a cohesive model.
1102+
*/
1103+
class JavaSecuritySecureRandom extends Crypto::RandomNumberGenerationInstance instanceof Call {
1104+
JavaSecuritySecureRandom() {
1105+
this.getCallee().hasQualifiedName("java.security", "SecureRandom", "nextBytes")
1106+
}
1107+
1108+
override Crypto::DataFlowNode getOutputNode() { result.asExpr() = this.(Call).getArgument(0) }
1109+
1110+
override string getGeneratorName() { result = this.(Call).getCallee().getName() }
1111+
}
1112+
10501113
class KeyGeneratorGenerateCall extends Crypto::KeyGenerationOperationInstance instanceof MethodCall
10511114
{
10521115
Crypto::KeyArtifactType type;
@@ -1624,6 +1687,196 @@ module JCAModel {
16241687
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
16251688
}
16261689

1690+
/**
1691+
* Signatures
1692+
*/
1693+
module SignatureKnownAlgorithmToConsumerConfig implements DataFlow::ConfigSig {
1694+
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SignatureStringLiteral }
1695+
1696+
predicate isSink(DataFlow::Node sink) {
1697+
sink = any(SignatureAlgorithmValueConsumer call).getInputNode()
1698+
}
1699+
}
1700+
1701+
module SignatureKnownAlgorithmToConsumerFlow =
1702+
TaintTracking::Global<SignatureKnownAlgorithmToConsumerConfig>;
1703+
1704+
class SignatureGetInstanceCall extends MethodCall {
1705+
SignatureGetInstanceCall() {
1706+
this.getCallee().hasQualifiedName("java.security", "Signature", "getInstance")
1707+
}
1708+
1709+
Expr getAlgorithmArg() { result = this.getArgument(0) }
1710+
}
1711+
1712+
class SignatureGetInstanceAlgorithmValueConsumer extends SignatureAlgorithmValueConsumer instanceof Expr
1713+
{
1714+
SignatureGetInstanceAlgorithmValueConsumer() {
1715+
this = any(SignatureGetInstanceCall c).getAlgorithmArg()
1716+
}
1717+
1718+
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
1719+
1720+
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
1721+
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
1722+
}
1723+
}
1724+
1725+
class SignatureStringLiteral extends StringLiteral {
1726+
SignatureStringLiteral() { signature_names(this.getValue()) }
1727+
}
1728+
1729+
class SignatureStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SignatureStringLiteral
1730+
{
1731+
SignatureAlgorithmValueConsumer consumer;
1732+
1733+
SignatureStringLiteralAlgorithmInstance() {
1734+
SignatureKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
1735+
}
1736+
1737+
SignatureAlgorithmValueConsumer getConsumer() { result = consumer }
1738+
1739+
override string getRawAlgorithmName() { result = super.getValue() }
1740+
1741+
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
1742+
if signature_name_to_type_known(_, super.getValue())
1743+
then signature_name_to_type_known(result, super.getValue())
1744+
else result = Crypto::KeyOpAlg::TOtherKeyOperationAlgorithmType()
1745+
}
1746+
1747+
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
1748+
// TODO: trace to any key size initializer?
1749+
none()
1750+
}
1751+
1752+
override int getKeySizeFixed() {
1753+
// TODO: are there known fixed key sizes to consider?
1754+
none()
1755+
}
1756+
1757+
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
1758+
1759+
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
1760+
}
1761+
1762+
class SignatureHashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof SignatureStringLiteralAlgorithmInstance
1763+
{
1764+
Crypto::THashType hashType;
1765+
int digestLength;
1766+
1767+
SignatureHashAlgorithmInstance() {
1768+
hashType = signature_name_to_hash_type_known(this.(StringLiteral).getValue(), digestLength)
1769+
}
1770+
1771+
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
1772+
1773+
override Crypto::THashType getHashFamily() { result = hashType }
1774+
1775+
override int getFixedDigestLength() { result = digestLength }
1776+
}
1777+
1778+
class SignatureInitCall extends MethodCall {
1779+
SignatureInitCall() {
1780+
this.getCallee().hasQualifiedName("java.security", "Signature", ["initSign", "initVerify"])
1781+
}
1782+
1783+
Expr getKeyArg() {
1784+
result = this.getArgument(0)
1785+
// TODO: verify can take in a certificate too?
1786+
}
1787+
}
1788+
1789+
private class SignatureOperationCall extends MethodCall {
1790+
SignatureOperationCall() {
1791+
this.getMethod().hasQualifiedName("java.security", "Signature", ["update", "sign", "verify"])
1792+
}
1793+
1794+
predicate isIntermediate() { super.getMethod().getName() = "update" }
1795+
1796+
Expr getMsgInput() { result = this.getArgument(0) and this.getMethod().getName() = "update" }
1797+
1798+
Expr getSignatureOutput() {
1799+
// no args, the signature is returned
1800+
result = this and this.getMethod().getName() = "sign" and not exists(this.getArgument(0))
1801+
or
1802+
// with args, the signature is written to the arg
1803+
result = this.getArgument(0) and this.getMethod().getName() = "sign"
1804+
}
1805+
1806+
Expr getSignatureInput() {
1807+
result = this.getArgument(0) and this.getMethod().getName() = "verify"
1808+
}
1809+
1810+
Crypto::KeyOperationSubtype getSubtype() {
1811+
result instanceof Crypto::TSignMode and this.getMethod().getName() = "sign"
1812+
or
1813+
result instanceof Crypto::TVerifyMode and this.getMethod().getName() = "verify"
1814+
}
1815+
}
1816+
1817+
class SignatureOperationInstance extends Crypto::SignatureOperationInstance instanceof SignatureOperationCall
1818+
{
1819+
SignatureOperationInstance() {
1820+
// exclude update (only include sign and verify)
1821+
not super.isIntermediate()
1822+
}
1823+
1824+
SignatureGetInstanceCall getInstantiationCall() {
1825+
result = SignatureFlowAnalysisImpl::getInstantiationFromUse(this, _, _)
1826+
}
1827+
1828+
SignatureInitCall getInitCall() {
1829+
result = SignatureFlowAnalysisImpl::getInitFromUse(this, _, _)
1830+
}
1831+
1832+
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
1833+
result.asExpr() = super.getMsgInput() or
1834+
result.asExpr() =
1835+
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getMsgInput()
1836+
}
1837+
1838+
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
1839+
result.asExpr() = this.getInitCall().getKeyArg()
1840+
}
1841+
1842+
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
1843+
result = this.getInstantiationCall().getAlgorithmArg()
1844+
}
1845+
1846+
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
1847+
result.asExpr() = super.getSignatureOutput() or
1848+
result.asExpr() =
1849+
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureOutput()
1850+
}
1851+
1852+
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
1853+
// TODO: RSASSA-PSS literal sets hashes differently, through a ParameterSpec
1854+
result = this.getInstantiationCall().getAlgorithmArg()
1855+
}
1856+
1857+
override predicate hasHashAlgorithmConsumer() {
1858+
// All jca signature algorithms specify a hash unless explicitly set as "NONEwith..."
1859+
exists(SignatureStringLiteralAlgorithmInstance i |
1860+
i.getConsumer() = this.getAnAlgorithmValueConsumer() and
1861+
not i.getRawAlgorithmName().toUpperCase().matches("NONEwith%".toUpperCase())
1862+
)
1863+
}
1864+
1865+
override Crypto::KeyOperationSubtype getKeyOperationSubtype() { result = super.getSubtype() }
1866+
1867+
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
1868+
1869+
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() {
1870+
result.asExpr() = super.getSignatureInput() or
1871+
result.asExpr() =
1872+
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureInput()
1873+
}
1874+
}
1875+
1876+
module SignatureFlowAnalysisImpl =
1877+
GetInstanceInitUseFlowAnalysis<SignatureGetInstanceCall, SignatureInitCall,
1878+
SignatureOperationCall>;
1879+
16271880
/*
16281881
* Elliptic Curves (EC)
16291882
*/

0 commit comments

Comments
 (0)