Skip to content

Commit 469e709

Browse files
authored
Merge pull request github#6055 from RasmusWL/rsa-modeling
Approved by yoff
2 parents 9b8f558 + 0774e98 commit 469e709

File tree

9 files changed

+219
-0
lines changed

9 files changed

+219
-0
lines changed

docs/codeql/support/reusables/frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,4 @@ Python built-in support
176176
cryptography, Cryptography library
177177
pycryptodome, Cryptography library
178178
pycryptodomex, Cryptography library
179+
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
@@ -20,6 +20,7 @@ private import semmle.python.frameworks.Mysql
2020
private import semmle.python.frameworks.MySQLdb
2121
private import semmle.python.frameworks.Psycopg2
2222
private import semmle.python.frameworks.PyMySQL
23+
private import semmle.python.frameworks.Rsa
2324
private import semmle.python.frameworks.Simplejson
2425
private import semmle.python.frameworks.Stdlib
2526
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) = hashNameArg.getALocalSource() 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) = hashNameArg.getALocalSource() 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/ConceptsTest.expected

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.ConceptsTest
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
argumentToEnsureNotTaintedNotMarkedAsSpurious
2+
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
3+
failures
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import experimental.meta.InlineTaintTest
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Following examples from https://stuvel.eu/python-rsa-doc/usage.html
2+
import rsa
3+
4+
# using a rather low keysize, since otherwise it takes quite long to run.
5+
(public_key, private_key) = rsa.newkeys(512) # $ PublicKeyGeneration keySize=512
6+
(public_key, private_key) = rsa.newkeys(nbits=512) # $ PublicKeyGeneration keySize=512
7+
8+
9+
# ------------------------------------------------------------------------------
10+
# encrypt/decrypt
11+
# ------------------------------------------------------------------------------
12+
13+
# Note: These are using PKCS#1 v1.5
14+
15+
print("encrypt/decrypt")
16+
17+
secret_message = b"secret message"
18+
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
21+
22+
print("encrypted={}".format(encrypted))
23+
24+
print()
25+
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
28+
29+
print("decrypted={}".format(decrypted))
30+
assert decrypted == secret_message
31+
32+
print("\n---\n")
33+
34+
# ------------------------------------------------------------------------------
35+
# sign/verify
36+
# ------------------------------------------------------------------------------
37+
38+
# Note: These are using PKCS#1 v1.5
39+
40+
print("sign/verify")
41+
42+
message = b"message"
43+
other_message = b"other message"
44+
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
49+
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
52+
53+
assert signature == signature_from_hash
54+
55+
print("signature={}".format(signature))
56+
57+
print()
58+
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
61+
62+
print("Signature verified (as expected)")
63+
64+
try:
65+
rsa.verify(other_message, signature, public_key) # $ CryptographicOperation CryptographicOperationAlgorithm=RSA CryptographicOperationInput=other_message CryptographicOperationInput=signature
66+
raise Exception("Signature verified (unexpected)")
67+
except rsa.VerificationError:
68+
print("Signature mismatch (as expected)")

0 commit comments

Comments
 (0)