• 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.hybrid;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.crypto.tink.internal.Util.isPrefix;
21 import static java.nio.charset.StandardCharsets.UTF_8;
22 import static org.junit.Assert.assertThrows;
23 
24 import com.google.crypto.tink.HybridDecrypt;
25 import com.google.crypto.tink.HybridEncrypt;
26 import com.google.crypto.tink.InsecureSecretKeyAccess;
27 import com.google.crypto.tink.KeysetHandle;
28 import com.google.crypto.tink.RegistryConfiguration;
29 import com.google.crypto.tink.aead.AesGcmParameters;
30 import com.google.crypto.tink.hybrid.internal.HpkeEncrypt;
31 import com.google.crypto.tink.internal.MonitoringAnnotations;
32 import com.google.crypto.tink.internal.MutableMonitoringRegistry;
33 import com.google.crypto.tink.internal.MutablePrimitiveRegistry;
34 import com.google.crypto.tink.internal.PrimitiveRegistry;
35 import com.google.crypto.tink.internal.testing.FakeMonitoringClient;
36 import com.google.crypto.tink.subtle.EciesAeadHkdfHybridDecrypt;
37 import com.google.crypto.tink.subtle.Hex;
38 import com.google.crypto.tink.util.SecretBigInteger;
39 import java.math.BigInteger;
40 import java.security.GeneralSecurityException;
41 import java.security.spec.ECPoint;
42 import java.util.List;
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.experimental.theories.Theories;
46 import org.junit.runner.RunWith;
47 
48 /** Tests for {@link HybridDecryptWrapper}. */
49 @RunWith(Theories.class)
50 public class HybridDecryptWrapperTest {
51   @Before
setUp()52   public void setUp() throws Exception {
53     MutablePrimitiveRegistry.resetGlobalInstanceTestOnly();
54     HybridConfig.register();
55   }
56 
57   @Test
decryptNoPrefix_works()58   public void decryptNoPrefix_works() throws Exception {
59     HpkeParameters parameters =
60         HpkeParameters.builder()
61             .setVariant(HpkeParameters.Variant.NO_PREFIX)
62             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
63             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
64             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
65             .build();
66     KeysetHandle handle = KeysetHandle.generateNew(parameters);
67     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
68     HybridEncrypt encrypter =
69         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey());
70 
71     byte[] message = "data".getBytes(UTF_8);
72     byte[] context = "context".getBytes(UTF_8);
73     assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message);
74   }
75 
76   @Test
decryptTink_works()77   public void decryptTink_works() throws Exception {
78     HpkeParameters parameters =
79         HpkeParameters.builder()
80             .setVariant(HpkeParameters.Variant.TINK)
81             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
82             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
83             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
84             .build();
85     KeysetHandle handle = KeysetHandle.generateNew(parameters);
86     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
87     HybridEncrypt encrypter =
88         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey());
89 
90     byte[] message = "data".getBytes(UTF_8);
91     byte[] context = "context".getBytes(UTF_8);
92     assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message);
93   }
94 
95   @Test
decryptCrunchy_works()96   public void decryptCrunchy_works() throws Exception {
97     HpkeParameters parameters =
98         HpkeParameters.builder()
99             .setVariant(HpkeParameters.Variant.CRUNCHY)
100             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
101             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
102             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
103             .build();
104     KeysetHandle handle = KeysetHandle.generateNew(parameters);
105     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
106     HybridEncrypt encrypter =
107         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey());
108 
109     byte[] message = "data".getBytes(UTF_8);
110     byte[] context = "context".getBytes(UTF_8);
111     assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message);
112   }
113 
114   @Test
decrypt_worksForEveryTinkKey()115   public void decrypt_worksForEveryTinkKey() throws Exception {
116     HpkeParameters parameters =
117         HpkeParameters.builder()
118             .setVariant(HpkeParameters.Variant.TINK)
119             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
120             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
121             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
122             .build();
123     KeysetHandle handle =
124         KeysetHandle.newBuilder()
125             .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId())
126             .addEntry(
127                 KeysetHandle.generateEntryFromParameters(parameters).withRandomId().makePrimary())
128             .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId())
129             .build();
130     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
131     HybridEncrypt encrypter0 =
132         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(0).getKey());
133     HybridEncrypt encrypter1 =
134         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(1).getKey());
135     HybridEncrypt encrypter2 =
136         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(2).getKey());
137 
138     byte[] message = "data".getBytes(UTF_8);
139     byte[] context = "context".getBytes(UTF_8);
140     assertThat(decrypter.decrypt(encrypter0.encrypt(message, context), context)).isEqualTo(message);
141     assertThat(decrypter.decrypt(encrypter1.encrypt(message, context), context)).isEqualTo(message);
142     assertThat(decrypter.decrypt(encrypter2.encrypt(message, context), context)).isEqualTo(message);
143   }
144 
145   @Test
decrypt_worksForEveryNoPrefixKey()146   public void decrypt_worksForEveryNoPrefixKey() throws Exception {
147     HpkeParameters parameters =
148         HpkeParameters.builder()
149             .setVariant(HpkeParameters.Variant.NO_PREFIX)
150             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
151             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
152             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
153             .build();
154     KeysetHandle handle =
155         KeysetHandle.newBuilder()
156             .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId())
157             .addEntry(
158                 KeysetHandle.generateEntryFromParameters(parameters).withRandomId().makePrimary())
159             .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId())
160             .build();
161     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
162     HybridEncrypt encrypter0 =
163         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(0).getKey());
164     HybridEncrypt encrypter1 =
165         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(1).getKey());
166     HybridEncrypt encrypter2 =
167         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(2).getKey());
168 
169     byte[] message = "data".getBytes(UTF_8);
170     byte[] context = "context".getBytes(UTF_8);
171     assertThat(decrypter.decrypt(encrypter0.encrypt(message, context), context)).isEqualTo(message);
172     assertThat(decrypter.decrypt(encrypter1.encrypt(message, context), context)).isEqualTo(message);
173     assertThat(decrypter.decrypt(encrypter2.encrypt(message, context), context)).isEqualTo(message);
174   }
175 
176   /**
177    * This test checks that we decrypt ciphertext even if it is encrypted under a NO_PREFIX
178    * non-primary, the primary is a Tink prefix primary, and the ciphertext matches the prefix by a
179    * 2^(-40) probability coincidence.
180    */
181   @Test
decrypt_rawCiphertextLookingLikeTinkCiphertext_createTest()182   public void decrypt_rawCiphertextLookingLikeTinkCiphertext_createTest() throws Exception {
183     // We are using LEGACY_UNCOMPRESSED because we can make the ciphertext start with 0x01 for this.
184     EciesParameters parameters =
185         EciesParameters.builder()
186             .setCurveType(EciesParameters.CurveType.NIST_P256)
187             .setHashType(EciesParameters.HashType.SHA256)
188             .setNistCurvePointFormat(EciesParameters.PointFormat.LEGACY_UNCOMPRESSED)
189             .setVariant(EciesParameters.Variant.NO_PREFIX)
190             .setDemParameters(
191                 AesGcmParameters.builder()
192                     .setIvSizeBytes(12)
193                     .setKeySizeBytes(16)
194                     .setTagSizeBytes(16)
195                     .setVariant(AesGcmParameters.Variant.NO_PREFIX)
196                     .build())
197             .build();
198     EciesPublicKey publicKey =
199         EciesPublicKey.createForNistCurve(
200             parameters,
201             new ECPoint(
202                 new BigInteger(
203                     "cc38c424b8c88e0d5726e0b05017b597e92c3dd8be412a458d12172180c6badd", 16),
204                 new BigInteger(
205                     "6ef995bf8e6a392dd038d0543b6f57f3e2283d0dc3a1c470faf6d4d0299ad80e", 16)),
206             /* idRequirement= */ null);
207     // This is now a private key for which we have a known ciphertext which starts with "0x01".
208     EciesPrivateKey privateKey =
209         EciesPrivateKey.createForNistCurve(
210             publicKey,
211             SecretBigInteger.fromBigInteger(
212                 new BigInteger(
213                     "57bd0131ccab56735932597e9414c4e9f6ed4a2d780f93d7d03573023100de5e", 16),
214                 InsecureSecretKeyAccess.get()));
215     // We verify the above claim first to make sure the test is correct.
216     byte[] message = "data".getBytes(UTF_8);
217     byte[] context = "context".getBytes(UTF_8);
218     byte[] ciphertext =
219         Hex.decode(
220             "01ae4755bd"
221                 + "66be54fdbfc60907e1ba0801dcc2f9a25c049f8fe1c2578d509019d048fd9dd2718b9b940711c0"
222                 + "10b86ca28786eb5a7b93da42ccd2ac950ea2614295f1bd6b0ad91e0369044ecfdd2fae8f31811472"
223                 + "426e4410fce68191f2cfe5aa");
224     {
225       HybridDecrypt decrypt = EciesAeadHkdfHybridDecrypt.create(privateKey);
226       assertThat(decrypt.decrypt(ciphertext, context)).isEqualTo(message);
227     }
228     HpkeParameters tinkHpkeParameters =
229         HpkeParameters.builder()
230             .setVariant(HpkeParameters.Variant.TINK)
231             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
232             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
233             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
234             .build();
235 
236     KeysetHandle handle =
237         KeysetHandle.newBuilder()
238             .addEntry(
239                 KeysetHandle.generateEntryFromParameters(tinkHpkeParameters)
240                     .withFixedId(0xae4755bd)
241                     .makePrimary())
242             .addEntry(KeysetHandle.importKey(privateKey).withRandomId())
243             .build();
244 
245     HybridDecrypt keysetHandleDecrypt =
246         handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
247     assertThat(keysetHandleDecrypt.decrypt(ciphertext, context)).isEqualTo(message);
248 
249     HybridEncrypt encrypt =
250         handle
251             .getPublicKeysetHandle()
252             .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
253     byte[] primaryCiphertext = encrypt.encrypt(message, context);
254     assertThat(keysetHandleDecrypt.decrypt(primaryCiphertext, context)).isEqualTo(message);
255     assertThat(isPrefix(Hex.decode("01ae4755bd"), primaryCiphertext)).isTrue();
256   }
257 
258   @Test
monitorsWithAnnotations()259   public void monitorsWithAnnotations() throws Exception {
260     FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient();
261     MutableMonitoringRegistry.globalInstance().clear();
262     MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient);
263     HybridDecryptWrapper.register();
264 
265     MonitoringAnnotations annotations =
266         MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build();
267     HpkeParameters parameters =
268         HpkeParameters.builder()
269             .setVariant(HpkeParameters.Variant.TINK)
270             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
271             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
272             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
273             .build();
274 
275     KeysetHandle handle =
276         KeysetHandle.newBuilder()
277             .addEntry(
278                 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary())
279             .setMonitoringAnnotations(annotations)
280             .build();
281 
282     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
283     HybridEncrypt encrypter =
284         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey());
285     byte[] data = "data".getBytes(UTF_8);
286     byte[] context = "context".getBytes(UTF_8);
287     byte[] ciphertext = encrypter.encrypt(data, context);
288     assertThat(decrypter.decrypt(ciphertext, context)).isEqualTo(data);
289 
290     List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries();
291     assertThat(logEntries).hasSize(1);
292     FakeMonitoringClient.LogEntry signEntry = logEntries.get(0);
293     assertThat(signEntry.getKeyId()).isEqualTo(123);
294     assertThat(signEntry.getPrimitive()).isEqualTo("hybrid_decrypt");
295     assertThat(signEntry.getApi()).isEqualTo("decrypt");
296     assertThat(signEntry.getNumBytesAsInput()).isEqualTo(ciphertext.length);
297     assertThat(signEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations);
298   }
299 
300   @Test
monitorsWithAnnotation_correctKeyIsAssociated()301   public void monitorsWithAnnotation_correctKeyIsAssociated() throws Exception {
302     FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient();
303     MutableMonitoringRegistry.globalInstance().clear();
304     MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient);
305     HybridDecryptWrapper.register();
306 
307     MonitoringAnnotations annotations =
308         MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build();
309     HpkeParameters parameters =
310         HpkeParameters.builder()
311             .setVariant(HpkeParameters.Variant.TINK)
312             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
313             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
314             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
315             .build();
316 
317     KeysetHandle handle =
318         KeysetHandle.newBuilder()
319             .addEntry(
320                 KeysetHandle.generateEntryFromParameters(
321                         HpkeParameters.builder()
322                             .setVariant(HpkeParameters.Variant.NO_PREFIX)
323                             .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256)
324                             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
325                             .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
326                             .build())
327                     .withFixedId(100))
328             .addEntry(
329                 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(200).makePrimary())
330             .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withFixedId(300))
331             .setMonitoringAnnotations(annotations)
332             .build();
333 
334     HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
335     HybridEncrypt encrypter0 =
336         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(0).getKey());
337     HybridEncrypt encrypter1 =
338         HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(1).getKey());
339     byte[] context = "context".getBytes(UTF_8);
340     byte[] ciphertext0 = encrypter0.encrypt(new byte[100], context);
341     Object unused = decrypter.decrypt(ciphertext0, context);
342     byte[] ciphertext1 = encrypter1.encrypt(new byte[200], context);
343     unused = decrypter.decrypt(ciphertext1, context);
344 
345     List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries();
346     assertThat(logEntries).hasSize(2);
347     FakeMonitoringClient.LogEntry signEntry0 = logEntries.get(0);
348     assertThat(signEntry0.getKeyId()).isEqualTo(100);
349     assertThat(signEntry0.getPrimitive()).isEqualTo("hybrid_decrypt");
350     assertThat(signEntry0.getApi()).isEqualTo("decrypt");
351     assertThat(signEntry0.getNumBytesAsInput()).isEqualTo(ciphertext0.length);
352     assertThat(signEntry0.getKeysetInfo().getAnnotations()).isEqualTo(annotations);
353 
354     FakeMonitoringClient.LogEntry signEntry1 = logEntries.get(1);
355     assertThat(signEntry1.getKeyId()).isEqualTo(200);
356     assertThat(signEntry1.getPrimitive()).isEqualTo("hybrid_decrypt");
357     assertThat(signEntry1.getApi()).isEqualTo("decrypt");
358     assertThat(signEntry1.getNumBytesAsInput()).isEqualTo(ciphertext1.length);
359     assertThat(signEntry1.getKeysetInfo().getAnnotations()).isEqualTo(annotations);
360   }
361 
362   @Test
registerToInternalPrimitiveRegistry_works()363   public void registerToInternalPrimitiveRegistry_works() throws Exception {
364     PrimitiveRegistry.Builder initialBuilder = PrimitiveRegistry.builder();
365     PrimitiveRegistry initialRegistry = initialBuilder.build();
366     PrimitiveRegistry.Builder processedBuilder = PrimitiveRegistry.builder(initialRegistry);
367 
368     HybridDecryptWrapper.registerToInternalPrimitiveRegistry(processedBuilder);
369     PrimitiveRegistry processedRegistry = processedBuilder.build();
370 
371     assertThrows(
372         GeneralSecurityException.class,
373         () -> initialRegistry.getInputPrimitiveClass(HybridDecrypt.class));
374     assertThat(processedRegistry.getInputPrimitiveClass(HybridDecrypt.class))
375         .isEqualTo(HybridDecrypt.class);
376   }
377 }
378