1 // Copyright 2017 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.subtle; 18 19 import com.google.crypto.tink.DeterministicAead; 20 import com.google.crypto.tink.testing.TestUtil; 21 import java.security.GeneralSecurityException; 22 import org.junit.Test; 23 import org.junit.runner.RunWith; 24 import org.junit.runners.JUnit4; 25 26 /** 27 * Test for thread safety of {@code DeterministicAead}-primitives. 28 * 29 * <p>If possible then this unit test should be run using a thread sanitizer. Otherwise only race 30 * conditions that actually happend during the test will be detected. 31 */ 32 @RunWith(JUnit4.class) 33 public class DaeadThreadSafetyTest { 34 35 /** 36 * Exception handler for uncaught exceptions in a thread. 37 * 38 * <p>TODO(bleichen): Surely there must be a better way to catch exceptions in threads in unit 39 * tests. junit ought to do this. However, at least for some setups, tests can pass despite 40 * uncaught exceptions in threads. 41 */ 42 public static class ExceptionHandler implements Thread.UncaughtExceptionHandler { 43 44 private Throwable firstException = null; 45 46 @Override uncaughtException(Thread thread, Throwable ex)47 public void uncaughtException(Thread thread, Throwable ex) { 48 if (firstException == null) { 49 firstException = ex; 50 } 51 } 52 check()53 public void check() throws Exception { 54 if (firstException != null) { 55 throw new Exception("Thread failed", firstException); 56 } 57 } 58 } 59 60 /** A thread that encrypts and decrypts random plaintexts. */ 61 public static class CryptingThread extends Thread { 62 private DeterministicAead cipher; 63 private int maxPlaintextSize; 64 private int count; 65 66 /** 67 * Constructs a thread that encrypts and decrypts a number of plaintexts. 68 * 69 * @param maxPlaintextSize the maximal size of a plaintext 70 * @param count the number of encryptions and decryptions done in the test 71 */ CryptingThread(DeterministicAead cipher, int maxPlaintextSize, int count)72 CryptingThread(DeterministicAead cipher, int maxPlaintextSize, int count) { 73 this.cipher = cipher; 74 this.maxPlaintextSize = maxPlaintextSize; 75 this.count = count; 76 } 77 78 /** 79 * Read the plaintext from the channel. This implementation assumes that the channel is blocking 80 * and throws an AssertionError if an attempt to read plaintext from the channel is incomplete. 81 */ 82 @Override run()83 public void run() { 84 try { 85 // Just an arbitrary prime to get the plaintext sizes. 86 int p = 28657; 87 for (int i = 0; i < count; i++) { 88 // All sizes are used once when count > maxPlaintextSize. 89 int size = i * p % (maxPlaintextSize + 1); 90 int aadSize = (i / 2) * p % (maxPlaintextSize + 1); 91 byte[] plaintext = new byte[size]; 92 byte[] aad = new byte[aadSize]; 93 byte[] ciphertext = cipher.encryptDeterministically(plaintext, aad); 94 byte[] ciphertext2 = cipher.encryptDeterministically(plaintext, aad); 95 TestUtil.assertByteArrayEquals("Encryption not deterministic", ciphertext, ciphertext2); 96 byte[] decrypted = cipher.decryptDeterministically(ciphertext, aad); 97 TestUtil.assertByteArrayEquals("Incorrect decryption", plaintext, decrypted); 98 } 99 } catch (Exception ex) { 100 getUncaughtExceptionHandler().uncaughtException(this, ex); 101 } 102 } 103 } 104 105 /** Encrypt and decrypt concurrently with one DeterministicAead cipher. */ testEncryptionDecryption( DeterministicAead cipher, int numberOfThreads, int maxPlaintextSize, int numberOfEncryptionsPerThread)106 public void testEncryptionDecryption( 107 DeterministicAead cipher, 108 int numberOfThreads, 109 int maxPlaintextSize, 110 int numberOfEncryptionsPerThread) 111 throws Exception { 112 ExceptionHandler exceptionHandler = new ExceptionHandler(); 113 Thread[] thread = new Thread[numberOfThreads]; 114 for (int i = 0; i < numberOfThreads; i++) { 115 thread[i] = new CryptingThread(cipher, maxPlaintextSize, numberOfEncryptionsPerThread); 116 thread[i].setUncaughtExceptionHandler(exceptionHandler); 117 } 118 for (int i = 0; i < numberOfThreads; i++) { 119 thread[i].start(); 120 } 121 for (int i = 0; i < numberOfThreads; i++) { 122 thread[i].join(); 123 } 124 exceptionHandler.check(); 125 } 126 127 @Test testAesSiv192()128 public void testAesSiv192() throws Exception { 129 byte[] key = Random.randBytes(48); 130 AesSiv siv; 131 try { 132 siv = new AesSiv(key); 133 } catch (GeneralSecurityException ex) { 134 System.out.println( 135 "Skipping test: AES-SIV with 192 bit AES keys is not supported: " + ex.toString()); 136 return; 137 } 138 testEncryptionDecryption(siv, 5, 128, 20); 139 } 140 141 @Test testAesSiv256()142 public void testAesSiv256() throws Exception { 143 byte[] key = Random.randBytes(64); 144 AesSiv siv; 145 try { 146 siv = new AesSiv(key); 147 } catch (GeneralSecurityException ex) { 148 System.out.println( 149 "Skipping test: AES-SIV with 256 bit AES keys is not supported: " + ex.toString()); 150 return; 151 } 152 testEncryptionDecryption(siv, 5, 128, 20); 153 } 154 } 155