Skip to content

Commit d56c82f

Browse files
add a query that detects hardcoded keys
1 parent f1fd470 commit d56c82f

File tree

6 files changed

+350
-0
lines changed

6 files changed

+350
-0
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Hardcoded keys should not be used for creating encryption ciphers. Data encrypted using hardcoded keys are more vulnerable to the possiblity of recovering them.</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>Use a randomly generated key material to initialize the encryption cipher.</p>
11+
</recommendation>
12+
13+
<example>
14+
<p>The following example shows a few cases of instantiating a cipher with various encryption keys. In the 'BAD' cases, the key material is hardcoded, making the encrypted data vulnerable to recovery. In the 'GOOD' cases, the key material is randomly generated and not hardcoded, which protects the encrypted data against recovery.</p>
15+
<sample src="HardcodedEncryptionKey.swift" />
16+
</example>
17+
</qhelp>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @name Hard-coded encryption key
3+
* @description Using hardcoded keys for encryption is not secure, because potential attacker can easiy guess them.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 8.1
7+
* @precision high
8+
* @id swift/hardcoded-key
9+
* @tags security
10+
* external/cwe/cwe-321
11+
*/
12+
13+
import swift
14+
import codeql.swift.dataflow.DataFlow
15+
import DataFlow::PathGraph
16+
17+
/**
18+
* An `Expr` that is used to initialize a key.
19+
*/
20+
abstract class KeySource extends Expr { }
21+
22+
/**
23+
* The creation of a literal byte array.
24+
*/
25+
class ByteArrayLiteralSource extends KeySource {
26+
ByteArrayLiteralSource() { this = any(ArrayExpr arr | arr.getType().toString() = "Array<UInt8>") }
27+
}
28+
29+
/**
30+
* Any string literal as a key source.
31+
*/
32+
class StringLiteralSource extends KeySource {
33+
StringLiteralSource() { this instanceof StringLiteralExpr }
34+
}
35+
36+
/**
37+
* A class for all ways to set a key.
38+
*/
39+
class EncryptionKeySink extends Expr {
40+
EncryptionKeySink() {
41+
// `key` arg in `init` is a sink
42+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
43+
c.getName() = ["AES", "HMAC", "ChaCha20", "CBCMAC", "CMAC", "Poly1305", "Blowfish", "Rabbit"] and
44+
c.getAMember() = f and
45+
f.getName().matches("init(key:%") and
46+
call.getStaticTarget() = f and
47+
call.getArgument(0).getExpr() = this
48+
)
49+
}
50+
}
51+
52+
/**
53+
* A dataflow configuration from the key source to expressions that use
54+
* it to initialize a cipher.
55+
*/
56+
class HardcodedKeyConfig extends DataFlow::Configuration {
57+
HardcodedKeyConfig() { this = "HardcodedKeyConfig" }
58+
59+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof KeySource }
60+
61+
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof EncryptionKeySink }
62+
}
63+
64+
// The query itself
65+
from HardcodedKeyConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
66+
where config.hasFlowPath(sourceNode, sinkNode)
67+
select sinkNode.getNode(), sourceNode, sinkNode,
68+
"The key '" + sinkNode.getNode().toString() +
69+
"' has been initialized with hard-coded values from $@.", sourceNode,
70+
sourceNode.getNode().toString()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
func encrypt(padding : Padding) {
3+
// ...
4+
5+
// BAD: Using hardcoded keys for encryption
6+
let key: Array<UInt8> = [0x2a, 0x3a, 0x80, 0x05]
7+
let keyString = "this is a constant string"
8+
let ivString = getRandomIV()
9+
_ = try AES(key: key, blockMode: CBC(), padding: padding)
10+
_ = try AES(key: keyString, iv: ivString)
11+
12+
13+
14+
// GOOD: Using randomly generated keys for encryption
15+
let key = (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
16+
let keyString = String(cString: key)
17+
let ivString = getRandomIV()
18+
_ = try Blowfish(key: key, blockMode: CBC(), padding: padding)
19+
_ = try Blowfish(key: keyString, iv: ivString)
20+
21+
// ...
22+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
edges
2+
| test.swift:76:3:76:3 | this string is constant : | test.swift:91:18:91:36 | call to getConstantString() : |
3+
| test.swift:90:26:90:121 | [...] : | test.swift:105:21:105:21 | key |
4+
| test.swift:90:26:90:121 | [...] : | test.swift:106:21:106:21 | key |
5+
| test.swift:90:26:90:121 | [...] : | test.swift:116:22:116:22 | key |
6+
| test.swift:90:26:90:121 | [...] : | test.swift:117:22:117:22 | key |
7+
| test.swift:90:26:90:121 | [...] : | test.swift:127:26:127:26 | key |
8+
| test.swift:90:26:90:121 | [...] : | test.swift:134:25:134:25 | key |
9+
| test.swift:90:26:90:121 | [...] : | test.swift:139:25:139:25 | key |
10+
| test.swift:90:26:90:121 | [...] : | test.swift:144:26:144:26 | key |
11+
| test.swift:90:26:90:121 | [...] : | test.swift:149:26:149:26 | key |
12+
| test.swift:90:26:90:121 | [...] : | test.swift:150:26:150:26 | key |
13+
| test.swift:90:26:90:121 | [...] : | test.swift:160:24:160:24 | key |
14+
| test.swift:90:26:90:121 | [...] : | test.swift:162:24:162:24 | key |
15+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:107:21:107:21 | keyString |
16+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:108:21:108:21 | keyString |
17+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:118:22:118:22 | keyString |
18+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:119:22:119:22 | keyString |
19+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:128:26:128:26 | keyString |
20+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:151:26:151:26 | keyString |
21+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:152:26:152:26 | keyString |
22+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:161:24:161:24 | keyString |
23+
| test.swift:91:18:91:36 | call to getConstantString() : | test.swift:163:24:163:24 | keyString |
24+
nodes
25+
| test.swift:76:3:76:3 | this string is constant : | semmle.label | this string is constant : |
26+
| test.swift:90:26:90:121 | [...] : | semmle.label | [...] : |
27+
| test.swift:91:18:91:36 | call to getConstantString() : | semmle.label | call to getConstantString() : |
28+
| test.swift:105:21:105:21 | key | semmle.label | key |
29+
| test.swift:106:21:106:21 | key | semmle.label | key |
30+
| test.swift:107:21:107:21 | keyString | semmle.label | keyString |
31+
| test.swift:108:21:108:21 | keyString | semmle.label | keyString |
32+
| test.swift:116:22:116:22 | key | semmle.label | key |
33+
| test.swift:117:22:117:22 | key | semmle.label | key |
34+
| test.swift:118:22:118:22 | keyString | semmle.label | keyString |
35+
| test.swift:119:22:119:22 | keyString | semmle.label | keyString |
36+
| test.swift:127:26:127:26 | key | semmle.label | key |
37+
| test.swift:128:26:128:26 | keyString | semmle.label | keyString |
38+
| test.swift:134:25:134:25 | key | semmle.label | key |
39+
| test.swift:139:25:139:25 | key | semmle.label | key |
40+
| test.swift:144:26:144:26 | key | semmle.label | key |
41+
| test.swift:149:26:149:26 | key | semmle.label | key |
42+
| test.swift:150:26:150:26 | key | semmle.label | key |
43+
| test.swift:151:26:151:26 | keyString | semmle.label | keyString |
44+
| test.swift:152:26:152:26 | keyString | semmle.label | keyString |
45+
| test.swift:160:24:160:24 | key | semmle.label | key |
46+
| test.swift:161:24:161:24 | keyString | semmle.label | keyString |
47+
| test.swift:162:24:162:24 | key | semmle.label | key |
48+
| test.swift:163:24:163:24 | keyString | semmle.label | keyString |
49+
subpaths
50+
#select
51+
| test.swift:105:21:105:21 | key | test.swift:90:26:90:121 | [...] : | test.swift:105:21:105:21 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
52+
| test.swift:106:21:106:21 | key | test.swift:90:26:90:121 | [...] : | test.swift:106:21:106:21 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
53+
| test.swift:107:21:107:21 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:107:21:107:21 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
54+
| test.swift:108:21:108:21 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:108:21:108:21 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
55+
| test.swift:116:22:116:22 | key | test.swift:90:26:90:121 | [...] : | test.swift:116:22:116:22 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
56+
| test.swift:117:22:117:22 | key | test.swift:90:26:90:121 | [...] : | test.swift:117:22:117:22 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
57+
| test.swift:118:22:118:22 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:118:22:118:22 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
58+
| test.swift:119:22:119:22 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:119:22:119:22 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
59+
| test.swift:127:26:127:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:127:26:127:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
60+
| test.swift:128:26:128:26 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:128:26:128:26 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
61+
| test.swift:134:25:134:25 | key | test.swift:90:26:90:121 | [...] : | test.swift:134:25:134:25 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
62+
| test.swift:139:25:139:25 | key | test.swift:90:26:90:121 | [...] : | test.swift:139:25:139:25 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
63+
| test.swift:144:26:144:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:144:26:144:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
64+
| test.swift:149:26:149:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:149:26:149:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
65+
| test.swift:150:26:150:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:150:26:150:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
66+
| test.swift:151:26:151:26 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:151:26:151:26 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
67+
| test.swift:152:26:152:26 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:152:26:152:26 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
68+
| test.swift:160:24:160:24 | key | test.swift:90:26:90:121 | [...] : | test.swift:160:24:160:24 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
69+
| test.swift:161:24:161:24 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:161:24:161:24 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
70+
| test.swift:162:24:162:24 | key | test.swift:90:26:90:121 | [...] : | test.swift:162:24:162:24 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
71+
| test.swift:163:24:163:24 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:163:24:163:24 | keyString | The key 'keyString' has been initialized with hard-coded values from $@. | test.swift:76:3:76:3 | this string is constant : | this string is constant |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/Security/CWE-321/HardcodedEncryptionKey.ql
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
2+
// --- stubs ---
3+
4+
// These stubs roughly follows the same structure as classes from CryptoSwift
5+
class AES
6+
{
7+
init(key: Array<UInt8>, blockMode: BlockMode, padding: Padding) { }
8+
init(key: Array<UInt8>, blockMode: BlockMode) { }
9+
init(key: String, iv: String) { }
10+
init(key: String, iv: String, padding: Padding) { }
11+
}
12+
13+
class Blowfish
14+
{
15+
init(key: Array<UInt8>, blockMode: BlockMode, padding: Padding) { }
16+
init(key: Array<UInt8>, blockMode: BlockMode) { }
17+
init(key: String, iv: String) { }
18+
init(key: String, iv: String, padding: Padding) { }
19+
}
20+
21+
class HMAC
22+
{
23+
init(key: Array<UInt8>) { }
24+
init(key: Array<UInt8>, variant: Variant) { }
25+
init(key: String) { }
26+
init(key: String, variant: Variant) { }
27+
}
28+
29+
class ChaCha20
30+
{
31+
init(key: Array<UInt8>, iv: Array<UInt8>) { }
32+
init(key: String, iv: String) { }
33+
}
34+
35+
class CBCMAC
36+
{
37+
init(key: Array<UInt8>) { }
38+
}
39+
40+
class CMAC
41+
{
42+
init(key: Array<UInt8>) { }
43+
}
44+
45+
class Poly1305
46+
{
47+
init(key: Array<UInt8>) { }
48+
}
49+
50+
class Rabbit
51+
{
52+
init(key: Array<UInt8>) { }
53+
init(key: String) { }
54+
init(key: Array<UInt8>, iv: Array<UInt8>) { }
55+
init(key: String, iv: String) { }
56+
}
57+
58+
enum Variant {
59+
case md5, sha1, sha2, sha3
60+
}
61+
62+
protocol BlockMode { }
63+
64+
struct CBC: BlockMode {
65+
init() { }
66+
}
67+
68+
protocol PaddingProtocol { }
69+
70+
enum Padding: PaddingProtocol {
71+
case noPadding, zeroPadding, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126
72+
}
73+
74+
// Helper functions
75+
func getConstantString() -> String {
76+
"this string is constant"
77+
}
78+
79+
func getConstantArray() -> Array<UInt8> {
80+
[UInt8](getConstantString().utf8)
81+
}
82+
83+
func getRandomArray() -> Array<UInt8> {
84+
(0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
85+
}
86+
87+
// --- tests ---
88+
89+
func test() {
90+
let key: Array<UInt8> = [0x2a, 0x3a, 0x80, 0x05, 0xaf, 0x46, 0x58, 0x2d, 0x66, 0x52, 0x10, 0xae, 0x86, 0xd3, 0x8e, 0x8f]
91+
let keyString = getConstantString()
92+
93+
let randomArray = getRandomArray()
94+
let randomKey = getRandomArray()
95+
let randomKeyString = String(cString: getRandomArray())
96+
97+
let blockMode = CBC()
98+
let padding = Padding.noPadding
99+
let variant = Variant.sha2
100+
101+
let iv = getRandomArray()
102+
let ivString = String(cString: iv)
103+
104+
// AES test cases
105+
let ab1 = AES(key: key, blockMode: blockMode, padding: padding) // BAD
106+
let ab2 = AES(key: key, blockMode: blockMode) // BAD
107+
let ab3 = AES(key: keyString, iv: ivString) // BAD
108+
let ab4 = AES(key: keyString, iv: ivString, padding: padding) // BAD
109+
110+
let ag1 = AES(key: randomKey, blockMode: blockMode, padding: padding) // GOOD
111+
let ag2 = AES(key: randomKey, blockMode: blockMode) // GOOD
112+
let ag3 = AES(key: randomKeyString, iv: ivString) // GOOD
113+
let ag4 = AES(key: randomKeyString, iv: ivString, padding: padding) // GOOD
114+
115+
// HMAC test cases
116+
let hb1 = HMAC(key: key) // BAD
117+
let hb2 = HMAC(key: key, variant: variant) // BAD
118+
let hb3 = HMAC(key: keyString) // BAD
119+
let hb4 = HMAC(key: keyString, variant: variant) // BAD
120+
121+
let hg1 = HMAC(key: randomKey) // GOOD
122+
let hg2 = HMAC(key: randomKey, variant: variant) // GOOD
123+
let hg3 = HMAC(key: randomKeyString) // GOOD
124+
let hg4 = HMAC(key: randomKeyString, variant: variant) // GOOD
125+
126+
// ChaCha20 test cases
127+
let cb1 = ChaCha20(key: key, iv: iv) // BAD
128+
let cb2 = ChaCha20(key: keyString, iv: ivString) // BAD
129+
130+
let cg1 = ChaCha20(key: randomKey, iv: iv) // GOOD
131+
let cg2 = ChaCha20(key: randomKeyString, iv: ivString) // GOOD
132+
133+
// CBCMAC test cases
134+
let cmb1 = CBCMAC(key: key) // BAD
135+
136+
let cmg1 = CBCMAC(key: randomKey) // GOOD
137+
138+
// CMAC test cases
139+
let cmacb1 = CMAC(key: key) // BAD
140+
141+
let cmacg1 = CMAC(key: randomKey) // GOOD
142+
143+
// Poly1305 test cases
144+
let pb1 = Poly1305(key: key) // BAD
145+
146+
let pg1 = Poly1305(key: randomKey) // GOOD
147+
148+
// Blowfish test cases
149+
let bb1 = Blowfish(key: key, blockMode: blockMode, padding: padding) // BAD
150+
let bb2 = Blowfish(key: key, blockMode: blockMode) // BAD
151+
let bb3 = Blowfish(key: keyString, iv: ivString) // BAD
152+
let bb4 = Blowfish(key: keyString, iv: ivString, padding: padding) // BAD
153+
154+
let bg1 = Blowfish(key: randomKey, blockMode: blockMode, padding: padding) // GOOD
155+
let bg2 = Blowfish(key: randomKey, blockMode: blockMode) // GOOD
156+
let bg3 = Blowfish(key: randomKeyString, iv: ivString) // GOOD
157+
let bg4 = Blowfish(key: randomKeyString, iv: ivString, padding: padding) // GOOD
158+
159+
// Rabbit
160+
let rb1 = Rabbit(key: key) // BAD
161+
let rb2 = Rabbit(key: keyString) // BAD
162+
let rb3 = Rabbit(key: key, iv: iv) // BAD
163+
let rb4 = Rabbit(key: keyString, iv: ivString) // BAD
164+
165+
let rg1 = Rabbit(key: randomKey) // GOOD
166+
let rg2 = Rabbit(key: randomKeyString) // GOOD
167+
let rg3 = Rabbit(key: randomKey, iv: iv) // GOOD
168+
let rg4 = Rabbit(key: randomKeyString, iv: ivString) // GOOD
169+
}

0 commit comments

Comments
 (0)