Skip to content

Commit b513033

Browse files
authored
Merge pull request #7021 from erik-krogh/cwe326
JS: Add insufficient key size query
2 parents 891694b + 8727060 commit b513033

File tree

9 files changed

+308
-19
lines changed

9 files changed

+308
-19
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The `js/insufficient-key-size` query has been added. It highlights the creation of cryptographic keys with a short key size.

javascript/ql/lib/semmle/javascript/frameworks/CryptoLibraries.qll

Lines changed: 161 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ abstract class CryptographicOperation extends Expr {
2525
*/
2626
abstract class CryptographicKey extends DataFlow::ValueNode { }
2727

28+
/**
29+
* The creation of a cryptographic key.
30+
*/
31+
abstract class CryptographicKeyCreation extends DataFlow::Node {
32+
/**
33+
* Gets the algorithm used to create the key.
34+
*/
35+
abstract CryptographicAlgorithm getAlgorithm();
36+
37+
/**
38+
* Gets the size of the key.
39+
*/
40+
abstract int getSize();
41+
42+
/**
43+
* Gets whether the key is symmetric.
44+
*/
45+
abstract predicate isSymmetricKey();
46+
}
47+
2848
/**
2949
* A key used in a cryptographic algorithm, viewed as a `CredentialsExpr`.
3050
*/
@@ -141,21 +161,62 @@ private module NodeJSCrypto {
141161
* Also matches `createHash`, `createHmac`, `createSign` instead of `createCipher`.
142162
*/
143163

144-
exists(DataFlow::SourceNode mod, string createSuffix |
145-
createSuffix = "Hash" or
146-
createSuffix = "Hmac" or
147-
createSuffix = "Sign" or
148-
createSuffix = "Cipher"
149-
|
164+
exists(DataFlow::SourceNode mod |
150165
mod = DataFlow::moduleImport("crypto") and
151-
this = mod.getAMemberCall("create" + createSuffix) and
166+
this = mod.getAMemberCall("create" + ["Hash", "Hmac", "Sign", "Cipher"]) and
152167
algorithm.matchesName(getArgument(0).getStringValue())
153168
)
154169
}
155170

156171
CryptographicAlgorithm getAlgorithm() { result = algorithm }
157172
}
158173

174+
private class CreateKey extends CryptographicKeyCreation, DataFlow::CallNode {
175+
boolean symmetric;
176+
177+
CreateKey() {
178+
// crypto.generateKey(type, options, callback)
179+
// crypto.generateKeyPair(type, options, callback)
180+
// crypto.generateKeyPairSync(type, options)
181+
// crypto.generateKeySync(type, options)
182+
exists(DataFlow::SourceNode mod, string keyType |
183+
keyType = "Key" and symmetric = true
184+
or
185+
keyType = "KeyPair" and symmetric = false
186+
|
187+
mod = DataFlow::moduleImport("crypto") and
188+
this = mod.getAMemberCall("generate" + keyType + ["", "Sync"])
189+
)
190+
}
191+
192+
override CryptographicAlgorithm getAlgorithm() {
193+
result.matchesName(getArgument(0).getStringValue())
194+
}
195+
196+
override int getSize() {
197+
symmetric = true and
198+
result = getOptionArgument(1, "length").getIntValue()
199+
or
200+
symmetric = false and
201+
result = getOptionArgument(1, "modulusLength").getIntValue()
202+
}
203+
204+
override predicate isSymmetricKey() { symmetric = true }
205+
}
206+
207+
private class CreateDiffieHellmanKey extends CryptographicKeyCreation, DataFlow::CallNode {
208+
// require("crypto").createDiffieHellman(prime_length);
209+
CreateDiffieHellmanKey() {
210+
this = DataFlow::moduleMember("crypto", "createDiffieHellman").getACall()
211+
}
212+
213+
override CryptographicAlgorithm getAlgorithm() { none() }
214+
215+
override int getSize() { result = getArgument(0).getIntValue() }
216+
217+
override predicate isSymmetricKey() { none() }
218+
}
219+
159220
private class Apply extends CryptographicOperation, MethodCallExpr {
160221
InstantiatedAlgorithm instantiation;
161222

@@ -282,6 +343,35 @@ private module CryptoJS {
282343
)
283344
}
284345
}
346+
347+
private class CreateKey extends CryptographicKeyCreation, DataFlow::CallNode {
348+
string algorithm;
349+
int optionArg;
350+
351+
CreateKey() {
352+
// var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 });
353+
this =
354+
getAlgorithmExpr(any(CryptographicAlgorithm algo | algo.getName() = algorithm)).getACall() and
355+
optionArg = 2
356+
or
357+
// var key = CryptoJS.algo.PBKDF2.create({ keySize: 8 });
358+
this =
359+
DataFlow::moduleMember("crypto-js", "algo")
360+
.getAPropertyRead(algorithm)
361+
.getAMethodCall("create") and
362+
optionArg = 0
363+
}
364+
365+
override CryptographicAlgorithm getAlgorithm() { result.matchesName(algorithm) }
366+
367+
override int getSize() {
368+
result = getOptionArgument(optionArg, "keySize").getIntValue() * 32 // size is in words
369+
or
370+
result = getArgument(optionArg).getIntValue() * 32 // size is in words
371+
}
372+
373+
override predicate isSymmetricKey() { any() }
374+
}
285375
}
286376

287377
/**
@@ -467,6 +557,39 @@ private module Forge {
467557
private class Key extends CryptographicKey {
468558
Key() { this = any(KeyCipher cipher).getKey() }
469559
}
560+
561+
private class CreateKey extends CryptographicKeyCreation, DataFlow::CallNode {
562+
CryptographicAlgorithm algorithm;
563+
564+
CreateKey() {
565+
// var cipher = forge.rc2.createEncryptionCipher(key, 128);
566+
this =
567+
getAnImportNode()
568+
.getAPropertyRead(any(string s | algorithm.matchesName(s)))
569+
.getAMemberCall("createEncryptionCipher")
570+
or
571+
// var key = forge.random.getBytesSync(16);
572+
// var cipher = forge.cipher.createCipher('AES-CBC', key);
573+
this =
574+
getAnImportNode()
575+
.getAPropertyRead("cipher")
576+
.getAMemberCall(["createCipher", "createDecipher"]) and
577+
algorithm.matchesName(this.getArgument(0).getStringValue())
578+
}
579+
580+
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
581+
582+
override int getSize() {
583+
result = this.getArgument(1).getIntValue()
584+
or
585+
exists(DataFlow::CallNode call | call.getCalleeName() = ["getBytes", "getBytesSync"] |
586+
getArgument(1).getALocalSource() = call and
587+
result = call.getArgument(0).getIntValue() * 8 // bytes to bits
588+
)
589+
}
590+
591+
override predicate isSymmetricKey() { any() }
592+
}
470593
}
471594

472595
/**
@@ -556,13 +679,38 @@ private module Hasha {
556679

557680
override CryptographicAlgorithm getAlgorithm() { result = algorithm }
558681
}
682+
}
559683

560-
/**
561-
* Provides classes for working with the `express-jwt` package (https://github.com/auth0/express-jwt);
562-
*/
563-
module ExpressJwt {
564-
private class Key extends CryptographicKey {
565-
Key() { this = DataFlow::moduleMember("express-jwt", "sign").getACall().getArgument(1) }
684+
/**
685+
* Provides classes for working with the `express-jwt` package (https://github.com/auth0/express-jwt);
686+
*/
687+
private module ExpressJwt {
688+
private class Key extends CryptographicKey {
689+
Key() { this = DataFlow::moduleMember("express-jwt", "sign").getACall().getArgument(1) }
690+
}
691+
}
692+
693+
/**
694+
* Provides classes for working with the `node-rsa` package (https://www.npmjs.com/package/node-rsa)
695+
*/
696+
private module NodeRsa {
697+
private class CreateKey extends CryptographicKeyCreation, API::InvokeNode {
698+
CryptographicAlgorithm algorithm;
699+
700+
CreateKey() {
701+
this = API::moduleImport("node-rsa").getAnInstantiation()
702+
or
703+
this = API::moduleImport("node-rsa").getInstance().getMember("generateKeyPair").getACall()
566704
}
705+
706+
override CryptographicAlgorithm getAlgorithm() { result.matchesName("rsa") }
707+
708+
override int getSize() {
709+
result = this.getArgument(0).getIntValue()
710+
or
711+
result = this.getOptionArgument(0, "b").getIntValue()
712+
}
713+
714+
override predicate isSymmetricKey() { none() }
567715
}
568716
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ private module AlgorithmNames {
4646
name = ["ARGON2", "PBKDF2", "BCRYPT", "SCRYPT"]
4747
}
4848

49-
predicate isWeakPasswordHashingAlgorithm(string name) { none() }
49+
predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
5050
}
5151

5252
private import AlgorithmNames
@@ -85,11 +85,13 @@ abstract class CryptographicAlgorithm extends TCryptographicAlgorithm {
8585

8686
/**
8787
* Holds if the name of this algorithm matches `name` modulo case,
88-
* white space, dashes, and underscores.
88+
* white space, dashes, underscores, and anything after a dash in the name
89+
* (to ignore modes of operation, such as CBC or ECB).
8990
*/
9091
bindingset[name]
9192
predicate matchesName(string name) {
92-
name.toUpperCase().regexpReplaceAll("[-_ ]", "") = getName()
93+
[name.toUpperCase(), name.toUpperCase().regexpCapture("^(\\w+)(?:-.*)?$", 1)]
94+
.regexpReplaceAll("[-_ ]", "") = getName()
9395
}
9496

9597
/**
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
An encryption key should be at least 2048-bit long when using RSA encryption, and 128-bit long when using
16+
symmetric encryption.
17+
</p>
18+
</recommendation>
19+
20+
<references>
21+
<li>
22+
Wikipedia:
23+
<a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)">RSA</a>.
24+
</li>
25+
<li>
26+
Wikipedia:
27+
<a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard">AES</a>.
28+
</li>
29+
<li>
30+
NodeJS:
31+
<a href="https://nodejs.org/api/crypto.html">Crypto</a>.
32+
</li>
33+
<li>
34+
NIST:
35+
<a href="https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf">
36+
Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths</a>.
37+
</li>
38+
<li>
39+
Wikipedia:
40+
<a href="https://en.wikipedia.org/wiki/Key_size">Key size</a>
41+
</li>
42+
</references>
43+
</qhelp>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @name Use of a weak cryptographic key
3+
* @description Using a weak cryptographic key can allow an attacker to compromise security.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @security-severity 7.5
7+
* @precision high
8+
* @id js/insufficient-key-size
9+
* @tags security
10+
* external/cwe/cwe-326
11+
*/
12+
13+
import javascript
14+
15+
from CryptographicKeyCreation key, int size, string msg, string algo
16+
where
17+
size = key.getSize() and
18+
(
19+
algo = key.getAlgorithm() + " "
20+
or
21+
not exists(key.getAlgorithm()) and algo = ""
22+
) and
23+
(
24+
size < 128 and
25+
key.isSymmetricKey() and
26+
msg =
27+
"Creation of an symmetric " + algo + "key uses " + size +
28+
" bits, which is below 128 and considered breakable."
29+
or
30+
size < 2048 and
31+
not key.isSymmetricKey() and
32+
msg =
33+
"Creation of an asymmetric " + algo + "key uses " + size +
34+
" bits, which is below 2048 and considered breakable."
35+
)
36+
select key, msg
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
| tst.js:3:14:3:71 | crypto. ... 1024 }) | Creation of an asymmetric RSA key uses 1024 bits, which is below 2048 and considered breakable. |
2+
| tst.js:7:14:7:59 | crypto. ... : 64 }) | Creation of an symmetric key uses 64 bits, which is below 128 and considered breakable. |
3+
| tst.js:13:14:13:56 | CryptoJ ... e: 2 }) | Creation of an symmetric PBKDF2 key uses 64 bits, which is below 128 and considered breakable. |
4+
| tst.js:14:14:14:60 | CryptoJ ... e: 2 }) | Creation of an symmetric PBKDF2 key uses 64 bits, which is below 128 and considered breakable. |
5+
| tst.js:15:14:15:60 | CryptoJ ... e: 2 }) | Creation of an symmetric EVPKDF key uses 64 bits, which is below 128 and considered breakable. |
6+
| tst.js:19:12:19:57 | forge.r ... rd, 64) | Creation of an symmetric RC2 key uses 64 bits, which is below 128 and considered breakable. |
7+
| tst.js:26:12:26:53 | forge.c ... , key2) | Creation of an symmetric AES key uses 64 bits, which is below 128 and considered breakable. |
8+
| tst.js:30:12:30:56 | forge.c ... , key3) | Creation of an symmetric 3DES key uses 64 bits, which is below 128 and considered breakable. |
9+
| tst.js:35:13:35:43 | crypto. ... an(512) | Creation of an asymmetric key uses 512 bits, which is below 2048 and considered breakable. |
10+
| tst.js:39:13:39:33 | new Nod ... : 512}) | Creation of an asymmetric RSA key uses 512 bits, which is below 2048 and considered breakable. |
11+
| tst.js:43:1:43:31 | key.gen ... 65537) | Creation of an asymmetric RSA key uses 512 bits, which is below 2048 and considered breakable. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-326/InsufficientKeySize.ql
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const crypto = require("crypto");
2+
3+
const bad1 = crypto.generateKeyPairSync("rsa", { modulusLength: 1024 }); // NOT OK
4+
5+
const good1 = crypto.generateKeyPairSync("rsa", { modulusLength: 4096 }); // OK
6+
7+
const bad2 = crypto.generateKeySync("hmac", { length: 64 }); // NOT OK
8+
9+
const good2 = crypto.generateKeySync("aes", { length: 256 }); // OK
10+
11+
var CryptoJS = require("crypto-js");
12+
13+
const bad3 = CryptoJS.algo.PBKDF2.create({ keySize: 2 }); // NOT OK
14+
const bad4 = CryptoJS.PBKDF2(password, salt, { keySize: 2 }); // NOT OK
15+
const bad5 = CryptoJS.EvpKDF(password, salt, { keySize: 2 }); // NOT OK
16+
const bad6 = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); // OK
17+
18+
const forge = require("node-forge");
19+
var bad7 = forge.rc2.createEncryptionCipher(password, 64); // NOT OK
20+
var good3 = forge.rc2.createEncryptionCipher(password, 128); // OK
21+
22+
var key1 = forge.random.getBytesSync(16);
23+
var good4 = forge.cipher.createCipher('AES-CBC', key1); // OK
24+
25+
var key2 = forge.random.getBytesSync(8);
26+
var bad8 = forge.cipher.createCipher('AES-CBC', key2); // NOT OK
27+
28+
var myBuffer = forge.util.createBuffer(manyBytes);
29+
var key3 = myBuffer.getBytes(8);
30+
var bad9 = forge.cipher.createDecipher('3DES-CBC', key3); // NOT OK
31+
32+
var key4 = myBuffer.getBytes(16);
33+
var good5 = forge.cipher.createDecipher('AES-CBC', key4); // OK
34+
35+
var bad10 = crypto.createDiffieHellman(512);
36+
var good6 = crypto.createDiffieHellman(2048);
37+
38+
const NodeRSA = require('node-rsa');
39+
var bad11 = new NodeRSA({b: 512}); // NOT OK
40+
var good7 = new NodeRSA({b: 4096}); // OK
41+
42+
var key = new NodeRSA(); // OK
43+
key.generateKeyPair(512, 65537); // NOT OK
44+
key.generateKeyPair(4096, 65537); // OK

0 commit comments

Comments
 (0)