1 // Copyright 2022 Google LLC 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 23 import com.google.crypto.tink.Aead; 24 import com.google.crypto.tink.DeterministicAead; 25 import com.google.crypto.tink.InsecureSecretKeyAccess; 26 import com.google.crypto.tink.KeyTemplates; 27 import com.google.crypto.tink.KeysetHandle; 28 import com.google.crypto.tink.RegistryConfiguration; 29 import com.google.crypto.tink.TinkJsonProtoKeysetFormat; 30 import com.google.crypto.tink.daead.DeterministicAeadConfig; 31 import java.security.GeneralSecurityException; 32 import org.junit.BeforeClass; 33 import org.junit.experimental.theories.DataPoints; 34 import org.junit.experimental.theories.FromDataPoints; 35 import org.junit.experimental.theories.Theories; 36 import org.junit.experimental.theories.Theory; 37 import org.junit.runner.RunWith; 38 39 /** Unit tests for the Aead package. Uses only the public API. */ 40 @RunWith(Theories.class) 41 public final class AeadTest { 42 43 @BeforeClass setUp()44 public static void setUp() throws Exception { 45 AeadConfig.register(); 46 DeterministicAeadConfig.register(); // Needed for getPrimitiveFromNonAeadKeyset_throws. 47 } 48 49 @DataPoints("templates") 50 public static final String[] TEMPLATES = 51 new String[] { 52 "AES128_EAX", 53 "AES128_EAX_RAW", 54 "AES256_EAX", 55 "AES256_EAX_RAW", 56 "AES128_GCM", 57 "AES128_GCM_RAW", 58 "AES256_GCM", 59 "AES256_GCM_RAW", 60 "AES128_CTR_HMAC_SHA256", 61 "AES128_CTR_HMAC_SHA256_RAW", 62 "AES256_CTR_HMAC_SHA256", 63 "AES256_CTR_HMAC_SHA256_RAW", 64 "CHACHA20_POLY1305", 65 "CHACHA20_POLY1305_RAW", 66 "XCHACHA20_POLY1305", 67 "XCHACHA20_POLY1305_RAW" 68 }; 69 70 @Theory createEncryptDecrypt(@romDataPoints"templates") String templateName)71 public void createEncryptDecrypt(@FromDataPoints("templates") String templateName) 72 throws Exception { 73 KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateName)); 74 Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class); 75 byte[] plaintext = "plaintext".getBytes(UTF_8); 76 byte[] associatedData = "associatedData".getBytes(UTF_8); 77 byte[] ciphertext = aead.encrypt(plaintext, associatedData); 78 byte[] decrypted = aead.decrypt(ciphertext, associatedData); 79 assertThat(decrypted).isEqualTo(plaintext); 80 81 KeysetHandle otherHandle = KeysetHandle.generateNew(KeyTemplates.get(templateName)); 82 Aead otherAead = otherHandle.getPrimitive(RegistryConfiguration.get(), Aead.class); 83 assertThrows( 84 GeneralSecurityException.class, () -> otherAead.decrypt(ciphertext, associatedData)); 85 86 byte[] invalid = "invalid".getBytes(UTF_8); 87 byte[] empty = "".getBytes(UTF_8); 88 assertThrows(GeneralSecurityException.class, () -> aead.decrypt(ciphertext, invalid)); 89 assertThrows(GeneralSecurityException.class, () -> aead.decrypt(invalid, associatedData)); 90 assertThrows(GeneralSecurityException.class, () -> aead.decrypt(empty, associatedData)); 91 assertThat(aead.decrypt(aead.encrypt(empty, associatedData), associatedData)).isEqualTo(empty); 92 assertThat(aead.decrypt(aead.encrypt(plaintext, empty), empty)).isEqualTo(plaintext); 93 } 94 95 // A keyset with one AEAD key, serialized in Tink's JSON format. 96 private static final String JSON_AEAD_KEYSET = 97 "" 98 + "{" 99 + " \"primaryKeyId\": 42818733," 100 + " \"key\": [" 101 + " {" 102 + " \"keyData\": {" 103 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesGcmKey\"," 104 + " \"keyMaterialType\": \"SYMMETRIC\"," 105 + " \"value\": \"GhCC74uJ+2f4qlpaHwR4ylNQ\"" 106 + " }," 107 + " \"outputPrefixType\": \"TINK\"," 108 + " \"keyId\": 42818733," 109 + " \"status\": \"ENABLED\"" 110 + " }" 111 + " ]" 112 + "}"; 113 114 @Theory readKeysetEncryptDecrypt()115 public void readKeysetEncryptDecrypt() 116 throws Exception { 117 KeysetHandle handle = 118 TinkJsonProtoKeysetFormat.parseKeyset(JSON_AEAD_KEYSET, InsecureSecretKeyAccess.get()); 119 120 Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class); 121 122 byte[] plaintext = "plaintext".getBytes(UTF_8); 123 byte[] associatedData = "associatedData".getBytes(UTF_8); 124 byte[] ciphertext = aead.encrypt(plaintext, associatedData); 125 byte[] decrypted = aead.decrypt(ciphertext, associatedData); 126 assertThat(decrypted).isEqualTo(plaintext); 127 } 128 129 // A keyset with multiple keys. The first key is the same as in JSON_AEAD_KEYSET. 130 private static final String JSON_AEAD_KEYSET_WITH_MULTIPLE_KEYS = 131 "" 132 + "{" 133 + " \"primaryKeyId\": 365202604," 134 + " \"key\": [" 135 + " {" 136 + " \"keyData\": {" 137 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesGcmKey\"," 138 + " \"keyMaterialType\": \"SYMMETRIC\"," 139 + " \"value\": \"GhCC74uJ+2f4qlpaHwR4ylNQ\"" 140 + " }," 141 + " \"outputPrefixType\": \"TINK\"," 142 + " \"keyId\": 42818733," 143 + " \"status\": \"ENABLED\"" 144 + " }, {" 145 + " \"keyData\": {" 146 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesEaxKey\"," 147 + " \"keyMaterialType\": \"SYMMETRIC\"," 148 + " \"value\": \"EgIIEBogU4nieBfIeJHBrhC+TjezFgxkkuhQHbyWkUMH+7atLxI=\"" 149 + " }," 150 + " \"outputPrefixType\": \"RAW\"," 151 + " \"keyId\": 365202604," 152 + " \"status\": \"ENABLED\"" 153 + " }, {" 154 + " \"keyData\": {" 155 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey\"," 156 + " \"keyMaterialType\": \"SYMMETRIC\"," 157 + " \"value\": \"GigaIMttlipP/JvQOpIB0NYhDPoLgWBiIxmtaWbSPa2TeQOmEgQQEAgDEhYaEPcCM" 158 + "mPLgRGhmMmSC4AJ1CESAggQ\"" 159 + " }," 160 + " \"outputPrefixType\": \"LEGACY\"," 161 + " \"keyId\": 277095770," 162 + " \"status\": \"ENABLED\"" 163 + " }" 164 + " ]" 165 + "}"; 166 167 @Theory multipleKeysReadKeysetWithEncryptDecrypt()168 public void multipleKeysReadKeysetWithEncryptDecrypt() 169 throws Exception { 170 KeysetHandle handle = 171 TinkJsonProtoKeysetFormat.parseKeyset( 172 JSON_AEAD_KEYSET_WITH_MULTIPLE_KEYS, InsecureSecretKeyAccess.get()); 173 174 Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class); 175 176 byte[] plaintext = "plaintext".getBytes(UTF_8); 177 byte[] associatedData = "associatedData".getBytes(UTF_8); 178 byte[] ciphertext = aead.encrypt(plaintext, associatedData); 179 assertThat(aead.decrypt(ciphertext, associatedData)).isEqualTo(plaintext); 180 181 // Also test that aead can decrypt ciphertexts encrypted with a non-primary key. We use 182 // JSON_AEAD_KEYSET to encrypt with the first key. 183 KeysetHandle handle1 = 184 TinkJsonProtoKeysetFormat.parseKeyset(JSON_AEAD_KEYSET, InsecureSecretKeyAccess.get()); 185 186 Aead aead1 = handle1.getPrimitive(RegistryConfiguration.get(), Aead.class); 187 byte[] ciphertext1 = aead1.encrypt(plaintext, associatedData); 188 assertThat(aead.decrypt(ciphertext1, associatedData)).isEqualTo(plaintext); 189 } 190 191 // A keyset with a valid DeterministicAead key. This keyset can't be used with the Aead primitive. 192 private static final String JSON_DAEAD_KEYSET = 193 "" 194 + "{" 195 + " \"primaryKeyId\": 961932622," 196 + " \"key\": [" 197 + " {" 198 + " \"keyData\": {" 199 + " \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesSivKey\"," 200 + " \"keyMaterialType\": \"SYMMETRIC\"," 201 + " \"value\": \"EkCJ9r5iwc5uxq5ugFyrHXh5dijTa7qalWUgZ8Gf08RxNd545FjtLMYL7ObcaFtCS" 202 + "kvV2+7u6F2DN+kqUjAfkf2W\"" 203 + " }," 204 + " \"outputPrefixType\": \"TINK\"," 205 + " \"keyId\": 961932622," 206 + " \"status\": \"ENABLED\"" 207 + " }" 208 + " ]" 209 + "}"; 210 211 @Theory getPrimitiveFromNonAeadKeyset_throws()212 public void getPrimitiveFromNonAeadKeyset_throws() 213 throws Exception { 214 KeysetHandle handle = 215 TinkJsonProtoKeysetFormat.parseKeyset(JSON_DAEAD_KEYSET, InsecureSecretKeyAccess.get()); 216 // Test that the keyset can create a DeterministicAead primitive, but not a Aead. 217 Object unused = handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class); 218 assertThrows( 219 GeneralSecurityException.class, 220 () -> handle.getPrimitive(RegistryConfiguration.get(), Aead.class)); 221 } 222 } 223