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