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