• 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;
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.AeadConfig;
24 import com.google.crypto.tink.aead.AesGcmParameters;
25 import com.google.crypto.tink.mac.MacConfig;
26 import com.google.crypto.tink.proto.KeyData;
27 import com.google.crypto.tink.proto.KeyStatusType;
28 import com.google.crypto.tink.proto.Keyset;
29 import com.google.crypto.tink.proto.OutputPrefixType;
30 import com.google.crypto.tink.signature.SignatureConfig;
31 import com.google.crypto.tink.subtle.Hex;
32 import com.google.protobuf.ByteString;
33 import java.io.ByteArrayOutputStream;
34 import java.security.GeneralSecurityException;
35 import org.junit.BeforeClass;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.junit.runners.JUnit4;
39 
40 @RunWith(JUnit4.class)
41 public final class TinkProtoKeysetFormatTest {
42 
43   @BeforeClass
setUp()44   public static void setUp() throws GeneralSecurityException {
45     MacConfig.register();
46     AeadConfig.register();
47     SignatureConfig.register();
48   }
49 
assertKeysetHandleAreEqual(KeysetHandle keysetHandle1, KeysetHandle keysetHandle2)50   private void assertKeysetHandleAreEqual(KeysetHandle keysetHandle1, KeysetHandle keysetHandle2)
51       throws Exception {
52     assertThat(keysetHandle2.equalsKeyset(keysetHandle1)).isTrue();
53   }
54 
generateKeyset()55   private KeysetHandle generateKeyset() throws GeneralSecurityException {
56     return KeysetHandle.newBuilder()
57         .addEntry(
58             KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_128BITTAG")
59                 .withRandomId()
60                 .makePrimary())
61         .addEntry(
62             KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_128BITTAG_RAW")
63                 .withRandomId())
64         .addEntry(
65             KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_256BITTAG")
66                 .withRandomId()
67                 .setStatus(KeyStatus.DESTROYED))
68         .addEntry(
69             KeysetHandle.generateEntryFromParametersName("HMAC_SHA256_256BITTAG_RAW")
70                 .withRandomId()
71                 .setStatus(KeyStatus.DISABLED))
72         .addEntry(KeysetHandle.generateEntryFromParametersName("AES256_CMAC").withRandomId())
73         .build();
74   }
75 
generatePublicKeyset()76   private KeysetHandle generatePublicKeyset() throws GeneralSecurityException {
77     return KeysetHandle.newBuilder()
78         .addEntry(
79             KeysetHandle.generateEntryFromParametersName("ECDSA_P256_RAW")
80                 .withRandomId()
81                 .setStatus(KeyStatus.DISABLED))
82         .addEntry(
83             KeysetHandle.generateEntryFromParametersName("ECDSA_P256").withRandomId().makePrimary())
84         .addEntry(
85             KeysetHandle.generateEntryFromParametersName("ECDSA_P521")
86                 .withRandomId()
87                 .setStatus(KeyStatus.DESTROYED))
88         .build()
89         .getPublicKeysetHandle();
90   }
91 
generateAead()92   private Aead generateAead() throws GeneralSecurityException {
93     KeysetHandle handle =
94         KeysetHandle.newBuilder()
95             .addEntry(
96                 KeysetHandle.generateEntryFromParametersName("AES128_CTR_HMAC_SHA256")
97                     .withRandomId()
98                     .makePrimary())
99             .build();
100     return handle.getPrimitive(RegistryConfiguration.get(), Aead.class);
101   }
102 
103   @Test
serializeAndParse_successWithSameKeyset()104   public void serializeAndParse_successWithSameKeyset() throws Exception {
105     KeysetHandle keysetHandle = generateKeyset();
106 
107     byte[] serializedKeyset =
108         TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get());
109     KeysetHandle parseKeysetHandle =
110         TinkProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get());
111 
112     assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle);
113   }
114 
115   @Test
serializeKeyset_withoutInsecureSecretKeyAccess_fails()116   public void serializeKeyset_withoutInsecureSecretKeyAccess_fails() throws Exception {
117     KeysetHandle keysetHandle = generateKeyset();
118 
119     assertThrows(
120         NullPointerException.class,
121         () -> TinkProtoKeysetFormat.serializeKeyset(keysetHandle, null));
122   }
123 
124   @Test
parseKeyset_withoutInsecureSecretKeyAccess_fails()125   public void parseKeyset_withoutInsecureSecretKeyAccess_fails() throws Exception {
126     byte[] serializedKeyset =
127         TinkProtoKeysetFormat.serializeKeyset(generateKeyset(), InsecureSecretKeyAccess.get());
128 
129     assertThrows(
130         NullPointerException.class,
131         () -> TinkProtoKeysetFormat.parseKeyset(serializedKeyset, null));
132   }
133 
134   @Test
parseInvalidSerializedKeyset_fails()135   public void parseInvalidSerializedKeyset_fails() throws Exception {
136     byte[] invalidSerializedKeyset = "invalid".getBytes(UTF_8);
137     assertThrows(
138         GeneralSecurityException.class,
139         () ->
140             TinkProtoKeysetFormat.parseKeyset(
141                 invalidSerializedKeyset, InsecureSecretKeyAccess.get()));
142   }
143 
144   @Test
parsingKeysetWithUnknownStatus_doesNotThrowButGetAtThrows()145   public void parsingKeysetWithUnknownStatus_doesNotThrowButGetAtThrows() throws Exception {
146     Keyset keyset =
147         Keyset.newBuilder()
148             .addKey(
149                 Keyset.Key.newBuilder()
150                     .setKeyData(
151                         KeyData.newBuilder()
152                             .setValue(ByteString.copyFromUtf8("value"))
153                             .setTypeUrl("unknown")
154                             .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
155                             .build())
156                     .setStatus(KeyStatusType.UNKNOWN_STATUS)
157                     .setKeyId(123)
158                     .setOutputPrefixType(OutputPrefixType.TINK)
159                     .build())
160             .setPrimaryKeyId(123)
161             .build();
162     KeysetHandle handle =
163         TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get());
164     assertThrows(IllegalStateException.class, () -> handle.getAt(0));
165 
166     // re-parse the KeysetHandle, as suggested in documentation of getAt.
167     assertThrows(GeneralSecurityException.class, () -> KeysetHandle.newBuilder(handle).build());
168   }
169 
170   @Test
parsingKeysetWithNonAsciiTypeUrl_doesNotThrowButGetAtThrows()171   public void parsingKeysetWithNonAsciiTypeUrl_doesNotThrowButGetAtThrows() throws Exception {
172     Keyset keyset =
173         Keyset.newBuilder()
174             .addKey(
175                 Keyset.Key.newBuilder()
176                     .setKeyData(
177                         KeyData.newBuilder()
178                             .setValue(ByteString.copyFromUtf8("value"))
179                             .setTypeUrl("\t")
180                             .setKeyMaterialType(KeyData.KeyMaterialType.SYMMETRIC)
181                             .build())
182                     .setStatus(KeyStatusType.ENABLED)
183                     .setKeyId(123)
184                     .setOutputPrefixType(OutputPrefixType.TINK)
185                     .build())
186             .setPrimaryKeyId(123)
187             .build();
188     KeysetHandle handle =
189         TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get());
190     assertThrows(IllegalStateException.class, () -> handle.getAt(0));
191 
192     // re-parse the KeysetHandle, as suggested in documentation of getAt.
193     assertThrows(GeneralSecurityException.class, () -> KeysetHandle.newBuilder(handle).build());
194   }
195 
196   @Test
serializeEncryptedAndParseEncrypted_successWithSameKeyset()197   public void serializeEncryptedAndParseEncrypted_successWithSameKeyset() throws Exception {
198     Aead keyEncryptionAead = generateAead();
199     KeysetHandle keysetHandle = generateKeyset();
200     byte[] associatedData = "associatedData".getBytes(UTF_8);
201 
202     byte[] serializedKeyset =
203         TinkProtoKeysetFormat.serializeEncryptedKeyset(
204             keysetHandle, keyEncryptionAead, associatedData);
205     KeysetHandle parseKeysetHandle =
206         TinkProtoKeysetFormat.parseEncryptedKeyset(
207             serializedKeyset, keyEncryptionAead, associatedData);
208 
209     assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle);
210   }
211 
212   @Test
parseEncryptedKeysetWithInvalidKey_fails()213   public void parseEncryptedKeysetWithInvalidKey_fails() throws Exception {
214     Aead keyEncryptionAead = generateAead();
215     Aead invalidKeyEncryptionAead = generateAead();
216     KeysetHandle keysetHandle = generateKeyset();
217     byte[] associatedData = "associatedData".getBytes(UTF_8);
218 
219     byte[] serializedKeyset =
220         TinkProtoKeysetFormat.serializeEncryptedKeyset(
221             keysetHandle, keyEncryptionAead, associatedData);
222 
223     assertThrows(
224         GeneralSecurityException.class,
225         () ->
226             TinkProtoKeysetFormat.parseEncryptedKeyset(
227                 serializedKeyset, invalidKeyEncryptionAead, associatedData));
228   }
229 
230   @Test
parseEncryptedKeysetWithInvalidAssociatedData_fails()231   public void parseEncryptedKeysetWithInvalidAssociatedData_fails() throws Exception {
232     Aead keyEncryptionAead = generateAead();
233     KeysetHandle keysetHandle = generateKeyset();
234 
235     byte[] serializedKeyset =
236         TinkProtoKeysetFormat.serializeEncryptedKeyset(
237             keysetHandle, keyEncryptionAead, "associatedData".getBytes(UTF_8));
238 
239     assertThrows(
240         GeneralSecurityException.class,
241         () ->
242             TinkProtoKeysetFormat.parseEncryptedKeyset(
243                 serializedKeyset, keyEncryptionAead, "invalidAssociatedData".getBytes(UTF_8)));
244   }
245 
246   @Test
serializeAndParseWithoutSecret_successWithSameKeyset()247   public void serializeAndParseWithoutSecret_successWithSameKeyset() throws Exception {
248     KeysetHandle publicKeysetHandle = generatePublicKeyset();
249 
250     byte[] serializedKeyset =
251         TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicKeysetHandle);
252     KeysetHandle parsePublicKeysetHandle =
253         TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedKeyset);
254 
255     assertKeysetHandleAreEqual(publicKeysetHandle, parsePublicKeysetHandle);
256   }
257 
258   @Test
serializeWithoutSecret_keysetWithSecretKeys_fails()259   public void serializeWithoutSecret_keysetWithSecretKeys_fails() throws Exception {
260     KeysetHandle secretKeysetHandle = generateKeyset();
261 
262     assertThrows(
263         GeneralSecurityException.class,
264         () ->
265             TinkProtoKeysetFormat.serializeKeysetWithoutSecret(secretKeysetHandle));
266   }
267 
268   @Test
parseWithoutSecret_keysetWithSecretKeys_fails()269   public void parseWithoutSecret_keysetWithSecretKeys_fails() throws Exception {
270     KeysetHandle secretKeysetHandle = generateKeyset();
271     byte[] serializedSecretKeyset =
272         TinkProtoKeysetFormat.serializeKeyset(secretKeysetHandle, InsecureSecretKeyAccess.get());
273 
274     assertThrows(
275         GeneralSecurityException.class,
276         () ->
277             TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedSecretKeyset));
278   }
279 
280   @Test
parseWithoutSecretInvalidSerializedKeyset_fails()281   public void parseWithoutSecretInvalidSerializedKeyset_fails() throws Exception {
282     byte[] invalidSerializedKeyset = "invalid".getBytes(UTF_8);
283     assertThrows(
284         GeneralSecurityException.class,
285         () -> TinkProtoKeysetFormat.parseKeysetWithoutSecret(invalidSerializedKeyset));
286   }
287 
288   @Test
serializeKeyset_worksWithCleartextKeysetHandleReadAndBinaryKeysetReader()289   public void serializeKeyset_worksWithCleartextKeysetHandleReadAndBinaryKeysetReader()
290       throws Exception {
291     KeysetHandle keysetHandle = generateKeyset();
292 
293     byte[] serializedKeyset =
294         TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get());
295 
296     KeysetHandle parseKeysetHandle =
297         CleartextKeysetHandle.read(BinaryKeysetReader.withBytes(serializedKeyset));
298 
299     assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle);
300   }
301 
302   @Test
parseKeyset_worksWithCleartextKeysetHandleWriteAndBinaryKeysetWriter()303   public void parseKeyset_worksWithCleartextKeysetHandleWriteAndBinaryKeysetWriter()
304       throws Exception {
305     KeysetHandle keysetHandle = generateKeyset();
306 
307     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
308     CleartextKeysetHandle.write(keysetHandle, BinaryKeysetWriter.withOutputStream(outputStream));
309     byte[] serializedKeyset = outputStream.toByteArray();
310 
311     KeysetHandle parseKeysetHandle =
312         TinkProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get());
313 
314     assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle);
315   }
316 
317   @Test
serializeKeysetWithoutSecret_worksWithKeysetHandleReadNoSecretAndBinaryKeysetReader()318   public void serializeKeysetWithoutSecret_worksWithKeysetHandleReadNoSecretAndBinaryKeysetReader()
319       throws Exception {
320     KeysetHandle publicKeysetHandle = generatePublicKeyset();
321 
322     byte[] serializedKeyset =
323         TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicKeysetHandle);
324 
325     KeysetHandle parsePublicKeysetHandle =
326         KeysetHandle.readNoSecret(BinaryKeysetReader.withBytes(serializedKeyset));
327 
328     assertKeysetHandleAreEqual(publicKeysetHandle, parsePublicKeysetHandle);
329   }
330 
331   @Test
parseKeysetWithoutSecret_worksWithKeysetHandleWriteNoSecretAndBinaryKeysetWriter()332   public void parseKeysetWithoutSecret_worksWithKeysetHandleWriteNoSecretAndBinaryKeysetWriter()
333       throws Exception {
334     KeysetHandle publicKeysetHandle = generatePublicKeyset();
335 
336     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
337     publicKeysetHandle.writeNoSecret(BinaryKeysetWriter.withOutputStream(outputStream));
338     byte[] serializedKeyset = outputStream.toByteArray();
339 
340     KeysetHandle parsePublicKeysetHandle =
341         TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedKeyset);
342 
343     assertKeysetHandleAreEqual(publicKeysetHandle, parsePublicKeysetHandle);
344   }
345 
346   @Test
serializeEncrypted_worksWithKeysetHandleReadWithAssociatedDataAndBinaryKeysetReader()347   public void serializeEncrypted_worksWithKeysetHandleReadWithAssociatedDataAndBinaryKeysetReader()
348       throws Exception {
349     Aead keyEncryptionAead = generateAead();
350     KeysetHandle keysetHandle = generateKeyset();
351     byte[] associatedData = "associatedData".getBytes(UTF_8);
352 
353     byte[] serializedKeyset =
354         TinkProtoKeysetFormat.serializeEncryptedKeyset(
355             keysetHandle, keyEncryptionAead, associatedData);
356 
357     KeysetHandle parseKeysetHandle =
358         KeysetHandle.readWithAssociatedData(
359             BinaryKeysetReader.withBytes(serializedKeyset), keyEncryptionAead, associatedData);
360 
361     assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle);
362   }
363 
364   @Test
parseEncrypted_worksWithKeysetHandleWriteWithAssociatedDataAndBinaryKeysetWriter()365   public void parseEncrypted_worksWithKeysetHandleWriteWithAssociatedDataAndBinaryKeysetWriter()
366       throws Exception {
367     Aead keyEncryptionAead = generateAead();
368     KeysetHandle keysetHandle = generateKeyset();
369     byte[] associatedData = "associatedData".getBytes(UTF_8);
370 
371     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
372     keysetHandle.writeWithAssociatedData(
373         BinaryKeysetWriter.withOutputStream(outputStream), keyEncryptionAead, associatedData);
374     byte[] serializedKeyset = outputStream.toByteArray();
375 
376     KeysetHandle parseKeysetHandle =
377         TinkProtoKeysetFormat.parseEncryptedKeyset(
378             serializedKeyset, keyEncryptionAead, associatedData);
379 
380     assertKeysetHandleAreEqual(keysetHandle, parseKeysetHandle);
381   }
382 
383   @Test
parseKeysetFromTestVector()384   public void parseKeysetFromTestVector()
385       throws Exception {
386     // This was generated in Python using the BinaryKeysetWriter. It contains one HMAC key.
387     byte[] serializedKeyset =
388         Hex.decode(
389             "0895e59bcc0612680a5c0a2e747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63"
390                 + "727970746f2e74696e6b2e486d61634b657912281a20cca20f02278003b3513f5d01759ac1302f7d"
391                 + "883f2f4a40025532ee1b11f9e587120410100803180110011895e59bcc062001");
392     KeysetHandle handle =
393         TinkProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get());
394     Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class);
395     mac.verifyMac(Hex.decode("016986f2956092d259136923c6f4323557714ec499"), "data".getBytes(UTF_8));
396   }
397 
398   @Test
parseEncryptedKeysetFromTestVector()399   public void parseEncryptedKeysetFromTestVector() throws Exception {
400     // This is the same test vector as in KeysetHandleTest.
401     // An AEAD key, with which we encrypted the mac keyset below.
402     final byte[] serializedKeysetEncryptionKeyset =
403         Hex.decode(
404             "08cd9bdff30312540a480a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970"
405                 + "746f2e74696e6b2e41657347636d4b657912121a1082bbe6de4bf9a7655305615af46e594c180110"
406                 + "0118cd9bdff3032001");
407     KeysetHandle keysetEncryptionHandle =
408         TinkProtoKeysetFormat.parseKeyset(
409             serializedKeysetEncryptionKeyset, InsecureSecretKeyAccess.get());
410     Aead keysetEncryptionAead =
411         keysetEncryptionHandle.getPrimitive(RegistryConfiguration.get(), Aead.class);
412 
413     // A keyset that contains one HMAC key, encrypted with the above, using associatedData
414     final byte[] encryptedSerializedKeyset =
415         Hex.decode(
416             "129101013e77cdcd28f57ffb418afa7f25d48a74efe720246e9aa538f33a702888bb7c48bce0e5a016a0c8"
417                 + "e9085066d67c7c7fb40dceb176a3a10c7f7ab30c564dd8e2d918a2fc2d2e9a0245c537ff6d1fd756"
418                 + "ff9d6de5cf4eb7f229de215e6e892f32fd703d0c9c3d2168813ad5bbc6ce108fcbfed0d9e3b14faa"
419                 + "e3e3789a891346d983b1ecca082f0546163351339aa142f574");
420     final byte[] associatedData = "associatedData".getBytes(UTF_8);
421 
422     KeysetHandle handle =
423         TinkProtoKeysetFormat.parseEncryptedKeyset(
424             encryptedSerializedKeyset, keysetEncryptionAead, associatedData);
425 
426     Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class);
427     final byte[] message = "data".getBytes(UTF_8);
428     final byte[] tag = Hex.decode("018f2d72de5055e622591fcf0fb85a7b4158e96f68");
429     mac.verifyMac(tag, message);
430   }
431 
432   @Test
serializationOverhead()433   public void serializationOverhead() throws Exception {
434     int ivSize = 12;
435     int keySize = 16;
436     int tagSize = 16;
437     AesGcmParameters aesGcm128Parameters =
438         AesGcmParameters.builder()
439             .setIvSizeBytes(ivSize)
440             .setKeySizeBytes(keySize)
441             .setTagSizeBytes(tagSize)
442             .setVariant(AesGcmParameters.Variant.NO_PREFIX)
443             .build();
444     KeysetHandle keysetHandle = KeysetHandle.generateNew(aesGcm128Parameters);
445     Aead keyEncryptionAead =
446         KeysetHandle.generateNew(aesGcm128Parameters)
447             .getPrimitive(RegistryConfiguration.get(), Aead.class);
448     byte[] serializedKeyset =
449         TinkProtoKeysetFormat.serializeKeyset(keysetHandle, InsecureSecretKeyAccess.get());
450 
451     byte[] rawEncryptedKeyset = keyEncryptionAead.encrypt(serializedKeyset, null);
452 
453     byte[] encryptedKeyset =
454         TinkProtoKeysetFormat.serializeEncryptedKeyset(keysetHandle, keyEncryptionAead, null);
455     // {@code encryptedKeyset} is a serialized protocol buffer that wraps the encrypted keyset bytes
456     // as a protobuf bytes field. So, it should only be slightly larger than {@code
457     // rawEncryptedKeyset}.
458     assertThat(encryptedKeyset.length).isLessThan(rawEncryptedKeyset.length + 6);
459   }
460 }
461