Skip to content

Commit f1eb651

Browse files
committed
Crypto: Add modeling for JCA signatures. Make consistent use of "unknown" or "other" for unrecognized types.
1 parent a46bd4c commit f1eb651

File tree

2 files changed

+223
-6
lines changed

2 files changed

+223
-6
lines changed

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

Lines changed: 218 additions & 1 deletion
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,6 +102,12 @@ module JCAModel {
100102
].toUpperCase())
101103
}
102104

105+
bindingset[name]
106+
predicate signature_names(string name) {
107+
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches(["RSA", "ECDSA", "DSA"])
108+
// note RSASSA-PSS is RSA with PSS where the digest is set through PSSParameterSpec
109+
}
110+
103111
bindingset[name]
104112
predicate key_agreement_names(string name) {
105113
name.toUpperCase().matches(["DH", "EDH", "ECDH", "X25519", "X448"].toUpperCase())
@@ -217,6 +225,25 @@ module JCAModel {
217225
name.toUpperCase() in ["ECDH", "X25519", "X448"]
218226
}
219227

228+
bindingset[name]
229+
predicate signature_name_to_type_known(Crypto::KeyOpAlg::TAlgorithm type, string name) {
230+
name.toUpperCase().splitAt("with".toUpperCase(), 1) = "RSA" and
231+
type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
232+
or
233+
name.toUpperCase().splitAt("with".toUpperCase(), 1) = "ECDSA" and
234+
type = KeyOpAlg::TSignature(KeyOpAlg::ECDSA())
235+
or
236+
name.toUpperCase().splitAt("with".toUpperCase(), 1) = "DSA" and
237+
type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
238+
or
239+
name.toUpperCase().matches("RSASSA-PSS") and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
240+
}
241+
242+
bindingset[name]
243+
Crypto::HashType signature_name_to_hash_type_known(string name, int digestLength) {
244+
result = hash_name_to_type_known(name.splitAt("with", 0), digestLength)
245+
}
246+
220247
/**
221248
* A `StringLiteral` in the `"ALG/MODE/PADDING"` or `"ALG"` format
222249
*/
@@ -345,7 +372,7 @@ module JCAModel {
345372
override KeyOpAlg::AlgorithmType getAlgorithmType() {
346373
if cipher_name_to_type_known(_, super.getAlgorithmName())
347374
then cipher_name_to_type_known(result, super.getAlgorithmName())
348-
else result instanceof KeyOpAlg::TUnknownKeyOperationAlgorithmType
375+
else result instanceof KeyOpAlg::TOtherKeyOperationAlgorithmType
349376
}
350377

351378
override int getKeySizeFixed() {
@@ -1639,6 +1666,196 @@ module JCAModel {
16391666
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
16401667
}
16411668

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

shared/quantum/codeql/quantum/experimental/Standardization.qll

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module Types {
1616
TSignature(TSignatureAlgorithmType t) or
1717
TMac(TMacAlgorithmType t) or
1818
TKeyEncapsulation(TKemAlgorithmType t) or
19-
TUnknownKeyOperationAlgorithmType()
19+
TOtherKeyOperationAlgorithmType()
2020

2121
// Parameterized algorithm types
2222
newtype TSymmetricCipherType =
@@ -64,15 +64,15 @@ module Types {
6464
newtype TCipherStructureType =
6565
Block() or
6666
Stream() or
67-
UnknownCipherStructureType()
67+
OtherCipherStructureType()
6868

6969
class CipherStructureType extends TCipherStructureType {
7070
string toString() {
7171
result = "Block" and this = Block()
7272
or
7373
result = "Stream" and this = Stream()
7474
or
75-
result = "Unknown" and this = UnknownCipherStructureType()
75+
result = "Unknown" and this = OtherCipherStructureType()
7676
}
7777
}
7878

@@ -119,7 +119,7 @@ module Types {
119119
or
120120
type = OtherSymmetricCipherType() and
121121
name = "UnknownSymmetricCipher" and
122-
s = UnknownCipherStructureType()
122+
s = OtherCipherStructureType()
123123
}
124124

125125
class AlgorithmType extends TAlgorithm {
@@ -157,7 +157,7 @@ module Types {
157157
this = TMac(OtherMacAlgorithmType()) and result = "UnknownMac"
158158
or
159159
// Unknown
160-
this = TUnknownKeyOperationAlgorithmType() and result = "Unknown"
160+
this = TOtherKeyOperationAlgorithmType() and result = "Unknown"
161161
}
162162

163163
int getImplicitKeySize() {

0 commit comments

Comments
 (0)