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.Aead; 20 import com.google.crypto.tink.Mac; 21 import com.google.crypto.tink.testing.TestUtil; 22 import javax.crypto.spec.SecretKeySpec; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 import org.junit.runners.JUnit4; 26 27 /** 28 * Test for thread safety of {@code Aead}-primitives. 29 * 30 * <p>If possible then this unit test should be run using a thread sanitizer. Otherwise only race 31 * conditions that actually happend during the test will be detected. 32 * 33 * <p>There are a few things that this test can't check: One of the goal of Tink is to achieve 34 * robust interfaces. In particular, no matter how the primitives are called there should be no way 35 * to leak the private key. Ideally, this guarantee should also cover modifying the input arrays in 36 * a concurrent thread while the primitive is encrypting. If a thread modifies the input arrays 37 * while the primitive is encrypting then this modification must not lead to ciphertext that leaks 38 * information about the key. If it does leak then the implementation should be modified to either 39 * lock the input or clone it before encrypting. 40 */ 41 @RunWith(JUnit4.class) 42 public class AeadThreadSafetyTest { 43 44 /** 45 * Exception handler for uncaught exceptions in a thread. 46 * 47 * <p>TODO(bleichen): Surely there must be a better way to catch exceptions in threads in unit 48 * tests. junit ought to do this. However, at least for some setups, tests can pass despite 49 * uncaught exceptions in threads. 50 */ 51 public static class ExceptionHandler implements Thread.UncaughtExceptionHandler { 52 53 private Throwable firstException = null; 54 55 @Override uncaughtException(Thread thread, Throwable ex)56 public void uncaughtException(Thread thread, Throwable ex) { 57 if (firstException == null) { 58 firstException = ex; 59 } 60 } 61 check()62 public void check() throws Exception { 63 if (firstException != null) { 64 throw new Exception("Thread failed", firstException); 65 } 66 } 67 } 68 69 /** A thread that encrypts and decrypts random plaintexts. */ 70 public static class CryptingThread extends Thread { 71 private Aead cipher; 72 private int maxPlaintextSize; 73 private int count; 74 75 /** 76 * Constructs a thread that encrypts and decrypts a number of plaintexts. 77 * 78 * @param maxPlaintextSize the maximal size of a plaintext 79 * @param count the number of encryptions and decryptions done in the test 80 */ CryptingThread(Aead cipher, int maxPlaintextSize, int count)81 CryptingThread(Aead cipher, int maxPlaintextSize, int count) { 82 this.cipher = cipher; 83 this.maxPlaintextSize = maxPlaintextSize; 84 this.count = count; 85 } 86 87 /** 88 * Read the plaintext from the channel. This implementation assumes that the channel is blocking 89 * and throws an AssertionError if an attempt to read plaintext from the channel is incomplete. 90 */ 91 @Override run()92 public void run() { 93 try { 94 // Just an arbitrary prime to get the plaintext sizes. 95 int p = 28657; 96 for (int i = 0; i < count; i++) { 97 // All sizes are used once when count > maxPlaintextSize. 98 int size = i * p % (maxPlaintextSize + 1); 99 int aadSize = (i / 2) * p % (maxPlaintextSize + 1); 100 byte[] plaintext = new byte[size]; 101 byte[] aad = new byte[aadSize]; 102 byte[] ciphertext = cipher.encrypt(plaintext, aad); 103 byte[] decrypted = cipher.decrypt(ciphertext, aad); 104 TestUtil.assertByteArrayEquals("Incorrect decryption", plaintext, decrypted); 105 } 106 } catch (Exception ex) { 107 getUncaughtExceptionHandler().uncaughtException(this, ex); 108 } 109 } 110 } 111 112 /** Encrypt and decrypt concurrently with one Aead cipher. */ testEncryptionDecryption( Aead cipher, int numberOfThreads, int maxPlaintextSize, int numberOfEncryptionsPerThread)113 public void testEncryptionDecryption( 114 Aead cipher, int numberOfThreads, int maxPlaintextSize, int numberOfEncryptionsPerThread) 115 throws Exception { 116 ExceptionHandler exceptionHandler = new ExceptionHandler(); 117 Thread[] thread = new Thread[numberOfThreads]; 118 for (int i = 0; i < numberOfThreads; i++) { 119 thread[i] = new CryptingThread(cipher, maxPlaintextSize, numberOfEncryptionsPerThread); 120 thread[i].setUncaughtExceptionHandler(exceptionHandler); 121 } 122 for (int i = 0; i < numberOfThreads; i++) { 123 thread[i].start(); 124 } 125 for (int i = 0; i < numberOfThreads; i++) { 126 thread[i].join(); 127 } 128 exceptionHandler.check(); 129 } 130 131 @Test testAesGcm()132 public void testAesGcm() throws Exception { 133 byte[] key = Random.randBytes(16); 134 AesGcmJce gcm = new AesGcmJce(key); 135 testEncryptionDecryption(gcm, 5, 128, 20); 136 } 137 138 @Test testAesEax()139 public void testAesEax() throws Exception { 140 byte[] key = Random.randBytes(16); 141 AesEaxJce eax = new AesEaxJce(key, 12); 142 testEncryptionDecryption(eax, 5, 128, 20); 143 } 144 145 @Test testAesCtrHmac()146 public void testAesCtrHmac() throws Exception { 147 byte[] key = Random.randBytes(16); 148 byte[] macKey = Random.randBytes(32); 149 int ivSize = 12; 150 int macSize = 12; 151 IndCpaCipher cipher = new AesCtrJceCipher(key, ivSize); 152 SecretKeySpec keySpec = new SecretKeySpec(macKey, "HMAC"); 153 Mac mac = new PrfMac(new PrfHmacJce("HMACSHA256", keySpec), macSize); 154 155 // TODO(b/148134669): Remove the following line. 156 // There is a potential (but unlikely) race in java.security.Provider. Since AesCtrHmac 157 // encryption creates a cipher for the first time in 158 // http://google3/third_party/tink/java_src/src/main/java/com/google/crypto/tink/subtle/AesCtrJceCipher.java?l=128&rcl=272896379 159 // if we do this multithreaded, there is a potential for a race in case we call encrypt 160 // for the first time at the same time in multiple threads. To get around this, we first encrypt 161 // an empty plaintext here. 162 Object unused = cipher.encrypt(new byte[0]); 163 164 Aead aesCtrHmac = new EncryptThenAuthenticate(cipher, mac, macSize); 165 testEncryptionDecryption(aesCtrHmac, 5, 128, 20); 166 } 167 168 @Test testChaCha20Poly1305()169 public void testChaCha20Poly1305() throws Exception { 170 byte[] key = Random.randBytes(32); 171 Aead cipher = new ChaCha20Poly1305(key); 172 testEncryptionDecryption(cipher, 5, 128, 20); 173 } 174 175 @Test testXChaCha20Poly1305()176 public void testXChaCha20Poly1305() throws Exception { 177 byte[] key = Random.randBytes(32); 178 Aead cipher = new XChaCha20Poly1305(key); 179 testEncryptionDecryption(cipher, 5, 128, 20); 180 } 181 } 182