Skip to content

Commit e18b2cf

Browse files
Merge pull request github#11084 from karimhamdanali/swift-static-iv
Swift: detect the use of static initialization vectors
2 parents 3d17c8f + d229d6a commit e18b2cf

File tree

6 files changed

+353
-0
lines changed

6 files changed

+353
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>When a cipher is used in certain modes (such as CBC or GCM), it requires an initialization vector (IV). Under the same secret key, IVs should be unique and ideally unpredictable. If the same IV is used with the same secret key, then the same plaintext results in the same ciphertext. This behavior may enable an attacker to learn if the same data pieces are transferred or stored, or help the attacker run a dictionary attack.</p>
6+
<p>In particular, if the IV is hardcoded or constant, an attacker may just look up potential keys in a dictionary, then concatenate those with the hardcoded or constant IV rather than trying to discover the entire encryption key.</p>
7+
</overview>
8+
9+
<recommendation>
10+
<p>Use a randomly generated IV.</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 IV is hardcoded or constant, making the encrypted data vulnerable to recovery. In the 'GOOD' cases, the IV is randomly generated and not hardcoded, which protects the encrypted data against recovery.</p>
15+
<sample src="StaticInitializationVector.swift" />
16+
</example>
17+
18+
<references>
19+
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Initialization_vector">Initialization vector</a>.</li>
20+
<li>National Institute of Standards and Technology: <a href="https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf">Recommendation for Block Cipher Modes of Operation</a>.</li>
21+
<li>National Institute of Standards and Technology: <a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf">FIPS 140-2: Security Requirements for Cryptographic Modules</a>.</li>
22+
</references>
23+
</qhelp>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @name Static initialization vector for encryption
3+
* @description Using a static initialization vector (IV) for encryption is not secure. To maximize encryption and prevent dictionary attacks, IVs should be unique and unpredictable.
4+
* @kind path-problem
5+
* @problem.severity error
6+
* @security-severity 7.5
7+
* @precision high
8+
* @id swift/static-initialization-vector
9+
* @tags security
10+
* external/cwe/cwe-329
11+
* external/cwe/cwe-1204
12+
*/
13+
14+
import swift
15+
import codeql.swift.dataflow.DataFlow
16+
import codeql.swift.dataflow.TaintTracking
17+
import DataFlow::PathGraph
18+
19+
/**
20+
* A static IV is created through either a byte array or string literals.
21+
*/
22+
class StaticInitializationVectorSource extends Expr {
23+
StaticInitializationVectorSource() {
24+
this = any(ArrayExpr arr | arr.getType().getName() = "Array<UInt8>") or
25+
this instanceof StringLiteralExpr
26+
}
27+
}
28+
29+
/**
30+
* A class for all ways to set an IV.
31+
*/
32+
class EncryptionInitializationSink extends Expr {
33+
EncryptionInitializationSink() {
34+
// `iv` arg in `init` is a sink
35+
exists(CallExpr call, string fName, int arg |
36+
call.getStaticTarget()
37+
.(MethodDecl)
38+
.hasQualifiedName([
39+
"AES", "ChaCha20", "Blowfish", "Rabbit", "CBC", "CFB", "GCM", "OCB", "OFB", "PCBC",
40+
"CCM", "CTR"
41+
], fName) and
42+
fName.matches("%init(%iv:%") and
43+
arg = [0, 1] and
44+
call.getStaticTarget().(MethodDecl).getParam(pragma[only_bind_into](arg)).getName() = "iv" and
45+
call.getArgument(pragma[only_bind_into](arg)).getExpr() = this
46+
)
47+
}
48+
}
49+
50+
/**
51+
* A dataflow configuration from the source of a static IV to expressions that use
52+
* it to initialize a cipher.
53+
*/
54+
class StaticInitializationVectorConfig extends TaintTracking::Configuration {
55+
StaticInitializationVectorConfig() { this = "StaticInitializationVectorConfig" }
56+
57+
override predicate isSource(DataFlow::Node node) {
58+
node.asExpr() instanceof StaticInitializationVectorSource
59+
}
60+
61+
override predicate isSink(DataFlow::Node node) {
62+
node.asExpr() instanceof EncryptionInitializationSink
63+
}
64+
}
65+
66+
// The query itself
67+
from
68+
StaticInitializationVectorConfig config, DataFlow::PathNode sourceNode,
69+
DataFlow::PathNode sinkNode
70+
where config.hasFlowPath(sourceNode, sinkNode)
71+
select sinkNode.getNode(), sourceNode, sinkNode,
72+
"The static value '" + sourceNode.getNode().toString() +
73+
"' is used as an initialization vector for encryption."
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
func encrypt(padding : Padding) {
3+
// ...
4+
5+
// BAD: Using static IVs for encryption
6+
let iv: Array<UInt8> = [0x2a, 0x3a, 0x80, 0x05]
7+
let ivString = "this is a constant string"
8+
let key = getRandomKey()
9+
_ = try AES(key: key, iv: ivString)
10+
_ = try CBC(iv: iv)
11+
12+
// GOOD: Using randomly generated IVs for encryption
13+
let iv = (0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
14+
let ivString = String(cString: iv)
15+
let key = getRandomKey())
16+
_ = try AES(key: key, iv: ivString)
17+
_ = try CBC(iv: iv)
18+
19+
// ...
20+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
edges
2+
| test.swift:85:3:85:3 | this string is constant : | test.swift:101:17:101:35 | call to getConstantString() : |
3+
| test.swift:99:25:99:120 | [...] : | test.swift:128:33:128:33 | iv |
4+
| test.swift:99:25:99:120 | [...] : | test.swift:135:22:135:22 | iv |
5+
| test.swift:99:25:99:120 | [...] : | test.swift:139:22:139:22 | iv |
6+
| test.swift:99:25:99:120 | [...] : | test.swift:140:22:140:22 | iv |
7+
| test.swift:99:25:99:120 | [...] : | test.swift:145:22:145:22 | iv |
8+
| test.swift:99:25:99:120 | [...] : | test.swift:146:22:146:22 | iv |
9+
| test.swift:99:25:99:120 | [...] : | test.swift:147:22:147:22 | iv |
10+
| test.swift:99:25:99:120 | [...] : | test.swift:153:22:153:22 | iv |
11+
| test.swift:99:25:99:120 | [...] : | test.swift:157:24:157:24 | iv |
12+
| test.swift:99:25:99:120 | [...] : | test.swift:161:22:161:22 | iv |
13+
| test.swift:99:25:99:120 | [...] : | test.swift:162:22:162:22 | iv |
14+
| test.swift:99:25:99:120 | [...] : | test.swift:167:22:167:22 | iv |
15+
| test.swift:99:25:99:120 | [...] : | test.swift:168:22:168:22 | iv |
16+
| test.swift:101:17:101:35 | call to getConstantString() : | test.swift:112:36:112:36 | ivString |
17+
| test.swift:101:17:101:35 | call to getConstantString() : | test.swift:113:36:113:36 | ivString |
18+
| test.swift:101:17:101:35 | call to getConstantString() : | test.swift:118:41:118:41 | ivString |
19+
| test.swift:101:17:101:35 | call to getConstantString() : | test.swift:122:41:122:41 | ivString |
20+
| test.swift:101:17:101:35 | call to getConstantString() : | test.swift:123:41:123:41 | ivString |
21+
| test.swift:101:17:101:35 | call to getConstantString() : | test.swift:130:39:130:39 | ivString |
22+
nodes
23+
| test.swift:85:3:85:3 | this string is constant : | semmle.label | this string is constant : |
24+
| test.swift:99:25:99:120 | [...] : | semmle.label | [...] : |
25+
| test.swift:101:17:101:35 | call to getConstantString() : | semmle.label | call to getConstantString() : |
26+
| test.swift:112:36:112:36 | ivString | semmle.label | ivString |
27+
| test.swift:113:36:113:36 | ivString | semmle.label | ivString |
28+
| test.swift:118:41:118:41 | ivString | semmle.label | ivString |
29+
| test.swift:122:41:122:41 | ivString | semmle.label | ivString |
30+
| test.swift:123:41:123:41 | ivString | semmle.label | ivString |
31+
| test.swift:128:33:128:33 | iv | semmle.label | iv |
32+
| test.swift:130:39:130:39 | ivString | semmle.label | ivString |
33+
| test.swift:135:22:135:22 | iv | semmle.label | iv |
34+
| test.swift:139:22:139:22 | iv | semmle.label | iv |
35+
| test.swift:140:22:140:22 | iv | semmle.label | iv |
36+
| test.swift:145:22:145:22 | iv | semmle.label | iv |
37+
| test.swift:146:22:146:22 | iv | semmle.label | iv |
38+
| test.swift:147:22:147:22 | iv | semmle.label | iv |
39+
| test.swift:153:22:153:22 | iv | semmle.label | iv |
40+
| test.swift:157:24:157:24 | iv | semmle.label | iv |
41+
| test.swift:161:22:161:22 | iv | semmle.label | iv |
42+
| test.swift:162:22:162:22 | iv | semmle.label | iv |
43+
| test.swift:167:22:167:22 | iv | semmle.label | iv |
44+
| test.swift:168:22:168:22 | iv | semmle.label | iv |
45+
subpaths
46+
#select
47+
| test.swift:112:36:112:36 | ivString | test.swift:85:3:85:3 | this string is constant : | test.swift:112:36:112:36 | ivString | The static value 'this string is constant' is used as an initialization vector for encryption. |
48+
| test.swift:113:36:113:36 | ivString | test.swift:85:3:85:3 | this string is constant : | test.swift:113:36:113:36 | ivString | The static value 'this string is constant' is used as an initialization vector for encryption. |
49+
| test.swift:118:41:118:41 | ivString | test.swift:85:3:85:3 | this string is constant : | test.swift:118:41:118:41 | ivString | The static value 'this string is constant' is used as an initialization vector for encryption. |
50+
| test.swift:122:41:122:41 | ivString | test.swift:85:3:85:3 | this string is constant : | test.swift:122:41:122:41 | ivString | The static value 'this string is constant' is used as an initialization vector for encryption. |
51+
| test.swift:123:41:123:41 | ivString | test.swift:85:3:85:3 | this string is constant : | test.swift:123:41:123:41 | ivString | The static value 'this string is constant' is used as an initialization vector for encryption. |
52+
| test.swift:128:33:128:33 | iv | test.swift:99:25:99:120 | [...] : | test.swift:128:33:128:33 | iv | The static value '[...]' is used as an initialization vector for encryption. |
53+
| test.swift:130:39:130:39 | ivString | test.swift:85:3:85:3 | this string is constant : | test.swift:130:39:130:39 | ivString | The static value 'this string is constant' is used as an initialization vector for encryption. |
54+
| test.swift:135:22:135:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:135:22:135:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
55+
| test.swift:139:22:139:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:139:22:139:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
56+
| test.swift:140:22:140:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:140:22:140:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
57+
| test.swift:145:22:145:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:145:22:145:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
58+
| test.swift:146:22:146:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:146:22:146:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
59+
| test.swift:147:22:147:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:147:22:147:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
60+
| test.swift:153:22:153:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:153:22:153:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
61+
| test.swift:157:24:157:24 | iv | test.swift:99:25:99:120 | [...] : | test.swift:157:24:157:24 | iv | The static value '[...]' is used as an initialization vector for encryption. |
62+
| test.swift:161:22:161:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:161:22:161:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
63+
| test.swift:162:22:162:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:162:22:162:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
64+
| test.swift:167:22:167:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:167:22:167:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
65+
| test.swift:168:22:168:22 | iv | test.swift:99:25:99:120 | [...] : | test.swift:168:22:168:22 | iv | The static value '[...]' is used as an initialization vector for encryption. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/Security/CWE-1204/StaticInitializationVector.ql
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
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 ChaCha20
22+
{
23+
init(key: Array<UInt8>, iv: Array<UInt8>) { }
24+
init(key: String, iv: String) { }
25+
}
26+
27+
class Rabbit
28+
{
29+
init(key: Array<UInt8>) { }
30+
init(key: String) { }
31+
init(key: Array<UInt8>, iv: Array<UInt8>) { }
32+
init(key: String, iv: String) { }
33+
}
34+
35+
protocol BlockMode { }
36+
37+
struct CBC: BlockMode {
38+
init(iv: Array<UInt8>) { }
39+
}
40+
41+
struct CFB: BlockMode {
42+
enum SegmentSize: Int {
43+
case cfb8 = 1
44+
case cfb128 = 16
45+
}
46+
47+
init(iv: Array<UInt8>, segmentSize: SegmentSize = .cfb128) { }
48+
}
49+
50+
final class GCM: BlockMode {
51+
enum Mode { case combined, detached }
52+
init(iv: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, tagLength: Int = 16, mode: Mode = .detached) { }
53+
convenience init(iv: Array<UInt8>, authenticationTag: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil, mode: Mode = .detached) {
54+
self.init(iv: iv, additionalAuthenticatedData: additionalAuthenticatedData, tagLength: authenticationTag.count, mode: mode)
55+
}
56+
}
57+
58+
struct OFB: BlockMode {
59+
init(iv: Array<UInt8>) { }
60+
}
61+
62+
struct PCBC: BlockMode {
63+
init(iv: Array<UInt8>) { }
64+
}
65+
66+
typealias StreamMode = BlockMode
67+
68+
struct CCM: StreamMode {
69+
init(iv: Array<UInt8>, tagLength: Int, messageLength: Int, additionalAuthenticatedData: Array<UInt8>? = nil) { }
70+
init(iv: Array<UInt8>, tagLength: Int, messageLength: Int, authenticationTag: Array<UInt8>, additionalAuthenticatedData: Array<UInt8>? = nil) { }
71+
}
72+
73+
struct CTR: StreamMode {
74+
init(iv: Array<UInt8>, counter: Int = 0) { }
75+
}
76+
77+
protocol PaddingProtocol { }
78+
79+
enum Padding: PaddingProtocol {
80+
case noPadding, zeroPadding, pkcs7, pkcs5, eme_pkcs1v15, emsa_pkcs1v15, iso78164, iso10126
81+
}
82+
83+
// Helper functions
84+
func getConstantString() -> String {
85+
"this string is constant"
86+
}
87+
88+
func getConstantArray() -> Array<UInt8> {
89+
[UInt8](getConstantString().utf8)
90+
}
91+
92+
func getRandomArray() -> Array<UInt8> {
93+
(0..<10).map({ _ in UInt8.random(in: 0...UInt8.max) })
94+
}
95+
96+
// --- tests ---
97+
98+
func test() {
99+
let iv: Array<UInt8> = [0x2a, 0x3a, 0x80, 0x05, 0xaf, 0x46, 0x58, 0x2d, 0x66, 0x52, 0x10, 0xae, 0x86, 0xd3, 0x8e, 0x8f]
100+
let iv2 = getConstantArray()
101+
let ivString = getConstantString()
102+
103+
let randomArray = getRandomArray()
104+
let randomIv = getRandomArray()
105+
let randomIvString = String(cString: getRandomArray())
106+
107+
let padding = Padding.noPadding
108+
let key = getRandomArray()
109+
let keyString = String(cString: key)
110+
111+
// AES test cases
112+
let ab1 = AES(key: keyString, iv: ivString) // BAD
113+
let ab2 = AES(key: keyString, iv: ivString, padding: padding) // BAD
114+
let ag1 = AES(key: keyString, iv: randomIvString) // GOOD
115+
let ag2 = AES(key: keyString, iv: randomIvString, padding: padding) // GOOD
116+
117+
// ChaCha20 test cases
118+
let cb1 = ChaCha20(key: keyString, iv: ivString) // BAD
119+
let cg1 = ChaCha20(key: keyString, iv: randomIvString) // GOOD
120+
121+
// Blowfish test cases
122+
let bb1 = Blowfish(key: keyString, iv: ivString) // BAD
123+
let bb2 = Blowfish(key: keyString, iv: ivString, padding: padding) // BAD
124+
let bg1 = Blowfish(key: keyString, iv: randomIvString) // GOOD
125+
let bg2 = Blowfish(key: keyString, iv: randomIvString, padding: padding) // GOOD
126+
127+
// Rabbit
128+
let rb1 = Rabbit(key: key, iv: iv) // BAD
129+
let rb2 = Rabbit(key: key, iv: iv2) // BAD [NOT DETECTED]
130+
let rb3 = Rabbit(key: keyString, iv: ivString) // BAD
131+
let rg1 = Rabbit(key: key, iv: randomIv) // GOOD
132+
let rg2 = Rabbit(key: keyString, iv: randomIvString) // GOOD
133+
134+
// CBC
135+
let cbcb1 = CBC(iv: iv) // BAD
136+
let cbcg1 = CBC(iv: randomIv) // GOOD
137+
138+
// CFB
139+
let cfbb1 = CFB(iv: iv) // BAD
140+
let cfbb2 = CFB(iv: iv, segmentSize: CFB.SegmentSize.cfb8) // BAD
141+
let cfbg1 = CFB(iv: randomIv) // GOOD
142+
let cfbg2 = CFB(iv: randomIv, segmentSize: CFB.SegmentSize.cfb8) // GOOD
143+
144+
// GCM
145+
let cgmb1 = GCM(iv: iv) // BAD
146+
let cgmb2 = GCM(iv: iv, additionalAuthenticatedData: randomArray, tagLength: 8, mode: GCM.Mode.combined) // BAD
147+
let cgmb3 = GCM(iv: iv, authenticationTag: randomArray, additionalAuthenticatedData: randomArray, mode: GCM.Mode.combined) // BAD
148+
let cgmg1 = GCM(iv: randomIv) // GOOD
149+
let cgmg2 = GCM(iv: randomIv, additionalAuthenticatedData: randomArray, tagLength: 8, mode: GCM.Mode.combined) // GOOD
150+
let cgmg3 = GCM(iv: randomIv, authenticationTag: randomArray, additionalAuthenticatedData: randomArray, mode: GCM.Mode.combined) // GOOD
151+
152+
// OFB
153+
let ofbb1 = OFB(iv: iv) // BAD
154+
let ofbg1 = OFB(iv: randomIv) // GOOD
155+
156+
// PCBC
157+
let pcbcb1 = PCBC(iv: iv) // BAD
158+
let pcbcg1 = PCBC(iv: randomIv) // GOOD
159+
160+
// CCM
161+
let ccmb1 = CCM(iv: iv, tagLength: 0, messageLength: 0, additionalAuthenticatedData: randomArray) // BAD
162+
let ccmb2 = CCM(iv: iv, tagLength: 0, messageLength: 0, authenticationTag: randomArray, additionalAuthenticatedData: randomArray) // BAD
163+
let ccmg1 = CCM(iv: randomIv, tagLength: 0, messageLength: 0, additionalAuthenticatedData: randomArray) // GOOD
164+
let ccmg2 = CCM(iv: randomIv, tagLength: 0, messageLength: 0, authenticationTag: randomArray, additionalAuthenticatedData: randomArray) // GOOD
165+
166+
// CTR
167+
let ctrb1 = CTR(iv: iv) // BAD
168+
let ctrb2 = CTR(iv: iv, counter: 0) // BAD
169+
let ctrg1 = CTR(iv: randomIv) // GOOD
170+
let ctrg2 = CTR(iv: randomIv, counter: 0) // GOOD
171+
}

0 commit comments

Comments
 (0)