• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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