From c2c85d32da330d7d242d1711290e83dfa7db1322 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Tue, 1 Jun 2021 20:43:46 +0200 Subject: [PATCH 01/31] Java: Added a query for timing attacks --- .../NotConstantTimeCryptoComparison.ql | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql new file mode 100644 index 00000000000..0096cc67427 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql @@ -0,0 +1,68 @@ +/** + * @name Using a not-constant time algorithm for comparison results of a cryptographic operation + * @description When comparing results of a cryptographic operation, a constant time algorithm should be used. + * Otherwise, an attacker may be able to implement a timing attack. + * A successful attack may result in leaking secrets or authentication bypass. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/not-constant-time-crypto-comparison + * @tags security + * external/cwe/cwe-208 + */ + +import java +import semmle.code.java.dataflow.TaintTracking +import DataFlow::PathGraph + +/** + * A method that returns a result of a cryptographic operation + * such as encryption, decryption, signing, etc. + */ +private class ReturnCryptoOperatinoResultMethod extends Method { + ReturnCryptoOperatinoResultMethod() { + getDeclaringType().hasQualifiedName("javax.crypto", ["Mac", "Cipher"]) and + hasName("doFinal") + or + getDeclaringType().hasQualifiedName("java.security", "Signature") and + hasName("sign") + or + getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + hasName("digest") + } +} + +/** + * A configuration that tracks data flows from cryptographic operations + * to methods that compare data using a not-constant time algorithm. + */ +private class NotConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { + NotConstantTimeCryptoComparisonConfig() { this = "NotConstantTimeCryptoComparisonConfig" } + + override predicate isSource(DataFlow::Node source) { + exists(MethodAccess ma | ma.getMethod() instanceof ReturnCryptoOperatinoResultMethod | + ma = source.asExpr() + ) + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType() instanceof TypeString and + m.hasName(["equals", "contentEquals", "equalsIgnoreCase"]) and + sink.asExpr() = [ma.getQualifier(), ma.getAnArgument()] + or + m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and + m.hasName("equals") and + ma.getAnArgument() = sink.asExpr() + or + m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "StringUtils") and + m.hasName(["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) and + ma.getAnArgument() = sink.asExpr() + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, NotConstantTimeCryptoComparisonConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, + "Using a not-constant time algorithm for comparison results of a cryptographic operation." From 67579dd1d89ff3b546e28d1ee274906de59d8aad Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Tue, 1 Jun 2021 20:44:28 +0200 Subject: [PATCH 02/31] Added tests for NotConstantTimeCryptoComparison.ql --- .../NotConstantTimeCryptoComparison.expected | 19 +++++ .../NotConstantTimeCryptoComparison.java | 73 +++++++++++++++++++ .../NotConstantTimeCryptoComparison.qlref | 1 + 3 files changed, 93 insertions(+) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected new file mode 100644 index 00000000000..23083ff7d18 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected @@ -0,0 +1,19 @@ +edges +| NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | +| NotConstantTimeCryptoComparison.java:28:36:28:50 | digest(...) : byte[] | NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | +| NotConstantTimeCryptoComparison.java:44:28:44:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | +| NotConstantTimeCryptoComparison.java:61:22:61:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | +nodes +| NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | semmle.label | actualMac | +| NotConstantTimeCryptoComparison.java:28:36:28:50 | digest(...) : byte[] | semmle.label | digest(...) : byte[] | +| NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | semmle.label | actual | +| NotConstantTimeCryptoComparison.java:44:28:44:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | semmle.label | signature | +| NotConstantTimeCryptoComparison.java:61:22:61:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | semmle.label | tag | +#select +| NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | Using a not-constant time algorithm for comparison results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | NotConstantTimeCryptoComparison.java:28:36:28:50 | digest(...) : byte[] | NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | Using a not-constant time algorithm for comparison results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | NotConstantTimeCryptoComparison.java:44:28:44:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | Using a not-constant time algorithm for comparison results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | NotConstantTimeCryptoComparison.java:61:22:61:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | Using a not-constant time algorithm for comparison results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java new file mode 100644 index 00000000000..995c342d776 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java @@ -0,0 +1,73 @@ +import java.security.Key; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.Signature; +import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.Mac; + +public class NotConstantTimeCryptoComparison { + + // BAD: compare MACs using a not-constant time method + public boolean unsafeMacCheck(byte[] expectedMac, byte[] data) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = mac.doFinal(data); + return Arrays.equals(expectedMac, actualMac); + } + + // GOOD: compare MACs using a constant time method + public boolean saferMacCheck(byte[] expectedMac, byte[] data) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = mac.doFinal(data); + return MessageDigest.isEqual(expectedMac, actualMac); + } + + // BAD: compare hashes using a not-constant time method + public boolean unsafeCheckMessageDigest(String expectedHash, byte[] data) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + String actual = new String(md.digest(data)); + return actual.equals(expectedHash); + } + + // GOOD: compare hashes using a constant time method + public boolean saferCheckMessageDigest(byte[] expected, byte[] data) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] actual = md.digest(data); + return MessageDigest.isEqual(expected, actual); + } + + // BAD: compare signatures using a not-constant time method + public boolean unsafeCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + engine.update(data); + byte[] signature = engine.sign(); + return Arrays.equals(expected, signature); + } + + // GOOD: compare signatures using a constant time method + public boolean saferCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + engine.update(data); + byte[] signature = engine.sign(); + return MessageDigest.isEqual(expected, signature); + } + + // BAD: compare ciphertexts using a not-constant time method + public boolean unsafeCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(plaintext); + return Arrays.equals(expected, tag); + } + + // GOOD: compare ciphertexts using a constant time method + public boolean saferCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(plaintext); + return MessageDigest.isEqual(expected, tag); + } + +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref new file mode 100644 index 00000000000..b5f48265986 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql \ No newline at end of file From 8a69b7b3ac3507b33b55944ae2d4606f92859080 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 3 Jun 2021 20:24:24 +0200 Subject: [PATCH 03/31] Added NotConstantTimeCryptoComparison.qhelp and examples --- .../NotConstantTimeCryptoComparison.qhelp | 53 +++++++++++++++++++ .../CWE/CWE-208/SafeCryptoHashComparison.java | 5 ++ .../CWE-208/UnsafeCryptoHashComparison.java | 5 ++ 3 files changed, 63 insertions(+) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp new file mode 100644 index 00000000000..37558f5c7d4 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp @@ -0,0 +1,53 @@ + + + + +

+When comparing results of cryptographic operations, such as MAC or cryptographic hash, +a constant time algorithm should be used. In other words, the comparison time should not depend on +the content of the input. Otherwise, an attacker may be able to implement a timing attack. +A successful timing attack may result in leaking secrets or authentication bypass. +

+
+ + +

+Use MessageDigest.isEqual() method to compare results of cryptographic operations. +If this method is used, then the calculation time depends only on the length of input byte arrays, +and does not depend on the contents of the arrays. +

+
+ + +

+The following example uses Arrays.equals() method for comparing cryptographic hashes. +This method implements a not-constant time algorithm: +

+ + +

+The next example example uses a safe not-constant time algorithm for comparing cryptographic hashes: +

+ + +
+ + +
  • + Wikipedia: + Timint attack. +
  • +li> + Common Weakness Enumeration: + CWE-208: Observable Timing Discrepancy. + +
  • + Common Weakness Enumeration: + CWE-385: Covert Timing Channel. +
  • +
  • + Java API Specification: + MessageDigest.isEqual() method +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java new file mode 100644 index 00000000000..563069ff654 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java @@ -0,0 +1,5 @@ +public boolean checkHash(byte[] expectedHash, byte[] data) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] actualHash = md.digest(data); + return MessageDigest.isEqual(expectedHash, actualHash); +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java new file mode 100644 index 00000000000..c145cc0f3c2 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java @@ -0,0 +1,5 @@ +public boolean checkHash(byte[] expectedHash, byte[] data) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] actualHash = md.digest(data); + return Arrays.equals(expectedHash, actualHash); +} \ No newline at end of file From f245dc3ac8f79a4e89fa10755ff4a55134a7c2ac Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Mon, 14 Jun 2021 08:57:10 +0200 Subject: [PATCH 04/31] Removed hashes from NotConstantTimeCryptoComparison.ql --- .../NotConstantTimeCryptoComparison.qhelp | 10 +++++----- .../NotConstantTimeCryptoComparison.ql | 3 --- .../CWE/CWE-208/SafeCryptoHashComparison.java | 5 ----- .../CWE/CWE-208/SafeMacComparison.java | 6 ++++++ .../CWE-208/UnsafeCryptoHashComparison.java | 5 ----- .../CWE/CWE-208/UnsafeMacComparison.java | 6 ++++++ .../NotConstantTimeCryptoComparison.expected | 20 ++++++++----------- .../NotConstantTimeCryptoComparison.java | 14 ------------- 8 files changed, 25 insertions(+), 44 deletions(-) delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp index 37558f5c7d4..ae0b54d12f7 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp @@ -3,7 +3,7 @@

    -When comparing results of cryptographic operations, such as MAC or cryptographic hash, +When comparing results of cryptographic operations, such as MAC or digital signature, a constant time algorithm should be used. In other words, the comparison time should not depend on the content of the input. Otherwise, an attacker may be able to implement a timing attack. A successful timing attack may result in leaking secrets or authentication bypass. @@ -20,15 +20,15 @@ and does not depend on the contents of the arrays.

    -The following example uses Arrays.equals() method for comparing cryptographic hashes. +The following example uses Arrays.equals() method for comparing MAC. This method implements a not-constant time algorithm:

    - +

    -The next example example uses a safe not-constant time algorithm for comparing cryptographic hashes: +The next example example uses a safe not-constant time algorithm for comparing MAC:

    - + diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql index 0096cc67427..8251c210331 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql @@ -26,9 +26,6 @@ private class ReturnCryptoOperatinoResultMethod extends Method { or getDeclaringType().hasQualifiedName("java.security", "Signature") and hasName("sign") - or - getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and - hasName("digest") } } diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java deleted file mode 100644 index 563069ff654..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/SafeCryptoHashComparison.java +++ /dev/null @@ -1,5 +0,0 @@ -public boolean checkHash(byte[] expectedHash, byte[] data) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] actualHash = md.digest(data); - return MessageDigest.isEqual(expectedHash, actualHash); -} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java new file mode 100644 index 00000000000..f2cc5f95ef8 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java @@ -0,0 +1,6 @@ +public boolean check(byte[] expected, byte[] data, SecretKey key) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); + byte[] actual = mac.doFinal(data); + return MessageDigest.isEqual(expected, actual); +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java deleted file mode 100644 index c145cc0f3c2..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeCryptoHashComparison.java +++ /dev/null @@ -1,5 +0,0 @@ -public boolean checkHash(byte[] expectedHash, byte[] data) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] actualHash = md.digest(data); - return Arrays.equals(expectedHash, actualHash); -} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java new file mode 100644 index 00000000000..ec5f423aa4b --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java @@ -0,0 +1,6 @@ +public boolean check(byte[] expected, byte[] data, SecretKey key) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); + byte[] actual = mac.doFinal(data); + return Arrays.equals(expected, actual); +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected index 23083ff7d18..e2efbbe1a59 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected @@ -1,19 +1,15 @@ edges | NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | -| NotConstantTimeCryptoComparison.java:28:36:28:50 | digest(...) : byte[] | NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | -| NotConstantTimeCryptoComparison.java:44:28:44:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | -| NotConstantTimeCryptoComparison.java:61:22:61:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | +| NotConstantTimeCryptoComparison.java:30:28:30:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | +| NotConstantTimeCryptoComparison.java:47:22:47:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | nodes | NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | semmle.label | actualMac | -| NotConstantTimeCryptoComparison.java:28:36:28:50 | digest(...) : byte[] | semmle.label | digest(...) : byte[] | -| NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | semmle.label | actual | -| NotConstantTimeCryptoComparison.java:44:28:44:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | semmle.label | signature | -| NotConstantTimeCryptoComparison.java:61:22:61:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | semmle.label | tag | +| NotConstantTimeCryptoComparison.java:30:28:30:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | semmle.label | signature | +| NotConstantTimeCryptoComparison.java:47:22:47:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | semmle.label | tag | #select | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | Using a not-constant time algorithm for comparison results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | NotConstantTimeCryptoComparison.java:28:36:28:50 | digest(...) : byte[] | NotConstantTimeCryptoComparison.java:29:16:29:21 | actual | Using a not-constant time algorithm for comparison results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | NotConstantTimeCryptoComparison.java:44:28:44:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:45:40:45:48 | signature | Using a not-constant time algorithm for comparison results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | NotConstantTimeCryptoComparison.java:61:22:61:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:62:40:62:42 | tag | Using a not-constant time algorithm for comparison results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | NotConstantTimeCryptoComparison.java:30:28:30:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | Using a not-constant time algorithm for comparison results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | NotConstantTimeCryptoComparison.java:47:22:47:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | Using a not-constant time algorithm for comparison results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java index 995c342d776..0e43103fffd 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java @@ -22,20 +22,6 @@ public class NotConstantTimeCryptoComparison { return MessageDigest.isEqual(expectedMac, actualMac); } - // BAD: compare hashes using a not-constant time method - public boolean unsafeCheckMessageDigest(String expectedHash, byte[] data) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - String actual = new String(md.digest(data)); - return actual.equals(expectedHash); - } - - // GOOD: compare hashes using a constant time method - public boolean saferCheckMessageDigest(byte[] expected, byte[] data) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] actual = md.digest(data); - return MessageDigest.isEqual(expected, actual); - } - // BAD: compare signatures using a not-constant time method public boolean unsafeCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); From 5c474f689d2ed036dc6bb4cfc37c876139ef3d32 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 16 Jun 2021 19:15:10 +0200 Subject: [PATCH 05/31] Better comments and descriptions --- .../NotConstantTimeCryptoComparison.qhelp | 16 +++--------- .../NotConstantTimeCryptoComparison.ql | 25 ++++++++++--------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp index ae0b54d12f7..2bee3cf9dad 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp @@ -21,12 +21,12 @@ and does not depend on the contents of the arrays.

    The following example uses Arrays.equals() method for comparing MAC. -This method implements a not-constant time algorithm: +This method implements a non-constant time algorithm:

    -The next example example uses a safe not-constant time algorithm for comparing MAC: +The next example uses a safe constant time algorithm for comparing MAC:

    @@ -35,19 +35,11 @@ The next example example uses a safe not-constant time algorithm for comparing M
  • Wikipedia: - Timint attack. -
  • -li> - Common Weakness Enumeration: - CWE-208: Observable Timing Discrepancy. - -
  • - Common Weakness Enumeration: - CWE-385: Covert Timing Channel. + Timing attack.
  • Java API Specification: - MessageDigest.isEqual() method + MessageDigest.isEqual() method
  • diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql index 8251c210331..3f455252b58 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql @@ -1,13 +1,14 @@ /** - * @name Using a not-constant time algorithm for comparison results of a cryptographic operation + * @name Using a non-constant time algorithm for comparing results of a cryptographic operation * @description When comparing results of a cryptographic operation, a constant time algorithm should be used. * Otherwise, an attacker may be able to implement a timing attack. * A successful attack may result in leaking secrets or authentication bypass. * @kind path-problem * @problem.severity error * @precision high - * @id java/not-constant-time-crypto-comparison + * @id java/non-constant-time-crypto-comparison * @tags security + * external/cwe/cwe-385 * external/cwe/cwe-208 */ @@ -16,11 +17,11 @@ import semmle.code.java.dataflow.TaintTracking import DataFlow::PathGraph /** - * A method that returns a result of a cryptographic operation + * A method that returns the result of a cryptographic operation * such as encryption, decryption, signing, etc. */ -private class ReturnCryptoOperatinoResultMethod extends Method { - ReturnCryptoOperatinoResultMethod() { +private class ReturnCryptoOperationResultMethod extends Method { + ReturnCryptoOperationResultMethod() { getDeclaringType().hasQualifiedName("javax.crypto", ["Mac", "Cipher"]) and hasName("doFinal") or @@ -30,14 +31,14 @@ private class ReturnCryptoOperatinoResultMethod extends Method { } /** - * A configuration that tracks data flows from cryptographic operations - * to methods that compare data using a not-constant time algorithm. + * A configuration that tracks data flow from cryptographic operations + * to methods that compare data using a non-constant time algorithm. */ -private class NotConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { - NotConstantTimeCryptoComparisonConfig() { this = "NotConstantTimeCryptoComparisonConfig" } +private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { + NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" } override predicate isSource(DataFlow::Node source) { - exists(MethodAccess ma | ma.getMethod() instanceof ReturnCryptoOperatinoResultMethod | + exists(MethodAccess ma | ma.getMethod() instanceof ReturnCryptoOperationResultMethod | ma = source.asExpr() ) } @@ -59,7 +60,7 @@ private class NotConstantTimeCryptoComparisonConfig extends TaintTracking::Confi } } -from DataFlow::PathNode source, DataFlow::PathNode sink, NotConstantTimeCryptoComparisonConfig conf +from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf where conf.hasFlowPath(source, sink) select sink.getNode(), source, sink, - "Using a not-constant time algorithm for comparison results of a cryptographic operation." + "Using a non-constant time algorithm for comparing results of a cryptographic operation." From 5dbcf1d611578ac5130591485f09a5b8484d72cf Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 16 Jun 2021 19:31:37 +0200 Subject: [PATCH 06/31] Covered Object.deepEquals() in NotConstantTimeCryptoComparison.ql --- .../NotConstantTimeCryptoComparison.ql | 4 ++++ .../NotConstantTimeCryptoComparison.expected | 24 +++++++++---------- .../NotConstantTimeCryptoComparison.java | 3 ++- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql index 3f455252b58..59d4092189a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql @@ -53,6 +53,10 @@ private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Confi m.hasName("equals") and ma.getAnArgument() = sink.asExpr() or + m.getDeclaringType().hasQualifiedName("java.util", "Objects") and + m.hasName("deepEquals") and + ma.getAnArgument() = sink.asExpr() + or m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "StringUtils") and m.hasName(["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) and ma.getAnArgument() = sink.asExpr() diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected index e2efbbe1a59..afe26a0b272 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected @@ -1,15 +1,15 @@ edges -| NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | -| NotConstantTimeCryptoComparison.java:30:28:30:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | -| NotConstantTimeCryptoComparison.java:47:22:47:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | +| NotConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | +| NotConstantTimeCryptoComparison.java:31:28:31:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | +| NotConstantTimeCryptoComparison.java:48:22:48:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | nodes -| NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | semmle.label | actualMac | -| NotConstantTimeCryptoComparison.java:30:28:30:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | semmle.label | signature | -| NotConstantTimeCryptoComparison.java:47:22:47:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | semmle.label | tag | +| NotConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | semmle.label | actualMac | +| NotConstantTimeCryptoComparison.java:31:28:31:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | semmle.label | signature | +| NotConstantTimeCryptoComparison.java:48:22:48:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | semmle.label | tag | #select -| NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | NotConstantTimeCryptoComparison.java:14:28:14:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:15:43:15:51 | actualMac | Using a not-constant time algorithm for comparison results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | NotConstantTimeCryptoComparison.java:30:28:30:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:31:40:31:48 | signature | Using a not-constant time algorithm for comparison results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | NotConstantTimeCryptoComparison.java:47:22:47:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:48:40:48:42 | tag | Using a not-constant time algorithm for comparison results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | NotConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | NotConstantTimeCryptoComparison.java:31:28:31:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | NotConstantTimeCryptoComparison.java:48:22:48:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java index 0e43103fffd..91adb2d0f7b 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java @@ -3,6 +3,7 @@ import java.security.MessageDigest; import java.security.PrivateKey; import java.security.Signature; import java.util.Arrays; +import java.util.Objects; import javax.crypto.Cipher; import javax.crypto.Mac; @@ -45,7 +46,7 @@ public class NotConstantTimeCryptoComparison { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] tag = cipher.doFinal(plaintext); - return Arrays.equals(expected, tag); + return Objects.deepEquals(expected, tag); } // GOOD: compare ciphertexts using a constant time method From 75f67959f3c3d3e5d152b9974712ecedff1c5781 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 16 Jun 2021 19:44:51 +0200 Subject: [PATCH 07/31] Covered Arrays.deepEquals() in NonConstantTimeCryptoComparison.ql --- .../NotConstantTimeCryptoComparison.ql | 2 +- ...a => NonConstantTimeCryptoComparison.java} | 24 ++++++++++++--- .../NotConstantTimeCryptoComparison.expected | 30 +++++++++++-------- 3 files changed, 39 insertions(+), 17 deletions(-) rename java/ql/test/experimental/query-tests/security/CWE-208/{NotConstantTimeCryptoComparison.java => NonConstantTimeCryptoComparison.java} (73%) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql index 59d4092189a..9a76f0a2fd6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql @@ -50,7 +50,7 @@ private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Confi sink.asExpr() = [ma.getQualifier(), ma.getAnArgument()] or m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and - m.hasName("equals") and + m.hasName(["equals", "deepEquals"]) and ma.getAnArgument() = sink.asExpr() or m.getDeclaringType().hasQualifiedName("java.util", "Objects") and diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java similarity index 73% rename from java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java rename to java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index 91adb2d0f7b..3a1ddc2accb 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -7,15 +7,31 @@ import java.util.Objects; import javax.crypto.Cipher; import javax.crypto.Mac; -public class NotConstantTimeCryptoComparison { +public class NonConstantTimeCryptoComparison { - // BAD: compare MACs using a not-constant time method + // BAD: compare MACs using a non-constant time method public boolean unsafeMacCheck(byte[] expectedMac, byte[] data) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] actualMac = mac.doFinal(data); return Arrays.equals(expectedMac, actualMac); } + + // BAD: compare MACs using a non-constant time method + public boolean unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, byte[] data) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = mac.doFinal(data); + return Arrays.deepEquals(cast(expectedMac), cast(actualMac)); + } + + private static Object[] cast(byte[] array) { + Object[] result = new Object[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i]; + } + return result; + } + // GOOD: compare MACs using a constant time method public boolean saferMacCheck(byte[] expectedMac, byte[] data) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); @@ -23,7 +39,7 @@ public class NotConstantTimeCryptoComparison { return MessageDigest.isEqual(expectedMac, actualMac); } - // BAD: compare signatures using a not-constant time method + // BAD: compare signatures using a non-constant time method public boolean unsafeCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); @@ -41,7 +57,7 @@ public class NotConstantTimeCryptoComparison { return MessageDigest.isEqual(expected, signature); } - // BAD: compare ciphertexts using a not-constant time method + // BAD: compare ciphertexts using a non-constant time method public boolean unsafeCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected index afe26a0b272..d0fc9c0165a 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected @@ -1,15 +1,21 @@ edges -| NotConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | -| NotConstantTimeCryptoComparison.java:31:28:31:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | -| NotConstantTimeCryptoComparison.java:48:22:48:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | +| NonConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | +| NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:58:24:66 | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:24:58:24:66 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | +| NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | +| NonConstantTimeCryptoComparison.java:64:22:64:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | nodes -| NotConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | semmle.label | actualMac | -| NotConstantTimeCryptoComparison.java:31:28:31:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | semmle.label | signature | -| NotConstantTimeCryptoComparison.java:48:22:48:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | semmle.label | cast(...) | +| NonConstantTimeCryptoComparison.java:24:58:24:66 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:64:22:64:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | semmle.label | tag | #select -| NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | NotConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | NotConstantTimeCryptoComparison.java:31:28:31:40 | sign(...) : byte[] | NotConstantTimeCryptoComparison.java:32:40:32:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | NotConstantTimeCryptoComparison.java:48:22:48:46 | doFinal(...) : byte[] | NotConstantTimeCryptoComparison.java:49:45:49:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | NonConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | NonConstantTimeCryptoComparison.java:64:22:64:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | From dfa3b523d076c92d7aedd1325e99df1f39a67334 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 16 Jun 2021 19:48:07 +0200 Subject: [PATCH 08/31] Renamed files --- ...ptoComparison.qhelp => NonConstantTimeCryptoComparison.qhelp} | 0 ...imeCryptoComparison.ql => NonConstantTimeCryptoComparison.ql} | 0 ...parison.expected => NonConstantTimeCryptoComparison.expected} | 0 .../security/CWE-208/NonConstantTimeCryptoComparison.qlref | 1 + .../security/CWE-208/NotConstantTimeCryptoComparison.qlref | 1 - 5 files changed, 1 insertion(+), 1 deletion(-) rename java/ql/src/experimental/Security/CWE/CWE-208/{NotConstantTimeCryptoComparison.qhelp => NonConstantTimeCryptoComparison.qhelp} (100%) rename java/ql/src/experimental/Security/CWE/CWE-208/{NotConstantTimeCryptoComparison.ql => NonConstantTimeCryptoComparison.ql} (100%) rename java/ql/test/experimental/query-tests/security/CWE-208/{NotConstantTimeCryptoComparison.expected => NonConstantTimeCryptoComparison.expected} (100%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp similarity index 100% rename from java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.qhelp rename to java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql similarity index 100% rename from java/ql/src/experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql rename to java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected similarity index 100% rename from java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.expected rename to java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref new file mode 100644 index 00000000000..620921375e5 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref deleted file mode 100644 index b5f48265986..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCryptoComparison.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-208/NotConstantTimeCryptoComparison.ql \ No newline at end of file From 8e6d227dc03f9fe16e2f2d49ffa783ad5699b974 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Thu, 17 Jun 2021 19:33:37 +0200 Subject: [PATCH 09/31] More sinks for java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql --- .../NonConstantTimeCryptoComparison.ql | 127 +++++++++++++----- .../NonConstantTimeCryptoComparison.expected | 46 +++++-- .../NonConstantTimeCryptoComparison.java | 69 ++++++++-- 3 files changed, 188 insertions(+), 54 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 9a76f0a2fd6..555bbd034b8 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -8,7 +8,6 @@ * @precision high * @id java/non-constant-time-crypto-comparison * @tags security - * external/cwe/cwe-385 * external/cwe/cwe-208 */ @@ -17,19 +16,97 @@ import semmle.code.java.dataflow.TaintTracking import DataFlow::PathGraph /** - * A method that returns the result of a cryptographic operation - * such as encryption, decryption, signing, etc. + * A source that produces a MAC. */ -private class ReturnCryptoOperationResultMethod extends Method { - ReturnCryptoOperationResultMethod() { - getDeclaringType().hasQualifiedName("javax.crypto", ["Mac", "Cipher"]) and - hasName("doFinal") - or - getDeclaringType().hasQualifiedName("java.security", "Signature") and - hasName("sign") +private class MacSource extends DataFlow::Node { + MacSource() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("javax.crypto", "Mac", "doFinal") and + ( + m.getReturnType() instanceof Array and ma = this.asExpr() + or + m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr() + ) + ) } } +/** + * A source that produces a signature. + */ +private class SignatureSource extends DataFlow::Node { + SignatureSource() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("java.security", "Signature", "sign") and + ( + m.getReturnType() instanceof Array and ma = this.asExpr() + or + m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr() + ) + ) + } +} + +/** + * A source that produces a ciphertext. + */ +private class CiphertextSource extends DataFlow::Node { + CiphertextSource() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("javax.crypto", "Cipher", "doFinal") and + ( + m.getReturnType() instanceof Array and ma = this.asExpr() + or + m.getParameterType([0, 3]) instanceof Array and ma.getArgument([0, 3]) = this.asExpr() + or + m.getParameterType(1).(RefType).hasQualifiedName("java.nio", "ByteBuffer") and + ma.getArgument(1) = this.asExpr() + ) + ) + } +} + +/** + * A sink that compares input using a non-constant time algorithm. + */ +private class KnownNonConstantTimeComparisonSink extends DataFlow::Node { + KnownNonConstantTimeComparisonSink() { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) and + this.asExpr() = [ma.getQualifier(), ma.getAnArgument()] + or + m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) and + this.asExpr() = [ma.getQualifier(), ma.getAnArgument()] + or + m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) and + ma.getAnArgument() = this.asExpr() + or + m.hasQualifiedName("java.util", "Objects", "deepEquals") and + ma.getAnArgument() = this.asExpr() + or + m.hasQualifiedName("org.apache.commons.lang3", "StringUtils", + ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) and + ma.getAnArgument() = this.asExpr() + ) + } +} + +/** + * Holds if `fromNode` to `toNode` is a dataflow step + * that converts a `ByteBuffer` to a byte array and vice versa. + */ +private predicate convertByteBufferStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("java.nio", "ByteBuffer", "array") and + ma.getQualifier() = fromNode.asExpr() and + ma = toNode.asExpr() + or + m.hasQualifiedName("java.nio", "ByteBuffer", "wrap") and + ma.getAnArgument() = fromNode.asExpr() and + ma = toNode.asExpr() + ) +} + /** * A configuration that tracks data flow from cryptographic operations * to methods that compare data using a non-constant time algorithm. @@ -38,29 +115,19 @@ private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Confi NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" } override predicate isSource(DataFlow::Node source) { - exists(MethodAccess ma | ma.getMethod() instanceof ReturnCryptoOperationResultMethod | - ma = source.asExpr() - ) + source instanceof MacSource + or + source instanceof SignatureSource + or + source instanceof CiphertextSource } override predicate isSink(DataFlow::Node sink) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.getDeclaringType() instanceof TypeString and - m.hasName(["equals", "contentEquals", "equalsIgnoreCase"]) and - sink.asExpr() = [ma.getQualifier(), ma.getAnArgument()] - or - m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and - m.hasName(["equals", "deepEquals"]) and - ma.getAnArgument() = sink.asExpr() - or - m.getDeclaringType().hasQualifiedName("java.util", "Objects") and - m.hasName("deepEquals") and - ma.getAnArgument() = sink.asExpr() - or - m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "StringUtils") and - m.hasName(["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) and - ma.getAnArgument() = sink.asExpr() - ) + sink instanceof KnownNonConstantTimeComparisonSink + } + + override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { + convertByteBufferStep(fromNode, toNode) } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected index d0fc9c0165a..1aff900f2c9 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected @@ -1,21 +1,41 @@ edges -| NonConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | -| NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:58:24:66 | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:24:58:24:66 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | +| NonConstantTimeCryptoComparison.java:16:28:16:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | +| NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:84:24:92 | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:24:84:24:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:31:21:31:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | | NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | -| NonConstantTimeCryptoComparison.java:64:22:64:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | +| NonConstantTimeCryptoComparison.java:56:21:56:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | +| NonConstantTimeCryptoComparison.java:73:22:73:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | +| NonConstantTimeCryptoComparison.java:83:24:83:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | +| NonConstantTimeCryptoComparison.java:93:52:93:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | +| NonConstantTimeCryptoComparison.java:103:52:103:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | nodes -| NonConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:16:28:16:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | semmle.label | actualMac | | NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | semmle.label | cast(...) | -| NonConstantTimeCryptoComparison.java:24:58:24:66 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:24:84:24:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:31:21:31:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | semmle.label | actualMac | | NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:64:22:64:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:56:21:56:29 | signature : byte[] | semmle.label | signature : byte[] | +| NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:73:22:73:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:83:24:83:26 | tag : byte[] | semmle.label | tag : byte[] | +| NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:93:52:93:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | semmle.label | array(...) | +| NonConstantTimeCryptoComparison.java:103:52:103:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | semmle.label | tag | #select -| NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | NonConstantTimeCryptoComparison.java:15:28:15:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:16:43:16:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:53:24:67 | cast(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | NonConstantTimeCryptoComparison.java:16:28:16:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | NonConstantTimeCryptoComparison.java:31:21:31:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | NonConstantTimeCryptoComparison.java:64:22:64:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:65:45:65:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | NonConstantTimeCryptoComparison.java:56:21:56:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | NonConstantTimeCryptoComparison.java:73:22:73:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | NonConstantTimeCryptoComparison.java:83:24:83:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | NonConstantTimeCryptoComparison.java:93:52:93:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | NonConstantTimeCryptoComparison.java:103:52:103:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index 3a1ddc2accb..12881c7458c 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -1,3 +1,4 @@ +import java.nio.ByteBuffer; import java.security.Key; import java.security.MessageDigest; import java.security.PrivateKey; @@ -10,26 +11,25 @@ import javax.crypto.Mac; public class NonConstantTimeCryptoComparison { // BAD: compare MACs using a non-constant time method - public boolean unsafeMacCheck(byte[] expectedMac, byte[] data) throws Exception { + public boolean unsafeMacCheckWithArrayEquals(byte[] expectedMac, byte[] data) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] actualMac = mac.doFinal(data); return Arrays.equals(expectedMac, actualMac); } - // BAD: compare MACs using a non-constant time method public boolean unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, byte[] data) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] actualMac = mac.doFinal(data); - return Arrays.deepEquals(cast(expectedMac), cast(actualMac)); + return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); } - private static Object[] cast(byte[] array) { - Object[] result = new Object[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; + // BAD: compare MACs using a non-constant time method + public boolean unsafeMacCheckWithDoFinalWithOutputArray(byte[] expectedMac, byte[] data) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = new byte[256]; + mac.doFinal(actualMac, 0); + return Arrays.equals(expectedMac, actualMac); } // GOOD: compare MACs using a constant time method @@ -48,6 +48,15 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expected, signature); } + // BAD: compare signatures using a non-constant time method + public boolean unsafeCheckSignaturesWithOutputArray(byte[] expected, byte[] data, PrivateKey key) throws Exception { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + byte[] signature = new byte[1024]; + engine.sign(signature, 0, 1024); + return Arrays.equals(expected, signature); + } + // GOOD: compare signatures using a constant time method public boolean saferCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); @@ -58,19 +67,57 @@ public class NonConstantTimeCryptoComparison { } // BAD: compare ciphertexts using a non-constant time method - public boolean unsafeCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean unsafeCheckCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] tag = cipher.doFinal(plaintext); return Objects.deepEquals(expected, tag); } + // BAD: compare ciphertexts using a non-constant time method + public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.update(plaintext); + byte[] tag = new byte[1024]; + cipher.doFinal(tag, 0); + return Arrays.equals(expected, tag); + } + + // BAD: compare ciphertexts using a non-constant time method + public boolean unsafeCheckCiphertextWithByteBuffer(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.update(plaintext); + ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); + cipher.doFinal(ByteBuffer.wrap(plaintext), tag); + return Arrays.equals(expected, tag.array()); + } + + // BAD: compare ciphertexts using a non-constant time method + public boolean unsafeCheckCiphertextWithByteBufferEquals(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.update(plaintext); + ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); + cipher.doFinal(ByteBuffer.wrap(plaintext), tag); + return ByteBuffer.wrap(expected).equals(tag); + } + // GOOD: compare ciphertexts using a constant time method - public boolean saferCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean saferCheckCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] tag = cipher.doFinal(plaintext); return MessageDigest.isEqual(expected, tag); } + + private static Object[] castToObjectArray(byte[] array) { + Object[] result = new Object[array.length]; + for (int i = 0; i < array.length; i++) { + result[i] = array[i]; + } + return result; + } } \ No newline at end of file From a4f3a5a88e33c9ac81f6d258598cb430cd638b8d Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 15:32:01 +0200 Subject: [PATCH 10/31] Take into account remote user input in java/non-constant-time-crypto-comparison --- .../NonConstantTimeCryptoComparison.ql | 166 +++++++++++++----- .../NonConstantTimeCryptoComparison.expected | 76 ++++---- .../NonConstantTimeCryptoComparison.java | 43 +++-- 3 files changed, 187 insertions(+), 98 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 555bbd034b8..c2f09bf2a70 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -13,12 +13,59 @@ import java import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.dataflow.TaintTracking2 +import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph +private class UserInputInCryptoOperationConfig extends TaintTracking2::Configuration { + UserInputInCryptoOperationConfig() { this = "UserInputInCryptoOperationConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + ( + ( + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") or + m.hasQualifiedName("java.security", "Signature", "sign") + ) and + ma.getQualifier() = sink.asExpr() + ) + ) + } + + override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + ( + ( + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], ["doFinal", "update"]) or + m.hasQualifiedName("java.security", "Signature", ["sign", "update"]) + ) and + ma.getAnArgument() = fromNode.asExpr() and + ma.getQualifier() = toNode.asExpr() + ) + ) + } +} + +abstract private class CryptoOperationSource extends DataFlow::Node { + Expr cryptoOperation; + + predicate includesUserInput() { + exists( + DataFlow2::PathNode source, DataFlow2::PathNode sink, UserInputInCryptoOperationConfig config + | + config.hasFlowPath(source, sink) + | + sink.getNode().asExpr() = cryptoOperation + ) + } +} + /** * A source that produces a MAC. */ -private class MacSource extends DataFlow::Node { +private class MacSource extends CryptoOperationSource { MacSource() { exists(MethodAccess ma, Method m | m = ma.getMethod() | m.hasQualifiedName("javax.crypto", "Mac", "doFinal") and @@ -26,7 +73,8 @@ private class MacSource extends DataFlow::Node { m.getReturnType() instanceof Array and ma = this.asExpr() or m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr() - ) + ) and + cryptoOperation = ma.getQualifier() ) } } @@ -34,7 +82,7 @@ private class MacSource extends DataFlow::Node { /** * A source that produces a signature. */ -private class SignatureSource extends DataFlow::Node { +private class SignatureSource extends CryptoOperationSource { SignatureSource() { exists(MethodAccess ma, Method m | m = ma.getMethod() | m.hasQualifiedName("java.security", "Signature", "sign") and @@ -42,7 +90,8 @@ private class SignatureSource extends DataFlow::Node { m.getReturnType() instanceof Array and ma = this.asExpr() or m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr() - ) + ) and + cryptoOperation = ma.getQualifier() ) } } @@ -50,7 +99,7 @@ private class SignatureSource extends DataFlow::Node { /** * A source that produces a ciphertext. */ -private class CiphertextSource extends DataFlow::Node { +private class CiphertextSource extends CryptoOperationSource { CiphertextSource() { exists(MethodAccess ma, Method m | m = ma.getMethod() | m.hasQualifiedName("javax.crypto", "Cipher", "doFinal") and @@ -61,7 +110,28 @@ private class CiphertextSource extends DataFlow::Node { or m.getParameterType(1).(RefType).hasQualifiedName("java.nio", "ByteBuffer") and ma.getArgument(1) = this.asExpr() - ) + ) and + cryptoOperation = ma.getQualifier() + ) + } +} + +private class UserInputComparisonConfig extends TaintTracking2::Configuration { + UserInputComparisonConfig() { this = "UserInputComparisonConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + ( + m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or + m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) or + m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or + m.hasQualifiedName("java.util", "Objects", "deepEquals") or + m.hasQualifiedName("org.apache.commons.lang3", "StringUtils", + ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) + ) and + [ma.getAnArgument(), ma.getQualifier()] = sink.asExpr() ) } } @@ -70,41 +140,46 @@ private class CiphertextSource extends DataFlow::Node { * A sink that compares input using a non-constant time algorithm. */ private class KnownNonConstantTimeComparisonSink extends DataFlow::Node { + Expr anotherParameter; + KnownNonConstantTimeComparisonSink() { exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) and - this.asExpr() = [ma.getQualifier(), ma.getAnArgument()] - or - m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) and - this.asExpr() = [ma.getQualifier(), ma.getAnArgument()] - or - m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) and - ma.getAnArgument() = this.asExpr() - or - m.hasQualifiedName("java.util", "Objects", "deepEquals") and - ma.getAnArgument() = this.asExpr() - or - m.hasQualifiedName("org.apache.commons.lang3", "StringUtils", - ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) and - ma.getAnArgument() = this.asExpr() + ( + m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or + m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) + ) and + ( + this.asExpr() = ma.getQualifier() and + anotherParameter = ma.getAnArgument() and + not ma.getAnArgument().isCompileTimeConstant() + or + this.asExpr() = ma.getAnArgument() and + anotherParameter = ma.getQualifier() and + not ma.getQualifier().isCompileTimeConstant() + ) + ) + or + exists(StaticMethodAccess ma, Method m | m = ma.getMethod() | + ( + m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or + m.hasQualifiedName("java.util", "Objects", "deepEquals") or + m.hasQualifiedName("org.apache.commons.lang3", "StringUtils", + ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) + ) and + ma.getAnArgument() = this.asExpr() and + ( + this.asExpr() = ma.getArgument(0) and anotherParameter = ma.getArgument(1) + or + this.asExpr() = ma.getArgument(1) and anotherParameter = ma.getArgument(0) + ) ) } -} -/** - * Holds if `fromNode` to `toNode` is a dataflow step - * that converts a `ByteBuffer` to a byte array and vice versa. - */ -private predicate convertByteBufferStep(DataFlow::Node fromNode, DataFlow::Node toNode) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasQualifiedName("java.nio", "ByteBuffer", "array") and - ma.getQualifier() = fromNode.asExpr() and - ma = toNode.asExpr() - or - m.hasQualifiedName("java.nio", "ByteBuffer", "wrap") and - ma.getAnArgument() = fromNode.asExpr() and - ma = toNode.asExpr() - ) + predicate includesUserInput() { + exists(UserInputComparisonConfig config | + config.hasFlowTo(DataFlow2::exprNode(anotherParameter)) + ) + } } /** @@ -114,24 +189,19 @@ private predicate convertByteBufferStep(DataFlow::Node fromNode, DataFlow::Node private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" } - override predicate isSource(DataFlow::Node source) { - source instanceof MacSource - or - source instanceof SignatureSource - or - source instanceof CiphertextSource - } + override predicate isSource(DataFlow::Node source) { source instanceof CryptoOperationSource } override predicate isSink(DataFlow::Node sink) { sink instanceof KnownNonConstantTimeComparisonSink } - - override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) { - convertByteBufferStep(fromNode, toNode) - } } from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf -where conf.hasFlowPath(source, sink) +where + conf.hasFlowPath(source, sink) and + ( + source.getNode().(CryptoOperationSource).includesUserInput() or + sink.getNode().(KnownNonConstantTimeComparisonSink).includesUserInput() + ) select sink.getNode(), source, sink, "Using a non-constant time algorithm for comparing results of a cryptographic operation." diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected index 1aff900f2c9..01b21f2ab41 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected @@ -1,41 +1,43 @@ edges -| NonConstantTimeCryptoComparison.java:16:28:16:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | -| NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:84:24:92 | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:24:84:24:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:31:21:31:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | -| NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | -| NonConstantTimeCryptoComparison.java:56:21:56:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | -| NonConstantTimeCryptoComparison.java:73:22:73:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | -| NonConstantTimeCryptoComparison.java:83:24:83:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | -| NonConstantTimeCryptoComparison.java:93:52:93:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | -| NonConstantTimeCryptoComparison.java:103:52:103:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | +| NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | +| NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:29:84:29:92 | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:29:84:29:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:36:21:36:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | +| NonConstantTimeCryptoComparison.java:56:28:56:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | +| NonConstantTimeCryptoComparison.java:67:21:67:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | +| NonConstantTimeCryptoComparison.java:85:22:85:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | +| NonConstantTimeCryptoComparison.java:97:24:97:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | +| NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | +| NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | nodes -| NonConstantTimeCryptoComparison.java:16:28:16:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:24:84:24:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:31:21:31:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:56:21:56:29 | signature : byte[] | semmle.label | signature : byte[] | +| NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:29:84:29:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:36:21:36:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:56:28:56:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:73:22:73:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:83:24:83:26 | tag : byte[] | semmle.label | tag : byte[] | -| NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:93:52:93:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | semmle.label | array(...) | -| NonConstantTimeCryptoComparison.java:103:52:103:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:67:21:67:29 | signature : byte[] | semmle.label | signature : byte[] | +| NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:85:22:85:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:97:24:97:26 | tag : byte[] | semmle.label | tag : byte[] | +| NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | semmle.label | array(...) | +| NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | semmle.label | tag | #select -| NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | NonConstantTimeCryptoComparison.java:16:28:16:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:17:43:17:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:23:28:23:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:24:66:24:93 | castToObjectArray(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | NonConstantTimeCryptoComparison.java:31:21:31:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:32:43:32:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | NonConstantTimeCryptoComparison.java:47:28:47:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:48:40:48:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | NonConstantTimeCryptoComparison.java:56:21:56:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | NonConstantTimeCryptoComparison.java:73:22:73:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:74:45:74:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | NonConstantTimeCryptoComparison.java:83:24:83:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:84:40:84:42 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | NonConstantTimeCryptoComparison.java:93:52:93:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:94:40:94:50 | array(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | NonConstantTimeCryptoComparison.java:103:52:103:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:104:49:104:51 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | NonConstantTimeCryptoComparison.java:36:21:36:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | NonConstantTimeCryptoComparison.java:56:28:56:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | NonConstantTimeCryptoComparison.java:67:21:67:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | NonConstantTimeCryptoComparison.java:85:22:85:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | NonConstantTimeCryptoComparison.java:97:24:97:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index 12881c7458c..7b9323fd483 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -1,3 +1,4 @@ +import java.net.Socket; import java.nio.ByteBuffer; import java.security.Key; import java.security.MessageDigest; @@ -11,73 +12,86 @@ import javax.crypto.Mac; public class NonConstantTimeCryptoComparison { // BAD: compare MACs using a non-constant time method - public boolean unsafeMacCheckWithArrayEquals(byte[] expectedMac, byte[] data) throws Exception { + public boolean unsafeMacCheckWithArrayEquals(byte[] expectedMac, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = new byte[1024]; + socket.getInputStream().read(data); byte[] actualMac = mac.doFinal(data); return Arrays.equals(expectedMac, actualMac); } // BAD: compare MACs using a non-constant time method - public boolean unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, byte[] data) throws Exception { + public boolean unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); - byte[] actualMac = mac.doFinal(data); + byte[] data = socket.getInputStream().readAllBytes(); + mac.update(data); + byte[] actualMac = mac.doFinal(); return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); } // BAD: compare MACs using a non-constant time method - public boolean unsafeMacCheckWithDoFinalWithOutputArray(byte[] expectedMac, byte[] data) throws Exception { + public boolean unsafeMacCheckWithDoFinalWithOutputArray(byte[] data, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] actualMac = new byte[256]; mac.doFinal(actualMac, 0); + byte[] expectedMac = socket.getInputStream().readNBytes(256); return Arrays.equals(expectedMac, actualMac); } // GOOD: compare MACs using a constant time method - public boolean saferMacCheck(byte[] expectedMac, byte[] data) throws Exception { + public boolean saferMacCheck(byte[] expectedMac, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = new byte[1024]; + socket.getInputStream().read(data); byte[] actualMac = mac.doFinal(data); return MessageDigest.isEqual(expectedMac, actualMac); } // BAD: compare signatures using a non-constant time method - public boolean unsafeCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { + public boolean unsafeCheckSignatures(byte[] expected, Socket socket, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); + byte[] data = socket.getInputStream().readAllBytes(); engine.update(data); byte[] signature = engine.sign(); return Arrays.equals(expected, signature); } // BAD: compare signatures using a non-constant time method - public boolean unsafeCheckSignaturesWithOutputArray(byte[] expected, byte[] data, PrivateKey key) throws Exception { + public boolean unsafeCheckSignaturesWithOutputArray(byte[] expected, Socket socket, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); + byte[] data = socket.getInputStream().readAllBytes(); + engine.update(data); byte[] signature = new byte[1024]; engine.sign(signature, 0, 1024); return Arrays.equals(expected, signature); } // GOOD: compare signatures using a constant time method - public boolean saferCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { + public boolean saferCheckSignatures(byte[] expected, Socket socket, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); + byte[] data = socket.getInputStream().readAllBytes(); engine.update(data); byte[] signature = engine.sign(); return MessageDigest.isEqual(expected, signature); } // BAD: compare ciphertexts using a non-constant time method - public boolean unsafeCheckCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean unsafeCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] tag = cipher.doFinal(plaintext); + byte[] expected = socket.getInputStream().readAllBytes(); return Objects.deepEquals(expected, tag); } // BAD: compare ciphertexts using a non-constant time method - public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, Socket socket, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] plaintext = socket.getInputStream().readAllBytes(); cipher.update(plaintext); byte[] tag = new byte[1024]; cipher.doFinal(tag, 0); @@ -85,19 +99,21 @@ public class NonConstantTimeCryptoComparison { } // BAD: compare ciphertexts using a non-constant time method - public boolean unsafeCheckCiphertextWithByteBuffer(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean unsafeCheckCiphertextWithByteBuffer(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); cipher.update(plaintext); ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); cipher.doFinal(ByteBuffer.wrap(plaintext), tag); + byte[] expected = socket.getInputStream().readNBytes(1024); return Arrays.equals(expected, tag.array()); } // BAD: compare ciphertexts using a non-constant time method - public boolean unsafeCheckCiphertextWithByteBufferEquals(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean unsafeCheckCiphertextWithByteBufferEquals(byte[] expected, Socket socket, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] plaintext = socket.getInputStream().readAllBytes(); cipher.update(plaintext); ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); cipher.doFinal(ByteBuffer.wrap(plaintext), tag); @@ -105,10 +121,11 @@ public class NonConstantTimeCryptoComparison { } // GOOD: compare ciphertexts using a constant time method - public boolean saferCheckCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { + public boolean saferCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] tag = cipher.doFinal(plaintext); + byte[] expected = socket.getInputStream().readAllBytes(); return MessageDigest.isEqual(expected, tag); } From 40e513ba52696046bdd95d9648d16c34ab0b6e91 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 15:33:10 +0200 Subject: [PATCH 11/31] Added more taint propagation steps for InputStream and ByteBuffer --- java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index e3d9e7e35cf..1813811b496 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -257,17 +257,22 @@ private predicate sinkModelCsv(string row) { ] } +// TODO: add ByteBuffer private predicate summaryModelCsv(string row) { row = [ // qualifier to arg "java.io;InputStream;true;read;(byte[]);;Argument[-1];Argument[0];taint", "java.io;InputStream;true;read;(byte[],int,int);;Argument[-1];Argument[0];taint", + "java.io;InputStream;true;readNBytes;(byte[],int,int);;Argument[-1];Argument[0];taint", + "java.io;InputStream;true;transferTo;(OutputStream);;Argument[-1];Argument[0];taint", "java.io;ByteArrayOutputStream;false;writeTo;;;Argument[-1];Argument[0];taint", "java.io;Reader;true;read;;;Argument[-1];Argument[0];taint", // qualifier to return "java.io;ByteArrayOutputStream;false;toByteArray;;;Argument[-1];ReturnValue;taint", "java.io;ByteArrayOutputStream;false;toString;;;Argument[-1];ReturnValue;taint", + "java.io;InputStream;true;readAllBytes;;;Argument[-1];ReturnValue;taint", + "java.io;InputStream;true;readNBytes;(int);;Argument[-1];ReturnValue;taint", "java.util;StringTokenizer;false;nextElement;();;Argument[-1];ReturnValue;taint", "java.util;StringTokenizer;false;nextToken;;;Argument[-1];ReturnValue;taint", "javax.xml.transform.sax;SAXSource;false;getInputSource;;;Argument[-1];ReturnValue;taint", @@ -278,10 +283,12 @@ private predicate summaryModelCsv(string row) { "java.net;URI;false;toAsciiString;;;Argument[-1];ReturnValue;taint", "java.io;File;false;toURI;;;Argument[-1];ReturnValue;taint", "java.io;File;false;toPath;;;Argument[-1];ReturnValue;taint", + "java.nio;ByteBuffer;false;array;();;Argument[-1];ReturnValue;taint", "java.nio.file;Path;false;toFile;;;Argument[-1];ReturnValue;taint", "java.io;BufferedReader;true;readLine;;;Argument[-1];ReturnValue;taint", "java.io;Reader;true;read;();;Argument[-1];ReturnValue;taint", // arg to return + "java.nio;ByteBuffer;false;wrap;(byte[]);;Argument[0];ReturnValue;taint", "java.util;Base64$Encoder;false;encode;(byte[]);;Argument[0];ReturnValue;taint", "java.util;Base64$Encoder;false;encode;(ByteBuffer);;Argument[0];ReturnValue;taint", "java.util;Base64$Encoder;false;encodeToString;(byte[]);;Argument[0];ReturnValue;taint", From d01dc3501193bf71502017850acfba08cb14ff38 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 16:40:23 +0200 Subject: [PATCH 12/31] Less duplicate code in java/non-constant-time-crypto-comparison --- .../NonConstantTimeCryptoComparison.ql | 213 +++++++++--------- 1 file changed, 103 insertions(+), 110 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index c2f09bf2a70..0e4588bcec4 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -17,40 +17,88 @@ import semmle.code.java.dataflow.TaintTracking2 import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph +private class ByteBuffer extends Class { + ByteBuffer() { hasQualifiedName("java.nio", "ByteBuffer") } +} + +abstract private class ProduceCryptoCall extends MethodAccess { + Expr output; + + Expr output() { result = output } +} + +private class ProduceMacCall extends ProduceCryptoCall { + ProduceMacCall() { + getMethod().hasQualifiedName("javax.crypto", "Mac", "doFinal") and + ( + getMethod().getReturnType() instanceof Array and this = output + or + getMethod().getParameterType(0) instanceof Array and getArgument(0) = output + ) + } +} + +private class ProduceSignatureCall extends ProduceCryptoCall { + ProduceSignatureCall() { + getMethod().hasQualifiedName("java.security", "Signature", "sign") and + ( + getMethod().getReturnType() instanceof Array and this = output + or + getMethod().getParameterType(0) instanceof Array and getArgument(0) = output + ) + } +} + +private class ProduceCiphertextCall extends ProduceCryptoCall { + ProduceCiphertextCall() { + getMethod().hasQualifiedName("javax.crypto", "Cipher", "doFinal") and + ( + getMethod().getReturnType() instanceof Array and this = output + or + getMethod().getParameterType([0, 3]) instanceof Array and getArgument([0, 3]) = output + or + getMethod().getParameterType(1) instanceof ByteBuffer and + getArgument(1) = output + ) + } +} + private class UserInputInCryptoOperationConfig extends TaintTracking2::Configuration { UserInputInCryptoOperationConfig() { this = "UserInputInCryptoOperationConfig" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - ( - ( - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") or - m.hasQualifiedName("java.security", "Signature", "sign") - ) and - ma.getQualifier() = sink.asExpr() - ) - ) + exists(ProduceCryptoCall call | call.getQualifier() = sink.asExpr()) } override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | + exists(MethodAccess call, Method m | + m = call.getMethod() and + call.getQualifier() = toNode.asExpr() and + call.getArgument(0) = fromNode.asExpr() + | ( - ( - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], ["doFinal", "update"]) or - m.hasQualifiedName("java.security", "Signature", ["sign", "update"]) - ) and - ma.getAnArgument() = fromNode.asExpr() and - ma.getQualifier() = toNode.asExpr() + m.hasQualifiedName("java.security", "Signature", "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and + not m.hasStringSignature("doFinal(byte[],int)") ) ) } } -abstract private class CryptoOperationSource extends DataFlow::Node { +private class CryptoOperationSource extends DataFlow::Node { Expr cryptoOperation; + CryptoOperationSource() { + exists(ProduceCryptoCall call | call.output() = this.asExpr() | + cryptoOperation = call.getQualifier() + ) + } + predicate includesUserInput() { exists( DataFlow2::PathNode source, DataFlow2::PathNode sink, UserInputInCryptoOperationConfig config @@ -62,121 +110,68 @@ abstract private class CryptoOperationSource extends DataFlow::Node { } } -/** - * A source that produces a MAC. - */ -private class MacSource extends CryptoOperationSource { - MacSource() { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasQualifiedName("javax.crypto", "Mac", "doFinal") and - ( - m.getReturnType() instanceof Array and ma = this.asExpr() - or - m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr() - ) and - cryptoOperation = ma.getQualifier() - ) +private class NonConstantTimeEqualsCall extends MethodAccess { + NonConstantTimeEqualsCall() { + getMethod() + .hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or + getMethod().hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) } } -/** - * A source that produces a signature. - */ -private class SignatureSource extends CryptoOperationSource { - SignatureSource() { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasQualifiedName("java.security", "Signature", "sign") and - ( - m.getReturnType() instanceof Array and ma = this.asExpr() - or - m.getParameterType(0) instanceof Array and ma.getArgument(0) = this.asExpr() - ) and - cryptoOperation = ma.getQualifier() - ) +private class NonConstantTimeComparisonCall extends StaticMethodAccess { + NonConstantTimeComparisonCall() { + getMethod().hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or + getMethod().hasQualifiedName("java.util", "Objects", "deepEquals") or + getMethod() + .hasQualifiedName("org.apache.commons.lang3", "StringUtils", + ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) } } -/** - * A source that produces a ciphertext. - */ -private class CiphertextSource extends CryptoOperationSource { - CiphertextSource() { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasQualifiedName("javax.crypto", "Cipher", "doFinal") and - ( - m.getReturnType() instanceof Array and ma = this.asExpr() - or - m.getParameterType([0, 3]) instanceof Array and ma.getArgument([0, 3]) = this.asExpr() - or - m.getParameterType(1).(RefType).hasQualifiedName("java.nio", "ByteBuffer") and - ma.getArgument(1) = this.asExpr() - ) and - cryptoOperation = ma.getQualifier() - ) - } -} - -private class UserInputComparisonConfig extends TaintTracking2::Configuration { - UserInputComparisonConfig() { this = "UserInputComparisonConfig" } +private class UserInputInComparisonConfig extends TaintTracking2::Configuration { + UserInputInComparisonConfig() { this = "UserInputInComparisonConfig" } override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - ( - m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or - m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) or - m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or - m.hasQualifiedName("java.util", "Objects", "deepEquals") or - m.hasQualifiedName("org.apache.commons.lang3", "StringUtils", - ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) - ) and - [ma.getAnArgument(), ma.getQualifier()] = sink.asExpr() + exists(NonConstantTimeEqualsCall call | + sink.asExpr() = [call.getAnArgument(), call.getQualifier()] ) + or + exists(NonConstantTimeComparisonCall call | sink.asExpr() = call.getAnArgument()) } } /** * A sink that compares input using a non-constant time algorithm. */ -private class KnownNonConstantTimeComparisonSink extends DataFlow::Node { +private class NonConstantTimeComparisonSink extends DataFlow::Node { Expr anotherParameter; - KnownNonConstantTimeComparisonSink() { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - ( - m.hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or - m.hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) - ) and - ( - this.asExpr() = ma.getQualifier() and - anotherParameter = ma.getAnArgument() and - not ma.getAnArgument().isCompileTimeConstant() + NonConstantTimeComparisonSink() { + not anotherParameter.isCompileTimeConstant() and + ( + exists(NonConstantTimeEqualsCall call | + this.asExpr() = call.getQualifier() and + anotherParameter = call.getAnArgument() or - this.asExpr() = ma.getAnArgument() and - anotherParameter = ma.getQualifier() and - not ma.getQualifier().isCompileTimeConstant() + this.asExpr() = call.getAnArgument() and + anotherParameter = call.getQualifier() ) - ) - or - exists(StaticMethodAccess ma, Method m | m = ma.getMethod() | - ( - m.hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or - m.hasQualifiedName("java.util", "Objects", "deepEquals") or - m.hasQualifiedName("org.apache.commons.lang3", "StringUtils", - ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) - ) and - ma.getAnArgument() = this.asExpr() and - ( - this.asExpr() = ma.getArgument(0) and anotherParameter = ma.getArgument(1) - or - this.asExpr() = ma.getArgument(1) and anotherParameter = ma.getArgument(0) + or + exists(NonConstantTimeComparisonCall call | + call.getAnArgument() = this.asExpr() and + ( + this.asExpr() = call.getArgument(0) and anotherParameter = call.getArgument(1) + or + this.asExpr() = call.getArgument(1) and anotherParameter = call.getArgument(0) + ) ) ) } predicate includesUserInput() { - exists(UserInputComparisonConfig config | + exists(UserInputInComparisonConfig config | config.hasFlowTo(DataFlow2::exprNode(anotherParameter)) ) } @@ -191,9 +186,7 @@ private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Confi override predicate isSource(DataFlow::Node source) { source instanceof CryptoOperationSource } - override predicate isSink(DataFlow::Node sink) { - sink instanceof KnownNonConstantTimeComparisonSink - } + override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink } } from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf @@ -201,7 +194,7 @@ where conf.hasFlowPath(source, sink) and ( source.getNode().(CryptoOperationSource).includesUserInput() or - sink.getNode().(KnownNonConstantTimeComparisonSink).includesUserInput() + sink.getNode().(NonConstantTimeComparisonSink).includesUserInput() ) select sink.getNode(), source, sink, "Using a non-constant time algorithm for comparing results of a cryptographic operation." From c977fd09cb57cc9423fd9b42bacd9b89cea2df07 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 17:02:49 +0200 Subject: [PATCH 13/31] Better constant check in java/non-constant-time-crypto-comparison --- .../CWE/CWE-208/NonConstantTimeCryptoComparison.ql | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 0e4588bcec4..3956d76c60a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -142,6 +142,12 @@ private class UserInputInComparisonConfig extends TaintTracking2::Configuration } } +private predicate looksLikeConstant(Expr expr) { + expr.isCompileTimeConstant() + or + expr.(VarAccess).getVariable().isFinal() and expr.getType() instanceof TypeString +} + /** * A sink that compares input using a non-constant time algorithm. */ @@ -149,7 +155,6 @@ private class NonConstantTimeComparisonSink extends DataFlow::Node { Expr anotherParameter; NonConstantTimeComparisonSink() { - not anotherParameter.isCompileTimeConstant() and ( exists(NonConstantTimeEqualsCall call | this.asExpr() = call.getQualifier() and @@ -167,7 +172,8 @@ private class NonConstantTimeComparisonSink extends DataFlow::Node { this.asExpr() = call.getArgument(1) and anotherParameter = call.getArgument(0) ) ) - ) + ) and + not looksLikeConstant(anotherParameter) } predicate includesUserInput() { From 295fd686cef30c260df7c7a9b1390917c685bbb0 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 17:16:23 +0200 Subject: [PATCH 14/31] Make java/non-constant-time-crypto-comparison a warning --- .../Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 3956d76c60a..b315b3e42d6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -4,7 +4,7 @@ * Otherwise, an attacker may be able to implement a timing attack. * A successful attack may result in leaking secrets or authentication bypass. * @kind path-problem - * @problem.severity error + * @problem.severity warning * @precision high * @id java/non-constant-time-crypto-comparison * @tags security From 8c4da1645996cccb184dc55aff407d88988d57d0 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 17:45:20 +0200 Subject: [PATCH 15/31] More test cases for java/non-constant-time-crypto-comparison --- .../NonConstantTimeCryptoComparison.expected | 3 +++ .../NonConstantTimeCryptoComparison.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected index 01b21f2ab41..c41e22c21d5 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected @@ -10,6 +10,7 @@ edges | NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | | NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | | NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | +| NonConstantTimeCryptoComparison.java:136:22:136:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:137:40:137:42 | tag | nodes | NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | semmle.label | actualMac | @@ -31,6 +32,8 @@ nodes | NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | semmle.label | array(...) | | NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | | NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:136:22:136:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:137:40:137:42 | tag | semmle.label | tag | #select | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | | NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index 7b9323fd483..c611c64e7ed 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -129,6 +129,23 @@ public class NonConstantTimeCryptoComparison { return MessageDigest.isEqual(expected, tag); } + // GOOD: compare ciphertexts using a constant time method, but no user input + public boolean noUserInputWhenCheckingCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(plaintext); + return Arrays.equals(expected, tag); + } + + // GOOD: compare MAC with constant using a constant time method + public boolean compareMacWithConstant(Socket socket) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = new byte[1024]; + socket.getInputStream().read(data); + byte[] actualMac = mac.doFinal(data); + return "constant".equals(new String(actualMac)); + } + private static Object[] castToObjectArray(byte[] array) { Object[] result = new Object[array.length]; for (int i = 0; i < array.length; i++) { From 1b4ee05b8074b17af47b5bb1c7469ec068a030bd Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 19 Jun 2021 18:20:18 +0200 Subject: [PATCH 16/31] Better docs for java/non-constant-time-crypto-comparison --- .../NonConstantTimeCryptoComparison.qhelp | 4 ++-- .../NonConstantTimeCryptoComparison.ql | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp index 2bee3cf9dad..f3edc5ec3a6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp @@ -5,8 +5,8 @@

    When comparing results of cryptographic operations, such as MAC or digital signature, a constant time algorithm should be used. In other words, the comparison time should not depend on -the content of the input. Otherwise, an attacker may be able to implement a timing attack. -A successful timing attack may result in leaking secrets or authentication bypass. +the content of the input. Otherwise, attackers may be able to implement a timing attack +if they can control input. A successful timing attack may result in leaking secrets or authentication bypass.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index b315b3e42d6..1ca2c461473 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -1,7 +1,7 @@ /** * @name Using a non-constant time algorithm for comparing results of a cryptographic operation * @description When comparing results of a cryptographic operation, a constant time algorithm should be used. - * Otherwise, an attacker may be able to implement a timing attack. + * Otherwise, attackers may be able to implement a timing attack if they can control input. * A successful attack may result in leaking secrets or authentication bypass. * @kind path-problem * @problem.severity warning @@ -21,12 +21,15 @@ private class ByteBuffer extends Class { ByteBuffer() { hasQualifiedName("java.nio", "ByteBuffer") } } +/** A method call that produces cryptographic result. */ abstract private class ProduceCryptoCall extends MethodAccess { Expr output; + /** Return the result of cryptographic operation. */ Expr output() { result = output } } +/** A method call that produces a MAC. */ private class ProduceMacCall extends ProduceCryptoCall { ProduceMacCall() { getMethod().hasQualifiedName("javax.crypto", "Mac", "doFinal") and @@ -38,6 +41,7 @@ private class ProduceMacCall extends ProduceCryptoCall { } } +/** A method call that produces a signature. */ private class ProduceSignatureCall extends ProduceCryptoCall { ProduceSignatureCall() { getMethod().hasQualifiedName("java.security", "Signature", "sign") and @@ -49,6 +53,7 @@ private class ProduceSignatureCall extends ProduceCryptoCall { } } +/** A method call that produces a ciphertext. */ private class ProduceCiphertextCall extends ProduceCryptoCall { ProduceCiphertextCall() { getMethod().hasQualifiedName("javax.crypto", "Cipher", "doFinal") and @@ -63,6 +68,10 @@ private class ProduceCiphertextCall extends ProduceCryptoCall { } } +/** + * A config that tracks data flow from remote user input to a cryptographic operation + * such as cipher, MAC or signature. + */ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configuration { UserInputInCryptoOperationConfig() { this = "UserInputInCryptoOperationConfig" } @@ -72,6 +81,7 @@ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configura exists(ProduceCryptoCall call | call.getQualifier() = sink.asExpr()) } + /** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */ override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { exists(MethodAccess call, Method m | m = call.getMethod() and @@ -90,6 +100,7 @@ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configura } } +/** A source that produces result of cryptographic operation. */ private class CryptoOperationSource extends DataFlow::Node { Expr cryptoOperation; @@ -99,6 +110,7 @@ private class CryptoOperationSource extends DataFlow::Node { ) } + /** Holds if remote user input was used in the cryptographic operation. */ predicate includesUserInput() { exists( DataFlow2::PathNode source, DataFlow2::PathNode sink, UserInputInCryptoOperationConfig config @@ -128,6 +140,10 @@ private class NonConstantTimeComparisonCall extends StaticMethodAccess { } } +/** + * A config that tracks data flow from remote user input to methods + * that compare inputs using a non-constant time algorithm. + */ private class UserInputInComparisonConfig extends TaintTracking2::Configuration { UserInputInComparisonConfig() { this = "UserInputInComparisonConfig" } @@ -142,15 +158,14 @@ private class UserInputInComparisonConfig extends TaintTracking2::Configuration } } +/** Holds if `expr` looks like a constant. */ private predicate looksLikeConstant(Expr expr) { expr.isCompileTimeConstant() or expr.(VarAccess).getVariable().isFinal() and expr.getType() instanceof TypeString } -/** - * A sink that compares input using a non-constant time algorithm. - */ +/** A sink that compares input using a non-constant time algorithm. */ private class NonConstantTimeComparisonSink extends DataFlow::Node { Expr anotherParameter; @@ -176,6 +191,7 @@ private class NonConstantTimeComparisonSink extends DataFlow::Node { not looksLikeConstant(anotherParameter) } + /** Holds if remote user input was used in the comparison. */ predicate includesUserInput() { exists(UserInputInComparisonConfig config | config.hasFlowTo(DataFlow2::exprNode(anotherParameter)) From 622c7ee957cb2e47a9b4e36c9295b6aad75d4bb1 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Tue, 22 Jun 2021 18:04:15 +0200 Subject: [PATCH 17/31] Added a change note for new steps for ByteBuffer and InputStream --- .../2021-06-22-more-steps-for-bytebuffer-inputstream.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 java/change-notes/2021-06-22-more-steps-for-bytebuffer-inputstream.md diff --git a/java/change-notes/2021-06-22-more-steps-for-bytebuffer-inputstream.md b/java/change-notes/2021-06-22-more-steps-for-bytebuffer-inputstream.md new file mode 100644 index 00000000000..8ffaf6926ea --- /dev/null +++ b/java/change-notes/2021-06-22-more-steps-for-bytebuffer-inputstream.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added more taint propagation steps for `InputStream` and `ByteBuffer`. From 860e8f379e5cef7418331f7366ab95e8481aadac Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Wed, 23 Jun 2021 08:40:39 +0200 Subject: [PATCH 18/31] Better signatures in java/non-constant-time-crypto-comparison --- .../NonConstantTimeCryptoComparison.ql | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 1ca2c461473..e0d82f61e2a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -32,11 +32,11 @@ abstract private class ProduceCryptoCall extends MethodAccess { /** A method call that produces a MAC. */ private class ProduceMacCall extends ProduceCryptoCall { ProduceMacCall() { - getMethod().hasQualifiedName("javax.crypto", "Mac", "doFinal") and + getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Mac") and ( - getMethod().getReturnType() instanceof Array and this = output + getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])"]) and this = output or - getMethod().getParameterType(0) instanceof Array and getArgument(0) = output + getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output ) } } @@ -44,11 +44,11 @@ private class ProduceMacCall extends ProduceCryptoCall { /** A method call that produces a signature. */ private class ProduceSignatureCall extends ProduceCryptoCall { ProduceSignatureCall() { - getMethod().hasQualifiedName("java.security", "Signature", "sign") and + getMethod().getDeclaringType().hasQualifiedName("java.security", "Signature") and ( - getMethod().getReturnType() instanceof Array and this = output + getMethod().hasStringSignature("sign()") and this = output or - getMethod().getParameterType(0) instanceof Array and getArgument(0) = output + getMethod().hasStringSignature("sign(byte[], int, int)") and getArgument(0) = output ) } } @@ -56,14 +56,20 @@ private class ProduceSignatureCall extends ProduceCryptoCall { /** A method call that produces a ciphertext. */ private class ProduceCiphertextCall extends ProduceCryptoCall { ProduceCiphertextCall() { - getMethod().hasQualifiedName("javax.crypto", "Cipher", "doFinal") and + getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Cipher") and ( - getMethod().getReturnType() instanceof Array and this = output + getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])", "doFinal(byte[], int, int)"]) and + this = output or - getMethod().getParameterType([0, 3]) instanceof Array and getArgument([0, 3]) = output + getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output or - getMethod().getParameterType(1) instanceof ByteBuffer and - getArgument(1) = output + getMethod() + .hasStringSignature([ + "doFinal(byte[], int, int, byte[])", "doFinal(byte[], int, int, byte[], int)" + ]) and + getArgument(3) = output + or + getMethod().hasStringSignature("doFinal(ByteBuffer, ByteBuffer)") and getArgument(1) = output ) } } @@ -88,14 +94,12 @@ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configura call.getQualifier() = toNode.asExpr() and call.getArgument(0) = fromNode.asExpr() | - ( - m.hasQualifiedName("java.security", "Signature", "update") - or - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") - or - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and - not m.hasStringSignature("doFinal(byte[],int)") - ) + m.hasQualifiedName("java.security", "Signature", "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and + not m.hasStringSignature("doFinal(byte[], int)") ) } } @@ -179,13 +183,10 @@ private class NonConstantTimeComparisonSink extends DataFlow::Node { anotherParameter = call.getQualifier() ) or - exists(NonConstantTimeComparisonCall call | - call.getAnArgument() = this.asExpr() and - ( - this.asExpr() = call.getArgument(0) and anotherParameter = call.getArgument(1) - or - this.asExpr() = call.getArgument(1) and anotherParameter = call.getArgument(0) - ) + exists(NonConstantTimeComparisonCall call | call.getAnArgument() = this.asExpr() | + this.asExpr() = call.getArgument(0) and anotherParameter = call.getArgument(1) + or + this.asExpr() = call.getArgument(1) and anotherParameter = call.getArgument(0) ) ) and not looksLikeConstant(anotherParameter) From 6500a1bbbbac3eebd839dc8c48d79961c17f785d Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 26 Jun 2021 11:01:26 +0200 Subject: [PATCH 19/31] More references in NonConstantTimeCryptoComparison.qhelp --- .../CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp index f3edc5ec3a6..6ae9274ee37 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp @@ -37,6 +37,14 @@ The next example uses a safe constant time algorithm for comparing MAC: Wikipedia: Timing attack. +
  • + Coursera: + Timing attacks on MAC verification +
  • +
  • + NCC Group: + Time Trial: Racing Towards Practical Remote Timing Attacks +
  • Java API Specification: MessageDigest.isEqual() method From c96d939cf57e40d84c4097c6cdefe667af62114d Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sat, 26 Jun 2021 16:32:38 +0200 Subject: [PATCH 20/31] Covered custom fast-fail checks in NonConstantTimeCryptoComparison.ql Co-authored-by: Marcono1234 --- .../NonConstantTimeCryptoComparison.qhelp | 6 +- .../NonConstantTimeCryptoComparison.ql | 81 +++++++++++++---- .../NonConstantTimeCryptoComparison.expected | 90 ++++++++++--------- .../NonConstantTimeCryptoComparison.java | 74 ++++++++++++--- 4 files changed, 173 insertions(+), 78 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp index 6ae9274ee37..520e861105f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp @@ -4,7 +4,7 @@

    When comparing results of cryptographic operations, such as MAC or digital signature, -a constant time algorithm should be used. In other words, the comparison time should not depend on +a constant-time algorithm should be used. In other words, the comparison time should not depend on the content of the input. Otherwise, attackers may be able to implement a timing attack if they can control input. A successful timing attack may result in leaking secrets or authentication bypass.

    @@ -21,12 +21,12 @@ and does not depend on the contents of the arrays.

    The following example uses Arrays.equals() method for comparing MAC. -This method implements a non-constant time algorithm: +This method implements a non-constant-time algorithm:

    -The next example uses a safe constant time algorithm for comparing MAC: +The next example uses a safe constant-time algorithm for comparing MAC:

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index e0d82f61e2a..59f9a9713d6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -1,6 +1,6 @@ /** - * @name Using a non-constant time algorithm for comparing results of a cryptographic operation - * @description When comparing results of a cryptographic operation, a constant time algorithm should be used. + * @name Using a non-constant-time algorithm for comparing results of a cryptographic operation + * @description When comparing results of a cryptographic operation, a constant-time algorithm should be used. * Otherwise, attackers may be able to implement a timing attack if they can control input. * A successful attack may result in leaking secrets or authentication bypass. * @kind path-problem @@ -12,6 +12,7 @@ */ import java +import semmle.code.java.controlflow.Guards import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.TaintTracking2 import semmle.code.java.dataflow.FlowSources @@ -146,7 +147,7 @@ private class NonConstantTimeComparisonCall extends StaticMethodAccess { /** * A config that tracks data flow from remote user input to methods - * that compare inputs using a non-constant time algorithm. + * that compare inputs using a non-constant-time algorithm. */ private class UserInputInComparisonConfig extends TaintTracking2::Configuration { UserInputInComparisonConfig() { this = "UserInputInComparisonConfig" } @@ -169,25 +170,69 @@ private predicate looksLikeConstant(Expr expr) { expr.(VarAccess).getVariable().isFinal() and expr.getType() instanceof TypeString } -/** A sink that compares input using a non-constant time algorithm. */ +/** + * Holds if `firstObject` and `secondObject` are compared using a method + * that does not use a constant-time algorithm, for example, `String.equals()`. + */ +private predicate isNonConstantEqualsCall(Expr firstObject, Expr secondObject) { + exists(NonConstantTimeEqualsCall call | + firstObject = call.getQualifier() and + secondObject = call.getAnArgument() + or + firstObject = call.getAnArgument() and + secondObject = call.getQualifier() + ) +} + +/** + * Holds if `firstInput` and `secondInput` are compared using a static method + * that does not use a constant-time algorithm, for example, `Arrays.equals()`. + */ +private predicate isNonConstantTimeComparisonCall(Expr firstInput, Expr secondInput) { + exists(NonConstantTimeComparisonCall call | + firstInput = call.getArgument(0) and secondInput = call.getArgument(1) + or + firstInput = call.getArgument(1) and secondInput = call.getArgument(0) + ) +} + +/** + * Holds if there is a fast-fail check while comparing `firstArray` and `secondArray`. + */ +private predicate existsFailFastCheck(Expr firstArray, Expr secondArray) { + exists( + Guard guard, EqualityTest eqTest, boolean branch, Stmt fastFailingStmt, + ArrayAccess firstArrayAccess, ArrayAccess secondArrayAccess + | + guard = eqTest and + // For `==` false branch is fail fast; for `!=` true branch is fail fast + branch = eqTest.polarity().booleanNot() and + ( + fastFailingStmt instanceof ReturnStmt or + fastFailingStmt instanceof BreakStmt or + fastFailingStmt instanceof ThrowStmt + ) and + guard.controls(fastFailingStmt.getBasicBlock(), branch) and + DataFlow::localExprFlow(firstArrayAccess, eqTest.getLeftOperand()) and + DataFlow::localExprFlow(secondArrayAccess, eqTest.getRightOperand()) + | + firstArrayAccess.getArray() = firstArray and secondArray = secondArrayAccess + or + secondArrayAccess.getArray() = firstArray and secondArray = firstArrayAccess + ) +} + +/** A sink that compares input using a non-constant-time algorithm. */ private class NonConstantTimeComparisonSink extends DataFlow::Node { Expr anotherParameter; NonConstantTimeComparisonSink() { ( - exists(NonConstantTimeEqualsCall call | - this.asExpr() = call.getQualifier() and - anotherParameter = call.getAnArgument() - or - this.asExpr() = call.getAnArgument() and - anotherParameter = call.getQualifier() - ) + isNonConstantEqualsCall(this.asExpr(), anotherParameter) or - exists(NonConstantTimeComparisonCall call | call.getAnArgument() = this.asExpr() | - this.asExpr() = call.getArgument(0) and anotherParameter = call.getArgument(1) - or - this.asExpr() = call.getArgument(1) and anotherParameter = call.getArgument(0) - ) + isNonConstantTimeComparisonCall(this.asExpr(), anotherParameter) + or + existsFailFastCheck(this.asExpr(), anotherParameter) ) and not looksLikeConstant(anotherParameter) } @@ -202,7 +247,7 @@ private class NonConstantTimeComparisonSink extends DataFlow::Node { /** * A configuration that tracks data flow from cryptographic operations - * to methods that compare data using a non-constant time algorithm. + * to methods that compare data using a non-constant-time algorithm. */ private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" } @@ -220,4 +265,4 @@ where sink.getNode().(NonConstantTimeComparisonSink).includesUserInput() ) select sink.getNode(), source, sink, - "Using a non-constant time algorithm for comparing results of a cryptographic operation." + "Using a non-constant-time algorithm for comparing results of a cryptographic operation." diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected index c41e22c21d5..ee8a0a1bd61 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected @@ -1,46 +1,50 @@ edges -| NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | -| NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:29:84:29:92 | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:29:84:29:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:36:21:36:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | -| NonConstantTimeCryptoComparison.java:56:28:56:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | -| NonConstantTimeCryptoComparison.java:67:21:67:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | -| NonConstantTimeCryptoComparison.java:85:22:85:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | -| NonConstantTimeCryptoComparison.java:97:24:97:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | -| NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | -| NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | -| NonConstantTimeCryptoComparison.java:136:22:136:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:137:40:137:42 | tag | +| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | +| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | +| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | +| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | +| NonConstantTimeCryptoComparison.java:86:22:86:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | +| NonConstantTimeCryptoComparison.java:98:24:98:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | +| NonConstantTimeCryptoComparison.java:108:52:108:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:110:40:110:42 | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:110:40:110:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | +| NonConstantTimeCryptoComparison.java:120:52:120:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | +| NonConstantTimeCryptoComparison.java:137:22:137:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:138:40:138:42 | tag | +| NonConstantTimeCryptoComparison.java:168:34:168:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | nodes -| NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:29:84:29:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:36:21:36:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:56:28:56:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:67:21:67:29 | signature : byte[] | semmle.label | signature : byte[] | -| NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:85:22:85:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:97:24:97:26 | tag : byte[] | semmle.label | tag : byte[] | -| NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:109:40:109:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | semmle.label | array(...) | -| NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:136:22:136:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:137:40:137:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | semmle.label | signature : byte[] | +| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:86:22:86:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:98:24:98:26 | tag : byte[] | semmle.label | tag : byte[] | +| NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:108:52:108:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:110:40:110:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | semmle.label | array(...) | +| NonConstantTimeCryptoComparison.java:120:52:120:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:137:22:137:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:138:40:138:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:168:34:168:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | semmle.label | computedTag | #select -| NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | NonConstantTimeCryptoComparison.java:19:28:19:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:20:43:20:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:28:28:28:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:29:66:29:93 | castToObjectArray(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | NonConstantTimeCryptoComparison.java:36:21:36:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:38:43:38:51 | actualMac | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | NonConstantTimeCryptoComparison.java:56:28:56:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:57:40:57:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | NonConstantTimeCryptoComparison.java:67:21:67:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:68:40:68:48 | signature | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | NonConstantTimeCryptoComparison.java:85:22:85:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:87:45:87:47 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | NonConstantTimeCryptoComparison.java:97:24:97:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:98:40:98:42 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | NonConstantTimeCryptoComparison.java:107:52:107:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:109:40:109:50 | array(...) | Using a non-constant time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | NonConstantTimeCryptoComparison.java:119:52:119:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:120:49:120:51 | tag | Using a non-constant time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | NonConstantTimeCryptoComparison.java:86:22:86:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | NonConstantTimeCryptoComparison.java:98:24:98:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | NonConstantTimeCryptoComparison.java:108:52:108:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | NonConstantTimeCryptoComparison.java:120:52:120:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | NonConstantTimeCryptoComparison.java:168:34:168:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index c611c64e7ed..cfeaec124a8 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -1,3 +1,4 @@ +import java.io.InputStream; import java.net.Socket; import java.nio.ByteBuffer; import java.security.Key; @@ -11,7 +12,7 @@ import javax.crypto.Mac; public class NonConstantTimeCryptoComparison { - // BAD: compare MACs using a non-constant time method + // BAD: compare MACs using a non-constant-time method public boolean unsafeMacCheckWithArrayEquals(byte[] expectedMac, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] data = new byte[1024]; @@ -20,7 +21,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expectedMac, actualMac); } - // BAD: compare MACs using a non-constant time method + // BAD: compare MACs using a non-constant-time method public boolean unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] data = socket.getInputStream().readAllBytes(); @@ -29,7 +30,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); } - // BAD: compare MACs using a non-constant time method + // BAD: compare MACs using a non-constant-time method public boolean unsafeMacCheckWithDoFinalWithOutputArray(byte[] data, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] actualMac = new byte[256]; @@ -38,7 +39,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expectedMac, actualMac); } - // GOOD: compare MACs using a constant time method + // GOOD: compare MACs using a constant-time method public boolean saferMacCheck(byte[] expectedMac, Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] data = new byte[1024]; @@ -47,7 +48,7 @@ public class NonConstantTimeCryptoComparison { return MessageDigest.isEqual(expectedMac, actualMac); } - // BAD: compare signatures using a non-constant time method + // BAD: compare signatures using a non-constant-time method public boolean unsafeCheckSignatures(byte[] expected, Socket socket, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); @@ -57,7 +58,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expected, signature); } - // BAD: compare signatures using a non-constant time method + // BAD: compare signatures using a non-constant-time method public boolean unsafeCheckSignaturesWithOutputArray(byte[] expected, Socket socket, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); @@ -68,7 +69,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expected, signature); } - // GOOD: compare signatures using a constant time method + // GOOD: compare signatures using a constant-time method public boolean saferCheckSignatures(byte[] expected, Socket socket, PrivateKey key) throws Exception { Signature engine = Signature.getInstance("SHA256withRSA"); engine.initSign(key); @@ -78,7 +79,7 @@ public class NonConstantTimeCryptoComparison { return MessageDigest.isEqual(expected, signature); } - // BAD: compare ciphertexts using a non-constant time method + // BAD: compare ciphertexts using a non-constant-time method public boolean unsafeCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -87,7 +88,7 @@ public class NonConstantTimeCryptoComparison { return Objects.deepEquals(expected, tag); } - // BAD: compare ciphertexts using a non-constant time method + // BAD: compare ciphertexts using a non-constant-time method public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, Socket socket, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -98,7 +99,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expected, tag); } - // BAD: compare ciphertexts using a non-constant time method + // BAD: compare ciphertexts using a non-constant-time method public boolean unsafeCheckCiphertextWithByteBuffer(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -109,7 +110,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expected, tag.array()); } - // BAD: compare ciphertexts using a non-constant time method + // BAD: compare ciphertexts using a non-constant-time method public boolean unsafeCheckCiphertextWithByteBufferEquals(byte[] expected, Socket socket, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -120,7 +121,7 @@ public class NonConstantTimeCryptoComparison { return ByteBuffer.wrap(expected).equals(tag); } - // GOOD: compare ciphertexts using a constant time method + // GOOD: compare ciphertexts using a constant-time method public boolean saferCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -129,7 +130,7 @@ public class NonConstantTimeCryptoComparison { return MessageDigest.isEqual(expected, tag); } - // GOOD: compare ciphertexts using a constant time method, but no user input + // GOOD: compare ciphertexts using a constant-time method, but no user input public boolean noUserInputWhenCheckingCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -137,7 +138,7 @@ public class NonConstantTimeCryptoComparison { return Arrays.equals(expected, tag); } - // GOOD: compare MAC with constant using a constant time method + // GOOD: compare MAC with constant using a constant-time method public boolean compareMacWithConstant(Socket socket) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); byte[] data = new byte[1024]; @@ -153,5 +154,50 @@ public class NonConstantTimeCryptoComparison { } return result; } + + // BAD: compare MAC using a non-constant-time loop + public boolean unsafeMacCheckWithLoop(Socket socket) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] data = new byte[256]; + byte[] tag = new byte[32]; + + is.read(data); + is.read(tag); + + Mac mac = Mac.getInstance("Hmac256"); + byte[] computedTag = mac.doFinal(data); + + for (int i = 0; i < computedTag.length; i++) { + byte a = computedTag[i]; + byte b = tag[i]; + if (a != b) { + return false; + } + } + + return true; + } + } + + // GOOD: compare MAC using a constant-time loop + public boolean safeMacCheckWithLoop(Socket socket) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] data = new byte[256]; + byte[] tag = new byte[32]; + + is.read(data); + is.read(tag); + + Mac mac = Mac.getInstance("Hmac256"); + byte[] computedTag = mac.doFinal(data); + + int result = 0; + for (int i = 0; i < computedTag.length; i++) { + result |= computedTag[i] ^ tag[i]; + } + + return result == 0; + } + } } \ No newline at end of file From 1f2a9cdda721128d7f3470d238ef2d9d4385aa9b Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sun, 4 Jul 2021 11:13:24 +0200 Subject: [PATCH 21/31] Added taint propagation steps for hashes in NonConstantTimeCryptoComparison.ql --- .../NonConstantTimeCryptoComparison.ql | 67 +++++++++++++++---- .../NonConstantTimeCryptoComparison.expected | 50 +++++++------- .../NonConstantTimeCryptoComparison.java | 29 +++++--- 3 files changed, 98 insertions(+), 48 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 59f9a9713d6..57b49c72d5d 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -75,6 +75,55 @@ private class ProduceCiphertextCall extends ProduceCryptoCall { } } +/** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */ +private predicate updateCryptoOperationStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess call, Method m | + m = call.getMethod() and + call.getQualifier() = toNode.asExpr() and + call.getArgument(0) = fromNode.asExpr() + | + m.hasQualifiedName("java.security", "Signature", "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and + not m.hasStringSignature("doFinal(byte[], int)") + ) +} + +/** Holds if `fromNode` to `toNode` is a dataflow step that creates a hash. */ +private predicate createMessageDigestStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + m.hasStringSignature("digest()") and + ma.getQualifier() = fromNode.asExpr() and + ma = toNode.asExpr() + ) + or + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + m.hasStringSignature("digest(byte[], int, int)") and + ma.getQualifier() = fromNode.asExpr() and + ma.getArgument(0) = toNode.asExpr() + ) + or + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + m.hasStringSignature("digest(byte[])") and + ma.getArgument(0) = fromNode.asExpr() and + ma = toNode.asExpr() + ) +} + +/** Holds if `fromNode` to `toNode` is a dataflow step that updates a hash. */ +private predicate updateMessageDigestStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("java.security", "MessageDigest", "update") and + ma.getArgument(0) = fromNode.asExpr() and + ma.getQualifier() = toNode.asExpr() + ) +} + /** * A config that tracks data flow from remote user input to a cryptographic operation * such as cipher, MAC or signature. @@ -88,20 +137,12 @@ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configura exists(ProduceCryptoCall call | call.getQualifier() = sink.asExpr()) } - /** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */ override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { - exists(MethodAccess call, Method m | - m = call.getMethod() and - call.getQualifier() = toNode.asExpr() and - call.getArgument(0) = fromNode.asExpr() - | - m.hasQualifiedName("java.security", "Signature", "update") - or - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") - or - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and - not m.hasStringSignature("doFinal(byte[], int)") - ) + updateCryptoOperationStep(fromNode, toNode) + or + createMessageDigestStep(fromNode, toNode) + or + updateMessageDigestStep(fromNode, toNode) } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected index ee8a0a1bd61..94f819bc19c 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected @@ -5,13 +5,13 @@ edges | NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | | NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | | NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | -| NonConstantTimeCryptoComparison.java:86:22:86:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | -| NonConstantTimeCryptoComparison.java:98:24:98:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | -| NonConstantTimeCryptoComparison.java:108:52:108:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:110:40:110:42 | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:110:40:110:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | -| NonConstantTimeCryptoComparison.java:120:52:120:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | -| NonConstantTimeCryptoComparison.java:137:22:137:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:138:40:138:42 | tag | -| NonConstantTimeCryptoComparison.java:168:34:168:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | +| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | +| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | +| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | +| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | +| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | +| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | nodes | NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | semmle.label | actualMac | @@ -24,27 +24,27 @@ nodes | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | semmle.label | signature | | NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | semmle.label | signature : byte[] | | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:86:22:86:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:98:24:98:26 | tag : byte[] | semmle.label | tag : byte[] | -| NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:108:52:108:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:110:40:110:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | semmle.label | array(...) | -| NonConstantTimeCryptoComparison.java:120:52:120:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:137:22:137:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:138:40:138:42 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:168:34:168:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | semmle.label | computedTag | +| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | semmle.label | tag : byte[] | +| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | semmle.label | array(...) | +| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | semmle.label | computedTag | #select | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | NonConstantTimeCryptoComparison.java:86:22:86:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:88:45:88:47 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | NonConstantTimeCryptoComparison.java:98:24:98:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:99:40:99:42 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | NonConstantTimeCryptoComparison.java:108:52:108:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:110:40:110:50 | array(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | NonConstantTimeCryptoComparison.java:120:52:120:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:121:49:121:51 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | NonConstantTimeCryptoComparison.java:168:34:168:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:171:26:171:36 | computedTag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index cfeaec124a8..7eb622a4b74 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -79,38 +79,46 @@ public class NonConstantTimeCryptoComparison { return MessageDigest.isEqual(expected, signature); } - // BAD: compare ciphertexts using a non-constant-time method + // BAD: compare ciphertexts (custom MAC) using a non-constant-time method public boolean unsafeCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { + byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] tag = cipher.doFinal(plaintext); + byte[] tag = cipher.doFinal(hash); byte[] expected = socket.getInputStream().readAllBytes(); return Objects.deepEquals(expected, tag); } - // BAD: compare ciphertexts using a non-constant-time method + // BAD: compare ciphertexts (custom MAC) using a non-constant-time method public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, Socket socket, Key key) throws Exception { + byte[] plaintext = socket.getInputStream().readAllBytes(); + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(plaintext); + byte[] hash = md.digest(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] plaintext = socket.getInputStream().readAllBytes(); - cipher.update(plaintext); + cipher.update(hash); byte[] tag = new byte[1024]; cipher.doFinal(tag, 0); return Arrays.equals(expected, tag); } - // BAD: compare ciphertexts using a non-constant-time method + // BAD: compare ciphertexts (custom MAC) using a non-constant-time method public boolean unsafeCheckCiphertextWithByteBuffer(Socket socket, byte[] plaintext, Key key) throws Exception { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(plaintext); + byte[] hash = new byte[1024]; + md.digest(hash, 0, hash.length); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); - cipher.update(plaintext); + cipher.update(hash); ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); cipher.doFinal(ByteBuffer.wrap(plaintext), tag); byte[] expected = socket.getInputStream().readNBytes(1024); return Arrays.equals(expected, tag.array()); } - // BAD: compare ciphertexts using a non-constant-time method + // BAD: compare ciphertexts (custom MAC) using a non-constant-time method public boolean unsafeCheckCiphertextWithByteBufferEquals(byte[] expected, Socket socket, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); @@ -121,11 +129,12 @@ public class NonConstantTimeCryptoComparison { return ByteBuffer.wrap(expected).equals(tag); } - // GOOD: compare ciphertexts using a constant-time method + // GOOD: compare ciphertexts (custom MAC) using a constant-time method public boolean saferCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] tag = cipher.doFinal(plaintext); + byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); + byte[] tag = cipher.doFinal(hash); byte[] expected = socket.getInputStream().readAllBytes(); return MessageDigest.isEqual(expected, tag); } From c3598526082e591ed6c32495a2f48ce1fce8e7fe Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sun, 4 Jul 2021 13:24:56 +0200 Subject: [PATCH 22/31] Consider only Cipher.ENCRYPT_MODE in NonConstantTimeCryptoComparison.ql --- .../NonConstantTimeCryptoComparison.ql | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index 57b49c72d5d..ebfcd03e7f1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -15,13 +15,10 @@ import java import semmle.code.java.controlflow.Guards import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.TaintTracking2 +import semmle.code.java.dataflow.DataFlow3 import semmle.code.java.dataflow.FlowSources import DataFlow::PathGraph -private class ByteBuffer extends Class { - ByteBuffer() { hasQualifiedName("java.nio", "ByteBuffer") } -} - /** A method call that produces cryptographic result. */ abstract private class ProduceCryptoCall extends MethodAccess { Expr output; @@ -54,23 +51,51 @@ private class ProduceSignatureCall extends ProduceCryptoCall { } } +/** + * A config that tracks data flow from initializing a cipher for encryption + * to producing a ciphertext using this cipher. + */ +private class InitializeEncryptorConfig extends DataFlow3::Configuration { + InitializeEncryptorConfig() { this = "InitializeEncryptorConfig" } + + override predicate isSource(DataFlow::Node source) { + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("javax.crypto", "Cipher", "init") and + ma.getArgument(0).(VarAccess).getVariable().hasName("ENCRYPT_MODE") and + ma.getQualifier() = source.asExpr() + ) + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("javax.crypto", "Cipher", "doFinal") and + ma.getQualifier() = sink.asExpr() + ) + } +} + /** A method call that produces a ciphertext. */ private class ProduceCiphertextCall extends ProduceCryptoCall { ProduceCiphertextCall() { - getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Cipher") and - ( - getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])", "doFinal(byte[], int, int)"]) and - this = output - or - getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output - or - getMethod() - .hasStringSignature([ - "doFinal(byte[], int, int, byte[])", "doFinal(byte[], int, int, byte[], int)" - ]) and - getArgument(3) = output - or - getMethod().hasStringSignature("doFinal(ByteBuffer, ByteBuffer)") and getArgument(1) = output + exists(Method m | m = this.getMethod() | + m.getDeclaringType().hasQualifiedName("javax.crypto", "Cipher") and + ( + m.hasStringSignature(["doFinal()", "doFinal(byte[])", "doFinal(byte[], int, int)"]) and + this = output + or + m.hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output + or + m.hasStringSignature([ + "doFinal(byte[], int, int, byte[])", "doFinal(byte[], int, int, byte[], int)" + ]) and + getArgument(3) = output + or + m.hasStringSignature("doFinal(ByteBuffer, ByteBuffer)") and + getArgument(1) = output + ) + ) and + exists(InitializeEncryptorConfig config | + config.hasFlowTo(DataFlow3::exprNode(this.getQualifier())) ) } } @@ -168,6 +193,7 @@ private class CryptoOperationSource extends DataFlow::Node { } } +/** Methods that use a non-constant-time algorithm for comparing inputs. */ private class NonConstantTimeEqualsCall extends MethodAccess { NonConstantTimeEqualsCall() { getMethod() @@ -176,6 +202,7 @@ private class NonConstantTimeEqualsCall extends MethodAccess { } } +/** Static methods that use a non-constant-time algorithm for comparing inputs. */ private class NonConstantTimeComparisonCall extends StaticMethodAccess { NonConstantTimeComparisonCall() { getMethod().hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or From 8b557765b30bc447291c2178d14a3f3b726ccc1c Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sun, 4 Jul 2021 15:27:44 +0200 Subject: [PATCH 23/31] Narrow NonConstantTimeCryptoComparison.ql to timing attack on signatures and MACs only --- .../NonConstantTimeCryptoComparison.qhelp | 10 +- .../NonConstantTimeCryptoComparison.ql | 37 +-- .../NonConstantTimeCryptoComparison.expected | 94 +++---- .../NonConstantTimeCryptoComparison.java | 245 ++++++++++-------- 4 files changed, 218 insertions(+), 168 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp index 520e861105f..1dccf35633f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp @@ -3,16 +3,16 @@

    -When comparing results of cryptographic operations, such as MAC or digital signature, -a constant-time algorithm should be used. In other words, the comparison time should not depend on -the content of the input. Otherwise, attackers may be able to implement a timing attack -if they can control input. A successful timing attack may result in leaking secrets or authentication bypass. +A constant-time algorithm should be used for checking a MAC or a digital signature. +In other words, the comparison time should not depend on the content of the input. +Otherwise, attackers may be able to implement a timing attack if they control inputs. +A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass.

    -Use MessageDigest.isEqual() method to compare results of cryptographic operations. +Use MessageDigest.isEqual() method to check MACs and signatures. If this method is used, then the calculation time depends only on the length of input byte arrays, and does not depend on the contents of the arrays.

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql index ebfcd03e7f1..17327df513c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql @@ -1,12 +1,12 @@ /** - * @name Using a non-constant-time algorithm for comparing results of a cryptographic operation - * @description When comparing results of a cryptographic operation, a constant-time algorithm should be used. - * Otherwise, attackers may be able to implement a timing attack if they can control input. - * A successful attack may result in leaking secrets or authentication bypass. + * @name Using a non-constant-time algorithm for comparing MAC or signature + * @description When checking MAC or signature, a constant-time algorithm should be used. + * Otherwise, attackers may be able to implement a timing attack if they control inputs. + * A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass. * @kind path-problem * @problem.severity warning * @precision high - * @id java/non-constant-time-crypto-comparison + * @id java/non-constant-time-in-signature-check * @tags security * external/cwe/cwe-208 */ @@ -25,6 +25,9 @@ abstract private class ProduceCryptoCall extends MethodAccess { /** Return the result of cryptographic operation. */ Expr output() { result = output } + + /** Return a type of the result of cryptographic operation such as MAC, signature or ciphertext. */ + abstract string getResultType(); } /** A method call that produces a MAC. */ @@ -37,6 +40,8 @@ private class ProduceMacCall extends ProduceCryptoCall { getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output ) } + + override string getResultType() { result = "MAC" } } /** A method call that produces a signature. */ @@ -49,6 +54,8 @@ private class ProduceSignatureCall extends ProduceCryptoCall { getMethod().hasStringSignature("sign(byte[], int, int)") and getArgument(0) = output ) } + + override string getResultType() { result = "signature" } } /** @@ -98,6 +105,8 @@ private class ProduceCiphertextCall extends ProduceCryptoCall { config.hasFlowTo(DataFlow3::exprNode(this.getQualifier())) ) } + + override string getResultType() { result = "ciphertext" } } /** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */ @@ -173,13 +182,9 @@ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configura /** A source that produces result of cryptographic operation. */ private class CryptoOperationSource extends DataFlow::Node { - Expr cryptoOperation; + ProduceCryptoCall call; - CryptoOperationSource() { - exists(ProduceCryptoCall call | call.output() = this.asExpr() | - cryptoOperation = call.getQualifier() - ) - } + CryptoOperationSource() { call.output() = this.asExpr() } /** Holds if remote user input was used in the cryptographic operation. */ predicate includesUserInput() { @@ -188,9 +193,11 @@ private class CryptoOperationSource extends DataFlow::Node { | config.hasFlowPath(source, sink) | - sink.getNode().asExpr() = cryptoOperation + sink.getNode().asExpr() = call.getQualifier() ) } + + ProduceCryptoCall getCall() { result = call } } /** Methods that use a non-constant-time algorithm for comparing inputs. */ @@ -329,8 +336,8 @@ from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoCo where conf.hasFlowPath(source, sink) and ( - source.getNode().(CryptoOperationSource).includesUserInput() or + source.getNode().(CryptoOperationSource).includesUserInput() and sink.getNode().(NonConstantTimeComparisonSink).includesUserInput() ) -select sink.getNode(), source, sink, - "Using a non-constant-time algorithm for comparing results of a cryptographic operation." +select sink.getNode(), source, sink, "Using a non-constant-time method for cheching a $@.", source, + source.getNode().(CryptoOperationSource).getCall().getResultType() diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected index 94f819bc19c..067c84e198e 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected @@ -1,50 +1,50 @@ edges -| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | -| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | -| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | -| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | -| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | -| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | -| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | -| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | -| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | -| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | +| NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | +| NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | +| NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | +| NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | +| NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | +| NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | +| NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | +| NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | +| NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | +| NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag | nodes -| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | semmle.label | signature : byte[] | -| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | semmle.label | tag : byte[] | -| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | semmle.label | array(...) | -| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | semmle.label | computedTag | +| NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | +| NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | semmle.label | actualMac | +| NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] | +| NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | semmle.label | signature | +| NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] | +| NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | semmle.label | array(...) | +| NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | semmle.label | tag | +| NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag | semmle.label | computedTag | #select -| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | -| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. | +| NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | MAC | +| NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | MAC | +| NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | MAC | +| NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | signature | +| NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | signature | +| NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext | +| NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | ciphertext | +| NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | ciphertext | +| NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | ciphertext | +| NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java index 7eb622a4b74..fb370ce50ac 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java @@ -13,147 +13,190 @@ import javax.crypto.Mac; public class NonConstantTimeCryptoComparison { // BAD: compare MACs using a non-constant-time method - public boolean unsafeMacCheckWithArrayEquals(byte[] expectedMac, Socket socket) throws Exception { - Mac mac = Mac.getInstance("HmacSHA256"); - byte[] data = new byte[1024]; - socket.getInputStream().read(data); - byte[] actualMac = mac.doFinal(data); - return Arrays.equals(expectedMac, actualMac); + public boolean unsafeMacCheckWithArrayEquals(Socket socket) throws Exception { + try (InputStream is = socket.getInputStream()) { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = new byte[1024]; + is.read(data); + byte[] actualMac = mac.doFinal(data); + byte[] expectedMac = is.readNBytes(32); + return Arrays.equals(expectedMac, actualMac); + } } // BAD: compare MACs using a non-constant-time method - public boolean unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, Socket socket) throws Exception { - Mac mac = Mac.getInstance("HmacSHA256"); - byte[] data = socket.getInputStream().readAllBytes(); - mac.update(data); - byte[] actualMac = mac.doFinal(); - return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); + public boolean unsafeMacCheckWithArraysDeepEquals(Socket socket) throws Exception { + try (InputStream is = socket.getInputStream()) { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = socket.getInputStream().readAllBytes(); + mac.update(data); + byte[] actualMac = mac.doFinal(); + byte[] expectedMac = is.readNBytes(32); + return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); + } } // BAD: compare MACs using a non-constant-time method - public boolean unsafeMacCheckWithDoFinalWithOutputArray(byte[] data, Socket socket) throws Exception { - Mac mac = Mac.getInstance("HmacSHA256"); - byte[] actualMac = new byte[256]; - mac.doFinal(actualMac, 0); - byte[] expectedMac = socket.getInputStream().readNBytes(256); - return Arrays.equals(expectedMac, actualMac); + public boolean unsafeMacCheckWithDoFinalWithOutputArray(Socket socket) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] data = is.readNBytes(100); + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = new byte[256]; + mac.update(data); + mac.doFinal(actualMac, 0); + byte[] expectedMac = socket.getInputStream().readNBytes(256); + return Arrays.equals(expectedMac, actualMac); + } } // GOOD: compare MACs using a constant-time method - public boolean saferMacCheck(byte[] expectedMac, Socket socket) throws Exception { - Mac mac = Mac.getInstance("HmacSHA256"); - byte[] data = new byte[1024]; - socket.getInputStream().read(data); - byte[] actualMac = mac.doFinal(data); - return MessageDigest.isEqual(expectedMac, actualMac); + public boolean saferMacCheck(Socket socket) throws Exception { + try (InputStream is = socket.getInputStream()) { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = new byte[1024]; + is.read(data); + byte[] actualMac = mac.doFinal(data); + byte[] expectedMac = is.readNBytes(32); + return MessageDigest.isEqual(expectedMac, actualMac); + } } // BAD: compare signatures using a non-constant-time method - public boolean unsafeCheckSignatures(byte[] expected, Socket socket, PrivateKey key) throws Exception { - Signature engine = Signature.getInstance("SHA256withRSA"); - engine.initSign(key); - byte[] data = socket.getInputStream().readAllBytes(); - engine.update(data); - byte[] signature = engine.sign(); - return Arrays.equals(expected, signature); + public boolean unsafeCheckSignatures(Socket socket, PrivateKey key) throws Exception { + try (InputStream is = socket.getInputStream()) { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + byte[] data = socket.getInputStream().readAllBytes(); + engine.update(data); + byte[] signature = engine.sign(); + byte[] expected = is.readNBytes(256); + return Arrays.equals(expected, signature); + } } // BAD: compare signatures using a non-constant-time method - public boolean unsafeCheckSignaturesWithOutputArray(byte[] expected, Socket socket, PrivateKey key) throws Exception { - Signature engine = Signature.getInstance("SHA256withRSA"); - engine.initSign(key); - byte[] data = socket.getInputStream().readAllBytes(); - engine.update(data); - byte[] signature = new byte[1024]; - engine.sign(signature, 0, 1024); - return Arrays.equals(expected, signature); + public boolean unsafeCheckSignaturesWithOutputArray(Socket socket, PrivateKey key) throws Exception { + try (InputStream is = socket.getInputStream()) { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + byte[] data = socket.getInputStream().readAllBytes(); + engine.update(data); + byte[] signature = new byte[1024]; + engine.sign(signature, 0, 1024); + byte[] expected = is.readNBytes(256); + return Arrays.equals(expected, signature); + } } // GOOD: compare signatures using a constant-time method - public boolean saferCheckSignatures(byte[] expected, Socket socket, PrivateKey key) throws Exception { - Signature engine = Signature.getInstance("SHA256withRSA"); - engine.initSign(key); - byte[] data = socket.getInputStream().readAllBytes(); - engine.update(data); - byte[] signature = engine.sign(); - return MessageDigest.isEqual(expected, signature); + public boolean saferCheckSignatures(Socket socket, PrivateKey key) throws Exception { + try (InputStream is = socket.getInputStream()) { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + byte[] data = socket.getInputStream().readAllBytes(); + engine.update(data); + byte[] signature = engine.sign(); + byte[] expected = is.readNBytes(256); + return MessageDigest.isEqual(expected, signature); + } } // BAD: compare ciphertexts (custom MAC) using a non-constant-time method - public boolean unsafeCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { - byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] tag = cipher.doFinal(hash); - byte[] expected = socket.getInputStream().readAllBytes(); - return Objects.deepEquals(expected, tag); + public boolean unsafeCheckCiphertext(Socket socket, Key key) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] plaintext = is.readNBytes(100); + byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(hash); + byte[] expected = socket.getInputStream().readAllBytes(); + return Objects.deepEquals(expected, tag); + } } // BAD: compare ciphertexts (custom MAC) using a non-constant-time method - public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, Socket socket, Key key) throws Exception { - byte[] plaintext = socket.getInputStream().readAllBytes(); - MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(plaintext); - byte[] hash = md.digest(); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - cipher.update(hash); - byte[] tag = new byte[1024]; - cipher.doFinal(tag, 0); - return Arrays.equals(expected, tag); + public boolean unsafeCheckCiphertextWithOutputArray(Socket socket, Key key) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] plaintext = socket.getInputStream().readAllBytes(); + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(plaintext); + byte[] hash = md.digest(); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.update(hash); + byte[] tag = new byte[1024]; + cipher.doFinal(tag, 0); + byte[] expected = is.readNBytes(32); + return Arrays.equals(expected, tag); + } } // BAD: compare ciphertexts (custom MAC) using a non-constant-time method - public boolean unsafeCheckCiphertextWithByteBuffer(Socket socket, byte[] plaintext, Key key) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(plaintext); - byte[] hash = new byte[1024]; - md.digest(hash, 0, hash.length); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - cipher.update(hash); - ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); - cipher.doFinal(ByteBuffer.wrap(plaintext), tag); - byte[] expected = socket.getInputStream().readNBytes(1024); - return Arrays.equals(expected, tag.array()); + public boolean unsafeCheckCiphertextWithByteBuffer(Socket socket, Key key) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] plaintext = is.readNBytes(300); + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(plaintext); + byte[] hash = new byte[1024]; + md.digest(hash, 0, hash.length); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + cipher.update(hash); + ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); + cipher.doFinal(ByteBuffer.wrap(plaintext), tag); + byte[] expected = socket.getInputStream().readNBytes(1024); + return Arrays.equals(expected, tag.array()); + } } // BAD: compare ciphertexts (custom MAC) using a non-constant-time method - public boolean unsafeCheckCiphertextWithByteBufferEquals(byte[] expected, Socket socket, Key key) throws Exception { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] plaintext = socket.getInputStream().readAllBytes(); - cipher.update(plaintext); - ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); - cipher.doFinal(ByteBuffer.wrap(plaintext), tag); - return ByteBuffer.wrap(expected).equals(tag); + public boolean unsafeCheckCiphertextWithByteBufferEquals(Socket socket, Key key) throws Exception { + try (InputStream is = socket.getInputStream()) { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] plaintext = socket.getInputStream().readAllBytes(); + cipher.update(plaintext); + ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); + cipher.doFinal(ByteBuffer.wrap(plaintext), tag); + byte[] expected = is.readNBytes(32); + return ByteBuffer.wrap(expected).equals(tag); + } } // GOOD: compare ciphertexts (custom MAC) using a constant-time method - public boolean saferCheckCiphertext(Socket socket, byte[] plaintext, Key key) throws Exception { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); - byte[] tag = cipher.doFinal(hash); - byte[] expected = socket.getInputStream().readAllBytes(); - return MessageDigest.isEqual(expected, tag); + public boolean saferCheckCiphertext(Socket socket, Key key) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] plaintext = is.readNBytes(200); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); + byte[] tag = cipher.doFinal(hash); + byte[] expected = socket.getInputStream().readAllBytes(); + return MessageDigest.isEqual(expected, tag); + } } // GOOD: compare ciphertexts using a constant-time method, but no user input - public boolean noUserInputWhenCheckingCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, key); - byte[] tag = cipher.doFinal(plaintext); - return Arrays.equals(expected, tag); + public boolean noUserInputWhenCheckingCiphertext(Socket socket, Key key) throws Exception { + try (InputStream is = socket.getInputStream()) { + byte[] plaintext = is.readNBytes(100); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(plaintext); + byte[] expected = is.readNBytes(32); + return Arrays.equals(expected, tag); + } } // GOOD: compare MAC with constant using a constant-time method public boolean compareMacWithConstant(Socket socket) throws Exception { - Mac mac = Mac.getInstance("HmacSHA256"); - byte[] data = new byte[1024]; - socket.getInputStream().read(data); - byte[] actualMac = mac.doFinal(data); - return "constant".equals(new String(actualMac)); + try (InputStream is = socket.getInputStream()) { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] data = new byte[1024]; + socket.getInputStream().read(data); + byte[] actualMac = mac.doFinal(data); + return "constant".equals(new String(actualMac)); + } } private static Object[] castToObjectArray(byte[] array) { From e3b6ceade5253848b1bdaf850c74dfb6645f2444 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Sun, 4 Jul 2021 15:37:43 +0200 Subject: [PATCH 24/31] Renamed NonConstantTimeCryptoComparison.ql to NonConstantTimeCheckOnSignature.ql --- ... => NonConstantTimeCheckOnSignature.qhelp} | 0 ....ql => NonConstantTimeCheckOnSignature.ql} | 0 .../NonConstantTimeCheckOnSignature.expected | 50 +++++++++++++++++++ ...a => NonConstantTimeCheckOnSignature.java} | 2 +- .../NonConstantTimeCheckOnSignature.qlref | 1 + .../NonConstantTimeCryptoComparison.expected | 50 ------------------- .../NonConstantTimeCryptoComparison.qlref | 1 - 7 files changed, 52 insertions(+), 52 deletions(-) rename java/ql/src/experimental/Security/CWE/CWE-208/{NonConstantTimeCryptoComparison.qhelp => NonConstantTimeCheckOnSignature.qhelp} (100%) rename java/ql/src/experimental/Security/CWE/CWE-208/{NonConstantTimeCryptoComparison.ql => NonConstantTimeCheckOnSignature.ql} (100%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected rename java/ql/test/experimental/query-tests/security/CWE-208/{NonConstantTimeCryptoComparison.java => NonConstantTimeCheckOnSignature.java} (99%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.qlref delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp similarity index 100% rename from java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.qhelp rename to java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql similarity index 100% rename from java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql rename to java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected new file mode 100644 index 00000000000..50c9184b5b2 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected @@ -0,0 +1,50 @@ +edges +| NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | +| NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:35:88:35:96 | actualMac : byte[] | +| NonConstantTimeCheckOnSignature.java:35:88:35:96 | actualMac : byte[] | NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | +| NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | +| NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | +| NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | +| NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | +| NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | +| NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:148:44:148:46 | tag : ByteBuffer | +| NonConstantTimeCheckOnSignature.java:148:44:148:46 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | +| NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | +| NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | +| NonConstantTimeCheckOnSignature.java:220:34:220:50 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:223:26:223:36 | computedTag | +nodes +| NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | semmle.label | actualMac | +| NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | +| NonConstantTimeCheckOnSignature.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | semmle.label | actualMac | +| NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | semmle.label | signature | +| NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] | +| NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | semmle.label | signature | +| NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | semmle.label | tag | +| NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] | +| NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | semmle.label | tag | +| NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCheckOnSignature.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | semmle.label | array(...) | +| NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | semmle.label | tag | +| NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | semmle.label | tag | +| NonConstantTimeCheckOnSignature.java:220:34:220:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| NonConstantTimeCheckOnSignature.java:223:26:223:36 | computedTag | semmle.label | computedTag | +#select +| NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | MAC | +| NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | MAC | +| NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | MAC | +| NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | signature | +| NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | signature | +| NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext | +| NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | ciphertext | +| NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | ciphertext | +| NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | ciphertext | +| NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.java similarity index 99% rename from java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java rename to java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.java index fb370ce50ac..c21ab625f36 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.java @@ -10,7 +10,7 @@ import java.util.Objects; import javax.crypto.Cipher; import javax.crypto.Mac; -public class NonConstantTimeCryptoComparison { +public class NonConstantTimeCheckOnSignature { // BAD: compare MACs using a non-constant-time method public boolean unsafeMacCheckWithArrayEquals(Socket socket) throws Exception { diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.qlref new file mode 100644 index 00000000000..e18bd8beaa7 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected deleted file mode 100644 index 067c84e198e..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.expected +++ /dev/null @@ -1,50 +0,0 @@ -edges -| NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | -| NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | -| NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | -| NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | -| NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | -| NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | -| NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | -| NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | -| NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | -| NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag | -nodes -| NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | -| NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | semmle.label | actualMac | -| NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] | -| NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | semmle.label | signature | -| NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] | -| NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | semmle.label | array(...) | -| NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | semmle.label | tag | -| NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag | semmle.label | computedTag | -#select -| NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | MAC | -| NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | MAC | -| NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | MAC | -| NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | signature | -| NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | signature | -| NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext | -| NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | ciphertext | -| NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | ciphertext | -| NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | ciphertext | -| NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref deleted file mode 100644 index 620921375e5..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCryptoComparison.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-208/NonConstantTimeCryptoComparison.ql \ No newline at end of file From ad54c9d937c8f6113eca64b01512c2bb303ab526 Mon Sep 17 00:00:00 2001 From: Fosstars Date: Sun, 25 Jul 2021 11:40:35 +0200 Subject: [PATCH 25/31] Two queries for timing attacks --- .../NonConstantTimeCheckOnSignature.qhelp | 8 +- .../NonConstantTimeCheckOnSignature.ql | 339 +----------------- .../NonConstantTimeCheckOnSignatureQuery.qll | 322 +++++++++++++++++ .../CWE/CWE-208/SafeMacComparison.java | 6 +- .../SafeMacComparisonWithRemoteInputs.java | 9 + .../TimingAttackAgainstSignature.qhelp | 55 +++ .../CWE-208/TimingAttackAgainstSignature.ql | 28 ++ .../CWE/CWE-208/UnsafeMacComparison.java | 6 +- .../UnsafeMacComparisonWithRemoteInputs.java | 9 + .../NonConstantTimeCheckOnSignature.expected | 50 --- .../Test.expected | 15 + .../NotConstantTimeCheckOnSignature/Test.java | 59 +++ .../Test.qlref} | 0 .../Test.expected | 50 +++ .../Test.java} | 3 +- .../TimingAttackAgainstSignagure/Test.qlref | 1 + 16 files changed, 569 insertions(+), 391 deletions(-) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.java rename java/ql/test/experimental/query-tests/security/CWE-208/{NonConstantTimeCheckOnSignature.qlref => NotConstantTimeCheckOnSignature/Test.qlref} (100%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected rename java/ql/test/experimental/query-tests/security/CWE-208/{NonConstantTimeCheckOnSignature.java => TimingAttackAgainstSignagure/Test.java} (99%) create mode 100644 java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.qlref diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp index 1dccf35633f..f163fec489d 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp @@ -5,8 +5,8 @@

    A constant-time algorithm should be used for checking a MAC or a digital signature. In other words, the comparison time should not depend on the content of the input. -Otherwise, attackers may be able to implement a timing attack if they control inputs. -A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass. +Otherwise, an attacker may be able to implement a timing attack. +A successful attack may uncover a valid signature that in turn can result in authentication bypass.

    @@ -20,13 +20,13 @@ and does not depend on the contents of the arrays.

    -The following example uses Arrays.equals() method for comparing MAC. +The following example uses Arrays.equals() method for validating a MAC. This method implements a non-constant-time algorithm:

    -The next example uses a safe constant-time algorithm for comparing MAC: +The next example uses a safe constant-time algorithm for validating a MAC:

    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql index 17327df513c..5ff86a9358f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql @@ -1,343 +1,22 @@ /** - * @name Using a non-constant-time algorithm for comparing MAC or signature - * @description When checking MAC or signature, a constant-time algorithm should be used. - * Otherwise, attackers may be able to implement a timing attack if they control inputs. - * A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass. + * @name Using a non-constant-time algorithm for checking a signature + * @description When checking a signature, a constant-time algorithm should be used. + * Otherwise, an attacker may be able to implement a timing attack. + * A successful attack may uncover a valid signature + * that in turn can result in authentication bypass. * @kind path-problem * @problem.severity warning - * @precision high + * @precision medium * @id java/non-constant-time-in-signature-check * @tags security * external/cwe/cwe-208 */ import java -import semmle.code.java.controlflow.Guards -import semmle.code.java.dataflow.TaintTracking -import semmle.code.java.dataflow.TaintTracking2 -import semmle.code.java.dataflow.DataFlow3 -import semmle.code.java.dataflow.FlowSources +import NonConstantTimeCheckOnSignatureQuery import DataFlow::PathGraph -/** A method call that produces cryptographic result. */ -abstract private class ProduceCryptoCall extends MethodAccess { - Expr output; - - /** Return the result of cryptographic operation. */ - Expr output() { result = output } - - /** Return a type of the result of cryptographic operation such as MAC, signature or ciphertext. */ - abstract string getResultType(); -} - -/** A method call that produces a MAC. */ -private class ProduceMacCall extends ProduceCryptoCall { - ProduceMacCall() { - getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Mac") and - ( - getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])"]) and this = output - or - getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output - ) - } - - override string getResultType() { result = "MAC" } -} - -/** A method call that produces a signature. */ -private class ProduceSignatureCall extends ProduceCryptoCall { - ProduceSignatureCall() { - getMethod().getDeclaringType().hasQualifiedName("java.security", "Signature") and - ( - getMethod().hasStringSignature("sign()") and this = output - or - getMethod().hasStringSignature("sign(byte[], int, int)") and getArgument(0) = output - ) - } - - override string getResultType() { result = "signature" } -} - -/** - * A config that tracks data flow from initializing a cipher for encryption - * to producing a ciphertext using this cipher. - */ -private class InitializeEncryptorConfig extends DataFlow3::Configuration { - InitializeEncryptorConfig() { this = "InitializeEncryptorConfig" } - - override predicate isSource(DataFlow::Node source) { - exists(MethodAccess ma | - ma.getMethod().hasQualifiedName("javax.crypto", "Cipher", "init") and - ma.getArgument(0).(VarAccess).getVariable().hasName("ENCRYPT_MODE") and - ma.getQualifier() = source.asExpr() - ) - } - - override predicate isSink(DataFlow::Node sink) { - exists(MethodAccess ma | - ma.getMethod().hasQualifiedName("javax.crypto", "Cipher", "doFinal") and - ma.getQualifier() = sink.asExpr() - ) - } -} - -/** A method call that produces a ciphertext. */ -private class ProduceCiphertextCall extends ProduceCryptoCall { - ProduceCiphertextCall() { - exists(Method m | m = this.getMethod() | - m.getDeclaringType().hasQualifiedName("javax.crypto", "Cipher") and - ( - m.hasStringSignature(["doFinal()", "doFinal(byte[])", "doFinal(byte[], int, int)"]) and - this = output - or - m.hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output - or - m.hasStringSignature([ - "doFinal(byte[], int, int, byte[])", "doFinal(byte[], int, int, byte[], int)" - ]) and - getArgument(3) = output - or - m.hasStringSignature("doFinal(ByteBuffer, ByteBuffer)") and - getArgument(1) = output - ) - ) and - exists(InitializeEncryptorConfig config | - config.hasFlowTo(DataFlow3::exprNode(this.getQualifier())) - ) - } - - override string getResultType() { result = "ciphertext" } -} - -/** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */ -private predicate updateCryptoOperationStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { - exists(MethodAccess call, Method m | - m = call.getMethod() and - call.getQualifier() = toNode.asExpr() and - call.getArgument(0) = fromNode.asExpr() - | - m.hasQualifiedName("java.security", "Signature", "update") - or - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") - or - m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and - not m.hasStringSignature("doFinal(byte[], int)") - ) -} - -/** Holds if `fromNode` to `toNode` is a dataflow step that creates a hash. */ -private predicate createMessageDigestStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and - m.hasStringSignature("digest()") and - ma.getQualifier() = fromNode.asExpr() and - ma = toNode.asExpr() - ) - or - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and - m.hasStringSignature("digest(byte[], int, int)") and - ma.getQualifier() = fromNode.asExpr() and - ma.getArgument(0) = toNode.asExpr() - ) - or - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and - m.hasStringSignature("digest(byte[])") and - ma.getArgument(0) = fromNode.asExpr() and - ma = toNode.asExpr() - ) -} - -/** Holds if `fromNode` to `toNode` is a dataflow step that updates a hash. */ -private predicate updateMessageDigestStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { - exists(MethodAccess ma, Method m | m = ma.getMethod() | - m.hasQualifiedName("java.security", "MessageDigest", "update") and - ma.getArgument(0) = fromNode.asExpr() and - ma.getQualifier() = toNode.asExpr() - ) -} - -/** - * A config that tracks data flow from remote user input to a cryptographic operation - * such as cipher, MAC or signature. - */ -private class UserInputInCryptoOperationConfig extends TaintTracking2::Configuration { - UserInputInCryptoOperationConfig() { this = "UserInputInCryptoOperationConfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } - - override predicate isSink(DataFlow::Node sink) { - exists(ProduceCryptoCall call | call.getQualifier() = sink.asExpr()) - } - - override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { - updateCryptoOperationStep(fromNode, toNode) - or - createMessageDigestStep(fromNode, toNode) - or - updateMessageDigestStep(fromNode, toNode) - } -} - -/** A source that produces result of cryptographic operation. */ -private class CryptoOperationSource extends DataFlow::Node { - ProduceCryptoCall call; - - CryptoOperationSource() { call.output() = this.asExpr() } - - /** Holds if remote user input was used in the cryptographic operation. */ - predicate includesUserInput() { - exists( - DataFlow2::PathNode source, DataFlow2::PathNode sink, UserInputInCryptoOperationConfig config - | - config.hasFlowPath(source, sink) - | - sink.getNode().asExpr() = call.getQualifier() - ) - } - - ProduceCryptoCall getCall() { result = call } -} - -/** Methods that use a non-constant-time algorithm for comparing inputs. */ -private class NonConstantTimeEqualsCall extends MethodAccess { - NonConstantTimeEqualsCall() { - getMethod() - .hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or - getMethod().hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) - } -} - -/** Static methods that use a non-constant-time algorithm for comparing inputs. */ -private class NonConstantTimeComparisonCall extends StaticMethodAccess { - NonConstantTimeComparisonCall() { - getMethod().hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or - getMethod().hasQualifiedName("java.util", "Objects", "deepEquals") or - getMethod() - .hasQualifiedName("org.apache.commons.lang3", "StringUtils", - ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) - } -} - -/** - * A config that tracks data flow from remote user input to methods - * that compare inputs using a non-constant-time algorithm. - */ -private class UserInputInComparisonConfig extends TaintTracking2::Configuration { - UserInputInComparisonConfig() { this = "UserInputInComparisonConfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } - - override predicate isSink(DataFlow::Node sink) { - exists(NonConstantTimeEqualsCall call | - sink.asExpr() = [call.getAnArgument(), call.getQualifier()] - ) - or - exists(NonConstantTimeComparisonCall call | sink.asExpr() = call.getAnArgument()) - } -} - -/** Holds if `expr` looks like a constant. */ -private predicate looksLikeConstant(Expr expr) { - expr.isCompileTimeConstant() - or - expr.(VarAccess).getVariable().isFinal() and expr.getType() instanceof TypeString -} - -/** - * Holds if `firstObject` and `secondObject` are compared using a method - * that does not use a constant-time algorithm, for example, `String.equals()`. - */ -private predicate isNonConstantEqualsCall(Expr firstObject, Expr secondObject) { - exists(NonConstantTimeEqualsCall call | - firstObject = call.getQualifier() and - secondObject = call.getAnArgument() - or - firstObject = call.getAnArgument() and - secondObject = call.getQualifier() - ) -} - -/** - * Holds if `firstInput` and `secondInput` are compared using a static method - * that does not use a constant-time algorithm, for example, `Arrays.equals()`. - */ -private predicate isNonConstantTimeComparisonCall(Expr firstInput, Expr secondInput) { - exists(NonConstantTimeComparisonCall call | - firstInput = call.getArgument(0) and secondInput = call.getArgument(1) - or - firstInput = call.getArgument(1) and secondInput = call.getArgument(0) - ) -} - -/** - * Holds if there is a fast-fail check while comparing `firstArray` and `secondArray`. - */ -private predicate existsFailFastCheck(Expr firstArray, Expr secondArray) { - exists( - Guard guard, EqualityTest eqTest, boolean branch, Stmt fastFailingStmt, - ArrayAccess firstArrayAccess, ArrayAccess secondArrayAccess - | - guard = eqTest and - // For `==` false branch is fail fast; for `!=` true branch is fail fast - branch = eqTest.polarity().booleanNot() and - ( - fastFailingStmt instanceof ReturnStmt or - fastFailingStmt instanceof BreakStmt or - fastFailingStmt instanceof ThrowStmt - ) and - guard.controls(fastFailingStmt.getBasicBlock(), branch) and - DataFlow::localExprFlow(firstArrayAccess, eqTest.getLeftOperand()) and - DataFlow::localExprFlow(secondArrayAccess, eqTest.getRightOperand()) - | - firstArrayAccess.getArray() = firstArray and secondArray = secondArrayAccess - or - secondArrayAccess.getArray() = firstArray and secondArray = firstArrayAccess - ) -} - -/** A sink that compares input using a non-constant-time algorithm. */ -private class NonConstantTimeComparisonSink extends DataFlow::Node { - Expr anotherParameter; - - NonConstantTimeComparisonSink() { - ( - isNonConstantEqualsCall(this.asExpr(), anotherParameter) - or - isNonConstantTimeComparisonCall(this.asExpr(), anotherParameter) - or - existsFailFastCheck(this.asExpr(), anotherParameter) - ) and - not looksLikeConstant(anotherParameter) - } - - /** Holds if remote user input was used in the comparison. */ - predicate includesUserInput() { - exists(UserInputInComparisonConfig config | - config.hasFlowTo(DataFlow2::exprNode(anotherParameter)) - ) - } -} - -/** - * A configuration that tracks data flow from cryptographic operations - * to methods that compare data using a non-constant-time algorithm. - */ -private class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { - NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" } - - override predicate isSource(DataFlow::Node source) { source instanceof CryptoOperationSource } - - override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink } -} - from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf -where - conf.hasFlowPath(source, sink) and - ( - source.getNode().(CryptoOperationSource).includesUserInput() and - sink.getNode().(NonConstantTimeComparisonSink).includesUserInput() - ) -select sink.getNode(), source, sink, "Using a non-constant-time method for cheching a $@.", source, +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Using a non-constant-time method for checking a $@.", source, source.getNode().(CryptoOperationSource).getCall().getResultType() diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll new file mode 100644 index 00000000000..3c5e52b312c --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll @@ -0,0 +1,322 @@ +/** + * Provides classes and predicates for queries that detect timing attacks. + */ + +import semmle.code.java.controlflow.Guards +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.dataflow.TaintTracking2 +import semmle.code.java.dataflow.DataFlow3 +import semmle.code.java.dataflow.FlowSources + +/** A method call that produces cryptographic result. */ +abstract private class ProduceCryptoCall extends MethodAccess { + Expr output; + + /** Return the result of cryptographic operation. */ + Expr output() { result = output } + + /** Return a type of the result of cryptographic operation such as MAC, signature or ciphertext. */ + abstract string getResultType(); +} + +/** A method call that produces a MAC. */ +private class ProduceMacCall extends ProduceCryptoCall { + ProduceMacCall() { + getMethod().getDeclaringType().hasQualifiedName("javax.crypto", "Mac") and + ( + getMethod().hasStringSignature(["doFinal()", "doFinal(byte[])"]) and this = output + or + getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output + ) + } + + override string getResultType() { result = "MAC" } +} + +/** A method call that produces a signature. */ +private class ProduceSignatureCall extends ProduceCryptoCall { + ProduceSignatureCall() { + getMethod().getDeclaringType().hasQualifiedName("java.security", "Signature") and + ( + getMethod().hasStringSignature("sign()") and this = output + or + getMethod().hasStringSignature("sign(byte[], int, int)") and getArgument(0) = output + ) + } + + override string getResultType() { result = "signature" } +} + +/** + * A config that tracks data flow from initializing a cipher for encryption + * to producing a ciphertext using this cipher. + */ +private class InitializeEncryptorConfig extends DataFlow3::Configuration { + InitializeEncryptorConfig() { this = "InitializeEncryptorConfig" } + + override predicate isSource(DataFlow::Node source) { + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("javax.crypto", "Cipher", "init") and + ma.getArgument(0).(VarAccess).getVariable().hasName("ENCRYPT_MODE") and + ma.getQualifier() = source.asExpr() + ) + } + + override predicate isSink(DataFlow::Node sink) { + exists(MethodAccess ma | + ma.getMethod().hasQualifiedName("javax.crypto", "Cipher", "doFinal") and + ma.getQualifier() = sink.asExpr() + ) + } +} + +/** A method call that produces a ciphertext. */ +private class ProduceCiphertextCall extends ProduceCryptoCall { + ProduceCiphertextCall() { + exists(Method m | m = this.getMethod() | + m.getDeclaringType().hasQualifiedName("javax.crypto", "Cipher") and + ( + m.hasStringSignature(["doFinal()", "doFinal(byte[])", "doFinal(byte[], int, int)"]) and + this = output + or + m.hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output + or + m.hasStringSignature([ + "doFinal(byte[], int, int, byte[])", "doFinal(byte[], int, int, byte[], int)" + ]) and + getArgument(3) = output + or + m.hasStringSignature("doFinal(ByteBuffer, ByteBuffer)") and + getArgument(1) = output + ) + ) and + exists(InitializeEncryptorConfig config | + config.hasFlowTo(DataFlow3::exprNode(this.getQualifier())) + ) + } + + override string getResultType() { result = "ciphertext" } +} + +/** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */ +private predicate updateCryptoOperationStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess call, Method m | + m = call.getMethod() and + call.getQualifier() = toNode.asExpr() and + call.getArgument(0) = fromNode.asExpr() + | + m.hasQualifiedName("java.security", "Signature", "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "update") + or + m.hasQualifiedName("javax.crypto", ["Mac", "Cipher"], "doFinal") and + not m.hasStringSignature("doFinal(byte[], int)") + ) +} + +/** Holds if `fromNode` to `toNode` is a dataflow step that creates a hash. */ +private predicate createMessageDigestStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + m.hasStringSignature("digest()") and + ma.getQualifier() = fromNode.asExpr() and + ma = toNode.asExpr() + ) + or + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + m.hasStringSignature("digest(byte[], int, int)") and + ma.getQualifier() = fromNode.asExpr() and + ma.getArgument(0) = toNode.asExpr() + ) + or + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and + m.hasStringSignature("digest(byte[])") and + ma.getArgument(0) = fromNode.asExpr() and + ma = toNode.asExpr() + ) +} + +/** Holds if `fromNode` to `toNode` is a dataflow step that updates a hash. */ +private predicate updateMessageDigestStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + exists(MethodAccess ma, Method m | m = ma.getMethod() | + m.hasQualifiedName("java.security", "MessageDigest", "update") and + ma.getArgument(0) = fromNode.asExpr() and + ma.getQualifier() = toNode.asExpr() + ) +} + +/** + * A config that tracks data flow from remote user input to a cryptographic operation + * such as cipher, MAC or signature. + */ +private class UserInputInCryptoOperationConfig extends TaintTracking2::Configuration { + UserInputInCryptoOperationConfig() { this = "UserInputInCryptoOperationConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(ProduceCryptoCall call | call.getQualifier() = sink.asExpr()) + } + + override predicate isAdditionalTaintStep(DataFlow2::Node fromNode, DataFlow2::Node toNode) { + updateCryptoOperationStep(fromNode, toNode) + or + createMessageDigestStep(fromNode, toNode) + or + updateMessageDigestStep(fromNode, toNode) + } +} + +/** A source that produces result of cryptographic operation. */ +class CryptoOperationSource extends DataFlow::Node { + ProduceCryptoCall call; + + CryptoOperationSource() { call.output() = this.asExpr() } + + /** Holds if remote user input was used in the cryptographic operation. */ + predicate includesUserInput() { + exists( + DataFlow2::PathNode source, DataFlow2::PathNode sink, UserInputInCryptoOperationConfig config + | + config.hasFlowPath(source, sink) + | + sink.getNode().asExpr() = call.getQualifier() + ) + } + + ProduceCryptoCall getCall() { result = call } +} + +/** Methods that use a non-constant-time algorithm for comparing inputs. */ +private class NonConstantTimeEqualsCall extends MethodAccess { + NonConstantTimeEqualsCall() { + getMethod() + .hasQualifiedName("java.lang", "String", ["equals", "contentEquals", "equalsIgnoreCase"]) or + getMethod().hasQualifiedName("java.nio", "ByteBuffer", ["equals", "compareTo"]) + } +} + +/** Static methods that use a non-constant-time algorithm for comparing inputs. */ +private class NonConstantTimeComparisonCall extends StaticMethodAccess { + NonConstantTimeComparisonCall() { + getMethod().hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or + getMethod().hasQualifiedName("java.util", "Objects", "deepEquals") or + getMethod() + .hasQualifiedName("org.apache.commons.lang3", "StringUtils", + ["equals", "equalsAny", "equalsAnyIgnoreCase", "equalsIgnoreCase"]) + } +} + +/** + * A config that tracks data flow from remote user input to methods + * that compare inputs using a non-constant-time algorithm. + */ +private class UserInputInComparisonConfig extends TaintTracking2::Configuration { + UserInputInComparisonConfig() { this = "UserInputInComparisonConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { + exists(NonConstantTimeEqualsCall call | + sink.asExpr() = [call.getAnArgument(), call.getQualifier()] + ) + or + exists(NonConstantTimeComparisonCall call | sink.asExpr() = call.getAnArgument()) + } +} + +/** Holds if `expr` looks like a constant. */ +private predicate looksLikeConstant(Expr expr) { + expr.isCompileTimeConstant() + or + expr.(VarAccess).getVariable().isFinal() and expr.getType() instanceof TypeString +} + +/** + * Holds if `firstObject` and `secondObject` are compared using a method + * that does not use a constant-time algorithm, for example, `String.equals()`. + */ +private predicate isNonConstantEqualsCall(Expr firstObject, Expr secondObject) { + exists(NonConstantTimeEqualsCall call | + firstObject = call.getQualifier() and + secondObject = call.getAnArgument() + or + firstObject = call.getAnArgument() and + secondObject = call.getQualifier() + ) +} + +/** + * Holds if `firstInput` and `secondInput` are compared using a static method + * that does not use a constant-time algorithm, for example, `Arrays.equals()`. + */ +private predicate isNonConstantTimeComparisonCall(Expr firstInput, Expr secondInput) { + exists(NonConstantTimeComparisonCall call | + firstInput = call.getArgument(0) and secondInput = call.getArgument(1) + or + firstInput = call.getArgument(1) and secondInput = call.getArgument(0) + ) +} + +/** + * Holds if there is a fast-fail check while comparing `firstArray` and `secondArray`. + */ +private predicate existsFailFastCheck(Expr firstArray, Expr secondArray) { + exists( + Guard guard, EqualityTest eqTest, boolean branch, Stmt fastFailingStmt, + ArrayAccess firstArrayAccess, ArrayAccess secondArrayAccess + | + guard = eqTest and + // For `==` false branch is fail fast; for `!=` true branch is fail fast + branch = eqTest.polarity().booleanNot() and + ( + fastFailingStmt instanceof ReturnStmt or + fastFailingStmt instanceof BreakStmt or + fastFailingStmt instanceof ThrowStmt + ) and + guard.controls(fastFailingStmt.getBasicBlock(), branch) and + DataFlow::localExprFlow(firstArrayAccess, eqTest.getLeftOperand()) and + DataFlow::localExprFlow(secondArrayAccess, eqTest.getRightOperand()) + | + firstArrayAccess.getArray() = firstArray and secondArray = secondArrayAccess + or + secondArrayAccess.getArray() = firstArray and secondArray = firstArrayAccess + ) +} + +/** A sink that compares input using a non-constant-time algorithm. */ +class NonConstantTimeComparisonSink extends DataFlow::Node { + Expr anotherParameter; + + NonConstantTimeComparisonSink() { + ( + isNonConstantEqualsCall(this.asExpr(), anotherParameter) + or + isNonConstantTimeComparisonCall(this.asExpr(), anotherParameter) + or + existsFailFastCheck(this.asExpr(), anotherParameter) + ) and + not looksLikeConstant(anotherParameter) + } + + /** Holds if remote user input was used in the comparison. */ + predicate includesUserInput() { + exists(UserInputInComparisonConfig config | + config.hasFlowTo(DataFlow2::exprNode(anotherParameter)) + ) + } +} + +/** + * A configuration that tracks data flow from cryptographic operations + * to methods that compare data using a non-constant-time algorithm. + */ +class NonConstantTimeCryptoComparisonConfig extends TaintTracking::Configuration { + NonConstantTimeCryptoComparisonConfig() { this = "NonConstantTimeCryptoComparisonConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof CryptoOperationSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java index f2cc5f95ef8..4e27f07caa1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java +++ b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java @@ -1,6 +1,6 @@ -public boolean check(byte[] expected, byte[] data, SecretKey key) throws Exception { +public boolean check(byte[] signature, byte[] message, SecretKey key) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); - byte[] actual = mac.doFinal(data); - return MessageDigest.isEqual(expected, actual); + byte[] actual = mac.doFinal(message); + return MessageDigest.isEqual(signature, actual); } \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java new file mode 100644 index 00000000000..fbdb6131a5c --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java @@ -0,0 +1,9 @@ +public boolean validate(HttpRequest request, SecretKey key) throws Exception { + byte[] message = getMessageFrom(request); + byte[] signature = getSignatureFrom(request); + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); + byte[] actual = mac.doFinal(message); + return MessageDigest.isEqual(signature, actual); +} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp new file mode 100644 index 00000000000..1f2746c3556 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp @@ -0,0 +1,55 @@ + + + + +

    +A constant-time algorithm should be used for checking a MAC or a digital signature. +In other words, the comparison time should not depend on the content of the input. +Otherwise, an attacker may be able to implement a timing attack if they control inputs +for the cryptographic operation and the checking prodedure. +A successful attack may uncover a valid signature that in turn can result in authentication bypass. +

    +
    + + +

    +Use MessageDigest.isEqual() method to check MACs and signatures. +If this method is used, then the calculation time depends only on the length of input byte arrays, +and does not depend on the contents of the arrays. +

    +
    + + +

    +The following example uses Arrays.equals() method for validating a MAC over a messgae. +This method implements a non-constant-time algorithm. +Both the message and the signature come from an untrusted HTTP request: +

    + + +

    +The next example uses a safe constant-time algorithm for validating a MAC: +

    + + +
    + + +
  • + Wikipedia: + Timing attack. +
  • +
  • + Coursera: + Timing attacks on MAC verification +
  • +
  • + NCC Group: + Time Trial: Racing Towards Practical Remote Timing Attacks +
  • +
  • + Java API Specification: + MessageDigest.isEqual() method +
  • + + diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql new file mode 100644 index 00000000000..255b44be6e6 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql @@ -0,0 +1,28 @@ +/** + * @name Timing attack against signature validation + * @description When checking a signature, a constant-time algorithm should be used. + * Otherwise, an attacker may be able to implement a timing attack + * if they control inputs for the cryptographic operation and the checking procedure. + * A successful attack may uncover a valid signature + * that in turn can result in authentication bypass. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/timing-attack-against-signature + * @tags security + * external/cwe/cwe-208 + */ + +import java +import NonConstantTimeCheckOnSignatureQuery +import DataFlow::PathGraph + +from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf +where + conf.hasFlowPath(source, sink) and + ( + source.getNode().(CryptoOperationSource).includesUserInput() and + sink.getNode().(NonConstantTimeComparisonSink).includesUserInput() + ) +select sink.getNode(), source, sink, "Timing attack against $@ validation.", source, + source.getNode().(CryptoOperationSource).getCall().getResultType() diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java index ec5f423aa4b..b0513329e6a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java +++ b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java @@ -1,6 +1,6 @@ -public boolean check(byte[] expected, byte[] data, SecretKey key) throws Exception { +public boolean check(byte[] signature, byte[] message, SecretKey key) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); - byte[] actual = mac.doFinal(data); - return Arrays.equals(expected, actual); + byte[] actual = mac.doFinal(message); + return Arrays.equals(signature, actual); } \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java new file mode 100644 index 00000000000..1785ff2e7c6 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java @@ -0,0 +1,9 @@ +public boolean validate(HttpRequest request, SecretKey key) throws Exception { + byte[] message = getMessageFrom(request); + byte[] signature = getSignatureFrom(request); + + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); + byte[] actual = mac.doFinal(message); + return Arrays.equals(signature, actual); +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected deleted file mode 100644 index 50c9184b5b2..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.expected +++ /dev/null @@ -1,50 +0,0 @@ -edges -| NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | -| NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:35:88:35:96 | actualMac : byte[] | -| NonConstantTimeCheckOnSignature.java:35:88:35:96 | actualMac : byte[] | NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | -| NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | -| NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | -| NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | -| NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | -| NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | -| NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:148:44:148:46 | tag : ByteBuffer | -| NonConstantTimeCheckOnSignature.java:148:44:148:46 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | -| NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | -| NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | -| NonConstantTimeCheckOnSignature.java:220:34:220:50 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:223:26:223:36 | computedTag | -nodes -| NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | semmle.label | actualMac | -| NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | -| NonConstantTimeCheckOnSignature.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | semmle.label | actualMac | -| NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | semmle.label | signature | -| NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] | -| NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | semmle.label | signature | -| NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | semmle.label | tag | -| NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] | -| NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | semmle.label | tag | -| NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCheckOnSignature.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | semmle.label | array(...) | -| NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | semmle.label | tag | -| NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | semmle.label | tag | -| NonConstantTimeCheckOnSignature.java:220:34:220:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| NonConstantTimeCheckOnSignature.java:223:26:223:36 | computedTag | semmle.label | computedTag | -#select -| NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:23:47:23:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:21:32:21:48 | doFinal(...) : byte[] | MAC | -| NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:35:70:35:97 | castToObjectArray(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:33:32:33:44 | doFinal(...) : byte[] | MAC | -| NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCheckOnSignature.java:48:47:48:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:46:25:46:33 | actualMac : byte[] | MAC | -| NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCheckOnSignature.java:73:44:73:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:71:32:71:44 | sign(...) : byte[] | signature | -| NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | NonConstantTimeCheckOnSignature.java:87:44:87:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:85:25:85:33 | signature : byte[] | signature | -| NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:113:49:113:51 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext | -| NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | NonConstantTimeCheckOnSignature.java:130:44:130:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:128:28:128:30 | tag : byte[] | ciphertext | -| NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:148:44:148:54 | array(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:146:56:146:58 | tag : ByteBuffer | ciphertext | -| NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCheckOnSignature.java:162:53:162:55 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:160:56:160:58 | tag : ByteBuffer | ciphertext | -| NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCheckOnSignature.java:187:44:187:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCheckOnSignature.java:185:26:185:50 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected new file mode 100644 index 00000000000..4ef23d754fe --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected @@ -0,0 +1,15 @@ +edges +| Test.java:14:28:14:44 | doFinal(...) : byte[] | Test.java:15:43:15:51 | actualMac | +| Test.java:30:28:30:40 | sign(...) : byte[] | Test.java:31:40:31:48 | signature | +| Test.java:47:22:47:46 | doFinal(...) : byte[] | Test.java:48:40:48:42 | tag | +nodes +| Test.java:14:28:14:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:15:43:15:51 | actualMac | semmle.label | actualMac | +| Test.java:30:28:30:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| Test.java:31:40:31:48 | signature | semmle.label | signature | +| Test.java:47:22:47:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:48:40:48:42 | tag | semmle.label | tag | +#select +| Test.java:15:43:15:51 | actualMac | Test.java:14:28:14:44 | doFinal(...) : byte[] | Test.java:15:43:15:51 | actualMac | Using a non-constant-time method for checking a $@. | Test.java:14:28:14:44 | doFinal(...) : byte[] | MAC | +| Test.java:31:40:31:48 | signature | Test.java:30:28:30:40 | sign(...) : byte[] | Test.java:31:40:31:48 | signature | Using a non-constant-time method for checking a $@. | Test.java:30:28:30:40 | sign(...) : byte[] | signature | +| Test.java:48:40:48:42 | tag | Test.java:47:22:47:46 | doFinal(...) : byte[] | Test.java:48:40:48:42 | tag | Using a non-constant-time method for checking a $@. | Test.java:47:22:47:46 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.java b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.java new file mode 100644 index 00000000000..7a4433e485d --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.java @@ -0,0 +1,59 @@ +import java.security.Key; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.Signature; +import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.Mac; + +public class Test { + + // BAD: compare MACs using a not-constant time method + public boolean unsafeMacCheck(byte[] expectedMac, byte[] data) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = mac.doFinal(data); + return Arrays.equals(expectedMac, actualMac); + } + + // GOOD: compare MACs using a constant time method + public boolean saferMacCheck(byte[] expectedMac, byte[] data) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + byte[] actualMac = mac.doFinal(data); + return MessageDigest.isEqual(expectedMac, actualMac); + } + + // BAD: compare signatures using a not-constant time method + public boolean unsafeCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + engine.update(data); + byte[] signature = engine.sign(); + return Arrays.equals(expected, signature); + } + + // GOOD: compare signatures using a constant time method + public boolean saferCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception { + Signature engine = Signature.getInstance("SHA256withRSA"); + engine.initSign(key); + engine.update(data); + byte[] signature = engine.sign(); + return MessageDigest.isEqual(expected, signature); + } + + // BAD: compare ciphertexts using a not-constant time method + public boolean unsafeCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(plaintext); + return Arrays.equals(expected, tag); + } + + // GOOD: compare ciphertexts using a constant time method + public boolean saferCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] tag = cipher.doFinal(plaintext); + return MessageDigest.isEqual(expected, tag); + } + +} \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.qlref similarity index 100% rename from java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.qlref rename to java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.qlref diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected new file mode 100644 index 00000000000..b80efc4d714 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected @@ -0,0 +1,50 @@ +edges +| Test.java:21:32:21:48 | doFinal(...) : byte[] | Test.java:23:47:23:55 | actualMac | +| Test.java:33:32:33:44 | doFinal(...) : byte[] | Test.java:35:88:35:96 | actualMac : byte[] | +| Test.java:35:88:35:96 | actualMac : byte[] | Test.java:35:70:35:97 | castToObjectArray(...) | +| Test.java:46:25:46:33 | actualMac : byte[] | Test.java:48:47:48:55 | actualMac | +| Test.java:71:32:71:44 | sign(...) : byte[] | Test.java:73:44:73:52 | signature | +| Test.java:85:25:85:33 | signature : byte[] | Test.java:87:44:87:52 | signature | +| Test.java:111:26:111:45 | doFinal(...) : byte[] | Test.java:113:49:113:51 | tag | +| Test.java:128:28:128:30 | tag : byte[] | Test.java:130:44:130:46 | tag | +| Test.java:146:56:146:58 | tag : ByteBuffer | Test.java:148:44:148:46 | tag : ByteBuffer | +| Test.java:148:44:148:46 | tag : ByteBuffer | Test.java:148:44:148:54 | array(...) | +| Test.java:160:56:160:58 | tag : ByteBuffer | Test.java:162:53:162:55 | tag | +| Test.java:186:26:186:50 | doFinal(...) : byte[] | Test.java:188:44:188:46 | tag | +| Test.java:221:34:221:50 | doFinal(...) : byte[] | Test.java:224:26:224:36 | computedTag | +nodes +| Test.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:23:47:23:55 | actualMac | semmle.label | actualMac | +| Test.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | +| Test.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| Test.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| Test.java:48:47:48:55 | actualMac | semmle.label | actualMac | +| Test.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| Test.java:73:44:73:52 | signature | semmle.label | signature | +| Test.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] | +| Test.java:87:44:87:52 | signature | semmle.label | signature | +| Test.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:113:49:113:51 | tag | semmle.label | tag | +| Test.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] | +| Test.java:130:44:130:46 | tag | semmle.label | tag | +| Test.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| Test.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| Test.java:148:44:148:54 | array(...) | semmle.label | array(...) | +| Test.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| Test.java:162:53:162:55 | tag | semmle.label | tag | +| Test.java:186:26:186:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:188:44:188:46 | tag | semmle.label | tag | +| Test.java:221:34:221:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:224:26:224:36 | computedTag | semmle.label | computedTag | +#select +| Test.java:23:47:23:55 | actualMac | Test.java:21:32:21:48 | doFinal(...) : byte[] | Test.java:23:47:23:55 | actualMac | Timing attack against $@ validation. | Test.java:21:32:21:48 | doFinal(...) : byte[] | MAC | +| Test.java:35:70:35:97 | castToObjectArray(...) | Test.java:33:32:33:44 | doFinal(...) : byte[] | Test.java:35:70:35:97 | castToObjectArray(...) | Timing attack against $@ validation. | Test.java:33:32:33:44 | doFinal(...) : byte[] | MAC | +| Test.java:48:47:48:55 | actualMac | Test.java:46:25:46:33 | actualMac : byte[] | Test.java:48:47:48:55 | actualMac | Timing attack against $@ validation. | Test.java:46:25:46:33 | actualMac : byte[] | MAC | +| Test.java:73:44:73:52 | signature | Test.java:71:32:71:44 | sign(...) : byte[] | Test.java:73:44:73:52 | signature | Timing attack against $@ validation. | Test.java:71:32:71:44 | sign(...) : byte[] | signature | +| Test.java:87:44:87:52 | signature | Test.java:85:25:85:33 | signature : byte[] | Test.java:87:44:87:52 | signature | Timing attack against $@ validation. | Test.java:85:25:85:33 | signature : byte[] | signature | +| Test.java:113:49:113:51 | tag | Test.java:111:26:111:45 | doFinal(...) : byte[] | Test.java:113:49:113:51 | tag | Timing attack against $@ validation. | Test.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext | +| Test.java:130:44:130:46 | tag | Test.java:128:28:128:30 | tag : byte[] | Test.java:130:44:130:46 | tag | Timing attack against $@ validation. | Test.java:128:28:128:30 | tag : byte[] | ciphertext | +| Test.java:148:44:148:54 | array(...) | Test.java:146:56:146:58 | tag : ByteBuffer | Test.java:148:44:148:54 | array(...) | Timing attack against $@ validation. | Test.java:146:56:146:58 | tag : ByteBuffer | ciphertext | +| Test.java:162:53:162:55 | tag | Test.java:160:56:160:58 | tag : ByteBuffer | Test.java:162:53:162:55 | tag | Timing attack against $@ validation. | Test.java:160:56:160:58 | tag : ByteBuffer | ciphertext | +| Test.java:188:44:188:46 | tag | Test.java:186:26:186:50 | doFinal(...) : byte[] | Test.java:188:44:188:46 | tag | Timing attack against $@ validation. | Test.java:186:26:186:50 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.java b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java similarity index 99% rename from java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.java rename to java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java index c21ab625f36..739f273f7ec 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NonConstantTimeCheckOnSignature.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java @@ -10,7 +10,7 @@ import java.util.Objects; import javax.crypto.Cipher; import javax.crypto.Mac; -public class NonConstantTimeCheckOnSignature { +public class Test { // BAD: compare MACs using a non-constant-time method public boolean unsafeMacCheckWithArrayEquals(Socket socket) throws Exception { @@ -177,6 +177,7 @@ public class NonConstantTimeCheckOnSignature { } // GOOD: compare ciphertexts using a constant-time method, but no user input + // but NonConstantTimeCheckOnSignature.ql still detects it public boolean noUserInputWhenCheckingCiphertext(Socket socket, Key key) throws Exception { try (InputStream is = socket.getInputStream()) { byte[] plaintext = is.readNBytes(100); diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.qlref new file mode 100644 index 00000000000..0ed112d4d1f --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql \ No newline at end of file From 9b953cf0fc8c5f62334efa33792418e412782a81 Mon Sep 17 00:00:00 2001 From: Artem Smotrakov Date: Fri, 30 Jul 2021 19:44:36 +0200 Subject: [PATCH 26/31] Apply suggestions from code review Co-authored-by: Chris Smowton --- .../CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll | 2 +- java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll index 3c5e52b312c..2b1e84114b1 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll @@ -238,7 +238,7 @@ private predicate looksLikeConstant(Expr expr) { * Holds if `firstObject` and `secondObject` are compared using a method * that does not use a constant-time algorithm, for example, `String.equals()`. */ -private predicate isNonConstantEqualsCall(Expr firstObject, Expr secondObject) { +private predicate isNonConstantTimeEqualsCall(Expr firstObject, Expr secondObject) { exists(NonConstantTimeEqualsCall call | firstObject = call.getQualifier() and secondObject = call.getAnArgument() diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index 1813811b496..6c12caa9d45 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -257,7 +257,6 @@ private predicate sinkModelCsv(string row) { ] } -// TODO: add ByteBuffer private predicate summaryModelCsv(string row) { row = [ From 0fc487fb04acae808935e0f96e0ce5876f3a125d Mon Sep 17 00:00:00 2001 From: Fosstars Date: Sun, 1 Aug 2021 09:45:57 +0200 Subject: [PATCH 27/31] Better qhelp for timing attacks --- .../NonConstantTimeCheckOnSignature.qhelp | 29 ++------------- .../NonConstantTimeCheckOnSignature.ql | 8 ++--- .../NonConstantTimeCheckOnSignatureQuery.qll | 2 +- ...nConstantTimeCheckRecommendation.inc.qhelp | 10 ++++++ .../NonConstantTimeCheckReferences.inc.qhelp | 21 +++++++++++ .../TimingAttackAgainstSignature.qhelp | 36 ++++--------------- .../CWE-208/TimingAttackAgainstSignature.ql | 10 +++--- 7 files changed, 50 insertions(+), 66 deletions(-) create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp index f163fec489d..46c692ff862 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp @@ -10,13 +10,7 @@ A successful attack may uncover a valid signature that in turn can result in aut

    - -

    -Use MessageDigest.isEqual() method to check MACs and signatures. -If this method is used, then the calculation time depends only on the length of input byte arrays, -and does not depend on the contents of the arrays. -

    -
    +

    @@ -29,25 +23,8 @@ This method implements a non-constant-time algorithm: The next example uses a safe constant-time algorithm for validating a MAC:

    -
    - -
  • - Wikipedia: - Timing attack. -
  • -
  • - Coursera: - Timing attacks on MAC verification -
  • -
  • - NCC Group: - Time Trial: Racing Towards Practical Remote Timing Attacks -
  • -
  • - Java API Specification: - MessageDigest.isEqual() method -
  • -
    + + diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql index 5ff86a9358f..a4ac9655aed 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql @@ -1,9 +1,9 @@ /** * @name Using a non-constant-time algorithm for checking a signature - * @description When checking a signature, a constant-time algorithm should be used. - * Otherwise, an attacker may be able to implement a timing attack. - * A successful attack may uncover a valid signature - * that in turn can result in authentication bypass. + * @description When checking a signature over a message, a constant-time algorithm should be used. + * Otherwise, there is a risk of a timing attack that allows an attacker + * to forge a valid signature for an arbitrary message. For a successful attack, + * the attacker has to be able to send to the validation procedure both the message and the signature. * @kind path-problem * @problem.severity warning * @precision medium diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll index 2b1e84114b1..d0e270d265f 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll @@ -292,7 +292,7 @@ class NonConstantTimeComparisonSink extends DataFlow::Node { NonConstantTimeComparisonSink() { ( - isNonConstantEqualsCall(this.asExpr(), anotherParameter) + isNonConstantTimeEqualsCall(this.asExpr(), anotherParameter) or isNonConstantTimeComparisonCall(this.asExpr(), anotherParameter) or diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp new file mode 100644 index 00000000000..3a842f45206 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp @@ -0,0 +1,10 @@ + + + +

    +Use MessageDigest.isEqual() method to check MACs and signatures. +If this method is used, then the calculation time depends only on the length of input byte arrays, +and does not depend on the contents of the arrays. +

    +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp new file mode 100644 index 00000000000..68797c021d7 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp @@ -0,0 +1,21 @@ + + + +
  • + Wikipedia: + Timing attack. +
  • +
  • + Coursera: + Timing attacks on MAC verification +
  • +
  • + NCC Group: + Time Trial: Racing Towards Practical Remote Timing Attacks +
  • +
  • + Java API Specification: + MessageDigest.isEqual() method +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp index 1f2746c3556..2daefdd0181 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp @@ -5,23 +5,17 @@

    A constant-time algorithm should be used for checking a MAC or a digital signature. In other words, the comparison time should not depend on the content of the input. -Otherwise, an attacker may be able to implement a timing attack if they control inputs -for the cryptographic operation and the checking prodedure. -A successful attack may uncover a valid signature that in turn can result in authentication bypass. +Otherwise, an attacker may be able to forge a valid signature for an arbitrary message +by running a timing attack if they can send to the validation procedure +both the message and the signature. A successful attack can result in authentication bypass.

    - -

    -Use MessageDigest.isEqual() method to check MACs and signatures. -If this method is used, then the calculation time depends only on the length of input byte arrays, -and does not depend on the contents of the arrays. -

    -
    +

    -The following example uses Arrays.equals() method for validating a MAC over a messgae. +The following example uses Arrays.equals() method for validating a MAC over a message. This method implements a non-constant-time algorithm. Both the message and the signature come from an untrusted HTTP request:

    @@ -31,25 +25,7 @@ Both the message and the signature come from an untrusted HTTP request: The next example uses a safe constant-time algorithm for validating a MAC:

    -
    - -
  • - Wikipedia: - Timing attack. -
  • -
  • - Coursera: - Timing attacks on MAC verification -
  • -
  • - NCC Group: - Time Trial: Racing Towards Practical Remote Timing Attacks -
  • -
  • - Java API Specification: - MessageDigest.isEqual() method -
  • -
    + diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql index 255b44be6e6..488b49684b2 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql @@ -1,10 +1,10 @@ /** * @name Timing attack against signature validation - * @description When checking a signature, a constant-time algorithm should be used. - * Otherwise, an attacker may be able to implement a timing attack - * if they control inputs for the cryptographic operation and the checking procedure. - * A successful attack may uncover a valid signature - * that in turn can result in authentication bypass. + * @description When checking a signature over a message, a constant-time algorithm should be used. + * Otherwise, an attacker may be able to forge a valid signature for an arbitrary message + * by running a timing attack if they can send to the validation procedure + * both the message and the signature. + * A successful attack can result in authentication bypass. * @kind path-problem * @problem.severity error * @precision high From 44e52517adb74bc32323d8bffc5b051b590ec5f1 Mon Sep 17 00:00:00 2001 From: Fosstars Date: Sun, 1 Aug 2021 10:12:38 +0200 Subject: [PATCH 28/31] Removed unsafeMacCheckWithArraysDeepEquals() test --- .../Test.expected | 80 +++++++++---------- .../TimingAttackAgainstSignagure/Test.java | 20 ----- 2 files changed, 37 insertions(+), 63 deletions(-) diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected index b80efc4d714..805be02abf3 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.expected @@ -1,50 +1,44 @@ edges | Test.java:21:32:21:48 | doFinal(...) : byte[] | Test.java:23:47:23:55 | actualMac | -| Test.java:33:32:33:44 | doFinal(...) : byte[] | Test.java:35:88:35:96 | actualMac : byte[] | -| Test.java:35:88:35:96 | actualMac : byte[] | Test.java:35:70:35:97 | castToObjectArray(...) | -| Test.java:46:25:46:33 | actualMac : byte[] | Test.java:48:47:48:55 | actualMac | -| Test.java:71:32:71:44 | sign(...) : byte[] | Test.java:73:44:73:52 | signature | -| Test.java:85:25:85:33 | signature : byte[] | Test.java:87:44:87:52 | signature | -| Test.java:111:26:111:45 | doFinal(...) : byte[] | Test.java:113:49:113:51 | tag | -| Test.java:128:28:128:30 | tag : byte[] | Test.java:130:44:130:46 | tag | -| Test.java:146:56:146:58 | tag : ByteBuffer | Test.java:148:44:148:46 | tag : ByteBuffer | -| Test.java:148:44:148:46 | tag : ByteBuffer | Test.java:148:44:148:54 | array(...) | -| Test.java:160:56:160:58 | tag : ByteBuffer | Test.java:162:53:162:55 | tag | -| Test.java:186:26:186:50 | doFinal(...) : byte[] | Test.java:188:44:188:46 | tag | -| Test.java:221:34:221:50 | doFinal(...) : byte[] | Test.java:224:26:224:36 | computedTag | +| Test.java:34:25:34:33 | actualMac : byte[] | Test.java:36:47:36:55 | actualMac | +| Test.java:59:32:59:44 | sign(...) : byte[] | Test.java:61:44:61:52 | signature | +| Test.java:73:25:73:33 | signature : byte[] | Test.java:75:44:75:52 | signature | +| Test.java:99:26:99:45 | doFinal(...) : byte[] | Test.java:101:49:101:51 | tag | +| Test.java:116:28:116:30 | tag : byte[] | Test.java:118:44:118:46 | tag | +| Test.java:134:56:134:58 | tag : ByteBuffer | Test.java:136:44:136:46 | tag : ByteBuffer | +| Test.java:136:44:136:46 | tag : ByteBuffer | Test.java:136:44:136:54 | array(...) | +| Test.java:148:56:148:58 | tag : ByteBuffer | Test.java:150:53:150:55 | tag | +| Test.java:174:26:174:50 | doFinal(...) : byte[] | Test.java:176:44:176:46 | tag | +| Test.java:201:34:201:50 | doFinal(...) : byte[] | Test.java:204:26:204:36 | computedTag | nodes | Test.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | Test.java:23:47:23:55 | actualMac | semmle.label | actualMac | -| Test.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| Test.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | -| Test.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| Test.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | -| Test.java:48:47:48:55 | actualMac | semmle.label | actualMac | -| Test.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | -| Test.java:73:44:73:52 | signature | semmle.label | signature | -| Test.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] | -| Test.java:87:44:87:52 | signature | semmle.label | signature | -| Test.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| Test.java:113:49:113:51 | tag | semmle.label | tag | -| Test.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] | -| Test.java:130:44:130:46 | tag | semmle.label | tag | -| Test.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| Test.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| Test.java:148:44:148:54 | array(...) | semmle.label | array(...) | -| Test.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | -| Test.java:162:53:162:55 | tag | semmle.label | tag | -| Test.java:186:26:186:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| Test.java:188:44:188:46 | tag | semmle.label | tag | -| Test.java:221:34:221:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | -| Test.java:224:26:224:36 | computedTag | semmle.label | computedTag | +| Test.java:34:25:34:33 | actualMac : byte[] | semmle.label | actualMac : byte[] | +| Test.java:36:47:36:55 | actualMac | semmle.label | actualMac | +| Test.java:59:32:59:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | +| Test.java:61:44:61:52 | signature | semmle.label | signature | +| Test.java:73:25:73:33 | signature : byte[] | semmle.label | signature : byte[] | +| Test.java:75:44:75:52 | signature | semmle.label | signature | +| Test.java:99:26:99:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:101:49:101:51 | tag | semmle.label | tag | +| Test.java:116:28:116:30 | tag : byte[] | semmle.label | tag : byte[] | +| Test.java:118:44:118:46 | tag | semmle.label | tag | +| Test.java:134:56:134:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| Test.java:136:44:136:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| Test.java:136:44:136:54 | array(...) | semmle.label | array(...) | +| Test.java:148:56:148:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | +| Test.java:150:53:150:55 | tag | semmle.label | tag | +| Test.java:174:26:174:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:176:44:176:46 | tag | semmle.label | tag | +| Test.java:201:34:201:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | +| Test.java:204:26:204:36 | computedTag | semmle.label | computedTag | #select | Test.java:23:47:23:55 | actualMac | Test.java:21:32:21:48 | doFinal(...) : byte[] | Test.java:23:47:23:55 | actualMac | Timing attack against $@ validation. | Test.java:21:32:21:48 | doFinal(...) : byte[] | MAC | -| Test.java:35:70:35:97 | castToObjectArray(...) | Test.java:33:32:33:44 | doFinal(...) : byte[] | Test.java:35:70:35:97 | castToObjectArray(...) | Timing attack against $@ validation. | Test.java:33:32:33:44 | doFinal(...) : byte[] | MAC | -| Test.java:48:47:48:55 | actualMac | Test.java:46:25:46:33 | actualMac : byte[] | Test.java:48:47:48:55 | actualMac | Timing attack against $@ validation. | Test.java:46:25:46:33 | actualMac : byte[] | MAC | -| Test.java:73:44:73:52 | signature | Test.java:71:32:71:44 | sign(...) : byte[] | Test.java:73:44:73:52 | signature | Timing attack against $@ validation. | Test.java:71:32:71:44 | sign(...) : byte[] | signature | -| Test.java:87:44:87:52 | signature | Test.java:85:25:85:33 | signature : byte[] | Test.java:87:44:87:52 | signature | Timing attack against $@ validation. | Test.java:85:25:85:33 | signature : byte[] | signature | -| Test.java:113:49:113:51 | tag | Test.java:111:26:111:45 | doFinal(...) : byte[] | Test.java:113:49:113:51 | tag | Timing attack against $@ validation. | Test.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext | -| Test.java:130:44:130:46 | tag | Test.java:128:28:128:30 | tag : byte[] | Test.java:130:44:130:46 | tag | Timing attack against $@ validation. | Test.java:128:28:128:30 | tag : byte[] | ciphertext | -| Test.java:148:44:148:54 | array(...) | Test.java:146:56:146:58 | tag : ByteBuffer | Test.java:148:44:148:54 | array(...) | Timing attack against $@ validation. | Test.java:146:56:146:58 | tag : ByteBuffer | ciphertext | -| Test.java:162:53:162:55 | tag | Test.java:160:56:160:58 | tag : ByteBuffer | Test.java:162:53:162:55 | tag | Timing attack against $@ validation. | Test.java:160:56:160:58 | tag : ByteBuffer | ciphertext | -| Test.java:188:44:188:46 | tag | Test.java:186:26:186:50 | doFinal(...) : byte[] | Test.java:188:44:188:46 | tag | Timing attack against $@ validation. | Test.java:186:26:186:50 | doFinal(...) : byte[] | ciphertext | +| Test.java:36:47:36:55 | actualMac | Test.java:34:25:34:33 | actualMac : byte[] | Test.java:36:47:36:55 | actualMac | Timing attack against $@ validation. | Test.java:34:25:34:33 | actualMac : byte[] | MAC | +| Test.java:61:44:61:52 | signature | Test.java:59:32:59:44 | sign(...) : byte[] | Test.java:61:44:61:52 | signature | Timing attack against $@ validation. | Test.java:59:32:59:44 | sign(...) : byte[] | signature | +| Test.java:75:44:75:52 | signature | Test.java:73:25:73:33 | signature : byte[] | Test.java:75:44:75:52 | signature | Timing attack against $@ validation. | Test.java:73:25:73:33 | signature : byte[] | signature | +| Test.java:101:49:101:51 | tag | Test.java:99:26:99:45 | doFinal(...) : byte[] | Test.java:101:49:101:51 | tag | Timing attack against $@ validation. | Test.java:99:26:99:45 | doFinal(...) : byte[] | ciphertext | +| Test.java:118:44:118:46 | tag | Test.java:116:28:116:30 | tag : byte[] | Test.java:118:44:118:46 | tag | Timing attack against $@ validation. | Test.java:116:28:116:30 | tag : byte[] | ciphertext | +| Test.java:136:44:136:54 | array(...) | Test.java:134:56:134:58 | tag : ByteBuffer | Test.java:136:44:136:54 | array(...) | Timing attack against $@ validation. | Test.java:134:56:134:58 | tag : ByteBuffer | ciphertext | +| Test.java:150:53:150:55 | tag | Test.java:148:56:148:58 | tag : ByteBuffer | Test.java:150:53:150:55 | tag | Timing attack against $@ validation. | Test.java:148:56:148:58 | tag : ByteBuffer | ciphertext | +| Test.java:176:44:176:46 | tag | Test.java:174:26:174:50 | doFinal(...) : byte[] | Test.java:176:44:176:46 | tag | Timing attack against $@ validation. | Test.java:174:26:174:50 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java index 739f273f7ec..0755f1fe668 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java +++ b/java/ql/test/experimental/query-tests/security/CWE-208/TimingAttackAgainstSignagure/Test.java @@ -24,18 +24,6 @@ public class Test { } } - // BAD: compare MACs using a non-constant-time method - public boolean unsafeMacCheckWithArraysDeepEquals(Socket socket) throws Exception { - try (InputStream is = socket.getInputStream()) { - Mac mac = Mac.getInstance("HmacSHA256"); - byte[] data = socket.getInputStream().readAllBytes(); - mac.update(data); - byte[] actualMac = mac.doFinal(); - byte[] expectedMac = is.readNBytes(32); - return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); - } - } - // BAD: compare MACs using a non-constant-time method public boolean unsafeMacCheckWithDoFinalWithOutputArray(Socket socket) throws Exception { try (InputStream is = socket.getInputStream()) { @@ -200,14 +188,6 @@ public class Test { } } - private static Object[] castToObjectArray(byte[] array) { - Object[] result = new Object[array.length]; - for (int i = 0; i < array.length; i++) { - result[i] = array[i]; - } - return result; - } - // BAD: compare MAC using a non-constant-time loop public boolean unsafeMacCheckWithLoop(Socket socket) throws Exception { try (InputStream is = socket.getInputStream()) { From bd7e7b1371cbb957750f8e3534c84a3e72fb4fe7 Mon Sep 17 00:00:00 2001 From: Fosstars Date: Sun, 1 Aug 2021 10:18:37 +0200 Subject: [PATCH 29/31] Better qldoc for timing attacks --- .../CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll index d0e270d265f..4c35b8e2940 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll +++ b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignatureQuery.qll @@ -12,10 +12,10 @@ import semmle.code.java.dataflow.FlowSources abstract private class ProduceCryptoCall extends MethodAccess { Expr output; - /** Return the result of cryptographic operation. */ + /** Gets the result of cryptographic operation. */ Expr output() { result = output } - /** Return a type of the result of cryptographic operation such as MAC, signature or ciphertext. */ + /** Gets a type of cryptographic operation such as MAC, signature or ciphertext. */ abstract string getResultType(); } @@ -186,6 +186,7 @@ class CryptoOperationSource extends DataFlow::Node { ) } + /** Gets a method call that produces cryptographic result. */ ProduceCryptoCall getCall() { result = call } } @@ -198,7 +199,7 @@ private class NonConstantTimeEqualsCall extends MethodAccess { } } -/** Static methods that use a non-constant-time algorithm for comparing inputs. */ +/** A static method that uses a non-constant-time algorithm for comparing inputs. */ private class NonConstantTimeComparisonCall extends StaticMethodAccess { NonConstantTimeComparisonCall() { getMethod().hasQualifiedName("java.util", "Arrays", ["equals", "deepEquals"]) or From b913928294a92e701e9d51de95808075f9f8a1d9 Mon Sep 17 00:00:00 2001 From: Fosstars Date: Wed, 4 Aug 2021 17:54:16 +0200 Subject: [PATCH 30/31] Renamed queries and merged qhelp files --- .../NonConstantTimeCheckOnSignature.qhelp | 30 ----------------- ...nConstantTimeCheckRecommendation.inc.qhelp | 10 ------ .../NonConstantTimeCheckReferences.inc.qhelp | 21 ------------ ...PossibleTimingAttackAgainstSignature.qhelp | 4 +++ ...> PossibleTimingAttackAgainstSignature.ql} | 6 ++-- .../CWE/CWE-208/SafeMacComparison.java | 5 ++- .../SafeMacComparisonWithRemoteInputs.java | 9 ------ .../TimingAttackAgainstSignature.qhelp | 32 ++++++++++++++++--- .../CWE/CWE-208/UnsafeMacComparison.java | 5 ++- .../UnsafeMacComparisonWithRemoteInputs.java | 9 ------ 10 files changed, 43 insertions(+), 88 deletions(-) delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp create mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.qhelp rename java/ql/src/experimental/Security/CWE/CWE-208/{NonConstantTimeCheckOnSignature.ql => PossibleTimingAttackAgainstSignature.ql} (78%) delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java delete mode 100644 java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp deleted file mode 100644 index 46c692ff862..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.qhelp +++ /dev/null @@ -1,30 +0,0 @@ - - - - -

    -A constant-time algorithm should be used for checking a MAC or a digital signature. -In other words, the comparison time should not depend on the content of the input. -Otherwise, an attacker may be able to implement a timing attack. -A successful attack may uncover a valid signature that in turn can result in authentication bypass. -

    -
    - - - - -

    -The following example uses Arrays.equals() method for validating a MAC. -This method implements a non-constant-time algorithm: -

    - - -

    -The next example uses a safe constant-time algorithm for validating a MAC: -

    - -
    - - - -
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp deleted file mode 100644 index 3a842f45206..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckRecommendation.inc.qhelp +++ /dev/null @@ -1,10 +0,0 @@ - - - -

    -Use MessageDigest.isEqual() method to check MACs and signatures. -If this method is used, then the calculation time depends only on the length of input byte arrays, -and does not depend on the contents of the arrays. -

    -
    -
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp deleted file mode 100644 index 68797c021d7..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckReferences.inc.qhelp +++ /dev/null @@ -1,21 +0,0 @@ - - - -
  • - Wikipedia: - Timing attack. -
  • -
  • - Coursera: - Timing attacks on MAC verification -
  • -
  • - NCC Group: - Time Trial: Racing Towards Practical Remote Timing Attacks -
  • -
  • - Java API Specification: - MessageDigest.isEqual() method -
  • -
    -
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.qhelp new file mode 100644 index 00000000000..aee0196686f --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.qhelp @@ -0,0 +1,4 @@ + + + + diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql b/java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.ql similarity index 78% rename from java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql rename to java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.ql index a4ac9655aed..9e0835e2aac 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql +++ b/java/ql/src/experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.ql @@ -1,5 +1,5 @@ /** - * @name Using a non-constant-time algorithm for checking a signature + * @name Possible timing attack against signature validation * @description When checking a signature over a message, a constant-time algorithm should be used. * Otherwise, there is a risk of a timing attack that allows an attacker * to forge a valid signature for an arbitrary message. For a successful attack, @@ -7,7 +7,7 @@ * @kind path-problem * @problem.severity warning * @precision medium - * @id java/non-constant-time-in-signature-check + * @id java/possible-timing-attack-against-signature * @tags security * external/cwe/cwe-208 */ @@ -18,5 +18,5 @@ import DataFlow::PathGraph from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoComparisonConfig conf where conf.hasFlowPath(source, sink) -select sink.getNode(), source, sink, "Using a non-constant-time method for checking a $@.", source, +select sink.getNode(), source, sink, "Possible timing attack against $@ validation.", source, source.getNode().(CryptoOperationSource).getCall().getResultType() diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java index 4e27f07caa1..fbdb6131a5c 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java +++ b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparison.java @@ -1,4 +1,7 @@ -public boolean check(byte[] signature, byte[] message, SecretKey key) throws Exception { +public boolean validate(HttpRequest request, SecretKey key) throws Exception { + byte[] message = getMessageFrom(request); + byte[] signature = getSignatureFrom(request); + Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); byte[] actual = mac.doFinal(message); diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java b/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java deleted file mode 100644 index fbdb6131a5c..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/SafeMacComparisonWithRemoteInputs.java +++ /dev/null @@ -1,9 +0,0 @@ -public boolean validate(HttpRequest request, SecretKey key) throws Exception { - byte[] message = getMessageFrom(request); - byte[] signature = getSignatureFrom(request); - - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); - byte[] actual = mac.doFinal(message); - return MessageDigest.isEqual(signature, actual); -} \ No newline at end of file diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp index 2daefdd0181..7815312414a 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp +++ b/java/ql/src/experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.qhelp @@ -11,7 +11,13 @@ both the message and the signature. A successful attack can result in authentica

    - + +

    +Use MessageDigest.isEqual() method to check MACs and signatures. +If this method is used, then the calculation time depends only on the length of input byte arrays, +and does not depend on the contents of the arrays. +

    +

    @@ -19,13 +25,31 @@ The following example uses Arrays.equals() method for validating a This method implements a non-constant-time algorithm. Both the message and the signature come from an untrusted HTTP request:

    - +

    The next example uses a safe constant-time algorithm for validating a MAC:

    - +
    - + +
  • + Wikipedia: + Timing attack. +
  • +
  • + Coursera: + Timing attacks on MAC verification +
  • +
  • + NCC Group: + Time Trial: Racing Towards Practical Remote Timing Attacks +
  • +
  • + Java API Specification: + MessageDigest.isEqual() method +
  • +
    + diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java index b0513329e6a..1785ff2e7c6 100644 --- a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java +++ b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparison.java @@ -1,4 +1,7 @@ -public boolean check(byte[] signature, byte[] message, SecretKey key) throws Exception { +public boolean validate(HttpRequest request, SecretKey key) throws Exception { + byte[] message = getMessageFrom(request); + byte[] signature = getSignatureFrom(request); + Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); byte[] actual = mac.doFinal(message); diff --git a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java b/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java deleted file mode 100644 index 1785ff2e7c6..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-208/UnsafeMacComparisonWithRemoteInputs.java +++ /dev/null @@ -1,9 +0,0 @@ -public boolean validate(HttpRequest request, SecretKey key) throws Exception { - byte[] message = getMessageFrom(request); - byte[] signature = getSignatureFrom(request); - - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(key.getEncoded(), "HmacSHA256")); - byte[] actual = mac.doFinal(message); - return Arrays.equals(signature, actual); -} \ No newline at end of file From 171dc26531ca669f903a3d2d656b3584ecff1c49 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 9 Aug 2021 13:56:55 +0100 Subject: [PATCH 31/31] Fix test reference and expectations --- .../CWE-208/NotConstantTimeCheckOnSignature/Test.expected | 6 +++--- .../CWE-208/NotConstantTimeCheckOnSignature/Test.qlref | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected index 4ef23d754fe..b6d3211a64e 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.expected @@ -10,6 +10,6 @@ nodes | Test.java:47:22:47:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | Test.java:48:40:48:42 | tag | semmle.label | tag | #select -| Test.java:15:43:15:51 | actualMac | Test.java:14:28:14:44 | doFinal(...) : byte[] | Test.java:15:43:15:51 | actualMac | Using a non-constant-time method for checking a $@. | Test.java:14:28:14:44 | doFinal(...) : byte[] | MAC | -| Test.java:31:40:31:48 | signature | Test.java:30:28:30:40 | sign(...) : byte[] | Test.java:31:40:31:48 | signature | Using a non-constant-time method for checking a $@. | Test.java:30:28:30:40 | sign(...) : byte[] | signature | -| Test.java:48:40:48:42 | tag | Test.java:47:22:47:46 | doFinal(...) : byte[] | Test.java:48:40:48:42 | tag | Using a non-constant-time method for checking a $@. | Test.java:47:22:47:46 | doFinal(...) : byte[] | ciphertext | +| Test.java:15:43:15:51 | actualMac | Test.java:14:28:14:44 | doFinal(...) : byte[] | Test.java:15:43:15:51 | actualMac | Possible timing attack against $@ validation. | Test.java:14:28:14:44 | doFinal(...) : byte[] | MAC | +| Test.java:31:40:31:48 | signature | Test.java:30:28:30:40 | sign(...) : byte[] | Test.java:31:40:31:48 | signature | Possible timing attack against $@ validation. | Test.java:30:28:30:40 | sign(...) : byte[] | signature | +| Test.java:48:40:48:42 | tag | Test.java:47:22:47:46 | doFinal(...) : byte[] | Test.java:48:40:48:42 | tag | Possible timing attack against $@ validation. | Test.java:47:22:47:46 | doFinal(...) : byte[] | ciphertext | diff --git a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.qlref b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.qlref index e18bd8beaa7..3a113b0fd71 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.qlref +++ b/java/ql/test/experimental/query-tests/security/CWE-208/NotConstantTimeCheckOnSignature/Test.qlref @@ -1 +1 @@ -experimental/Security/CWE/CWE-208/NonConstantTimeCheckOnSignature.ql \ No newline at end of file +experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.ql