Skip to content

Commit 6f29b01

Browse files
committed
Python: Model rsa
1 parent 40714c0 commit 6f29b01

File tree

5 files changed

+160
-15
lines changed

5 files changed

+160
-15
lines changed

docs/codeql/support/reusables/frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,4 @@ Python built-in support
169169
cryptography, Cryptography library
170170
pycryptodome, Cryptography library
171171
pycryptodomex, Cryptography library
172+
rsa, Cryptography library
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Added modeling of the PyPI package `rsa`.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ private import semmle.python.frameworks.MysqlConnectorPython
1616
private import semmle.python.frameworks.MySQLdb
1717
private import semmle.python.frameworks.Psycopg2
1818
private import semmle.python.frameworks.PyMySQL
19+
private import semmle.python.frameworks.Rsa
1920
private import semmle.python.frameworks.Simplejson
2021
private import semmle.python.frameworks.Stdlib
2122
private import semmle.python.frameworks.Tornado
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `rsa` PyPI package.
3+
* See https://stuvel.eu/python-rsa-doc/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import semmle.python.dataflow.new.TaintTracking
9+
private import semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
12+
/**
13+
* Provides models for the `rsa` PyPI package.
14+
* See https://stuvel.eu/python-rsa-doc/.
15+
*/
16+
private module Rsa {
17+
/**
18+
* A call to `rsa.newkeys`
19+
*
20+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.newkeys
21+
*/
22+
class RsaNewkeysCall extends Cryptography::PublicKey::KeyGeneration::RsaRange,
23+
DataFlow::CallCfgNode {
24+
RsaNewkeysCall() { this = API::moduleImport("rsa").getMember("newkeys").getACall() }
25+
26+
override DataFlow::Node getKeySizeArg() {
27+
result in [this.getArg(0), this.getArgByName("nbits")]
28+
}
29+
}
30+
31+
/**
32+
* A call to `rsa.encrypt`
33+
*
34+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.encrypt
35+
*/
36+
class RsaEncryptCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
37+
RsaEncryptCall() { this = API::moduleImport("rsa").getMember("encrypt").getACall() }
38+
39+
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" }
40+
41+
override DataFlow::Node getAnInput() {
42+
result in [this.getArg(0), this.getArgByName("message")]
43+
}
44+
}
45+
46+
/**
47+
* A call to `rsa.decrypt`
48+
*
49+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.decrypt
50+
*/
51+
class RsaDecryptCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
52+
RsaDecryptCall() { this = API::moduleImport("rsa").getMember("decrypt").getACall() }
53+
54+
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" }
55+
56+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("crypto")] }
57+
}
58+
59+
/**
60+
* A call to `rsa.sign`, which both hashes and signs in the input message.
61+
*
62+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.sign
63+
*/
64+
class RsaSignCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
65+
RsaSignCall() { this = API::moduleImport("rsa").getMember("sign").getACall() }
66+
67+
override Cryptography::CryptographicAlgorithm getAlgorithm() {
68+
// signature part
69+
result.getName() = "RSA"
70+
or
71+
// hashing part
72+
exists(StrConst str, DataFlow::Node hashNameArg |
73+
hashNameArg in [this.getArg(2), this.getArgByName("hash_method")] and
74+
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(hashNameArg) and
75+
result.matchesName(str.getText())
76+
)
77+
}
78+
79+
override DataFlow::Node getAnInput() {
80+
result in [this.getArg(0), this.getArgByName("message")]
81+
}
82+
}
83+
84+
/**
85+
* A call to `rsa.verify`
86+
*
87+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.verify
88+
*/
89+
class RsaVerifyCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
90+
RsaVerifyCall() { this = API::moduleImport("rsa").getMember("verify").getACall() }
91+
92+
override Cryptography::CryptographicAlgorithm getAlgorithm() {
93+
// note that technically there is also a hashing operation going on but we don't
94+
// know what algorithm is used up front, since it is encoded in the signature
95+
result.getName() = "RSA"
96+
}
97+
98+
override DataFlow::Node getAnInput() {
99+
result in [this.getArg(0), this.getArgByName("message")]
100+
or
101+
result in [this.getArg(1), this.getArgByName("signature")]
102+
}
103+
}
104+
105+
/**
106+
* A call to `rsa.compute_hash`
107+
*
108+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.compute_hash
109+
*/
110+
class RsaComputeHashCall extends Cryptography::CryptographicOperation::Range,
111+
DataFlow::CallCfgNode {
112+
RsaComputeHashCall() { this = API::moduleImport("rsa").getMember("compute_hash").getACall() }
113+
114+
override Cryptography::CryptographicAlgorithm getAlgorithm() {
115+
exists(StrConst str, DataFlow::Node hashNameArg |
116+
hashNameArg in [this.getArg(1), this.getArgByName("method_name")] and
117+
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(hashNameArg) and
118+
result.matchesName(str.getText())
119+
)
120+
}
121+
122+
override DataFlow::Node getAnInput() {
123+
result in [this.getArg(0), this.getArgByName("message")]
124+
}
125+
}
126+
127+
/**
128+
* A call to `rsa.sign_hash`
129+
*
130+
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.sign_hash
131+
*/
132+
class RsaSignHashCall extends Cryptography::CryptographicOperation::Range, DataFlow::CallCfgNode {
133+
RsaSignHashCall() { this = API::moduleImport("rsa").getMember("sign_hash").getACall() }
134+
135+
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" }
136+
137+
override DataFlow::Node getAnInput() {
138+
result in [this.getArg(0), this.getArgByName("hash_value")]
139+
}
140+
}
141+
}

python/ql/test/library-tests/frameworks/rsa/test_rsa.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import rsa
33

44
# using a rather low keysize, since otherwise it takes quite long to run.
5-
(public_key, private_key) = rsa.newkeys(512) # $ MISSING: PublicKeyGeneration keySize=2048
6-
(public_key, private_key) = rsa.newkeys(nbits=512) # $ MISSING: PublicKeyGeneration keySize=2048
5+
(public_key, private_key) = rsa.newkeys(512) # $ PublicKeyGeneration keySize=512
6+
(public_key, private_key) = rsa.newkeys(nbits=512) # $ PublicKeyGeneration keySize=512
77

88

99
# ------------------------------------------------------------------------------
@@ -16,15 +16,15 @@
1616

1717
secret_message = b"secret message"
1818

19-
encrypted = rsa.encrypt(secret_message, public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message
20-
encrypted = rsa.encrypt(message=secret_message, pub_key=public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message
19+
encrypted = rsa.encrypt(secret_message, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message
20+
encrypted = rsa.encrypt(message=secret_message, pub_key=public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=secret_message
2121

2222
print("encrypted={}".format(encrypted))
2323

2424
print()
2525

26-
decrypted = rsa.decrypt(encrypted, private_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted
27-
decrypted = rsa.decrypt(crypto=encrypted, priv_key=private_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted
26+
decrypted = rsa.decrypt(encrypted, private_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted
27+
decrypted = rsa.decrypt(crypto=encrypted, priv_key=private_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=encrypted
2828

2929
print("decrypted={}".format(decrypted))
3030
assert decrypted == secret_message
@@ -42,27 +42,27 @@
4242
message = b"message"
4343
other_message = b"other message"
4444

45-
hash = rsa.compute_hash(message, "SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
46-
hash = rsa.compute_hash(message=message, method_name="SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
47-
signature_from_hash = rsa.sign_hash(hash, private_key, "SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash
48-
signature_from_hash = rsa.sign_hash(hash_value=hash, priv_key=private_key, hash_method="SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash
45+
hash = rsa.compute_hash(message, "SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
46+
hash = rsa.compute_hash(message=message, method_name="SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
47+
signature_from_hash = rsa.sign_hash(hash, private_key, "SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash
48+
signature_from_hash = rsa.sign_hash(hash_value=hash, priv_key=private_key, hash_method="SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=hash
4949

50-
signature = rsa.sign(message, private_key, "SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
51-
signature = rsa.sign(message=message, priv_key=private_key, hash_method="SHA-256") # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
50+
signature = rsa.sign(message, private_key, "SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
51+
signature = rsa.sign(message=message, priv_key=private_key, hash_method="SHA-256") # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationAlgorithm=SHA256 CryptographicOperationInput=message
5252

5353
assert signature == signature_from_hash
5454

5555
print("signature={}".format(signature))
5656

5757
print()
5858

59-
rsa.verify(message, signature, public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature
60-
rsa.verify(message=message, signature=signature, pub_key=public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature
59+
rsa.verify(message, signature, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature
60+
rsa.verify(message=message, signature=signature, pub_key=public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=message CryptographicOperationInput=signature
6161

6262
print("Signature verified (as expected)")
6363

6464
try:
65-
rsa.verify(other_message, signature, public_key) # $ MISSING: CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=other_message CryptographicOperationInput=signature
65+
rsa.verify(other_message, signature, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=other_message CryptographicOperationInput=signature
6666
raise Exception("Signature verified (unexpected)")
6767
except rsa.VerificationError:
6868
print("Signature mismatch (as expected)")

0 commit comments

Comments
 (0)