• 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.aead;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static java.nio.charset.StandardCharsets.UTF_8;
21 import static org.junit.Assert.assertThrows;
22 import static org.junit.Assert.assertTrue;
23 
24 import com.google.crypto.tink.Aead;
25 import com.google.crypto.tink.InsecureSecretKeyAccess;
26 import com.google.crypto.tink.Key;
27 import com.google.crypto.tink.KeyTemplate;
28 import com.google.crypto.tink.KeyTemplates;
29 import com.google.crypto.tink.KeysetHandle;
30 import com.google.crypto.tink.Parameters;
31 import com.google.crypto.tink.RegistryConfiguration;
32 import com.google.crypto.tink.aead.AesGcmSivParameters.Variant;
33 import com.google.crypto.tink.aead.subtle.AesGcmSiv;
34 import com.google.crypto.tink.internal.KeyManagerRegistry;
35 import com.google.crypto.tink.internal.SlowInputStream;
36 import com.google.crypto.tink.internal.Util;
37 import com.google.crypto.tink.subtle.Hex;
38 import com.google.crypto.tink.util.SecretBytes;
39 import java.io.ByteArrayInputStream;
40 import java.security.GeneralSecurityException;
41 import java.security.Security;
42 import java.util.Arrays;
43 import javax.annotation.Nullable;
44 import org.conscrypt.Conscrypt;
45 import org.junit.Assume;
46 import org.junit.Before;
47 import org.junit.Test;
48 import org.junit.experimental.theories.DataPoints;
49 import org.junit.experimental.theories.FromDataPoints;
50 import org.junit.experimental.theories.Theories;
51 import org.junit.experimental.theories.Theory;
52 import org.junit.runner.RunWith;
53 
54 /** Test for AesGcmSivKeyManagerTest. */
55 @RunWith(Theories.class)
56 public class AesGcmSivKeyManagerTest {
57   @Before
setUp()58   public void setUp() throws Exception {
59     try {
60       Conscrypt.checkAvailability();
61       Security.addProvider(Conscrypt.newProvider());
62     } catch (Throwable cause) {
63       // Ignore. This fails on android, in which case Conscrypt is already installed by default.
64     }
65     AeadConfig.register();
66   }
67 
68   @Test
testKeyManagerRegistered()69   public void testKeyManagerRegistered() throws Exception {
70     assertThat(
71             KeyManagerRegistry.globalInstance()
72                 .getKeyManager("type.googleapis.com/google.crypto.tink.AesGcmSivKey", Aead.class))
73         .isNotNull();
74   }
75 
76   @Test
testKeyCreationWorks()77   public void testKeyCreationWorks() throws Exception {
78     Parameters validParameters =
79         AesGcmSivParameters.builder()
80             .setKeySizeBytes(32)
81             .setVariant(AesGcmSivParameters.Variant.TINK)
82             .build();
83     assertThat(KeysetHandle.generateNew(validParameters).getAt(0).getKey().getParameters())
84         .isEqualTo(validParameters);
85   }
86 
87   @Test
testCiphertextSize()88   public void testCiphertextSize() throws Exception {
89     @Nullable Integer apiLevel = Util.getAndroidApiLevel();
90     Assume.assumeTrue(apiLevel == null || apiLevel >= 30); // Run the test on java and android >= 30
91 
92     AesGcmSivParameters parameters =
93         AesGcmSivParameters.builder().setKeySizeBytes(16).setVariant(Variant.NO_PREFIX).build();
94     AesGcmSivKey key =
95         AesGcmSivKey.builder()
96             .setParameters(parameters)
97             .setKeyBytes(SecretBytes.randomBytes(16))
98             .build();
99     KeysetHandle keysetHandle =
100         KeysetHandle.newBuilder()
101             .addEntry(KeysetHandle.importKey(key).withRandomId().makePrimary())
102             .build();
103     byte[] plaintext = "plaintext".getBytes(UTF_8);
104     byte[] associatedData = "aad".getBytes(UTF_8);
105 
106     Aead aead = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
107 
108     byte[] ciphertext = aead.encrypt(plaintext, associatedData);
109     assertThat(ciphertext.length)
110         .isEqualTo(12 /* IV_SIZE */ + plaintext.length + 16 /* TAG_SIZE */);
111   }
112 
113   @Test
testAes128GcmSivTemplate()114   public void testAes128GcmSivTemplate() throws Exception {
115     KeyTemplate template = AesGcmSivKeyManager.aes128GcmSivTemplate();
116     assertThat(template.toParameters())
117         .isEqualTo(
118             AesGcmSivParameters.builder()
119                 .setKeySizeBytes(16)
120                 .setVariant(AesGcmSivParameters.Variant.TINK)
121                 .build());
122   }
123 
124   @Test
testRawAes128GcmSivTemplate()125   public void testRawAes128GcmSivTemplate() throws Exception {
126     KeyTemplate template = AesGcmSivKeyManager.rawAes128GcmSivTemplate();
127     assertThat(template.toParameters())
128         .isEqualTo(
129             AesGcmSivParameters.builder()
130                 .setKeySizeBytes(16)
131                 .setVariant(AesGcmSivParameters.Variant.NO_PREFIX)
132                 .build());
133   }
134 
135   @Test
testAes256GcmSivTemplate()136   public void testAes256GcmSivTemplate() throws Exception {
137     KeyTemplate template = AesGcmSivKeyManager.aes256GcmSivTemplate();
138     assertThat(template.toParameters())
139         .isEqualTo(
140             AesGcmSivParameters.builder()
141                 .setKeySizeBytes(32)
142                 .setVariant(AesGcmSivParameters.Variant.TINK)
143                 .build());
144   }
145 
146   @Test
testRawAes256GcmSivTemplate()147   public void testRawAes256GcmSivTemplate() throws Exception {
148     KeyTemplate template = AesGcmSivKeyManager.rawAes256GcmSivTemplate();
149     assertThat(template.toParameters())
150         .isEqualTo(
151             AesGcmSivParameters.builder()
152                 .setKeySizeBytes(32)
153                 .setVariant(AesGcmSivParameters.Variant.NO_PREFIX)
154                 .build());
155   }
156 
157   @Test
testKeyTemplatesWork()158   public void testKeyTemplatesWork() throws Exception {
159     Parameters p = AesGcmSivKeyManager.aes128GcmSivTemplate().toParameters();
160     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
161 
162     p = AesGcmSivKeyManager.rawAes128GcmSivTemplate().toParameters();
163     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
164 
165     p = AesGcmSivKeyManager.aes256GcmSivTemplate().toParameters();
166     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
167 
168     p = AesGcmSivKeyManager.rawAes256GcmSivTemplate().toParameters();
169     assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p);
170   }
171 
172   @DataPoints("templateNames")
173   public static final String[] KEY_TEMPLATES =
174       new String[] {"AES128_GCM_SIV", "AES256_GCM_SIV", "AES256_GCM_SIV_RAW", "AES128_GCM_SIV_RAW"};
175 
176   @Theory
testTemplates(@romDataPoints"templateNames") String templateName)177   public void testTemplates(@FromDataPoints("templateNames") String templateName) throws Exception {
178     KeysetHandle h = KeysetHandle.generateNew(KeyTemplates.get(templateName));
179     assertThat(h.size()).isEqualTo(1);
180     assertThat(h.getAt(0).getKey().getParameters())
181         .isEqualTo(KeyTemplates.get(templateName).toParameters());
182   }
183 
184   @Theory
testCreateKeyFromRandomness(@romDataPoints"templateNames") String templateName)185   public void testCreateKeyFromRandomness(@FromDataPoints("templateNames") String templateName)
186       throws Exception {
187     byte[] keyMaterial =
188         new byte[] {
189           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
190           25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
191         };
192     AesGcmSivParameters parameters =
193         (AesGcmSivParameters) KeyTemplates.get(templateName).toParameters();
194     com.google.crypto.tink.aead.AesGcmSivKey key =
195         AesGcmSivKeyManager.createAesGcmSivKeyFromRandomness(
196             parameters,
197             new ByteArrayInputStream(keyMaterial),
198             parameters.hasIdRequirement() ? 123 : null,
199             InsecureSecretKeyAccess.get());
200     byte[] truncatedKeyMaterial = Arrays.copyOf(keyMaterial, parameters.getKeySizeBytes());
201     Key expectedKey =
202         com.google.crypto.tink.aead.AesGcmSivKey.builder()
203             .setParameters(parameters)
204             .setIdRequirement(parameters.hasIdRequirement() ? 123 : null)
205             .setKeyBytes(SecretBytes.copyFrom(truncatedKeyMaterial, InsecureSecretKeyAccess.get()))
206             .build();
207     assertTrue(key.equalsKey(expectedKey));
208   }
209 
210   @Test
testCreateKeyFromRandomness_slowInputStream_works()211   public void testCreateKeyFromRandomness_slowInputStream_works() throws Exception {
212     AesGcmSivParameters parameters =
213         AesGcmSivParameters.builder()
214             .setKeySizeBytes(32)
215             .setVariant(AesGcmSivParameters.Variant.NO_PREFIX)
216             .build();
217     byte[] keyMaterial =
218         new byte[] {
219           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
220           25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
221         };
222     com.google.crypto.tink.aead.AesGcmSivKey key =
223         AesGcmSivKeyManager.createAesGcmSivKeyFromRandomness(
224             parameters,
225             SlowInputStream.copyFrom(keyMaterial),
226             parameters.hasIdRequirement() ? 123 : null,
227             InsecureSecretKeyAccess.get());
228     byte[] truncatedKeyMaterial = Arrays.copyOf(keyMaterial, parameters.getKeySizeBytes());
229     Key expectedKey =
230         com.google.crypto.tink.aead.AesGcmSivKey.builder()
231             .setParameters(parameters)
232             .setIdRequirement(parameters.hasIdRequirement() ? 123 : null)
233             .setKeyBytes(SecretBytes.copyFrom(truncatedKeyMaterial, InsecureSecretKeyAccess.get()))
234             .build();
235     assertTrue(key.equalsKey(expectedKey));
236   }
237 
238   @Test
testEncryptDecrypt_works()239   public void testEncryptDecrypt_works() throws Exception {
240     @Nullable Integer apiLevel = Util.getAndroidApiLevel();
241     Assume.assumeTrue(apiLevel == null || apiLevel >= 30); // Run the test on java and android >= 30
242 
243     AesGcmSivKey aesGcmSivKey =
244         AesGcmSivKey.builder()
245             .setParameters(
246                 AesGcmSivParameters.builder()
247                     .setKeySizeBytes(16)
248                     .setVariant(AesGcmSivParameters.Variant.NO_PREFIX)
249                     .build())
250             .setKeyBytes(
251                 SecretBytes.copyFrom(
252                     Hex.decode("5b9604fe14eadba931b0ccf34843dab9"), InsecureSecretKeyAccess.get()))
253             .build();
254     KeysetHandle keysetHandle =
255         KeysetHandle.newBuilder()
256             .addEntry(KeysetHandle.importKey(aesGcmSivKey).withRandomId().makePrimary())
257             .build();
258     Aead aead = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
259 
260     // Encrypt an empty plaintext, and verify that it can be decrypted.
261     byte[] ciphertext = aead.encrypt(new byte[] {1, 2, 3}, new byte[] {4, 5, 6});
262     byte[] decrypted = aead.decrypt(ciphertext, new byte[] {4, 5, 6});
263     assertThat(decrypted).isEqualTo(new byte[] {1, 2, 3});
264     assertThrows(GeneralSecurityException.class, () -> aead.decrypt(ciphertext, new byte[] {4, 5}));
265   }
266 
267   @Test
testEncryptAndDecryptFailBeforeAndroid30()268   public void testEncryptAndDecryptFailBeforeAndroid30() throws Exception {
269     @Nullable Integer apiLevel = Util.getAndroidApiLevel();
270     Assume.assumeNotNull(apiLevel);
271     Assume.assumeTrue(apiLevel < 30);
272 
273     // Use an AES GCM test vector from AesGcmJceTest.testWithAesGcmKey_noPrefix_works
274     byte[] keyBytes = Hex.decode("5b9604fe14eadba931b0ccf34843dab9");
275     AesGcmSivParameters parameters =
276         AesGcmSivParameters.builder()
277             .setKeySizeBytes(16)
278             .setVariant(AesGcmSivParameters.Variant.NO_PREFIX)
279             .build();
280     com.google.crypto.tink.aead.AesGcmSivKey key =
281         com.google.crypto.tink.aead.AesGcmSivKey.builder()
282             .setParameters(parameters)
283             .setKeyBytes(SecretBytes.copyFrom(keyBytes, InsecureSecretKeyAccess.get()))
284             .build();
285     // Create an AEAD primitive for aesGcmSivKey.
286     KeysetHandle keysetHandle =
287         KeysetHandle.newBuilder()
288             .addEntry(KeysetHandle.importKey(key).withRandomId().makePrimary())
289             .build();
290     Aead aead = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
291 
292     assertThrows(GeneralSecurityException.class, () -> aead.encrypt(new byte[] {}, new byte[] {}));
293     byte[] fixedCiphertext = Hex.decode("c3561ce7f48b8a6b9b8d5ef957d2e512368f7da837bcf2aeebe176e3");
294     assertThrows(
295         GeneralSecurityException.class, () -> aead.decrypt(fixedCiphertext, new byte[] {}));
296   }
297 
298   // This test shows how ciphertexts created with older versions of Tink on older versions of
299   // Android can still be decrypted with the current version of Tink.
300   @Test
testDecryptCiphertextCreatedOnOlderVersionOfAndroid()301   public void testDecryptCiphertextCreatedOnOlderVersionOfAndroid() throws Exception {
302     @Nullable Integer apiLevel = Util.getAndroidApiLevel();
303     Assume.assumeTrue(apiLevel == null || apiLevel >= 30); // Run the test on java and android >= 30
304 
305     // A valid AES GCM SIV key.
306     AesGcmSivKey aesGcmSivKey =
307         AesGcmSivKey.builder()
308             .setParameters(
309                 AesGcmSivParameters.builder()
310                     .setKeySizeBytes(16)
311                     .setVariant(AesGcmSivParameters.Variant.NO_PREFIX)
312                     .build())
313             .setKeyBytes(
314                 SecretBytes.copyFrom(
315                     Hex.decode("5b9604fe14eadba931b0ccf34843dab9"), InsecureSecretKeyAccess.get()))
316             .build();
317 
318     // Valid ciphertext of an empty plaintext created with aesGcmSivKey.
319     byte[] validCiphertext = Hex.decode("17871550708697c27881d04753337526f2bed57b7e2eac30ecde0202");
320 
321     // Ciphertext created with aesGcmSivKey on Android version 29 before
322     // https://github.com/tink-crypto/tink-java/issues/18 was fixed.
323     byte[] legacyCiphertext =
324         Hex.decode("c3561ce7f48b8a6b9b8d5ef957d2e512368f7da837bcf2aeebe176e3");
325 
326     // Create an Aead instance that can decrypt in both AES GCM and AES GCM SIV.
327     AesGcmKey legacyKey =
328         AesGcmKey.builder()
329             .setParameters(
330                 AesGcmParameters.builder()
331                     .setIvSizeBytes(12)
332                     .setKeySizeBytes(16)
333                     .setTagSizeBytes(16)
334                     .setVariant(AesGcmParameters.Variant.NO_PREFIX)
335                     .build())
336             .setKeyBytes(aesGcmSivKey.getKeyBytes())
337             .build();
338     KeysetHandle backwardsCompatibleKeysetHandle =
339         KeysetHandle.newBuilder()
340             .addEntry(KeysetHandle.importKey(aesGcmSivKey).withRandomId().makePrimary())
341             .addEntry(KeysetHandle.importKey(legacyKey).withRandomId())
342             .build();
343     Aead backwardsCompatibleAead =
344         backwardsCompatibleKeysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
345 
346     // Check that backwardsCompatibleAead can decrypt both valid and legacy ciphertexts.
347     assertThat(backwardsCompatibleAead.decrypt(validCiphertext, new byte[] {})).isEmpty();
348     assertThat(backwardsCompatibleAead.decrypt(legacyCiphertext, new byte[] {})).isEmpty();
349   }
350 
351   @Test
getPrimitiveFromKeysetHandle()352   public void getPrimitiveFromKeysetHandle() throws Exception {
353     @Nullable Integer apiLevel = Util.getAndroidApiLevel();
354     Assume.assumeTrue(apiLevel == null || apiLevel >= 30); // Run the test on java and android >= 30
355 
356     AesGcmSivParameters parameters =
357         AesGcmSivParameters.builder().setKeySizeBytes(16).setVariant(Variant.TINK).build();
358     AesGcmSivKey key =
359         AesGcmSivKey.builder()
360             .setParameters(parameters)
361             .setKeyBytes(SecretBytes.randomBytes(16))
362             .setIdRequirement(42)
363             .build();
364     KeysetHandle keysetHandle =
365         KeysetHandle.newBuilder().addEntry(KeysetHandle.importKey(key).makePrimary()).build();
366     byte[] plaintext = "plaintext".getBytes(UTF_8);
367     byte[] aad = "aad".getBytes(UTF_8);
368 
369     Aead aead = keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
370     Aead directAead = AesGcmSiv.create(key);
371 
372     assertThat(aead.decrypt(directAead.encrypt(plaintext, aad), aad)).isEqualTo(plaintext);
373     assertThat(directAead.decrypt(aead.encrypt(plaintext, aad), aad)).isEqualTo(plaintext);
374   }
375 }
376