Skip to content

Commit 13f9834

Browse files
authored
Merge pull request github#10780 from karimhamdanali/swift-hardcoded-key
Swift: detect hardcoded encryption keys
2 parents 7af4c08 + bbc03a1 commit 13f9834

File tree

6 files changed

+346
-0
lines changed

6 files changed

+346
-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 possibility of recovering them.</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>Use 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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @name Hard-coded encryption key
3+
* @description Using hardcoded keys for encryption is not secure, because potential attackers can easily 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+
* A literal byte array is a key source.
24+
*/
25+
class ByteArrayLiteralSource extends KeySource {
26+
ByteArrayLiteralSource() { this = any(ArrayExpr arr | arr.getType().getName() = "Array<UInt8>") }
27+
}
28+
29+
/**
30+
* A string literal is a key source.
31+
*/
32+
class StringLiteralSource extends KeySource instanceof StringLiteralExpr { }
33+
34+
/**
35+
* A class for all ways to set a key.
36+
*/
37+
class EncryptionKeySink extends Expr {
38+
EncryptionKeySink() {
39+
// `key` arg in `init` is a sink
40+
exists(ClassDecl c, AbstractFunctionDecl f, CallExpr call |
41+
c.getName() = ["AES", "HMAC", "ChaCha20", "CBCMAC", "CMAC", "Poly1305", "Blowfish", "Rabbit"] and
42+
c.getAMember() = f and
43+
f.getName().matches("init(key:%") and
44+
call.getStaticTarget() = f and
45+
call.getArgument(0).getExpr() = this
46+
)
47+
}
48+
}
49+
50+
/**
51+
* A dataflow configuration from the key source to expressions that use
52+
* it to initialize a cipher.
53+
*/
54+
class HardcodedKeyConfig extends DataFlow::Configuration {
55+
HardcodedKeyConfig() { this = "HardcodedKeyConfig" }
56+
57+
override predicate isSource(DataFlow::Node node) { node.asExpr() instanceof KeySource }
58+
59+
override predicate isSink(DataFlow::Node node) { node.asExpr() instanceof EncryptionKeySink }
60+
}
61+
62+
// The query itself
63+
from HardcodedKeyConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode
64+
where config.hasFlowPath(sourceNode, sinkNode)
65+
select sinkNode.getNode(), sourceNode, sinkNode,
66+
"The key '" + sinkNode.getNode().toString() +
67+
"' has been initialized with hard-coded values from $@.", sourceNode,
68+
sourceNode.getNode().toString()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
_ = try Blowfish(key: key, blockMode: CBC(), padding: padding)
12+
_ = try Blowfish(key: keyString, iv: ivString)
13+
14+
15+
// GOOD: Using randomly generated keys for encryption
16+
let key = (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
17+
let keyString = String(cString: key)
18+
let ivString = getRandomIV()
19+
_ = try AES(key: key, blockMode: CBC(), padding: padding)
20+
_ = try AES(key: keyString, iv: ivString)
21+
_ = try Blowfish(key: key, blockMode: CBC(), padding: padding)
22+
_ = try Blowfish(key: keyString, iv: ivString)
23+
24+
// ...
25+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
edges
2+
| test.swift:76:3:76:3 | this string is constant : | test.swift:92:18:92:36 | call to getConstantString() : |
3+
| test.swift:90:26:90:121 | [...] : | test.swift:117:22:117:22 | key |
4+
| test.swift:90:26:90:121 | [...] : | test.swift:118:22:118:22 | key |
5+
| test.swift:90:26:90:121 | [...] : | test.swift:128:26:128:26 | key |
6+
| test.swift:90:26:90:121 | [...] : | test.swift:135:25:135:25 | key |
7+
| test.swift:90:26:90:121 | [...] : | test.swift:140:25:140:25 | key |
8+
| test.swift:90:26:90:121 | [...] : | test.swift:145:26:145:26 | key |
9+
| test.swift:90:26:90:121 | [...] : | test.swift:150:26:150:26 | key |
10+
| test.swift:90:26:90:121 | [...] : | test.swift:151:26:151:26 | key |
11+
| test.swift:90:26:90:121 | [...] : | test.swift:161:24:161:24 | key |
12+
| test.swift:90:26:90:121 | [...] : | test.swift:163:24:163:24 | key |
13+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:108:21:108:21 | keyString |
14+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:109:21:109:21 | keyString |
15+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:119:22:119:22 | keyString |
16+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:120:22:120:22 | keyString |
17+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:129:26:129:26 | keyString |
18+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:152:26:152:26 | keyString |
19+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:153:26:153:26 | keyString |
20+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:162:24:162:24 | keyString |
21+
| test.swift:92:18:92:36 | call to getConstantString() : | test.swift:164:24:164:24 | keyString |
22+
nodes
23+
| test.swift:76:3:76:3 | this string is constant : | semmle.label | this string is constant : |
24+
| test.swift:90:26:90:121 | [...] : | semmle.label | [...] : |
25+
| test.swift:92:18:92:36 | call to getConstantString() : | semmle.label | call to getConstantString() : |
26+
| test.swift:108:21:108:21 | keyString | semmle.label | keyString |
27+
| test.swift:109:21:109:21 | keyString | semmle.label | keyString |
28+
| test.swift:117:22:117:22 | key | semmle.label | key |
29+
| test.swift:118:22:118:22 | key | semmle.label | key |
30+
| test.swift:119:22:119:22 | keyString | semmle.label | keyString |
31+
| test.swift:120:22:120:22 | keyString | semmle.label | keyString |
32+
| test.swift:128:26:128:26 | key | semmle.label | key |
33+
| test.swift:129:26:129:26 | keyString | semmle.label | keyString |
34+
| test.swift:135:25:135:25 | key | semmle.label | key |
35+
| test.swift:140:25:140:25 | key | semmle.label | key |
36+
| test.swift:145:26:145:26 | key | semmle.label | key |
37+
| test.swift:150:26:150:26 | key | semmle.label | key |
38+
| test.swift:151:26:151:26 | key | semmle.label | key |
39+
| test.swift:152:26:152:26 | keyString | semmle.label | keyString |
40+
| test.swift:153:26:153:26 | keyString | semmle.label | keyString |
41+
| test.swift:161:24:161:24 | key | semmle.label | key |
42+
| test.swift:162:24:162:24 | keyString | semmle.label | keyString |
43+
| test.swift:163:24:163:24 | key | semmle.label | key |
44+
| test.swift:164:24:164:24 | keyString | semmle.label | keyString |
45+
subpaths
46+
#select
47+
| 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 |
48+
| test.swift:109:21:109:21 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:109:21:109: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 |
49+
| 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 | [...] : | [...] |
50+
| test.swift:118:22:118:22 | key | test.swift:90:26:90:121 | [...] : | test.swift:118:22:118:22 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
51+
| 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 |
52+
| test.swift:120:22:120:22 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:120:22:120: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 |
53+
| test.swift:128:26:128:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:128:26:128:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
54+
| test.swift:129:26:129:26 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:129:26:129: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 |
55+
| test.swift:135:25:135:25 | key | test.swift:90:26:90:121 | [...] : | test.swift:135:25:135:25 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
56+
| test.swift:140:25:140:25 | key | test.swift:90:26:90:121 | [...] : | test.swift:140:25:140:25 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
57+
| test.swift:145:26:145:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:145:26:145:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
58+
| 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 | [...] : | [...] |
59+
| test.swift:151:26:151:26 | key | test.swift:90:26:90:121 | [...] : | test.swift:151:26:151:26 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
60+
| 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 |
61+
| test.swift:153:26:153:26 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:153:26:153: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 |
62+
| test.swift:161:24:161:24 | key | test.swift:90:26:90:121 | [...] : | test.swift:161:24:161:24 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
63+
| test.swift:162:24:162:24 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:162:24:162: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 |
64+
| test.swift:163:24:163:24 | key | test.swift:90:26:90:121 | [...] : | test.swift:163:24:163:24 | key | The key 'key' has been initialized with hard-coded values from $@. | test.swift:90:26:90:121 | [...] : | [...] |
65+
| test.swift:164:24:164:24 | keyString | test.swift:76:3:76:3 | this string is constant : | test.swift:164:24:164: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: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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 key2 = getConstantArray()
92+
let keyString = getConstantString()
93+
94+
let randomArray = getRandomArray()
95+
let randomKey = getRandomArray()
96+
let randomKeyString = String(cString: getRandomArray())
97+
98+
let blockMode = CBC()
99+
let padding = Padding.noPadding
100+
let variant = Variant.sha2
101+
102+
let iv = getRandomArray()
103+
let ivString = String(cString: iv)
104+
105+
// AES test cases
106+
let ab1 = AES(key: key2, blockMode: blockMode, padding: padding) // BAD [NOT DETECTED]
107+
let ab2 = AES(key: key2, blockMode: blockMode) // BAD [NOT DETECTED]
108+
let ab3 = AES(key: keyString, iv: ivString) // BAD
109+
let ab4 = AES(key: keyString, iv: ivString, padding: padding) // BAD
110+
111+
let ag1 = AES(key: randomKey, blockMode: blockMode, padding: padding) // GOOD
112+
let ag2 = AES(key: randomKey, blockMode: blockMode) // GOOD
113+
let ag3 = AES(key: randomKeyString, iv: ivString) // GOOD
114+
let ag4 = AES(key: randomKeyString, iv: ivString, padding: padding) // GOOD
115+
116+
// HMAC test cases
117+
let hb1 = HMAC(key: key) // BAD
118+
let hb2 = HMAC(key: key, variant: variant) // BAD
119+
let hb3 = HMAC(key: keyString) // BAD
120+
let hb4 = HMAC(key: keyString, variant: variant) // BAD
121+
122+
let hg1 = HMAC(key: randomKey) // GOOD
123+
let hg2 = HMAC(key: randomKey, variant: variant) // GOOD
124+
let hg3 = HMAC(key: randomKeyString) // GOOD
125+
let hg4 = HMAC(key: randomKeyString, variant: variant) // GOOD
126+
127+
// ChaCha20 test cases
128+
let cb1 = ChaCha20(key: key, iv: iv) // BAD
129+
let cb2 = ChaCha20(key: keyString, iv: ivString) // BAD
130+
131+
let cg1 = ChaCha20(key: randomKey, iv: iv) // GOOD
132+
let cg2 = ChaCha20(key: randomKeyString, iv: ivString) // GOOD
133+
134+
// CBCMAC test cases
135+
let cmb1 = CBCMAC(key: key) // BAD
136+
137+
let cmg1 = CBCMAC(key: randomKey) // GOOD
138+
139+
// CMAC test cases
140+
let cmacb1 = CMAC(key: key) // BAD
141+
142+
let cmacg1 = CMAC(key: randomKey) // GOOD
143+
144+
// Poly1305 test cases
145+
let pb1 = Poly1305(key: key) // BAD
146+
147+
let pg1 = Poly1305(key: randomKey) // GOOD
148+
149+
// Blowfish test cases
150+
let bb1 = Blowfish(key: key, blockMode: blockMode, padding: padding) // BAD
151+
let bb2 = Blowfish(key: key, blockMode: blockMode) // BAD
152+
let bb3 = Blowfish(key: keyString, iv: ivString) // BAD
153+
let bb4 = Blowfish(key: keyString, iv: ivString, padding: padding) // BAD
154+
155+
let bg1 = Blowfish(key: randomKey, blockMode: blockMode, padding: padding) // GOOD
156+
let bg2 = Blowfish(key: randomKey, blockMode: blockMode) // GOOD
157+
let bg3 = Blowfish(key: randomKeyString, iv: ivString) // GOOD
158+
let bg4 = Blowfish(key: randomKeyString, iv: ivString, padding: padding) // GOOD
159+
160+
// Rabbit
161+
let rb1 = Rabbit(key: key) // BAD
162+
let rb2 = Rabbit(key: keyString) // BAD
163+
let rb3 = Rabbit(key: key, iv: iv) // BAD
164+
let rb4 = Rabbit(key: keyString, iv: ivString) // BAD
165+
166+
let rg1 = Rabbit(key: randomKey) // GOOD
167+
let rg2 = Rabbit(key: randomKeyString) // GOOD
168+
let rg3 = Rabbit(key: randomKey, iv: iv) // GOOD
169+
let rg4 = Rabbit(key: randomKeyString, iv: ivString) // GOOD
170+
}

0 commit comments

Comments
 (0)