• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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