Narrow NonConstantTimeCryptoComparison.ql to timing attack on signatures and MACs only

This commit is contained in:
Artem Smotrakov
2021-07-04 15:27:44 +02:00
committed by Fosstars
parent c359852608
commit 8b557765b3
4 changed files with 218 additions and 168 deletions

View File

@@ -3,16 +3,16 @@
<overview> <overview>
<p> <p>
When comparing results of cryptographic operations, such as MAC or digital signature, A constant-time algorithm should be used for checking a MAC or a digital signature.
a constant-time algorithm should be used. In other words, the comparison time should not depend on In other words, the comparison time should not depend on the content of the input.
the content of the input. Otherwise, attackers may be able to implement a timing attack Otherwise, attackers may be able to implement a timing attack if they control inputs.
if they can control input. A successful timing attack may result in leaking secrets or authentication bypass. A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass.
</p> </p>
</overview> </overview>
<recommendation> <recommendation>
<p> <p>
Use <code>MessageDigest.isEqual()</code> method to compare results of cryptographic operations. Use <code>MessageDigest.isEqual()</code> method to check MACs and signatures.
If this method is used, then the calculation time depends only on the length of input byte arrays, 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. and does not depend on the contents of the arrays.
</p> </p>

View File

@@ -1,12 +1,12 @@
/** /**
* @name Using a non-constant-time algorithm for comparing results of a cryptographic operation * @name Using a non-constant-time algorithm for comparing MAC or signature
* @description When comparing results of a cryptographic operation, a constant-time algorithm should be used. * @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 can control input. * Otherwise, attackers may be able to implement a timing attack if they control inputs.
* A successful attack may result in leaking secrets or authentication bypass. * A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass.
* @kind path-problem * @kind path-problem
* @problem.severity warning * @problem.severity warning
* @precision high * @precision high
* @id java/non-constant-time-crypto-comparison * @id java/non-constant-time-in-signature-check
* @tags security * @tags security
* external/cwe/cwe-208 * external/cwe/cwe-208
*/ */
@@ -25,6 +25,9 @@ abstract private class ProduceCryptoCall extends MethodAccess {
/** Return the result of cryptographic operation. */ /** Return the result of cryptographic operation. */
Expr output() { result = output } 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. */ /** 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 getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output
) )
} }
override string getResultType() { result = "MAC" }
} }
/** A method call that produces a signature. */ /** 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 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())) 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. */ /** 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. */ /** A source that produces result of cryptographic operation. */
private class CryptoOperationSource extends DataFlow::Node { private class CryptoOperationSource extends DataFlow::Node {
Expr cryptoOperation; ProduceCryptoCall call;
CryptoOperationSource() { CryptoOperationSource() { call.output() = this.asExpr() }
exists(ProduceCryptoCall call | call.output() = this.asExpr() |
cryptoOperation = call.getQualifier()
)
}
/** Holds if remote user input was used in the cryptographic operation. */ /** Holds if remote user input was used in the cryptographic operation. */
predicate includesUserInput() { predicate includesUserInput() {
@@ -188,9 +193,11 @@ private class CryptoOperationSource extends DataFlow::Node {
| |
config.hasFlowPath(source, sink) 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. */ /** Methods that use a non-constant-time algorithm for comparing inputs. */
@@ -329,8 +336,8 @@ from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoCo
where where
conf.hasFlowPath(source, sink) and conf.hasFlowPath(source, sink) and
( (
source.getNode().(CryptoOperationSource).includesUserInput() or source.getNode().(CryptoOperationSource).includesUserInput() and
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput() sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
) )
select sink.getNode(), source, sink, select sink.getNode(), source, sink, "Using a non-constant-time method for cheching a $@.", source,
"Using a non-constant-time algorithm for comparing results of a cryptographic operation." source.getNode().(CryptoOperationSource).getCall().getResultType()

View File

@@ -1,50 +1,50 @@
edges edges
| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac |
| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | | NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) |
| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac |
| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature |
| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature |
| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag |
| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag |
| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | | NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) |
| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag |
| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag |
| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | | NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag |
nodes nodes
| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | semmle.label | actualMac | | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | semmle.label | actualMac |
| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) | | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) |
| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | semmle.label | actualMac : byte[] | | NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | semmle.label | actualMac : byte[] | | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | semmle.label | actualMac | | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | semmle.label | actualMac |
| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] | | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] |
| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | semmle.label | signature | | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | semmle.label | signature |
| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | semmle.label | signature : byte[] | | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] |
| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | semmle.label | signature | | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | semmle.label | signature |
| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | semmle.label | tag | | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | semmle.label | tag : byte[] | | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] |
| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | semmle.label | tag | | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | | NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | semmle.label | array(...) | | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | semmle.label | array(...) |
| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer | | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | semmle.label | tag | | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | semmle.label | tag | | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] | | NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | semmle.label | computedTag | | NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag | semmle.label | computedTag |
#select #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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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: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 |

View File

@@ -13,74 +13,98 @@ import javax.crypto.Mac;
public class NonConstantTimeCryptoComparison { 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 { public boolean unsafeMacCheckWithArrayEquals(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024]; byte[] data = new byte[1024];
socket.getInputStream().read(data); is.read(data);
byte[] actualMac = mac.doFinal(data); byte[] actualMac = mac.doFinal(data);
byte[] expectedMac = is.readNBytes(32);
return Arrays.equals(expectedMac, actualMac); 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 { public boolean unsafeMacCheckWithArraysDeepEquals(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = socket.getInputStream().readAllBytes(); byte[] data = socket.getInputStream().readAllBytes();
mac.update(data); mac.update(data);
byte[] actualMac = mac.doFinal(); byte[] actualMac = mac.doFinal();
byte[] expectedMac = is.readNBytes(32);
return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac)); 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 { public boolean unsafeMacCheckWithDoFinalWithOutputArray(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] data = is.readNBytes(100);
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
byte[] actualMac = new byte[256]; byte[] actualMac = new byte[256];
mac.update(data);
mac.doFinal(actualMac, 0); mac.doFinal(actualMac, 0);
byte[] expectedMac = socket.getInputStream().readNBytes(256); byte[] expectedMac = socket.getInputStream().readNBytes(256);
return Arrays.equals(expectedMac, actualMac); 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 { public boolean saferMacCheck(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024]; byte[] data = new byte[1024];
socket.getInputStream().read(data); is.read(data);
byte[] actualMac = mac.doFinal(data); byte[] actualMac = mac.doFinal(data);
byte[] expectedMac = is.readNBytes(32);
return MessageDigest.isEqual(expectedMac, actualMac); 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 { public boolean unsafeCheckSignatures(Socket socket, PrivateKey key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Signature engine = Signature.getInstance("SHA256withRSA"); Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key); engine.initSign(key);
byte[] data = socket.getInputStream().readAllBytes(); byte[] data = socket.getInputStream().readAllBytes();
engine.update(data); engine.update(data);
byte[] signature = engine.sign(); byte[] signature = engine.sign();
byte[] expected = is.readNBytes(256);
return Arrays.equals(expected, signature); 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 { public boolean unsafeCheckSignaturesWithOutputArray(Socket socket, PrivateKey key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Signature engine = Signature.getInstance("SHA256withRSA"); Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key); engine.initSign(key);
byte[] data = socket.getInputStream().readAllBytes(); byte[] data = socket.getInputStream().readAllBytes();
engine.update(data); engine.update(data);
byte[] signature = new byte[1024]; byte[] signature = new byte[1024];
engine.sign(signature, 0, 1024); engine.sign(signature, 0, 1024);
byte[] expected = is.readNBytes(256);
return Arrays.equals(expected, signature); 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 { public boolean saferCheckSignatures(Socket socket, PrivateKey key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Signature engine = Signature.getInstance("SHA256withRSA"); Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key); engine.initSign(key);
byte[] data = socket.getInputStream().readAllBytes(); byte[] data = socket.getInputStream().readAllBytes();
engine.update(data); engine.update(data);
byte[] signature = engine.sign(); byte[] signature = engine.sign();
byte[] expected = is.readNBytes(256);
return MessageDigest.isEqual(expected, signature); return MessageDigest.isEqual(expected, signature);
} }
}
// BAD: compare ciphertexts (custom MAC) 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 { 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); byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key); cipher.init(Cipher.ENCRYPT_MODE, key);
@@ -88,9 +112,11 @@ public class NonConstantTimeCryptoComparison {
byte[] expected = socket.getInputStream().readAllBytes(); byte[] expected = socket.getInputStream().readAllBytes();
return Objects.deepEquals(expected, tag); return Objects.deepEquals(expected, tag);
} }
}
// BAD: compare ciphertexts (custom MAC) 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 { public boolean unsafeCheckCiphertextWithOutputArray(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] plaintext = socket.getInputStream().readAllBytes(); byte[] plaintext = socket.getInputStream().readAllBytes();
MessageDigest md = MessageDigest.getInstance("SHA-512"); MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(plaintext); md.update(plaintext);
@@ -100,11 +126,15 @@ public class NonConstantTimeCryptoComparison {
cipher.update(hash); cipher.update(hash);
byte[] tag = new byte[1024]; byte[] tag = new byte[1024];
cipher.doFinal(tag, 0); cipher.doFinal(tag, 0);
byte[] expected = is.readNBytes(32);
return Arrays.equals(expected, tag); return Arrays.equals(expected, tag);
} }
}
// BAD: compare ciphertexts (custom MAC) 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 { 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"); MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(plaintext); md.update(plaintext);
byte[] hash = new byte[1024]; byte[] hash = new byte[1024];
@@ -117,20 +147,26 @@ public class NonConstantTimeCryptoComparison {
byte[] expected = socket.getInputStream().readNBytes(1024); byte[] expected = socket.getInputStream().readNBytes(1024);
return Arrays.equals(expected, tag.array()); return Arrays.equals(expected, tag.array());
} }
}
// BAD: compare ciphertexts (custom MAC) 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 { public boolean unsafeCheckCiphertextWithByteBufferEquals(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key); cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plaintext = socket.getInputStream().readAllBytes(); byte[] plaintext = socket.getInputStream().readAllBytes();
cipher.update(plaintext); cipher.update(plaintext);
ByteBuffer tag = ByteBuffer.wrap(new byte[1024]); ByteBuffer tag = ByteBuffer.wrap(new byte[1024]);
cipher.doFinal(ByteBuffer.wrap(plaintext), tag); cipher.doFinal(ByteBuffer.wrap(plaintext), tag);
byte[] expected = is.readNBytes(32);
return ByteBuffer.wrap(expected).equals(tag); return ByteBuffer.wrap(expected).equals(tag);
} }
}
// GOOD: compare ciphertexts (custom MAC) 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 { 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 cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key); cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext); byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext);
@@ -138,23 +174,30 @@ public class NonConstantTimeCryptoComparison {
byte[] expected = socket.getInputStream().readAllBytes(); byte[] expected = socket.getInputStream().readAllBytes();
return MessageDigest.isEqual(expected, tag); 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 { 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 cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key); cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] tag = cipher.doFinal(plaintext); byte[] tag = cipher.doFinal(plaintext);
byte[] expected = is.readNBytes(32);
return Arrays.equals(expected, tag); 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 { public boolean compareMacWithConstant(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024]; byte[] data = new byte[1024];
socket.getInputStream().read(data); socket.getInputStream().read(data);
byte[] actualMac = mac.doFinal(data); byte[] actualMac = mac.doFinal(data);
return "constant".equals(new String(actualMac)); return "constant".equals(new String(actualMac));
} }
}
private static Object[] castToObjectArray(byte[] array) { private static Object[] castToObjectArray(byte[] array) {
Object[] result = new Object[array.length]; Object[] result = new Object[array.length];