Added a query for static initialization vectors in encryption

- Added StaticInitializationVector.ql
- Added StaticInitializationVector.qhelp
- Added tests
This commit is contained in:
Artem Smotrakov
2021-07-11 17:35:26 +02:00
parent 68b3c28202
commit 218731ca0a
7 changed files with 380 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
byte[] iv = new byte[16]; // all zeroes
GCMParameterSpec params = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, params);

View File

@@ -0,0 +1,6 @@
byte[] iv = new byte[16];
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(iv);
GCMParameterSpec params = new GCMParameterSpec(128, iv);
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, params);

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
A cipher needs an initialization vector (IV) when it is used in certain modes
such as CBC or GCM. Under the same secret key, IVs should be unique and ideally unpredictable.
Given a secret key, if the same IV is used for encryption, the same plaintexts result in the same ciphertexts.
This lets an attacker learn if the same data pieces are transfered or stored,
or this can help the attacker run a dictionary attack.
</p>
</overview>
<recommendation>
<p>
Use a random IV generated by <code>SecureRandom</code>.
</p>
</recommendation>
<example>
<p>
The following example initializes a cipher with a static IV which is unsafe:
</p>
<sample src="BadStaticInitializationVector.java" />
<p>
The next example initializes a cipher with a random IV:
</p>
<sample src="GoodRandomInitializationVector.java" />
</example>
<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/Initialization_vector">Initialization vector</a>.
</li>
<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>
<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>
</references>
</qhelp>

View File

@@ -0,0 +1,190 @@
/**
* @name Using a static initialization vector for encryption
* @description A cipher needs an initialization vector (IV) in some cases,
* for example, when CBC or GCM modes are used. IVs are used to randomize the encryption,
* therefore they should be unique and ideally unpredictable.
* Otherwise, the same plaintexts result in same ciphertexts under a given secret key.
* If a static IV is used for encryption, this lets an attacker learn
* if the same data pieces are transfered or stored,
* or this can help the attacker run a dictionary attack.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id java/static-initialization-vector
* @tags security
* external/cwe/cwe-329
* external/cwe/cwe-1204
*/
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.TaintTracking2
import DataFlow::PathGraph
/**
* Holds if `array` is initialized only with constants, for example,
* `new byte[8]` or `new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }`.
*/
private predicate initializedWithConstants(ArrayCreationExpr array) {
not exists(array.getInit())
or
forex(Expr element | element = array.getInit().getAChildExpr() |
element instanceof CompileTimeConstantExpr
)
}
/**
* An expression that creates a byte array that is initialized with constants.
*/
private class StaticByteArrayCreation extends ArrayCreationExpr {
StaticByteArrayCreation() {
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
initializedWithConstants(this)
}
}
/** Defines a sub-set of expressions that update an array. */
private class ArrayUpdate extends Expr {
Expr array;
ArrayUpdate() {
exists(Assignment assign, ArrayAccess arrayAccess | arrayAccess = assign.getDest() |
assign = this and
arrayAccess.getArray() = array and
not assign.getSource() instanceof CompileTimeConstantExpr
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.lang", "System", "arraycopy") and
ma = this and
ma.getArgument(2) = array
)
or
exists(StaticMethodAccess ma |
ma.getMethod().hasQualifiedName("java.util", "Arrays", "copyOf") and
ma = this and
ma = array
)
or
exists(MethodAccess ma, Method m |
m = ma.getMethod() and
ma = this and
ma.getArgument(0) = array
|
m.hasQualifiedName("java.io", "InputStream", "read") or
m.hasQualifiedName("java.nio", "ByteBuffer", "get") or
m.hasQualifiedName("java.security", "SecureRandom", "nextBytes") or
m.hasQualifiedName("java.util", "Random", "nextBytes")
)
}
/** Returns the updated array. */
Expr getArray() { result = array }
}
/**
* A config that tracks dataflow from creating an array to an operation that updates it.
*/
private class ArrayUpdateConfig extends TaintTracking2::Configuration {
ArrayUpdateConfig() { this = "ArrayUpdateConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof StaticByteArrayCreation
}
override predicate isSink(DataFlow::Node sink) {
exists(ArrayUpdate update | update.getArray() = sink.asExpr())
}
}
/**
* A source that defines an array that doesn't get updated.
*/
private class StaticInitializationVectorSource extends DataFlow::Node {
StaticInitializationVectorSource() {
exists(StaticByteArrayCreation array | array = this.asExpr() |
not exists(ArrayUpdate update, ArrayUpdateConfig config |
config.hasFlow(DataFlow2::exprNode(array), DataFlow2::exprNode(update.getArray()))
)
)
}
}
/**
* A config that tracks initialization of a cipher for encryption.
*/
private class EncryptionModeConfig extends TaintTracking2::Configuration {
EncryptionModeConfig() { this = "EncryptionModeConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr().(VarAccess).getVariable().hasName("ENCRYPT_MODE")
}
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
ma.getArgument(0) = sink.asExpr()
)
}
}
/**
* A sink that initializes a cipher for encryption with unsafe parameters.
*/
private class EncryptionInitializationSink extends DataFlow::Node {
EncryptionInitializationSink() {
exists(MethodAccess ma, Method m, EncryptionModeConfig config | m = ma.getMethod() |
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
m.getParameterType(2)
.(RefType)
.hasQualifiedName("java.security.spec", "AlgorithmParameterSpec") and
ma.getArgument(2) = this.asExpr() and
config.hasFlowToExpr(ma.getArgument(0))
)
}
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step
* that creates cipher's parameters with initialization vector.
*/
private predicate createInitializationVectorSpecStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(ConstructorCall cc, RefType type |
cc = toNode.asExpr() and type = cc.getConstructedType()
|
type.hasQualifiedName("javax.crypto.spec", "IvParameterSpec") and
cc.getArgument(0) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", ["GCMParameterSpec", "RC2ParameterSpec"]) and
cc.getArgument(1) = fromNode.asExpr()
or
type.hasQualifiedName("javax.crypto.spec", "RC5ParameterSpec") and
cc.getArgument(3) = fromNode.asExpr()
)
}
/**
* A config that tracks dataflow to initializing a cipher with a static initialization vector.
*/
private class StaticInitializationVectorConfig extends TaintTracking::Configuration {
StaticInitializationVectorConfig() { this = "StaticInitializationVectorConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof StaticInitializationVectorSource
}
override predicate isSink(DataFlow::Node sink) { sink instanceof EncryptionInitializationSink }
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createInitializationVectorSpecStep(fromNode, toNode)
}
override predicate isSanitizer(DataFlow::Node node) {
exists(ArrayUpdate update | update.getArray() = node.asExpr())
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, StaticInitializationVectorConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "A $@ should not be used for encryption.", source.getNode(),
"static initialization vector"

View File

@@ -0,0 +1,15 @@
edges
| StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | StaticInitializationVector.java:19:51:19:56 | ivSpec |
| StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | StaticInitializationVector.java:32:51:32:56 | ivSpec |
| StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | StaticInitializationVector.java:48:51:48:56 | ivSpec |
nodes
| StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
| StaticInitializationVector.java:19:51:19:56 | ivSpec | semmle.label | ivSpec |
| StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
| StaticInitializationVector.java:32:51:32:56 | ivSpec | semmle.label | ivSpec |
| StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
| StaticInitializationVector.java:48:51:48:56 | ivSpec | semmle.label | ivSpec |
#select
| StaticInitializationVector.java:19:51:19:56 | ivSpec | StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | StaticInitializationVector.java:19:51:19:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:13:21:13:81 | new byte[] | static initialization vector |
| StaticInitializationVector.java:32:51:32:56 | ivSpec | StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | StaticInitializationVector.java:32:51:32:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:26:21:26:32 | new byte[] | static initialization vector |
| StaticInitializationVector.java:48:51:48:56 | ivSpec | StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | StaticInitializationVector.java:48:51:48:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:39:21:39:32 | new byte[] | static initialization vector |

View File

@@ -0,0 +1,118 @@
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Arrays;
public class StaticInitializationVector {
// BAD: AES-GCM with static IV from a byte array
public byte[] encryptWithStaticIvByteArrayWithInitializer(byte[] key, byte[] plaintext) throws Exception {
byte[] iv = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 };
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
// BAD: AES-GCM with static IV from zero-initialized byte array
public byte[] encryptWithZeroStaticIvByteArray(byte[] key, byte[] plaintext) throws Exception {
byte[] iv = new byte[16];
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
// BAD: AES-CBC with static IV from zero-initialized byte array
public byte[] encryptWithStaticIvByteArray(byte[] key, byte[] plaintext) throws Exception {
byte[] iv = new byte[16];
for (byte i = 0; i < iv.length; i++) {
iv[i] = 1;
}
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
// GOOD: AES-GCM with a random IV
public byte[] encryptWithRandomIv(byte[] key, byte[] plaintext) throws Exception {
byte[] iv = new byte[16];
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(iv);
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
// GOOD: AES-GCM with a random IV
public byte[] encryptWithRandomIvByteByByte(byte[] key, byte[] plaintext) throws Exception {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] iv = new byte[16];
for (int i = 0; i < iv.length; i++) {
iv[i] = (byte) random.nextInt();
}
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
// GOOD: AES-GCM with a random IV
public byte[] encryptWithRandomIvWithSystemArrayCopy(byte[] key, byte[] plaintext) throws Exception {
byte[] randomBytes = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(randomBytes);
byte[] iv = new byte[16];
System.arraycopy(randomBytes, 0, iv, 0, 16);
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
// GOOD: AES-GCM with a random IV
public byte[] encryptWithRandomIvWithArraysCopy(byte[] key, byte[] plaintext) throws Exception {
byte[] randomBytes = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(randomBytes);
byte[] iv = Arrays.copyOf(randomBytes, 16);
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
cipher.update(plaintext);
return cipher.doFinal();
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-1204/StaticInitializationVector.ql