• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.streamingaead;
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.DeterministicAead;
24 import com.google.crypto.tink.InsecureSecretKeyAccess;
25 import com.google.crypto.tink.KeyTemplates;
26 import com.google.crypto.tink.KeysetHandle;
27 import com.google.crypto.tink.RegistryConfiguration;
28 import com.google.crypto.tink.StreamingAead;
29 import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
30 import com.google.crypto.tink.daead.DeterministicAeadConfig;
31 import java.io.ByteArrayInputStream;
32 import java.io.ByteArrayOutputStream;
33 import java.io.IOException;
34 import java.nio.ByteBuffer;
35 import java.nio.channels.Channels;
36 import java.nio.channels.ReadableByteChannel;
37 import java.nio.channels.WritableByteChannel;
38 import java.security.GeneralSecurityException;
39 import org.junit.BeforeClass;
40 import org.junit.experimental.theories.DataPoints;
41 import org.junit.experimental.theories.FromDataPoints;
42 import org.junit.experimental.theories.Theories;
43 import org.junit.experimental.theories.Theory;
44 import org.junit.runner.RunWith;
45 
46 /** Unit tests for the StreamingAead package. Uses only the public API. */
47 @RunWith(Theories.class)
48 public final class StreamingAeadTest {
49 
50   @BeforeClass
setUp()51   public static void setUp() throws Exception {
52     StreamingAeadConfig.register();
53     DeterministicAeadConfig.register(); // Needed for getPrimitiveFromNonStreamingAeadKeyset_throws.
54   }
55 
56   @DataPoints("templates")
57   public static final String[] TEMPLATES =
58       new String[] {
59         "AES128_GCM_HKDF_4KB",
60         "AES128_GCM_HKDF_1MB",
61         "AES256_GCM_HKDF_4KB",
62         "AES256_GCM_HKDF_1MB",
63         "AES128_CTR_HMAC_SHA256_4KB",
64         "AES128_CTR_HMAC_SHA256_1MB",
65         "AES256_CTR_HMAC_SHA256_4KB",
66         "AES256_CTR_HMAC_SHA256_1MB"
67       };
68 
69   /** Writes {@code data} to {@code writeableChannel}. */
writeToChannel(WritableByteChannel writeableChannel, byte[] data)70   private void writeToChannel(WritableByteChannel writeableChannel, byte[] data)
71       throws IOException {
72     ByteBuffer buffer = ByteBuffer.wrap(data);
73     int bytesWritten = 0;
74     while (bytesWritten < data.length) {
75       bytesWritten += writeableChannel.write(buffer);
76     }
77   }
78 
79   /** Reads {@code bytesToRead} bytes from {@code readableChannel}.*/
readFromChannel(ReadableByteChannel readableChannel, int bytesToRead)80   private byte[] readFromChannel(ReadableByteChannel readableChannel, int bytesToRead)
81       throws IOException {
82     ByteBuffer buffer = ByteBuffer.allocate(bytesToRead);
83     int bytesRead = 0;
84     while (bytesRead < bytesToRead) {
85       bytesRead += readableChannel.read(buffer);
86     }
87     return buffer.array();
88   }
89 
90   @Theory
createEncryptDecrypt(@romDataPoints"templates") String templateName)91   public void createEncryptDecrypt(@FromDataPoints("templates") String templateName)
92       throws Exception {
93     KeysetHandle handle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
94     StreamingAead streamingAead =
95         handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class);
96 
97     byte[] plaintext = "plaintext".getBytes(UTF_8);
98     byte[] associatedData = "associatedData".getBytes(UTF_8);
99 
100     // Encrypt
101     ByteArrayOutputStream ciphertextOutputStream = new ByteArrayOutputStream();
102     try (WritableByteChannel encryptingChannel =
103         streamingAead.newEncryptingChannel(
104             Channels.newChannel(ciphertextOutputStream), associatedData)) {
105       writeToChannel(encryptingChannel, plaintext);
106     }
107     byte[] ciphertext = ciphertextOutputStream.toByteArray();
108 
109     // Decrypt
110     byte[] decrypted = null;
111     ReadableByteChannel ciphertextSource =
112         Channels.newChannel(new ByteArrayInputStream(ciphertext));
113     try (ReadableByteChannel decryptingChannel =
114         streamingAead.newDecryptingChannel(ciphertextSource, associatedData)) {
115       decrypted = readFromChannel(decryptingChannel, plaintext.length);
116     }
117     assertThat(decrypted).isEqualTo(plaintext);
118 
119     // Decrypt with invalid associatedData fails
120     byte[] invalidAssociatedData = "invalid".getBytes(UTF_8);
121     ByteBuffer decrypted2 = ByteBuffer.allocate(plaintext.length);
122     ReadableByteChannel ciphertextSource2 =
123         Channels.newChannel(new ByteArrayInputStream(ciphertext));
124     try (ReadableByteChannel decryptingChannel =
125         streamingAead.newDecryptingChannel(ciphertextSource2, invalidAssociatedData)) {
126       assertThrows(IOException.class, () -> decryptingChannel.read(decrypted2));
127     }
128   }
129 
130   // A keyset with one StreamingAead key, serialized in Tink's JSON format.
131   private static final String JSON_STREAMING_AEAD_KEYSET =
132       ""
133       + "{"
134       + "  \"primaryKeyId\": 1261393457,"
135       + "  \"key\": ["
136       + "    {"
137       + "      \"keyData\": {"
138       + "        \"typeUrl\":"
139       + "\"type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey\","
140       + "        \"value\": \"GhBqEXuGvNvVCRjJX1IMvC0kEg4iBBAgCAMYAxAQCICAQA==\","
141       + "        \"keyMaterialType\": \"SYMMETRIC\""
142       + "      },"
143       + "      \"status\": \"ENABLED\","
144       + "      \"keyId\": 1261393457,"
145       + "      \"outputPrefixType\": \"RAW\""
146       + "    }"
147       + "  ]"
148       + "}";
149 
150   @Theory
readKeyset_encryptDecrypt_success()151   public void readKeyset_encryptDecrypt_success() throws Exception {
152     KeysetHandle handle =
153         TinkJsonProtoKeysetFormat.parseKeyset(
154             JSON_STREAMING_AEAD_KEYSET, InsecureSecretKeyAccess.get());
155     StreamingAead streamingAead =
156         handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class);
157 
158     byte[] plaintext = "plaintext".getBytes(UTF_8);
159     byte[] associatedData = "associatedData".getBytes(UTF_8);
160 
161     ByteArrayOutputStream ciphertextOutputStream = new ByteArrayOutputStream();
162     try (WritableByteChannel encryptingChannel =
163         streamingAead.newEncryptingChannel(
164             Channels.newChannel(ciphertextOutputStream), associatedData)) {
165       writeToChannel(encryptingChannel, plaintext);
166     }
167     byte[] ciphertext = ciphertextOutputStream.toByteArray();
168 
169     byte[] decrypted = null;
170     ReadableByteChannel ciphertextSource =
171         Channels.newChannel(new ByteArrayInputStream(ciphertext));
172     try (ReadableByteChannel decryptingChannel =
173         streamingAead.newDecryptingChannel(ciphertextSource, associatedData)) {
174       decrypted = readFromChannel(decryptingChannel, plaintext.length);
175     }
176     assertThat(decrypted).isEqualTo(plaintext);
177   }
178 
179   // A keyset with multiple keys. The first key is the same as in JSON_STREAMING_AEAD_KEYSET.
180   private static final String JSON_STREAMING_AEAD_KEYSET_WITH_MULTIPLE_KEYS =
181       ""
182           + "{"
183           + "  \"primaryKeyId\": 1539463392,"
184           + "  \"key\": ["
185           + "    {"
186           + "      \"keyData\": {"
187           + "        \"typeUrl\":"
188           + "\"type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey\","
189           + "        \"value\": \"GhBqEXuGvNvVCRjJX1IMvC0kEg4iBBAgCAMYAxAQCICAQA==\","
190           + "        \"keyMaterialType\": \"SYMMETRIC\""
191           + "      },"
192           + "      \"status\": \"ENABLED\","
193           + "      \"keyId\": 1261393457,"
194           + "      \"outputPrefixType\": \"RAW\""
195           + "    },"
196           + "    {"
197           + "      \"keyData\": {"
198           + "        \"typeUrl\":"
199           + "\"type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey\","
200           + "        \"value\":"
201           + "\"GiA33jWXeuaAVvmFGQdU71KKA1K0rUQV8moj5LupxpgCJRIOIgQQIAgDGAMQIAiAgEA=\","
202           + "        \"keyMaterialType\": \"SYMMETRIC\""
203           + "      },"
204           + "      \"status\": \"ENABLED\","
205           + "      \"keyId\": 1539463392,"
206           + "      \"outputPrefixType\": \"RAW\""
207           + "    },"
208           + "    {"
209           + "      \"keyData\": {"
210           + "        \"typeUrl\":"
211           + "\"type.googleapis.com/google.crypto.tink.AesCtrHmacStreamingKey\","
212           + "        \"value\":"
213           + "\"GiBpie88LEnKNiGNtyDiUwDmDzeHgrpmf4k2tC1OaWUpcRIOIgQQIAgDGAMQIAiAgEA=\","
214           + "        \"keyMaterialType\": \"SYMMETRIC\""
215           + "      },"
216           + "      \"status\": \"ENABLED\","
217           + "      \"keyId\": 552736913,"
218           + "      \"outputPrefixType\": \"RAW\""
219           + "    }"
220           + "  ]"
221           + "}";
222 
223   @Theory
multipleKeysReadKeyset_encryptDecrypt_success()224   public void multipleKeysReadKeyset_encryptDecrypt_success() throws Exception {
225     KeysetHandle handle =
226         TinkJsonProtoKeysetFormat.parseKeyset(
227             JSON_STREAMING_AEAD_KEYSET_WITH_MULTIPLE_KEYS, InsecureSecretKeyAccess.get());
228     StreamingAead streamingAead =
229         handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class);
230 
231     byte[] plaintext = "plaintext".getBytes(UTF_8);
232     byte[] associatedData = "associatedData".getBytes(UTF_8);
233 
234     ByteArrayOutputStream ciphertextOutputStream = new ByteArrayOutputStream();
235     try (WritableByteChannel encryptingChannel =
236         streamingAead.newEncryptingChannel(
237             Channels.newChannel(ciphertextOutputStream), associatedData)) {
238       writeToChannel(encryptingChannel, plaintext);
239     }
240     byte[] ciphertext = ciphertextOutputStream.toByteArray();
241 
242     byte[] decrypted = null;
243     ReadableByteChannel ciphertextSource =
244         Channels.newChannel(new ByteArrayInputStream(ciphertext));
245     try (ReadableByteChannel decryptingChannel =
246         streamingAead.newDecryptingChannel(ciphertextSource, associatedData)) {
247       decrypted = readFromChannel(decryptingChannel, plaintext.length);
248     }
249     assertThat(decrypted).isEqualTo(plaintext);
250   }
251 
252   // A keyset with a valid DeterministicAead key. This keyset can't be used with the StreamingAead
253   // primitive.
254   private static final String JSON_DAEAD_KEYSET =
255       ""
256           + "{"
257           + "  \"primaryKeyId\": 961932622,"
258           + "  \"key\": ["
259           + "    {"
260           + "      \"keyData\": {"
261           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesSivKey\","
262           + "        \"keyMaterialType\": \"SYMMETRIC\","
263           + "        \"value\": \"EkCJ9r5iwc5uxq5ugFyrHXh5dijTa7qalWUgZ8Gf08RxNd545FjtLMYL7ObcaFtCS"
264           + "kvV2+7u6F2DN+kqUjAfkf2W\""
265           + "      },"
266           + "      \"outputPrefixType\": \"TINK\","
267           + "      \"keyId\": 961932622,"
268           + "      \"status\": \"ENABLED\""
269           + "    }"
270           + "  ]"
271           + "}";
272 
273   @Theory
getPrimitiveFromNonStreamingAeadKeyset_throws()274   public void getPrimitiveFromNonStreamingAeadKeyset_throws()
275       throws Exception {
276     KeysetHandle handle =
277         TinkJsonProtoKeysetFormat.parseKeyset(
278             JSON_DAEAD_KEYSET, InsecureSecretKeyAccess.get());
279     // Test that the keyset can create a DeterministicAead primitive, but not a StreamingAead.
280     Object unused = handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class);
281     assertThrows(
282         GeneralSecurityException.class,
283         () -> handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class));
284   }
285 }
286