1 // Copyright 2021 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.KeyTemplate; 25 import com.google.crypto.tink.KeyTemplates; 26 import com.google.crypto.tink.KeysetHandle; 27 import com.google.crypto.tink.KmsClient; 28 import com.google.crypto.tink.KmsClients; 29 import com.google.crypto.tink.RegistryConfiguration; 30 import com.google.crypto.tink.internal.KeyTemplateProtoConverter; 31 import com.google.crypto.tink.mac.HmacKeyManager; 32 import com.google.crypto.tink.subtle.Random; 33 import com.google.crypto.tink.testing.FakeKmsClient; 34 import java.security.GeneralSecurityException; 35 import org.junit.BeforeClass; 36 import org.junit.Test; 37 import org.junit.experimental.theories.DataPoints; 38 import org.junit.experimental.theories.FromDataPoints; 39 import org.junit.experimental.theories.Theories; 40 import org.junit.experimental.theories.Theory; 41 import org.junit.runner.RunWith; 42 43 /** Tests for {@link KmsEnvelopeAead} */ 44 @RunWith(Theories.class) 45 public final class KmsEnvelopeAeadTest { 46 private static final byte[] EMPTY_ADD = new byte[0]; 47 48 @BeforeClass setUp()49 public static void setUp() throws GeneralSecurityException { 50 AeadConfig.register(); 51 } 52 generateNewRemoteAead()53 private Aead generateNewRemoteAead() throws GeneralSecurityException { 54 KeysetHandle keysetHandle = KeysetHandle.generateNew(KeyTemplates.get("AES128_EAX")); 55 return keysetHandle.getPrimitive(RegistryConfiguration.get(), Aead.class); 56 } 57 58 @DataPoints("dekParameters") 59 public static final AeadParameters[] DEK_PARAMETERS = 60 new AeadParameters[] { 61 PredefinedAeadParameters.AES128_GCM, 62 PredefinedAeadParameters.AES256_GCM, 63 PredefinedAeadParameters.AES128_EAX, 64 PredefinedAeadParameters.AES256_EAX, 65 PredefinedAeadParameters.AES128_CTR_HMAC_SHA256, 66 PredefinedAeadParameters.AES256_CTR_HMAC_SHA256, 67 PredefinedAeadParameters.CHACHA20_POLY1305, 68 PredefinedAeadParameters.XCHACHA20_POLY1305, 69 }; 70 71 @Theory createEncryptDecrypt_works( @romDataPoints"dekParameters") AeadParameters dekParameters)72 public void createEncryptDecrypt_works( 73 @FromDataPoints("dekParameters") AeadParameters dekParameters) throws Exception { 74 Aead remoteAead = this.generateNewRemoteAead(); 75 Aead envAead = KmsEnvelopeAead.create(dekParameters, remoteAead); 76 byte[] plaintext = "plaintext".getBytes(UTF_8); 77 byte[] associatedData = "associatedData".getBytes(UTF_8); 78 byte[] ciphertext = envAead.encrypt(plaintext, associatedData); 79 assertThat(envAead.decrypt(ciphertext, associatedData)).isEqualTo(plaintext); 80 81 assertThat(envAead.decrypt(envAead.encrypt(plaintext, EMPTY_ADD), EMPTY_ADD)) 82 .isEqualTo(plaintext); 83 } 84 85 @DataPoints("tinkDekTemplates") 86 public static final String[] TINK_DEK_TEMPLATES = 87 new String[] { 88 "AES128_GCM", 89 "AES256_GCM", 90 "AES128_EAX", 91 "AES256_EAX", 92 "AES128_CTR_HMAC_SHA256", 93 "AES256_CTR_HMAC_SHA256", 94 "CHACHA20_POLY1305", 95 "XCHACHA20_POLY1305", 96 "AES128_GCM_RAW", 97 }; 98 99 @Theory legacyConstructorEncryptDecrypt_works( @romDataPoints"tinkDekTemplates") String dekTemplateName)100 public void legacyConstructorEncryptDecrypt_works( 101 @FromDataPoints("tinkDekTemplates") String dekTemplateName) throws Exception { 102 Aead remoteAead = this.generateNewRemoteAead(); 103 Aead envAead = 104 new KmsEnvelopeAead( 105 KeyTemplateProtoConverter.toProto(KeyTemplates.get(dekTemplateName)), remoteAead); 106 byte[] plaintext = "plaintext".getBytes(UTF_8); 107 byte[] associatedData = "associatedData".getBytes(UTF_8); 108 byte[] ciphertext = envAead.encrypt(plaintext, associatedData); 109 assertThat(envAead.decrypt(ciphertext, associatedData)).isEqualTo(plaintext); 110 111 assertThat(envAead.decrypt(envAead.encrypt(plaintext, EMPTY_ADD), EMPTY_ADD)) 112 .isEqualTo(plaintext); 113 } 114 115 @Test createKeyFormatWithInvalidDekTemplate_fails()116 public void createKeyFormatWithInvalidDekTemplate_fails() throws Exception { 117 Aead remoteAead = this.generateNewRemoteAead(); 118 KeyTemplate invalidDekTemplate = HmacKeyManager.hmacSha256Template(); 119 120 assertThrows( 121 IllegalArgumentException.class, 122 () -> 123 new KmsEnvelopeAead(KeyTemplateProtoConverter.toProto(invalidDekTemplate), remoteAead)); 124 } 125 126 @Test decryptWithInvalidAssociatedData_fails()127 public void decryptWithInvalidAssociatedData_fails() throws GeneralSecurityException { 128 Aead remoteAead = this.generateNewRemoteAead(); 129 Aead envAead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_EAX, remoteAead); 130 byte[] plaintext = "plaintext".getBytes(UTF_8); 131 byte[] associatedData = "associatedData".getBytes(UTF_8); 132 byte[] ciphertext = envAead.encrypt(plaintext, associatedData); 133 byte[] invalidAssociatedData = "invalidAssociatedData".getBytes(UTF_8); 134 assertThrows( 135 GeneralSecurityException.class, () -> envAead.decrypt(ciphertext, invalidAssociatedData)); 136 assertThrows(GeneralSecurityException.class, () -> envAead.decrypt(ciphertext, EMPTY_ADD)); 137 } 138 139 @Test corruptedCiphertext_fails()140 public void corruptedCiphertext_fails() throws GeneralSecurityException { 141 Aead remoteAead = this.generateNewRemoteAead(); 142 Aead envAead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_EAX, remoteAead); 143 byte[] associatedData = "envelope_ad".getBytes(UTF_8); 144 byte[] plaintext = "helloworld".getBytes(UTF_8); 145 byte[] ciphertext = envAead.encrypt(plaintext, associatedData); 146 ciphertext[ciphertext.length - 1] = (byte) (ciphertext[ciphertext.length - 1] ^ 0x1); 147 byte[] corruptedCiphertext = ciphertext; 148 assertThrows( 149 GeneralSecurityException.class, () -> envAead.decrypt(corruptedCiphertext, associatedData)); 150 } 151 152 @Test corruptedDek_fails()153 public void corruptedDek_fails() throws GeneralSecurityException { 154 Aead remoteAead = this.generateNewRemoteAead(); 155 Aead envAead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_EAX, remoteAead); 156 byte[] plaintext = "helloworld".getBytes(UTF_8); 157 byte[] associatedData = "envelope_ad".getBytes(UTF_8); 158 byte[] ciphertext = envAead.encrypt(plaintext, associatedData); 159 ciphertext[4] = (byte) (ciphertext[4] ^ 0x1); 160 byte[] corruptedCiphertext = ciphertext; 161 assertThrows( 162 GeneralSecurityException.class, () -> envAead.decrypt(corruptedCiphertext, associatedData)); 163 } 164 165 @Test ciphertextTooShort_fails()166 public void ciphertextTooShort_fails() throws GeneralSecurityException { 167 Aead remoteAead = this.generateNewRemoteAead(); 168 Aead envAead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_EAX, remoteAead); 169 assertThrows( 170 GeneralSecurityException.class, 171 () -> envAead.decrypt("foo".getBytes(UTF_8), "envelope_ad".getBytes(UTF_8))); 172 } 173 174 @Test encryptedDekTooLong_fails()175 public void encryptedDekTooLong_fails() throws GeneralSecurityException { 176 Aead remoteAead = this.generateNewRemoteAead(); 177 Aead envAead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_EAX, remoteAead); 178 179 byte[] ciphertext = 180 new byte[] { 181 (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88, 182 }; 183 GeneralSecurityException expected = 184 assertThrows( 185 GeneralSecurityException.class, () -> envAead.decrypt(ciphertext, new byte[0])); 186 assertThat(expected).hasMessageThat().contains("length of encrypted DEK too large"); 187 } 188 189 @Test malformedDekLength_fails()190 public void malformedDekLength_fails() throws GeneralSecurityException { 191 Aead remoteAead = this.generateNewRemoteAead(); 192 Aead envAead = KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_EAX, remoteAead); 193 194 byte[] plaintext = "helloworld".getBytes(UTF_8); 195 byte[] associatedData = "envelope_ad".getBytes(UTF_8); 196 byte[] ciphertext = envAead.encrypt(plaintext, associatedData); 197 for (int i = 0; i <= 3; i++) { 198 ciphertext[i] = (byte) 0xff; 199 } 200 byte[] corruptedCiphertext1 = ciphertext; 201 202 assertThrows( 203 GeneralSecurityException.class, 204 () -> envAead.decrypt(corruptedCiphertext1, associatedData)); 205 for (int i = 0; i <= 3; i++) { 206 ciphertext[i] = 0; 207 } 208 byte[] corruptedCiphertext2 = ciphertext; 209 210 assertThrows( 211 GeneralSecurityException.class, 212 () -> envAead.decrypt(corruptedCiphertext2, associatedData)); 213 } 214 215 @Test create_isCompatibleWithOldConstructor()216 public void create_isCompatibleWithOldConstructor() throws Exception { 217 String kekUri = FakeKmsClient.createFakeKeyUri(); 218 Aead remoteAead = new FakeKmsClient().getAead(kekUri); 219 220 Aead aead1 = 221 new KmsEnvelopeAead( 222 KeyTemplateProtoConverter.toProto( 223 AesCtrHmacAeadKeyManager.aes128CtrHmacSha256Template()), 224 remoteAead); 225 Aead aead2 = 226 KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_CTR_HMAC_SHA256, remoteAead); 227 228 byte[] plaintext = Random.randBytes(20); 229 byte[] associatedData = Random.randBytes(20); 230 assertThat(aead1.decrypt(aead2.encrypt(plaintext, associatedData), associatedData)) 231 .isEqualTo(plaintext); 232 assertThat(aead2.decrypt(aead1.encrypt(plaintext, associatedData), associatedData)) 233 .isEqualTo(plaintext); 234 } 235 236 @Test create_isCompatibleWithKmsEnvelopeAeadKey()237 public void create_isCompatibleWithKmsEnvelopeAeadKey() throws Exception { 238 String kekUri = FakeKmsClient.createFakeKeyUri(); 239 KeyTemplate dekTemplate = AesCtrHmacAeadKeyManager.aes128CtrHmacSha256Template(); 240 241 // Register kmsClient and create a keyset with a KmsEnvelopeAeadKey key. 242 KmsClient kmsClient1 = new FakeKmsClient(kekUri); 243 KmsClients.add(kmsClient1); 244 KeysetHandle handle1 = 245 KeysetHandle.generateNew(KmsEnvelopeAeadKeyManager.createKeyTemplate(kekUri, dekTemplate)); 246 Aead aead1 = handle1.getPrimitive(RegistryConfiguration.get(), Aead.class); 247 248 // Get Aead object from the kmsClient, and create the envelope AEAD without the registry. 249 Aead remoteAead = new FakeKmsClient().getAead(kekUri); 250 Aead aead2 = 251 KmsEnvelopeAead.create(PredefinedAeadParameters.AES128_CTR_HMAC_SHA256, remoteAead); 252 253 // Check that aead1 and aead2 implement the same primitive 254 byte[] plaintext = Random.randBytes(20); 255 byte[] associatedData = Random.randBytes(20); 256 assertThat(aead1.decrypt(aead2.encrypt(plaintext, associatedData), associatedData)) 257 .isEqualTo(plaintext); 258 assertThat(aead2.decrypt(aead1.encrypt(plaintext, associatedData), associatedData)) 259 .isEqualTo(plaintext); 260 } 261 } 262 263 264 265