Skip to content

Commit 595e6fc

Browse files
authored
Merge pull request #550 from markshannon/python-weak-crypto
Python: Query for use of weak crypto keys.
2 parents c75fa28 + a345727 commit 595e6fc

File tree

14 files changed

+244
-0
lines changed

14 files changed

+244
-0
lines changed

change-notes/1.19/analysis-python.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ A new predicate `Stmt.getAnEntryNode()` has been added to make it easier to writ
6060
| Information exposure through an exception (`py/stack-trace-exposure`) | security, external/cwe/cwe-209, external/cwe/cwe-497 | Finds instances where information about an exception may be leaked to an external user. Results are shown on LGTM by default. |
6161
| Jinja2 templating with autoescape=False (`py/jinja2/autoescape-false`) | security, external/cwe/cwe-079 | Finds instantiations of `jinja2.Environment` with `autoescape=False` which may allow XSS attacks. Results are hidden on LGTM by default. |
6262
| Request without certificate validation (`py/request-without-cert-validation`) | security, external/cwe/cwe-295 | Finds requests where certificate verification has been explicitly turned off, possibly allowing man-in-the-middle attacks. Results are hidden on LGTM by default. |
63+
| Use of weak cryptographic key (`py/weak-crypto-key`) | security, external/cwe/cwe-326 | Finds creation of weak cryptographic keys. Results are shown on LGTM by default. |
6364

6465
## Changes to existing queries
6566

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Modern encryption relies on it being computationally infeasible to break the cipher and decode a message without the key.
9+
As computational power increases, the ability to break ciphers grows and keys need to become larger.
10+
</p>
11+
<p>
12+
The three main asymmetric key algorithms currently in use are Rivest–Shamir–Adleman (RSA) cryptography, Digital Signature Algorithm (DSA), and Elliptic-curve cryptography (ECC).
13+
With current technology, key sizes of 2048 bits for RSA and DSA,
14+
or 224 bits for ECC, are regarded as unbreakable.
15+
</p>
16+
</overview>
17+
18+
<recommendation>
19+
<p>
20+
Increase the key size to the recommended amount or larger. For RSA or DSA this is at least 2048 bits, for ECC this is at least 224 bits.
21+
</p>
22+
</recommendation>
23+
24+
<references>
25+
<li>
26+
Wikipedia:
27+
<a href="https://en.wikipedia.org/wiki/Digital_Signature_Algorithm">Digital Signature Algorithm</a>.
28+
</li>
29+
<li>
30+
Wikipedia:
31+
<a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA cryptosystem</a>.
32+
</li>
33+
<li>
34+
Wikipedia:
35+
<a href="https://en.wikipedia.org/wiki/Elliptic-curve_cryptography">Elliptic-curve cryptography</a>.
36+
</li>
37+
<li>
38+
Python cryptography module:
39+
<a href="https://cryptography.io/en/latest/">cryptography.io</a>.
40+
</li>
41+
<li>
42+
NIST:
43+
<a href="https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf">
44+
Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</a>.
45+
</li>
46+
</references>
47+
</qhelp>
48+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @name Use of weak cryptographic key
3+
* @description Use of a cryptographic key that is too small may allow the encryption to be broken.
4+
* @kind problem
5+
* @problem.severity error
6+
* @precision high
7+
* @id py/weak-crypto-key
8+
* @tags security
9+
* external/cwe/cwe-326
10+
*/
11+
12+
import python
13+
14+
int minimumSecureKeySize(string algo) {
15+
algo = "DSA" and result = 2048
16+
or
17+
algo = "RSA" and result = 2048
18+
or
19+
algo = "ECC" and result = 224
20+
}
21+
22+
predicate dsaRsaKeySizeArg(FunctionObject obj, string algorithm, string arg) {
23+
exists(ModuleObject mod |
24+
mod.getAttribute(_) = obj |
25+
algorithm = "DSA" and
26+
(
27+
mod.getName() = "cryptography.hazmat.primitives.asymmetric.dsa" and arg = "key_size"
28+
or
29+
mod.getName() = "Crypto.PublicKey.DSA" and arg = "bits"
30+
or
31+
mod.getName() = "Cryptodome.PublicKey.DSA" and arg = "bits"
32+
)
33+
or
34+
algorithm = "RSA" and
35+
(
36+
mod.getName() = "cryptography.hazmat.primitives.asymmetric.rsa" and arg = "key_size"
37+
or
38+
mod.getName() = "Crypto.PublicKey.RSA" and arg = "bits"
39+
or
40+
mod.getName() = "Cryptodome.PublicKey.RSA" and arg = "bits"
41+
)
42+
)
43+
}
44+
45+
predicate ecKeySizeArg(FunctionObject obj, string arg) {
46+
exists(ModuleObject mod |
47+
mod.getAttribute(_) = obj |
48+
mod.getName() = "cryptography.hazmat.primitives.asymmetric.ec" and arg = "curve"
49+
)
50+
}
51+
52+
int keySizeFromCurve(ClassObject curveClass) {
53+
result = curveClass.declaredAttribute("key_size").(NumericObject).intValue()
54+
}
55+
56+
predicate algorithmAndKeysizeForCall(CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin) {
57+
exists(FunctionObject func, string argname, ControlFlowNode arg |
58+
arg = func.getNamedArgumentForCall(call, argname) |
59+
exists(NumericObject key |
60+
arg.refersTo(key, keyOrigin) and
61+
dsaRsaKeySizeArg(func, algorithm, argname) and
62+
keySize = key.intValue()
63+
)
64+
or
65+
exists(ClassObject curve |
66+
arg.refersTo(_, curve, keyOrigin) and
67+
ecKeySizeArg(func, argname) and
68+
algorithm = "ECC" and
69+
keySize = keySizeFromCurve(curve)
70+
)
71+
)
72+
}
73+
74+
75+
from CallNode call, ControlFlowNode origin, string algo, int keySize
76+
where
77+
algorithmAndKeysizeForCall(call, algo, keySize, origin) and
78+
keySize < minimumSecureKeySize(algo)
79+
select call, "Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) + " and considered breakable.", origin, keySize.toString()
80+
81+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| weak_crypto.py:67:1:67:30 | ControlFlowNode for dsa_gen_key() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
2+
| weak_crypto.py:68:1:68:28 | ControlFlowNode for ec_gen_key() | Creation of an ECC key uses $@ bits, which is below 224 and considered breakable. | weak_crypto.py:21:11:21:33 | ControlFlowNode for FakeWeakEllipticCurve() | 160 |
3+
| weak_crypto.py:69:1:69:37 | ControlFlowNode for rsa_gen_key() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
4+
| weak_crypto.py:71:1:71:22 | ControlFlowNode for Attribute() | Creation of an DSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
5+
| weak_crypto.py:72:1:72:22 | ControlFlowNode for Attribute() | Creation of an RSA key uses $@ bits, which is below 2048 and considered breakable. | weak_crypto.py:12:12:12:15 | ControlFlowNode for IntegerLiteral | 1024 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-326/WeakCrypto.ql
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
semmle-extractor-options: -p ../lib/ --max-import-depth=3
2+
optimize: true
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from cryptography.hazmat import backends
2+
from cryptography.hazmat.primitives.asymmetric import ec, dsa, rsa
3+
4+
#Crypto and Cryptodome have same API
5+
if random():
6+
from Crypto.PublicKey import DSA
7+
from Crypto.PublicKey import RSA
8+
else:
9+
from Cryptodome.PublicKey import DSA
10+
from Cryptodome.PublicKey import RSA
11+
12+
RSA_WEAK = 1024
13+
RSA_OK = 2048
14+
RSA_STRONG = 3076
15+
BIG = 10000
16+
17+
class FakeWeakEllipticCurve:
18+
name = "fake"
19+
key_size = 160
20+
21+
EC_WEAK = FakeWeakEllipticCurve()
22+
EC_OK = ec.SECP224R1()
23+
EC_STRONG = ec.SECP384R1()
24+
EC_BIG = ec.SECT571R1()
25+
26+
dsa_gen_key = dsa.generate_private_key
27+
ec_gen_key = ec.generate_private_key
28+
rsa_gen_key = rsa.generate_private_key
29+
30+
default = backends.default_backend()
31+
32+
#Strong and OK keys.
33+
34+
dsa_gen_key(key_size=RSA_OK, backend=default)
35+
dsa_gen_key(key_size=RSA_STRONG, backend=default)
36+
dsa_gen_key(key_size=BIG, backend=default)
37+
ec_gen_key(key_size=EC_OK, backend=default)
38+
ec_gen_key(key_size=EC_STRONG, backend=default)
39+
ec_gen_key(key_size=EC_BIG, backend=default)
40+
rsa_gen_key(public_exponent=65537, key_size=RSA_OK, backend=default)
41+
rsa_gen_key(public_exponent=65537, key_size=RSA_STRONG, backend=default)
42+
rsa_gen_key(public_exponent=65537, key_size=BIG, backend=default)
43+
44+
DSA.generate(bits=RSA_OK)
45+
DSA.generate(bits=RSA_STRONG)
46+
RSA.generate(bits=RSA_OK)
47+
RSA.generate(bits=RSA_STRONG)
48+
49+
dsa_gen_key(RSA_OK, default)
50+
dsa_gen_key(RSA_STRONG, default)
51+
dsa_gen_key(BIG, default)
52+
ec_gen_key(EC_OK, default)
53+
ec_gen_key(EC_STRONG, default)
54+
ec_gen_key(EC_BIG, default)
55+
rsa_gen_key(65537, RSA_OK, default)
56+
rsa_gen_key(65537, RSA_STRONG, default)
57+
rsa_gen_key(65537, BIG, default)
58+
59+
DSA.generate(RSA_OK)
60+
DSA.generate(RSA_STRONG)
61+
RSA.generate(RSA_OK)
62+
RSA.generate(RSA_STRONG)
63+
64+
65+
# Weak keys
66+
67+
dsa_gen_key(RSA_WEAK, default)
68+
ec_gen_key(EC_WEAK, default)
69+
rsa_gen_key(65537, RSA_WEAK, default)
70+
71+
DSA.generate(RSA_WEAK)
72+
RSA.generate(RSA_WEAK)
73+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def generate(bits):
2+
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def generate(bits):
2+
pass

python/ql/test/query-tests/Security/lib/Crypto/PublicKey/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)