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