Skip to content

Commit 9176564

Browse files
committed
Add ECMQV, PSS, MFG1 logic, fix tests
1 parent 4c23111 commit 9176564

9 files changed

Lines changed: 1111 additions & 356 deletions

File tree

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

Lines changed: 279 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ module JCAModel {
107107
bindingset[name]
108108
predicate key_agreement_names(string name) {
109109
name.toUpperCase()
110-
.matches(["DH", "EDH", "ECDH", "X25519", "X448", "ML-KEM%", "XDH"].toUpperCase())
110+
.matches(["DH", "EDH", "ECDH", "ECMQV", "X25519", "X448", "ML-KEM%", "XDH"].toUpperCase())
111111
}
112112

113113
bindingset[name]
@@ -265,6 +265,9 @@ module JCAModel {
265265
type = Crypto::ECDH() and
266266
name.toUpperCase() in ["ECDH", "X25519", "X448", "XDH"]
267267
or
268+
type = Crypto::ECMQV() and
269+
name.toUpperCase() = "ECMQV"
270+
or
268271
type = Crypto::OtherKeyAgreementType() and
269272
name.toUpperCase().matches("ML-KEM%")
270273
}
@@ -1876,7 +1879,16 @@ module JCAModel {
18761879

18771880
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
18781881

1879-
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
1882+
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() {
1883+
result = this
1884+
}
1885+
1886+
override predicate shouldHaveModeOfOperation() { none() }
1887+
1888+
override predicate shouldHavePaddingScheme() {
1889+
// Only RSA-based signatures have a meaningful padding concept (PSS or PKCS1v1.5)
1890+
signature_name_to_type_known(KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA()), super.getValue())
1891+
}
18801892
}
18811893

18821894
class SignatureHashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof SignatureStringLiteralAlgorithmInstance
@@ -1895,6 +1907,201 @@ module JCAModel {
18951907
override int getFixedDigestLength() { result = digestLength }
18961908
}
18971909

1910+
/**
1911+
* Determines if a signature algorithm name implies PSS padding.
1912+
*/
1913+
bindingset[name]
1914+
private predicate signatureImpliesPss(string name) {
1915+
name.toUpperCase().matches("%RSASSA-PSS%") or
1916+
name.toUpperCase().matches("%WITHRSA%MGF1%") or
1917+
name.toUpperCase().matches("%WITHRSA/PSS%")
1918+
}
1919+
1920+
/**
1921+
* Base class for PSS padding derived from signature algorithm names.
1922+
* Provides getPaddingType() on PaddingAlgorithmInstance to break the non-monotonic
1923+
* recursion that would occur if the derived PssPaddingAlgorithmInstance class
1924+
* defined getPaddingType() itself (since PssPaddingAlgorithmInstance's charpred
1925+
* calls getPaddingType()).
1926+
* Follows the same two-class pattern used for OAEP:
1927+
* CipherStringLiteralPaddingAlgorithmInstance → OaepPaddingAlgorithmInstance.
1928+
*/
1929+
private class SignaturePssPaddingBase extends SignatureStringLiteralAlgorithmInstance,
1930+
Crypto::PaddingAlgorithmInstance instanceof SignatureStringLiteral
1931+
{
1932+
SignaturePssPaddingBase() { signatureImpliesPss(super.getValue()) }
1933+
1934+
override string getRawPaddingAlgorithmName() { result = "PSS" }
1935+
1936+
override KeyOpAlg::PaddingSchemeType getPaddingType() { result instanceof KeyOpAlg::PSS }
1937+
}
1938+
1939+
/**
1940+
* A PSS padding algorithm instance derived from a signature algorithm literal.
1941+
* Extends PssPaddingAlgorithmInstance (whose charpred evaluates through
1942+
* SignaturePssPaddingBase.getPaddingType()) to produce MD and MGF1Hash edges.
1943+
*
1944+
* For name-implied PSS (e.g., "SHA256withRSAandMGF1"), the same literal element
1945+
* is also a SignatureHashAlgorithmInstance, so `result = this` yields the hash.
1946+
* For bare "RSASSA-PSS", `result = this` has no result (this is not a
1947+
* HashAlgorithmInstance), so the graph falls back to self-referencing (unknown).
1948+
* When a PSSParameterSpec is connected via setParameter(), the explicit hash
1949+
* from the spec is used instead.
1950+
*/
1951+
class SignaturePssPaddingAlgorithmInstance extends Crypto::PssPaddingAlgorithmInstance,
1952+
SignaturePssPaddingBase instanceof SignatureStringLiteral
1953+
{
1954+
override Crypto::HashAlgorithmInstance getHashAlgorithm() {
1955+
// Name-implied hash (e.g., SHA256withRSAandMGF1 → SHA-256)
1956+
result = this
1957+
or
1958+
// Explicit PSS hash from PSSParameterSpec via Signature.setParameter()
1959+
exists(PSSParameterSpecInstantiation spec |
1960+
pssSpecForSignatureLiteral(spec, this) and
1961+
result.(PSSParameterSpecDigestHashAlgorithmInstance).getSpec() = spec
1962+
)
1963+
}
1964+
1965+
override Crypto::HashAlgorithmInstance getMgf1HashAlgorithm() {
1966+
// Name-implied MGF1 hash (defaults to same hash as digest)
1967+
result = this
1968+
or
1969+
// Explicit MGF1 hash from PSSParameterSpec via Signature.setParameter()
1970+
exists(PSSParameterSpecInstantiation spec |
1971+
pssSpecForSignatureLiteral(spec, this) and
1972+
result.(PSSParameterSpecMgf1HashAlgorithmInstance).getSpec() = spec
1973+
)
1974+
}
1975+
}
1976+
1977+
/**
1978+
* A PSSParameterSpec instantiation, e.g.,
1979+
* new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)
1980+
*/
1981+
class PSSParameterSpecInstantiation extends ClassInstanceExpr {
1982+
PSSParameterSpecInstantiation() {
1983+
this.getConstructedType().hasQualifiedName("java.security.spec", "PSSParameterSpec")
1984+
}
1985+
1986+
/** Gets the digest algorithm name argument (arg 0). */
1987+
Expr getDigestAlgorithmArg() {
1988+
result = this.getArgument(0)
1989+
}
1990+
1991+
/** Gets the MGF algorithm name argument (arg 1). */
1992+
Expr getMgfAlgorithmArg() {
1993+
result = this.getArgument(1)
1994+
}
1995+
1996+
/** Gets the salt length argument (arg 3). */
1997+
Expr getSaltLengthArg() {
1998+
result = this.getArgument(3)
1999+
}
2000+
2001+
/** Gets the MGF parameter spec argument (arg 2), e.g., MGF1ParameterSpec.SHA256. */
2002+
Expr getMgfSpecArg() {
2003+
result = this.getArgument(2)
2004+
}
2005+
}
2006+
2007+
/**
2008+
* A static field access on `java.security.spec.MGF1ParameterSpec`, e.g.,
2009+
* `MGF1ParameterSpec.SHA256`. These fields represent well-known MGF1 hash
2010+
* algorithm configurations.
2011+
*/
2012+
class MGF1ParameterSpecFieldAccess extends FieldAccess {
2013+
MGF1ParameterSpecFieldAccess() {
2014+
this.getField()
2015+
.getDeclaringType()
2016+
.hasQualifiedName("java.security.spec", "MGF1ParameterSpec") and
2017+
this.getField().isStatic()
2018+
}
2019+
2020+
/** Gets the hash algorithm name corresponding to this MGF1 field. */
2021+
string getHashAlgorithmName() {
2022+
this.getField().getName() = "SHA1" and result = "SHA-1"
2023+
or
2024+
this.getField().getName() = "SHA224" and result = "SHA-224"
2025+
or
2026+
this.getField().getName() = "SHA256" and result = "SHA-256"
2027+
or
2028+
this.getField().getName() = "SHA384" and result = "SHA-384"
2029+
or
2030+
this.getField().getName() = "SHA512" and result = "SHA-512"
2031+
or
2032+
this.getField().getName() = "SHA512_224" and result = "SHA-512/224"
2033+
or
2034+
this.getField().getName() = "SHA512_256" and result = "SHA-512/256"
2035+
}
2036+
}
2037+
2038+
/**
2039+
* A hash algorithm instance for the digest algorithm argument (arg 0) of a
2040+
* PSSParameterSpec instantiation, e.g., "SHA-256" in:
2041+
* new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)
2042+
*
2043+
* Type resolution delegates to hash_name_to_type_known from Standardization.
2044+
*/
2045+
class PSSParameterSpecDigestHashAlgorithmInstance extends Crypto::HashAlgorithmInstance
2046+
instanceof JavaConstant
2047+
{
2048+
PSSParameterSpecInstantiation spec;
2049+
2050+
PSSParameterSpecDigestHashAlgorithmInstance() {
2051+
this = spec.getDigestAlgorithmArg() and
2052+
// Only instantiate when the value resolves to a known hash type
2053+
exists(hash_name_to_type_known(super.getValue(), _))
2054+
}
2055+
2056+
/** Gets the PSSParameterSpec this digest hash belongs to. */
2057+
PSSParameterSpecInstantiation getSpec() { result = spec }
2058+
2059+
override string getRawHashAlgorithmName() { result = super.getValue() }
2060+
2061+
override Crypto::THashType getHashType() {
2062+
result = hash_name_to_type_known(super.getValue(), _)
2063+
}
2064+
2065+
override int getFixedDigestLength() {
2066+
exists(hash_name_to_type_known(super.getValue(), result))
2067+
}
2068+
}
2069+
2070+
/**
2071+
* A hash algorithm instance for the MGF1 parameter spec argument (arg 2) of a
2072+
* PSSParameterSpec instantiation, e.g., MGF1ParameterSpec.SHA256 in:
2073+
* new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)
2074+
*
2075+
* The field name is normalized to a standard hash algorithm name (e.g.,
2076+
* SHA256 -> SHA-256), then type resolution delegates to hash_name_to_type_known.
2077+
*/
2078+
class PSSParameterSpecMgf1HashAlgorithmInstance extends Crypto::HashAlgorithmInstance
2079+
instanceof MGF1ParameterSpecFieldAccess
2080+
{
2081+
PSSParameterSpecInstantiation spec;
2082+
string normalizedName;
2083+
2084+
PSSParameterSpecMgf1HashAlgorithmInstance() {
2085+
this = spec.getMgfSpecArg() and
2086+
normalizedName = super.getHashAlgorithmName() and
2087+
// Only instantiate when the normalized name resolves to a known hash type
2088+
exists(hash_name_to_type_known(normalizedName, _))
2089+
}
2090+
2091+
/** Gets the PSSParameterSpec this MGF1 hash belongs to. */
2092+
PSSParameterSpecInstantiation getSpec() { result = spec }
2093+
2094+
override string getRawHashAlgorithmName() { result = super.getField().getName() }
2095+
2096+
override Crypto::THashType getHashType() {
2097+
result = hash_name_to_type_known(normalizedName, _)
2098+
}
2099+
2100+
override int getFixedDigestLength() {
2101+
exists(hash_name_to_type_known(normalizedName, result))
2102+
}
2103+
}
2104+
18982105
class SignatureInitCall extends MethodCall {
18992106
SignatureInitCall() {
19002107
this.getCallee().hasQualifiedName("java.security", "Signature", ["initSign", "initVerify"])
@@ -1906,6 +2113,21 @@ module JCAModel {
19062113
}
19072114
}
19082115

2116+
/**
2117+
* A call to `Signature.setParameter(AlgorithmParameterSpec)`, used to
2118+
* configure algorithm parameters such as PSSParameterSpec on a Signature instance.
2119+
*/
2120+
class SignatureSetParameterCall extends MethodCall {
2121+
SignatureSetParameterCall() {
2122+
this.getMethod()
2123+
.hasQualifiedName("java.security", "Signature", "setParameter") and
2124+
this.getMethod().getParameterType(0).(RefType).hasQualifiedName("java.security.spec", "AlgorithmParameterSpec")
2125+
}
2126+
2127+
/** Gets the AlgorithmParameterSpec argument. */
2128+
Expr getParameterSpecArg() { result = this.getArgument(0) }
2129+
}
2130+
19092131
class SignatureOperationCall extends MethodCall {
19102132
SignatureOperationCall() {
19112133
this.getMethod().hasQualifiedName("java.security", "Signature", ["update", "sign", "verify"])
@@ -1970,7 +2192,6 @@ module JCAModel {
19702192
}
19712193

19722194
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
1973-
// TODO: RSASSA-PSS literal sets hashes differently, through a ParameterSpec
19742195
result = this.getInstantiationCall().getAlgorithmArg()
19752196
}
19762197

@@ -1997,6 +2218,61 @@ module JCAModel {
19972218
GetInstanceInitUseFlowAnalysis<SignatureGetInstanceCall, SignatureInitCall,
19982219
SignatureOperationCall>;
19992220

2221+
/**
2222+
* Flow from `Signature.getInstance()` return value to `Signature.setParameter()` qualifier.
2223+
* Used to connect a signature algorithm literal to its PSSParameterSpec configuration.
2224+
*/
2225+
module SignatureToSetParameterConfig implements DataFlow::ConfigSig {
2226+
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SignatureGetInstanceCall }
2227+
2228+
predicate isSink(DataFlow::Node sink) {
2229+
exists(SignatureSetParameterCall c | sink.asExpr() = c.getQualifier())
2230+
}
2231+
}
2232+
2233+
module SignatureToSetParameterFlow = DataFlow::Global<SignatureToSetParameterConfig>;
2234+
2235+
/**
2236+
* Flow from `PSSParameterSpec` instantiation to `Signature.setParameter()` argument.
2237+
*/
2238+
module PSSSpecToSetParameterConfig implements DataFlow::ConfigSig {
2239+
predicate isSource(DataFlow::Node src) {
2240+
src.asExpr() instanceof PSSParameterSpecInstantiation
2241+
}
2242+
2243+
predicate isSink(DataFlow::Node sink) {
2244+
exists(SignatureSetParameterCall c | sink.asExpr() = c.getParameterSpecArg())
2245+
}
2246+
}
2247+
2248+
module PSSSpecToSetParameterFlow = DataFlow::Global<PSSSpecToSetParameterConfig>;
2249+
2250+
/**
2251+
* Connects a PSSParameterSpec instantiation to the signature PSS padding literal
2252+
* for which it provides configuration, via `Signature.setParameter()`.
2253+
*
2254+
* The connection requires:
2255+
* 1. The padding literal flows (via its consumer) to a `Signature.getInstance()` call
2256+
* 2. That getInstance call flows to a `Signature.setParameter()` qualifier
2257+
* 3. The PSSParameterSpec flows to the same setParameter's argument
2258+
*/
2259+
private predicate pssSpecForSignatureLiteral(
2260+
PSSParameterSpecInstantiation spec, SignaturePssPaddingAlgorithmInstance literal
2261+
) {
2262+
exists(
2263+
SignatureSetParameterCall setParam,
2264+
SignatureGetInstanceCall getInstance,
2265+
SignatureGetInstanceAlgorithmValueConsumer consumer
2266+
|
2267+
consumer = literal.getConsumer() and
2268+
consumer = getInstance.getAlgorithmArg() and
2269+
SignatureToSetParameterFlow::flow(DataFlow::exprNode(getInstance),
2270+
DataFlow::exprNode(setParam.getQualifier())) and
2271+
PSSSpecToSetParameterFlow::flow(DataFlow::exprNode(spec),
2272+
DataFlow::exprNode(setParam.getParameterSpecArg()))
2273+
)
2274+
}
2275+
20002276
/*
20012277
* Elliptic Curves (EC)
20022278
*/

java/ql/test/experimental/library-tests/quantum/jca/KeyExchange.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,40 @@ public byte[] deriveX448Secret(PrivateKey privateKey, PublicKey publicKey) throw
214214
}
215215

216216
//////////////////////////////////////////
217-
// 5. Nuanced Insecure Key Exchange Example
217+
// 5. ECMQV (Elliptic Curve Menezes-Qu-Vanstone)
218+
//////////////////////////////////////////
219+
220+
/**
221+
* Generates an ECMQV key pair.
222+
*
223+
* CBOM/SAST Classification:
224+
* - Parent: Elliptic Curve Key Agreement (ECMQV).
225+
* - ECMQV is quantum-vulnerable.
226+
*
227+
* @return An ECMQV KeyPair.
228+
*/
229+
public KeyPair generateECMQVKeyPair() throws Exception {
230+
KeyPairGenerator ecmqvKpg = KeyPairGenerator.getInstance("ECMQV");
231+
ecmqvKpg.initialize(256);
232+
return ecmqvKpg.generateKeyPair();
233+
}
234+
235+
/**
236+
* Derives a shared secret using the ECMQV key agreement.
237+
*
238+
* @param privateKey The ECMQV private key.
239+
* @param publicKey The corresponding public key.
240+
* @return The derived ECMQV shared secret.
241+
*/
242+
public byte[] deriveECMQVSecret(PrivateKey privateKey, PublicKey publicKey) throws Exception {
243+
KeyAgreement ka = KeyAgreement.getInstance("ECMQV");
244+
ka.init(privateKey);
245+
ka.doPhase(publicKey, true);
246+
return ka.generateSecret();
247+
}
248+
249+
//////////////////////////////////////////
250+
// 6. Nuanced Insecure Key Exchange Example
218251
//////////////////////////////////////////
219252

220253
/**

0 commit comments

Comments
 (0)