Skip to content

Commit 6e4c627

Browse files
committed
Python: Add modeling for pycryptodomex PyPI package
1 parent bd40965 commit 6e4c627

5 files changed

Lines changed: 358 additions & 3 deletions

File tree

python/ql/src/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Helper file that imports all framework modeling.
33
*/
44

5+
private import semmle.python.frameworks.Cryptodome
56
private import semmle.python.frameworks.Cryptography
67
private import semmle.python.frameworks.Dill
78
private import semmle.python.frameworks.Django
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of
3+
* - the `pycryptodome` PyPI package (imported as `Crypto`)
4+
* - the `pycryptodomex` PyPI package (imported as `Cryptodome`)
5+
* See https://pycryptodome.readthedocs.io/en/latest/.
6+
*/
7+
8+
private import python
9+
private import semmle.python.dataflow.new.DataFlow
10+
private import semmle.python.Concepts
11+
12+
/**
13+
* Provides models for
14+
* - the `pycryptodome` PyPI package (imported as `Crypto`)
15+
* - the `pycryptodomex` PyPI package (imported as `Cryptodome`)
16+
* See https://pycryptodome.readthedocs.io/en/latest/
17+
*/
18+
private module CryptodomeModel {
19+
// ---------------------------------------------------------------------------
20+
// Cryptodome
21+
// ---------------------------------------------------------------------------
22+
/** Gets a reference to the `Cryptodome` module. */
23+
private DataFlow::Node cryptodome(DataFlow::TypeTracker t) {
24+
t.start() and
25+
result = DataFlow::importNode("Cryptodome")
26+
or
27+
exists(DataFlow::TypeTracker t2 | result = cryptodome(t2).track(t2, t))
28+
}
29+
30+
/** Gets a reference to the `Cryptodome` module. */
31+
DataFlow::Node cryptodome() { result = cryptodome(DataFlow::TypeTracker::end()) }
32+
33+
/** Provides models for the `Cryptodome` module. */
34+
module Cryptodome {
35+
/**
36+
* Gets a reference to the attribute `attr_name` of the `Cryptodome` module.
37+
* WARNING: Only holds for a few predefined attributes.
38+
*/
39+
private DataFlow::Node cryptodome_attr(DataFlow::TypeTracker t, string attr_name) {
40+
attr_name in ["PublicKey"] and
41+
(
42+
t.start() and
43+
result = DataFlow::importNode("Cryptodome" + "." + attr_name)
44+
or
45+
t.startInAttr(attr_name) and
46+
result = cryptodome()
47+
)
48+
or
49+
// Due to bad performance when using normal setup with `cryptodome_attr(t2, attr_name).track(t2, t)`
50+
// we have inlined that code and forced a join
51+
exists(DataFlow::TypeTracker t2 |
52+
exists(DataFlow::StepSummary summary |
53+
cryptodome_attr_first_join(t2, attr_name, result, summary) and
54+
t = t2.append(summary)
55+
)
56+
)
57+
}
58+
59+
pragma[nomagic]
60+
private predicate cryptodome_attr_first_join(
61+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
62+
) {
63+
DataFlow::StepSummary::step(cryptodome_attr(t2, attr_name), res, summary)
64+
}
65+
66+
/**
67+
* Gets a reference to the attribute `attr_name` of the `Cryptodome` module.
68+
* WARNING: Only holds for a few predefined attributes.
69+
*/
70+
private DataFlow::Node cryptodome_attr(string attr_name) {
71+
result = cryptodome_attr(DataFlow::TypeTracker::end(), attr_name)
72+
}
73+
74+
// -------------------------------------------------------------------------
75+
// Cryptodome.PublicKey
76+
// -------------------------------------------------------------------------
77+
/** Gets a reference to the `Cryptodome.PublicKey` module. */
78+
DataFlow::Node publicKey() { result = cryptodome_attr("PublicKey") }
79+
80+
/** Provides models for the `Cryptodome.PublicKey` module */
81+
module PublicKey {
82+
/**
83+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey` module.
84+
* WARNING: Only holds for a few predefined attributes.
85+
*/
86+
private DataFlow::Node publicKey_attr(DataFlow::TypeTracker t, string attr_name) {
87+
attr_name in ["RSA", "DSA", "ECC"] and
88+
(
89+
t.start() and
90+
result = DataFlow::importNode("Cryptodome.PublicKey" + "." + attr_name)
91+
or
92+
t.startInAttr(attr_name) and
93+
result = publicKey()
94+
)
95+
or
96+
// Due to bad performance when using normal setup with `publicKey_attr(t2, attr_name).track(t2, t)`
97+
// we have inlined that code and forced a join
98+
exists(DataFlow::TypeTracker t2 |
99+
exists(DataFlow::StepSummary summary |
100+
publicKey_attr_first_join(t2, attr_name, result, summary) and
101+
t = t2.append(summary)
102+
)
103+
)
104+
}
105+
106+
pragma[nomagic]
107+
private predicate publicKey_attr_first_join(
108+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
109+
DataFlow::StepSummary summary
110+
) {
111+
DataFlow::StepSummary::step(publicKey_attr(t2, attr_name), res, summary)
112+
}
113+
114+
/**
115+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey` module.
116+
* WARNING: Only holds for a few predefined attributes.
117+
*/
118+
private DataFlow::Node publicKey_attr(string attr_name) {
119+
result = publicKey_attr(DataFlow::TypeTracker::end(), attr_name)
120+
}
121+
122+
// -------------------------------------------------------------------------
123+
// Cryptodome.PublicKey.RSA
124+
// -------------------------------------------------------------------------
125+
/** Gets a reference to the `Cryptodome.PublicKey.RSA` module. */
126+
DataFlow::Node rsa() { result = publicKey_attr("RSA") }
127+
128+
/** Provides models for the `Cryptodome.PublicKey.RSA` module */
129+
module RSA {
130+
/**
131+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA` module.
132+
* WARNING: Only holds for a few predefined attributes.
133+
*/
134+
private DataFlow::Node rsa_attr(DataFlow::TypeTracker t, string attr_name) {
135+
attr_name in ["generate"] and
136+
(
137+
t.start() and
138+
result = DataFlow::importNode("Cryptodome.PublicKey.RSA" + "." + attr_name)
139+
or
140+
t.startInAttr(attr_name) and
141+
result = rsa()
142+
)
143+
or
144+
// Due to bad performance when using normal setup with `rsa_attr(t2, attr_name).track(t2, t)`
145+
// we have inlined that code and forced a join
146+
exists(DataFlow::TypeTracker t2 |
147+
exists(DataFlow::StepSummary summary |
148+
rsa_attr_first_join(t2, attr_name, result, summary) and
149+
t = t2.append(summary)
150+
)
151+
)
152+
}
153+
154+
pragma[nomagic]
155+
private predicate rsa_attr_first_join(
156+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
157+
DataFlow::StepSummary summary
158+
) {
159+
DataFlow::StepSummary::step(rsa_attr(t2, attr_name), res, summary)
160+
}
161+
162+
/**
163+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.RSA` module.
164+
* WARNING: Only holds for a few predefined attributes.
165+
*/
166+
private DataFlow::Node rsa_attr(string attr_name) {
167+
result = rsa_attr(DataFlow::TypeTracker::end(), attr_name)
168+
}
169+
170+
/** Gets a reference to the `Cryptodome.PublicKey.RSA.generate` function. */
171+
DataFlow::Node generate() { result = rsa_attr("generate") }
172+
}
173+
174+
// -------------------------------------------------------------------------
175+
// Cryptodome.PublicKey.DSA
176+
// -------------------------------------------------------------------------
177+
/** Gets a reference to the `Cryptodome.PublicKey.DSA` module. */
178+
DataFlow::Node dsa() { result = publicKey_attr("DSA") }
179+
180+
/** Provides models for the `Cryptodome.PublicKey.DSA` module */
181+
module DSA {
182+
/**
183+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA` module.
184+
* WARNING: Only holds for a few predefined attributes.
185+
*/
186+
private DataFlow::Node dsa_attr(DataFlow::TypeTracker t, string attr_name) {
187+
attr_name in ["generate"] and
188+
(
189+
t.start() and
190+
result = DataFlow::importNode("Cryptodome.PublicKey.DSA" + "." + attr_name)
191+
or
192+
t.startInAttr(attr_name) and
193+
result = dsa()
194+
)
195+
or
196+
// Due to bad performance when using normal setup with `dsa_attr(t2, attr_name).track(t2, t)`
197+
// we have inlined that code and forced a join
198+
exists(DataFlow::TypeTracker t2 |
199+
exists(DataFlow::StepSummary summary |
200+
dsa_attr_first_join(t2, attr_name, result, summary) and
201+
t = t2.append(summary)
202+
)
203+
)
204+
}
205+
206+
pragma[nomagic]
207+
private predicate dsa_attr_first_join(
208+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
209+
DataFlow::StepSummary summary
210+
) {
211+
DataFlow::StepSummary::step(dsa_attr(t2, attr_name), res, summary)
212+
}
213+
214+
/**
215+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.DSA` module.
216+
* WARNING: Only holds for a few predefined attributes.
217+
*/
218+
private DataFlow::Node dsa_attr(string attr_name) {
219+
result = dsa_attr(DataFlow::TypeTracker::end(), attr_name)
220+
}
221+
222+
/** Gets a reference to the `Cryptodome.PublicKey.DSA.generate` function. */
223+
DataFlow::Node generate() { result = dsa_attr("generate") }
224+
}
225+
226+
// -------------------------------------------------------------------------
227+
// Cryptodome.PublicKey.ECC
228+
// -------------------------------------------------------------------------
229+
/** Gets a reference to the `Cryptodome.PublicKey.ECC` module. */
230+
DataFlow::Node ecc() { result = publicKey_attr("ECC") }
231+
232+
/** Provides models for the `Cryptodome.PublicKey.ECC` module */
233+
module ECC {
234+
/**
235+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC` module.
236+
* WARNING: Only holds for a few predefined attributes.
237+
*/
238+
private DataFlow::Node ecc_attr(DataFlow::TypeTracker t, string attr_name) {
239+
attr_name in ["generate"] and
240+
(
241+
t.start() and
242+
result = DataFlow::importNode("Cryptodome.PublicKey.ECC" + "." + attr_name)
243+
or
244+
t.startInAttr(attr_name) and
245+
result = ecc()
246+
)
247+
or
248+
// Due to bad performance when using normal setup with `ecc_attr(t2, attr_name).track(t2, t)`
249+
// we have inlined that code and forced a join
250+
exists(DataFlow::TypeTracker t2 |
251+
exists(DataFlow::StepSummary summary |
252+
ecc_attr_first_join(t2, attr_name, result, summary) and
253+
t = t2.append(summary)
254+
)
255+
)
256+
}
257+
258+
pragma[nomagic]
259+
private predicate ecc_attr_first_join(
260+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
261+
DataFlow::StepSummary summary
262+
) {
263+
DataFlow::StepSummary::step(ecc_attr(t2, attr_name), res, summary)
264+
}
265+
266+
/**
267+
* Gets a reference to the attribute `attr_name` of the `Cryptodome.PublicKey.ECC` module.
268+
* WARNING: Only holds for a few predefined attributes.
269+
*/
270+
private DataFlow::Node ecc_attr(string attr_name) {
271+
result = ecc_attr(DataFlow::TypeTracker::end(), attr_name)
272+
}
273+
274+
/** Gets a reference to the `Cryptodome.PublicKey.ECC.generate` function. */
275+
DataFlow::Node generate() { result = ecc_attr("generate") }
276+
}
277+
}
278+
}
279+
280+
// ---------------------------------------------------------------------------
281+
/**
282+
* A call to `Cryptodome.PublicKey.RSA.generate`
283+
*
284+
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate
285+
*/
286+
class CryptodomePublicKeyRSAGenerateCall extends Cryptography::PublicKey::KeyGeneration::RSARange,
287+
DataFlow::CfgNode {
288+
override CallNode node;
289+
290+
CryptodomePublicKeyRSAGenerateCall() {
291+
node.getFunction() = Cryptodome::PublicKey::RSA::generate().asCfgNode()
292+
}
293+
294+
override DataFlow::Node getKeySizeArg() {
295+
result.asCfgNode() in [node.getArg(0), node.getArgByName("bits")]
296+
}
297+
}
298+
299+
/**
300+
* A call to `Cryptodome.PublicKey.DSA.generate`
301+
*
302+
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate
303+
*/
304+
class CryptodomePublicKeyDSAGenerateCall extends Cryptography::PublicKey::KeyGeneration::DSARange,
305+
DataFlow::CfgNode {
306+
override CallNode node;
307+
308+
CryptodomePublicKeyDSAGenerateCall() {
309+
node.getFunction() = Cryptodome::PublicKey::DSA::generate().asCfgNode()
310+
}
311+
312+
override DataFlow::Node getKeySizeArg() {
313+
result.asCfgNode() in [node.getArg(0), node.getArgByName("bits")]
314+
}
315+
}
316+
317+
/**
318+
* A call to `Cryptodome.PublicKey.ECC.generate`
319+
*
320+
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate
321+
*/
322+
class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::ECCRange,
323+
DataFlow::CfgNode {
324+
override CallNode node;
325+
326+
CryptodomePublicKeyEccGenerateCall() {
327+
node.getFunction() = Cryptodome::PublicKey::ECC::generate().asCfgNode()
328+
}
329+
330+
/** Gets the argument that specifies the curve to use (a string). */
331+
DataFlow::Node getCurveArg() { result.asCfgNode() in [node.getArgByName("curve")] }
332+
333+
string getCurveWithOrigin(DataFlow::Node origin) {
334+
exists(StrConst str | origin = DataFlow::exprNode(str) |
335+
origin.(DataFlow::LocalSourceNode).flowsTo(this.getCurveArg()) and
336+
result = str.getText()
337+
)
338+
}
339+
340+
override int getKeySizeWithOrigin(DataFlow::Node origin) {
341+
exists(string curve | curve = getCurveWithOrigin(origin) |
342+
// using list from https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html
343+
curve in ["NIST P-256", "p256", "P-256", "prime256v1", "secp256r1"] and result = 256
344+
or
345+
curve in ["NIST P-384", "p384", "P-384", "prime384v1", "secp384r1"] and result = 384
346+
or
347+
curve in ["NIST P-521", "p521", "P-521", "prime521v1", "secp521r1"] and result = 521
348+
)
349+
}
350+
351+
// Note: There is not really a key-size argument, since it's always specified by the curve.
352+
override DataFlow::Node getKeySizeArg() { none() }
353+
}
354+
}

python/ql/test/experimental/library-tests/frameworks/cryptodome/test_dsa.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from Cryptodome.Hash import SHA256
77

88

9-
private_key = DSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048
9+
private_key = DSA.generate(2048) # $ PublicKeyGeneration keySize=2048
1010
public_key = private_key.publickey()
1111

1212
# ------------------------------------------------------------------------------

python/ql/test/experimental/library-tests/frameworks/cryptodome/test_ec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from Cryptodome.Hash import SHA256
44

55

6-
private_key = ECC.generate(curve="P-256") # $ MISSING: PublicKeyGeneration keySize=256
6+
private_key = ECC.generate(curve="P-256") # $ PublicKeyGeneration keySize=256
77
public_key = private_key.public_key()
88

99
# ------------------------------------------------------------------------------

python/ql/test/experimental/library-tests/frameworks/cryptodome/test_rsa.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from Cryptodome.Signature import pss
66
from Cryptodome.Hash import SHA256
77

8-
private_key = RSA.generate(2048) # $ MISSING: PublicKeyGeneration keySize=2048
8+
private_key = RSA.generate(2048) # $ PublicKeyGeneration keySize=2048
99
public_key = private_key.publickey()
1010

1111
# ------------------------------------------------------------------------------

0 commit comments

Comments
 (0)