Skip to content

Commit c5f8265

Browse files
committed
Python: Model encrypt/decrypt in cryptography package
I introduced a InternalTypeTracking module, since the type-tracking code got so verbose, that it was impossible to get an overview of the relevant predicates. (this means the "first" type-tracking predicate that is usually private, cannot be marked private anymore, since it needs to be exposed in the private module.
1 parent bf6f507 commit c5f8265

File tree

3 files changed

+166
-5
lines changed

3 files changed

+166
-5
lines changed

python/ql/src/semmle/python/frameworks/Cryptography.qll

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,165 @@ private module CryptographyModel {
169169
// Note: There is not really a key-size argument, since it's always specified by the curve.
170170
override DataFlow::Node getKeySizeArg() { none() }
171171
}
172+
173+
/** Provides models for the `cryptography.hazmat.primitives.ciphers` package */
174+
private module Ciphers {
175+
/** Gets a reference to a `cryptography.hazmat.primitives.ciphers.algorithms` Class */
176+
API::Node algorithmClassRef(string algorithmName) {
177+
result =
178+
API::moduleImport("cryptography")
179+
.getMember("hazmat")
180+
.getMember("primitives")
181+
.getMember("ciphers")
182+
.getMember("algorithms")
183+
.getMember(algorithmName)
184+
}
185+
186+
/**
187+
* Internal module making it easy to hide verbose type-tracking helpers.
188+
*
189+
* These turned out to be so verbose, that it was impossible to get an overview of
190+
* the relevant predicates without hiding them away.
191+
*/
192+
private module InternalTypeTracking {
193+
/** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */
194+
DataFlow::LocalSourceNode cipherInstance(DataFlow::TypeTracker t, string algorithmName) {
195+
t.start() and
196+
exists(DataFlow::CallCfgNode call | result = call |
197+
call =
198+
API::moduleImport("cryptography")
199+
.getMember("hazmat")
200+
.getMember("primitives")
201+
.getMember("ciphers")
202+
.getMember("Cipher")
203+
.getACall() and
204+
algorithmClassRef(algorithmName).getReturn().getAUse() in [
205+
call.getArg(0), call.getArgByName("algorithm")
206+
]
207+
)
208+
or
209+
// Due to bad performance when using normal setup with `cipherInstance(t2, algorithmName).track(t2, t)`
210+
// we have inlined that code and forced a join
211+
exists(DataFlow::TypeTracker t2 |
212+
exists(DataFlow::StepSummary summary |
213+
cipherInstance_first_join(t2, algorithmName, result, summary) and
214+
t = t2.append(summary)
215+
)
216+
)
217+
}
218+
219+
pragma[nomagic]
220+
private predicate cipherInstance_first_join(
221+
DataFlow::TypeTracker t2, string algorithmName, DataFlow::Node res,
222+
DataFlow::StepSummary summary
223+
) {
224+
DataFlow::StepSummary::step(cipherInstance(t2, algorithmName), res, summary)
225+
}
226+
227+
/** Gets a reference to the encryptor of a Cipher instance using algorithm with `algorithmName`. */
228+
DataFlow::LocalSourceNode cipherEncryptor(DataFlow::TypeTracker t, string algorithmName) {
229+
t.start() and
230+
exists(DataFlow::AttrRead attr |
231+
result.(DataFlow::CallCfgNode).getFunction() = attr and
232+
attr.getAttributeName() = "encryptor" and
233+
attr.getObject() = cipherInstance(algorithmName)
234+
)
235+
or
236+
// Due to bad performance when using normal setup with `cipherEncryptor(t2, algorithmName).track(t2, t)`
237+
// we have inlined that code and forced a join
238+
exists(DataFlow::TypeTracker t2 |
239+
exists(DataFlow::StepSummary summary |
240+
cipherEncryptor_first_join(t2, algorithmName, result, summary) and
241+
t = t2.append(summary)
242+
)
243+
)
244+
}
245+
246+
pragma[nomagic]
247+
private predicate cipherEncryptor_first_join(
248+
DataFlow::TypeTracker t2, string algorithmName, DataFlow::Node res,
249+
DataFlow::StepSummary summary
250+
) {
251+
DataFlow::StepSummary::step(cipherEncryptor(t2, algorithmName), res, summary)
252+
}
253+
254+
/** Gets a reference to the dncryptor of a Cipher instance using algorithm with `algorithmName`. */
255+
DataFlow::LocalSourceNode cipherDecryptor(DataFlow::TypeTracker t, string algorithmName) {
256+
t.start() and
257+
exists(DataFlow::AttrRead attr |
258+
result.(DataFlow::CallCfgNode).getFunction() = attr and
259+
attr.getAttributeName() = "decryptor" and
260+
attr.getObject() = cipherInstance(algorithmName)
261+
)
262+
or
263+
// Due to bad performance when using normal setup with `cipherDecryptor(t2, algorithmName).track(t2, t)`
264+
// we have inlined that code and forced a join
265+
exists(DataFlow::TypeTracker t2 |
266+
exists(DataFlow::StepSummary summary |
267+
cipherDecryptor_first_join(t2, algorithmName, result, summary) and
268+
t = t2.append(summary)
269+
)
270+
)
271+
}
272+
273+
pragma[nomagic]
274+
private predicate cipherDecryptor_first_join(
275+
DataFlow::TypeTracker t2, string algorithmName, DataFlow::Node res,
276+
DataFlow::StepSummary summary
277+
) {
278+
DataFlow::StepSummary::step(cipherDecryptor(t2, algorithmName), res, summary)
279+
}
280+
}
281+
282+
private import InternalTypeTracking
283+
284+
/** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */
285+
DataFlow::Node cipherInstance(string algorithmName) {
286+
cipherInstance(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
287+
}
288+
289+
/**
290+
* Gets a reference to the encryptor of a Cipher instance using algorithm with `algorithmName`.
291+
*
292+
* You obtain an encryptor by using the `encryptor()` method on a Cipher instance.
293+
*/
294+
DataFlow::Node cipherEncryptor(string algorithmName) {
295+
cipherEncryptor(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
296+
}
297+
298+
/**
299+
* Gets a reference to the decryptor of a Cipher instance using algorithm with `algorithmName`.
300+
*
301+
* You obtain an decryptor by using the `decryptor()` method on a Cipher instance.
302+
*/
303+
DataFlow::Node cipherDecryptor(string algorithmName) {
304+
cipherDecryptor(DataFlow::TypeTracker::end(), algorithmName).flowsTo(result)
305+
}
306+
307+
/**
308+
* An encrypt or decrypt operation from `cryptography.hazmat.primitives.ciphers`.
309+
*/
310+
class CryptographyGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
311+
DataFlow::CallCfgNode {
312+
string algorithmName;
313+
314+
CryptographyGenericCipherOperation() {
315+
exists(DataFlow::AttrRead attr |
316+
this.getFunction() = attr and
317+
attr.getAttributeName() = ["update", "update_into"] and
318+
(
319+
attr.getObject() = cipherEncryptor(algorithmName)
320+
or
321+
attr.getObject() = cipherDecryptor(algorithmName)
322+
)
323+
)
324+
}
325+
326+
override Cryptography::CryptographicAlgorithm getAlgorithm() {
327+
result.matchesName(algorithmName)
328+
}
329+
330+
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
331+
}
332+
}
172333
}

python/ql/test/experimental/library-tests/frameworks/cryptography/test_aes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@
2222

2323
encryptor = cipher.encryptor()
2424
print(padding_len)
25-
encrypted = encryptor.update(secret_message)
26-
encrypted += encryptor.update(padding)
25+
encrypted = encryptor.update(secret_message) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=secret_message
26+
encrypted += encryptor.update(padding) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=padding
2727
encrypted += encryptor.finalize()
2828

2929
print("encrypted={}".format(encrypted))
3030

3131
print()
3232

3333
decryptor = cipher.decryptor()
34-
decrypted = decryptor.update(encrypted)
34+
decrypted = decryptor.update(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=AES CryptographicOperationInput=encrypted
3535
decrypted += decryptor.finalize()
3636

3737
decrypted = decrypted[:-padding_len]

python/ql/test/experimental/library-tests/frameworks/cryptography/test_rc4.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@
1717
secret_message = b"secret message"
1818

1919
encryptor = cipher.encryptor()
20-
encrypted = encryptor.update(secret_message)
20+
encrypted = encryptor.update(secret_message) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=secret_message
2121
encrypted += encryptor.finalize()
2222

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

2525
print()
2626

2727
decryptor = cipher.decryptor()
28-
decrypted = decryptor.update(encrypted)
28+
decrypted = decryptor.update(encrypted) # $ CryptographicOperation CryptographicOperationAlgorithm=ARC4 CryptographicOperationInput=encrypted
2929
decrypted += decryptor.finalize()
3030

3131
print("decrypted={}".format(decrypted))

0 commit comments

Comments
 (0)