Skip to content

Commit 8d195e3

Browse files
authored
Merge pull request github#9157 from alexrford/crypto-op-block-mode
Ruby/Python: Add a `BlockMode` concept for `CryptographicOperations`
2 parents 6c8982b + 4861a98 commit 8d195e3

File tree

34 files changed

+478
-113
lines changed

34 files changed

+478
-113
lines changed

javascript/ql/lib/semmle/javascript/internal/ConceptsImports.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
*/
55

66
import semmle.javascript.dataflow.DataFlow::DataFlow as DataFlow
7+
import semmle.javascript.security.CryptoAlgorithms as CryptoAlgorithms

javascript/ql/lib/semmle/javascript/internal/ConceptsShared.qll

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,79 @@
1111
*/
1212

1313
private import ConceptsImports
14+
15+
/**
16+
* Provides models for cryptographic concepts.
17+
*
18+
* Note: The `CryptographicAlgorithm` class currently doesn't take weak keys into
19+
* consideration for the `isWeak` member predicate. So RSA is always considered
20+
* secure, although using a low number of bits will actually make it insecure. We plan
21+
* to improve our libraries in the future to more precisely capture this aspect.
22+
*/
23+
module Cryptography {
24+
class CryptographicAlgorithm = CryptoAlgorithms::CryptographicAlgorithm;
25+
26+
class EncryptionAlgorithm = CryptoAlgorithms::EncryptionAlgorithm;
27+
28+
class HashingAlgorithm = CryptoAlgorithms::HashingAlgorithm;
29+
30+
class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm;
31+
32+
/**
33+
* A data-flow node that is an application of a cryptographic algorithm. For example,
34+
* encryption, decryption, signature-validation.
35+
*
36+
* Extend this class to refine existing API models. If you want to model new APIs,
37+
* extend `CryptographicOperation::Range` instead.
38+
*/
39+
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
40+
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
41+
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
42+
43+
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
44+
DataFlow::Node getAnInput() { result = super.getAnInput() }
45+
46+
/**
47+
* Gets the block mode used to perform this cryptographic operation.
48+
* This may have no result - for example if the `CryptographicAlgorithm` used
49+
* is a stream cipher rather than a block cipher.
50+
*/
51+
BlockMode getBlockMode() { result = super.getBlockMode() }
52+
}
53+
54+
/** Provides classes for modeling new applications of a cryptographic algorithms. */
55+
module CryptographicOperation {
56+
/**
57+
* A data-flow node that is an application of a cryptographic algorithm. For example,
58+
* encryption, decryption, signature-validation.
59+
*
60+
* Extend this class to model new APIs. If you want to refine existing API models,
61+
* extend `CryptographicOperation` instead.
62+
*/
63+
abstract class Range extends DataFlow::Node {
64+
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
65+
abstract CryptographicAlgorithm getAlgorithm();
66+
67+
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
68+
abstract DataFlow::Node getAnInput();
69+
70+
/**
71+
* Gets the block mode used to perform this cryptographic operation.
72+
* This may have no result - for example if the `CryptographicAlgorithm` used
73+
* is a stream cipher rather than a block cipher.
74+
*/
75+
abstract BlockMode getBlockMode();
76+
}
77+
}
78+
79+
/**
80+
* A cryptographic block cipher mode of operation. This can be used to encrypt
81+
* data of arbitrary length using a block encryption algorithm.
82+
*/
83+
class BlockMode extends string {
84+
BlockMode() { this = ["ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR", "OPENPGP"] }
85+
86+
/** Holds if this block mode is considered to be insecure. */
87+
predicate isWeak() { this = "ECB" }
88+
}
89+
}

javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm
8181
override string getName() { result = name }
8282

8383
override predicate isWeak() { isWeak = true }
84+
85+
/** Holds if this algorithm is a stream cipher. */
86+
predicate isStreamCipher() { isStreamCipher(name) }
8487
}
8588

8689
/**

javascript/ql/lib/semmle/javascript/security/internal/CryptoAlgorithmNames.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ predicate isStrongPasswordHashingAlgorithm(string name) {
6767
predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
6868

6969
/**
70-
* Holds if `name` corresponds to a weak block cipher mode of operation.
70+
* Holds if `name` corresponds to a stream cipher.
7171
*/
72-
predicate isWeakBlockMode(string name) { name = "ECB" }
72+
predicate isStreamCipher(string name) { name = ["CHACHA", "RC4", "ARC4", "ARCFOUR", "RABBIT"] }

python/ql/lib/semmle/python/Concepts.qll

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,38 +1211,5 @@ module Cryptography {
12111211
}
12121212
}
12131213

1214-
import semmle.python.concepts.CryptoAlgorithms
1215-
1216-
/**
1217-
* A data-flow node that is an application of a cryptographic algorithm. For example,
1218-
* encryption, decryption, signature-validation.
1219-
*
1220-
* Extend this class to refine existing API models. If you want to model new APIs,
1221-
* extend `CryptographicOperation::Range` instead.
1222-
*/
1223-
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
1224-
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
1225-
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
1226-
1227-
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
1228-
DataFlow::Node getAnInput() { result = super.getAnInput() }
1229-
}
1230-
1231-
/** Provides classes for modeling new applications of a cryptographic algorithms. */
1232-
module CryptographicOperation {
1233-
/**
1234-
* A data-flow node that is an application of a cryptographic algorithm. For example,
1235-
* encryption, decryption, signature-validation.
1236-
*
1237-
* Extend this class to model new APIs. If you want to refine existing API models,
1238-
* extend `CryptographicOperation` instead.
1239-
*/
1240-
abstract class Range extends DataFlow::Node {
1241-
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
1242-
abstract CryptographicAlgorithm getAlgorithm();
1243-
1244-
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
1245-
abstract DataFlow::Node getAnInput();
1246-
}
1247-
}
1214+
import semmle.python.internal.ConceptsShared::Cryptography
12481215
}

python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm
8181
override string getName() { result = name }
8282

8383
override predicate isWeak() { isWeak = true }
84+
85+
/** Holds if this algorithm is a stream cipher. */
86+
predicate isStreamCipher() { isStreamCipher(name) }
8487
}
8588

8689
/**

python/ql/lib/semmle/python/concepts/internal/CryptoAlgorithmNames.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ predicate isStrongPasswordHashingAlgorithm(string name) {
6767
predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
6868

6969
/**
70-
* Holds if `name` corresponds to a weak block cipher mode of operation.
70+
* Holds if `name` corresponds to a stream cipher.
7171
*/
72-
predicate isWeakBlockMode(string name) { name = "ECB" }
72+
predicate isStreamCipher(string name) { name = ["CHACHA", "RC4", "ARC4", "ARCFOUR", "RABBIT"] }

python/ql/lib/semmle/python/frameworks/Cryptodome.qll

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,20 @@ private module CryptodomeModel {
108108
DataFlow::CallCfgNode {
109109
string methodName;
110110
string cipherName;
111+
API::CallNode newCall;
111112

112113
CryptodomeGenericCipherOperation() {
113114
methodName in [
114115
"encrypt", "decrypt", "verify", "update", "hexverify", "encrypt_and_digest",
115116
"decrypt_and_verify"
116117
] and
117-
this =
118+
newCall =
118119
API::moduleImport(["Crypto", "Cryptodome"])
119120
.getMember("Cipher")
120121
.getMember(cipherName)
121122
.getMember("new")
122-
.getReturn()
123-
.getMember(methodName)
124-
.getACall()
123+
.getACall() and
124+
this = newCall.getReturn().getMember(methodName).getACall()
125125
}
126126

127127
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(cipherName) }
@@ -155,6 +155,20 @@ private module CryptodomeModel {
155155
this.getArgByName("mac_tag")
156156
]
157157
}
158+
159+
override Cryptography::BlockMode getBlockMode() {
160+
// `modeName` is of the form "MODE_<BlockMode>"
161+
exists(string modeName |
162+
newCall.getArg(1) =
163+
API::moduleImport(["Crypto", "Cryptodome"])
164+
.getMember("Cipher")
165+
.getMember(cipherName)
166+
.getMember(modeName)
167+
.getAUse()
168+
|
169+
result = modeName.splitAt("_", 1)
170+
)
171+
}
158172
}
159173

160174
/**
@@ -192,6 +206,8 @@ private module CryptodomeModel {
192206
result in [this.getArg(1), this.getArgByName("signature")]
193207
)
194208
}
209+
210+
override Cryptography::BlockMode getBlockMode() { none() }
195211
}
196212

197213
/**
@@ -215,5 +231,7 @@ private module CryptodomeModel {
215231
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
216232

217233
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
234+
235+
override Cryptography::BlockMode getBlockMode() { none() }
218236
}
219237
}

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,19 @@ private module CryptographyModel {
170170
.getMember(algorithmName)
171171
}
172172

173+
/** Gets a reference to a `cryptography.hazmat.primitives.ciphers.modes` Class */
174+
API::Node modeClassRef(string modeName) {
175+
result =
176+
API::moduleImport("cryptography")
177+
.getMember("hazmat")
178+
.getMember("primitives")
179+
.getMember("ciphers")
180+
.getMember("modes")
181+
.getMember(modeName)
182+
}
183+
173184
/** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */
174-
API::Node cipherInstance(string algorithmName) {
185+
API::Node cipherInstance(string algorithmName, string modeName) {
175186
exists(API::CallNode call | result = call.getReturn() |
176187
call =
177188
API::moduleImport("cryptography")
@@ -182,7 +193,12 @@ private module CryptographyModel {
182193
.getACall() and
183194
algorithmClassRef(algorithmName).getReturn().getAUse() in [
184195
call.getArg(0), call.getArgByName("algorithm")
185-
]
196+
] and
197+
exists(DataFlow::Node modeArg | modeArg in [call.getArg(1), call.getArgByName("mode")] |
198+
if modeArg = modeClassRef(_).getReturn().getAUse()
199+
then modeArg = modeClassRef(modeName).getReturn().getAUse()
200+
else modeName = "<None or unknown>"
201+
)
186202
)
187203
}
188204

@@ -192,10 +208,11 @@ private module CryptographyModel {
192208
class CryptographyGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
193209
DataFlow::MethodCallNode {
194210
string algorithmName;
211+
string modeName;
195212

196213
CryptographyGenericCipherOperation() {
197214
this =
198-
cipherInstance(algorithmName)
215+
cipherInstance(algorithmName, modeName)
199216
.getMember(["decryptor", "encryptor"])
200217
.getReturn()
201218
.getMember(["update", "update_into"])
@@ -207,6 +224,8 @@ private module CryptographyModel {
207224
}
208225

209226
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
227+
228+
override Cryptography::BlockMode getBlockMode() { result = modeName }
210229
}
211230
}
212231

@@ -257,6 +276,8 @@ private module CryptographyModel {
257276
}
258277

259278
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
279+
280+
override Cryptography::BlockMode getBlockMode() { none() }
260281
}
261282
}
262283
}

python/ql/lib/semmle/python/frameworks/Rsa.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ private module Rsa {
4141
override DataFlow::Node getAnInput() {
4242
result in [this.getArg(0), this.getArgByName("message")]
4343
}
44+
45+
override Cryptography::BlockMode getBlockMode() { none() }
4446
}
4547

4648
/**
@@ -54,6 +56,8 @@ private module Rsa {
5456
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" }
5557

5658
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("crypto")] }
59+
60+
override Cryptography::BlockMode getBlockMode() { none() }
5761
}
5862

5963
/**
@@ -79,6 +83,8 @@ private module Rsa {
7983
override DataFlow::Node getAnInput() {
8084
result in [this.getArg(0), this.getArgByName("message")]
8185
}
86+
87+
override Cryptography::BlockMode getBlockMode() { none() }
8288
}
8389

8490
/**
@@ -100,6 +106,8 @@ private module Rsa {
100106
or
101107
result in [this.getArg(1), this.getArgByName("signature")]
102108
}
109+
110+
override Cryptography::BlockMode getBlockMode() { none() }
103111
}
104112

105113
/**
@@ -122,6 +130,8 @@ private module Rsa {
122130
override DataFlow::Node getAnInput() {
123131
result in [this.getArg(0), this.getArgByName("message")]
124132
}
133+
134+
override Cryptography::BlockMode getBlockMode() { none() }
125135
}
126136

127137
/**
@@ -137,5 +147,7 @@ private module Rsa {
137147
override DataFlow::Node getAnInput() {
138148
result in [this.getArg(0), this.getArgByName("hash_value")]
139149
}
150+
151+
override Cryptography::BlockMode getBlockMode() { none() }
140152
}
141153
}

0 commit comments

Comments
 (0)