blob: cd2995899bfabbbf3bf358c4b04ddc9f60421804 [file] [log] [blame]
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package com.google.crypto.tink.subtle;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.crypto.tink.PublicKeySign;
import com.google.crypto.tink.PublicKeyVerify;
import com.google.crypto.tink.subtle.EllipticCurves.EcdsaEncoding;
import com.google.crypto.tink.subtle.Enums.HashType;
import com.google.crypto.tink.testing.TestUtil;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Test for thread safety of {@code Signature}-primitives. This test covers signers.
*
* <p>If possible then this unit test should be run using a thread sanitizer. Otherwise only race
* conditions that actually happend during the test will be detected.
*/
@RunWith(JUnit4.class)
public class SignatureThreadSafetyTest {
/**
* Exception handler for uncaught exceptions in a thread.
*
* <p>TODO(bleichen): Surely there must be a better way to catch exceptions in threads in unit
* tests. junit ought to do this. However, at least for some setups, tests can pass despite
* uncaught exceptions in threads.
*
* <p>TODO(bleichen): Nonce reuse of non-deterministic signatures.
*
* <p>TODO(bleichen): Overwriting nonces in deterministic signature schemes.
*/
public static class ExceptionHandler implements Thread.UncaughtExceptionHandler {
private Throwable firstException = null;
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (firstException == null) {
firstException = ex;
}
}
public void check() throws Exception {
if (firstException != null) {
throw new Exception("Thread failed", firstException);
}
}
}
/** A thread that signs the same message multiple times. */
public static class SigningThread extends Thread {
private PublicKeySign signer;
private Set<String> signatures;
private byte[] message;
private int count;
/**
* Constructs a thread that generates a number of signatures of a given message.
*
* @param signer an instance that is signing messages.
* @param signatures a set to which the generated signatures are added. This must be a
* synchronized set if it is shared among multiple threads.
* @param message the number message to be signed.
* @param count the number of signatures that are generated.
*/
SigningThread(PublicKeySign signer, Set<String> signatures, byte[] message, int count) {
this.signer = signer;
this.signatures = signatures;
this.message = message;
this.count = count;
}
/**
* Read the plaintext from the channel. This implementation assumes that the channel is blocking
* and throws an AssertionError if an attempt to read plaintext from the channel is incomplete.
*/
@Override
public void run() {
try {
for (int i = 0; i < count; i++) {
byte[] signature = signer.sign(message);
signatures.add(TestUtil.hexEncode(signature));
}
} catch (Exception ex) {
getUncaughtExceptionHandler().uncaughtException(this, ex);
}
}
}
/**
* Sign distinct messages concurrently in multiple threads. The purpose of this test is to find
* deterministic signatures schemes, that share state to compute nonces.
*/
public void testSigningDistinctMessages(
PublicKeySign signer,
PublicKeyVerify verifier,
boolean isDeterministic,
int maxMessageSize,
int numberOfThreads,
int numberOfSignatures)
throws Exception {
ExceptionHandler exceptionHandler = new ExceptionHandler();
Thread[] thread = new Thread[numberOfThreads];
ArrayList<HashSet<String>> signatures = new ArrayList<HashSet<String>>();
byte[][] messages = new byte[numberOfThreads][];
for (int i = 0; i < numberOfThreads; i++) {
// Just an arbitrary prime to get the plaintext sizes.
int p = 28657;
int size = i * p % (maxMessageSize + 1);
messages[i] = new byte[size];
HashSet<String> sigs = new HashSet<String>();
signatures.add(sigs);
thread[i] = new SigningThread(signer, sigs, messages[i], numberOfSignatures);
thread[i].setUncaughtExceptionHandler(exceptionHandler);
}
for (int i = 0; i < numberOfThreads; i++) {
thread[i].start();
}
for (int i = 0; i < numberOfThreads; i++) {
thread[i].join();
}
exceptionHandler.check();
if (isDeterministic) {
for (int i = 0; i < numberOfThreads; i++) {
String expectedSignature = TestUtil.hexEncode(signer.sign(messages[i]));
assertEquals(1, signatures.get(i).size());
assertTrue(signatures.get(i).contains(expectedSignature));
}
} else {
for (int i = 0; i < numberOfThreads; i++) {
assertEquals(numberOfSignatures, signatures.get(i).size());
for (String sig : signatures.get(i)) {
verifier.verify(TestUtil.hexDecode(sig), messages[i]);
}
}
}
}
/**
* Sign the same message concurrently in multiple threads. The purpose of this test is to find
* non-deterministic signatures schemes, that reuse nonces when signing concurrently.
*/
public void testSigningSameMessage(
PublicKeySign signer,
PublicKeyVerify verifier,
boolean isDeterministic,
byte[] message,
int numberOfThreads,
int numberOfSignatures)
throws Exception {
ExceptionHandler exceptionHandler = new ExceptionHandler();
Thread[] thread = new Thread[numberOfThreads];
ArrayList<HashSet<String>> signatures = new ArrayList<HashSet<String>>();
for (int i = 0; i < numberOfThreads; i++) {
HashSet<String> sigs = new HashSet<String>();
signatures.add(sigs);
thread[i] = new SigningThread(signer, sigs, message, numberOfSignatures);
thread[i].setUncaughtExceptionHandler(exceptionHandler);
}
for (int i = 0; i < numberOfThreads; i++) {
thread[i].start();
}
for (int i = 0; i < numberOfThreads; i++) {
thread[i].join();
}
exceptionHandler.check();
if (isDeterministic) {
String expectedSignature = TestUtil.hexEncode(signer.sign(message));
for (int i = 0; i < numberOfThreads; i++) {
assertEquals(1, signatures.get(i).size());
assertTrue(signatures.get(i).contains(expectedSignature));
}
} else {
HashSet<String> allSignatures = new HashSet<String>();
for (int i = 0; i < numberOfThreads; i++) {
for (String sig : signatures.get(i)) {
verifier.verify(TestUtil.hexDecode(sig), message);
assertFalse(allSignatures.contains(sig));
allSignatures.add(sig);
}
}
assertEquals(numberOfThreads * numberOfSignatures, allSignatures.size());
}
}
@Test
public void testEcdsa() throws Exception {
ECParameterSpec ecParams = EllipticCurves.getNistP256Params();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(ecParams);
KeyPair keyPair = keyGen.generateKeyPair();
ECPublicKey pub = (ECPublicKey) keyPair.getPublic();
ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate();
EcdsaSignJce signer = new EcdsaSignJce(priv, HashType.SHA256, EcdsaEncoding.DER);
EcdsaVerifyJce verifier = new EcdsaVerifyJce(pub, HashType.SHA256, EcdsaEncoding.DER);
byte[] msg = Random.randBytes(20);
testSigningSameMessage(signer, verifier, false, msg, 5, 20);
testSigningDistinctMessages(signer, verifier, false, 64, 5, 20);
}
@Test
public void testEddsa() throws Exception {
Ed25519Sign.KeyPair keyPair = Ed25519Sign.KeyPair.newKeyPair();
Ed25519Sign signer = new Ed25519Sign(keyPair.getPrivateKey());
Ed25519Verify verifier = new Ed25519Verify(keyPair.getPublicKey());
byte[] msg = Random.randBytes(20);
testSigningSameMessage(signer, verifier, true, msg, 5, 20);
testSigningDistinctMessages(signer, verifier, true, 64, 5, 20);
}
}