• 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 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.HybridDecrypt;
25 import com.google.crypto.tink.HybridEncrypt;
26 import com.google.crypto.tink.InsecureSecretKeyAccess;
27 import com.google.crypto.tink.KeyTemplates;
28 import com.google.crypto.tink.KeysetHandle;
29 import com.google.crypto.tink.RegistryConfiguration;
30 import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
31 import com.google.crypto.tink.daead.DeterministicAeadConfig;
32 import com.google.crypto.tink.testing.TestUtil;
33 import java.security.GeneralSecurityException;
34 import org.junit.BeforeClass;
35 import org.junit.experimental.theories.DataPoints;
36 import org.junit.experimental.theories.FromDataPoints;
37 import org.junit.experimental.theories.Theories;
38 import org.junit.experimental.theories.Theory;
39 import org.junit.runner.RunWith;
40 
41 /** Unit tests for the Hybrid package. Uses only the public API. */
42 @RunWith(Theories.class)
43 public final class HybridTest {
44 
45   @BeforeClass
setUp()46   public static void setUp() throws Exception {
47     HybridConfig.register();
48     DeterministicAeadConfig.register(); // Needed for getPrimitiveFromNonSignatureKeyset_throws.
49   }
50 
51   @DataPoints("templates")
52   public static final String[] TEMPLATES =
53       new String[] {
54         "ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM",
55         "ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_GCM_RAW",
56         "ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256_RAW",
57         "ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256",
58         "DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_AES_256_GCM_RAW",
59         "DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_CHACHA20_POLY1305",
60         "DHKEM_P256_HKDF_SHA256_HKDF_SHA256_AES_256_GCM",
61         "DHKEM_P384_HKDF_SHA384_HKDF_SHA384_AES_256_GCM_RAW",
62         "DHKEM_P521_HKDF_SHA512_HKDF_SHA512_AES_128_GCM"
63       };
64 
65   @Theory
createEncryptDecrypt(@romDataPoints"templates") String templateName)66   public void createEncryptDecrypt(@FromDataPoints("templates") String templateName)
67       throws Exception {
68     if (TestUtil.isTsan()) {
69       // KeysetHandle.generateNew is too slow in Tsan.
70       return;
71     }
72     KeysetHandle privateHandle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
73     KeysetHandle publicHandle = privateHandle.getPublicKeysetHandle();
74 
75     HybridEncrypt encrypter =
76         publicHandle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
77     HybridDecrypt decrypter =
78         privateHandle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
79 
80     byte[] plaintext = "plaintext".getBytes(UTF_8);
81     byte[] contextInfo = "contextInfo".getBytes(UTF_8);
82     byte[] ciphertext = encrypter.encrypt(plaintext, contextInfo);
83     assertThat(decrypter.decrypt(ciphertext, contextInfo)).isEqualTo(plaintext);
84 
85     KeysetHandle otherPrivateHandle = KeysetHandle.generateNew(KeyTemplates.get(templateName));
86     HybridDecrypt otherDecrypter =
87         otherPrivateHandle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
88     assertThrows(
89         GeneralSecurityException.class, () -> otherDecrypter.decrypt(ciphertext, contextInfo));
90 
91     byte[] invalid = "invalid".getBytes(UTF_8);
92     byte[] empty = "".getBytes(UTF_8);
93     assertThrows(GeneralSecurityException.class, () -> decrypter.decrypt(ciphertext, invalid));
94     assertThrows(GeneralSecurityException.class, () -> decrypter.decrypt(invalid, contextInfo));
95     assertThrows(GeneralSecurityException.class, () -> decrypter.decrypt(empty, contextInfo));
96     assertThat(decrypter.decrypt(encrypter.encrypt(empty, contextInfo), contextInfo))
97         .isEqualTo(empty);
98     assertThat(decrypter.decrypt(encrypter.encrypt(plaintext, empty), empty)).isEqualTo(plaintext);
99   }
100 
101   // Keyset with one private key for HybridDecrypt, serialized in Tink's JSON format.
102   private static final String JSON_PRIVATE_KEYSET = ""
103       + "{"
104       + "  \"primaryKeyId\": 1885000158,"
105       + "  \"key\": ["
106       + "    {"
107       + "      \"keyData\": {"
108       + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HpkePrivateKey\","
109       + "        \"value\": \"GiBXM1jmpJqe7HUTTkQxRwEld3bvIPTBhqGcI09ki9H0mRIqGiCwWh0y63G"
110       + "fObeWuYZcuLIiFz+15ElOFL7rhf9rbWxdBBIGGAEQAQgB\","
111       + "        \"keyMaterialType\": \"ASYMMETRIC_PRIVATE\""
112       + "      },"
113       + "      \"status\": \"ENABLED\","
114       + "      \"keyId\": 1885000158,"
115       + "      \"outputPrefixType\": \"TINK\""
116       + "    }"
117       + "  ]"
118       + "}";
119 
120   // Keyset with the corresponding public key for HybridEncrypt, serialized in Tink's JSON format.
121   private static final String JSON_PUBLIC_KEYSET = ""
122       + "{"
123       + "  \"primaryKeyId\": 1885000158,"
124       + "  \"key\": ["
125       + "    {"
126       + "      \"keyData\": {"
127       + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HpkePublicKey\","
128       + "        \"value\": \"GiCwWh0y63GfObeWuYZcuLIiFz+15ElOFL7rhf9rbWxdBBIGGAEQAQgB\","
129       + "        \"keyMaterialType\": \"ASYMMETRIC_PUBLIC\""
130       + "      },"
131       + "      \"status\": \"ENABLED\","
132       + "      \"keyId\": 1885000158,"
133       + "      \"outputPrefixType\": \"TINK\""
134       + "    }"
135       + "  ]"
136       + "}";
137 
138   @Theory
readKeysetEncryptDecrypt_success()139   public void readKeysetEncryptDecrypt_success()
140       throws Exception {
141     KeysetHandle privateHandle =
142         TinkJsonProtoKeysetFormat.parseKeyset(JSON_PRIVATE_KEYSET, InsecureSecretKeyAccess.get());
143     KeysetHandle publicHandle =
144         TinkJsonProtoKeysetFormat.parseKeyset(JSON_PUBLIC_KEYSET, InsecureSecretKeyAccess.get());
145 
146     HybridEncrypt encrypter =
147         publicHandle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
148     HybridDecrypt decrypter =
149         privateHandle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
150 
151     byte[] plaintext = "plaintext".getBytes(UTF_8);
152     byte[] contextInfo = "contextInfo".getBytes(UTF_8);
153     byte[] ciphertext = encrypter.encrypt(plaintext, contextInfo);
154     assertThat(decrypter.decrypt(ciphertext, contextInfo)).isEqualTo(plaintext);
155   }
156 
157   // Keyset with multiple keys. The first key is the same as in JSON_PRIVATE_KEYSET. The second
158   // key is the primary key and will be used for signing.
159   private static final String JSON_PRIVATE_KEYSET_WITH_MULTIPLE_KEYS =
160       ""
161           + "{"
162           + "  \"primaryKeyId\": 405658073,"
163           + "  \"key\": ["
164           + "    {"
165           + "      \"keyData\": {"
166           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HpkePrivateKey\","
167           + "        \"value\": \"GiBXM1jmpJqe7HUTTkQxRwEld3bvIPTBhqGcI09ki9H0mRIqGiCwWh0y63G"
168           + "fObeWuYZcuLIiFz+15ElOFL7rhf9rbWxdBBIGGAEQAQgB\","
169           + "        \"keyMaterialType\": \"ASYMMETRIC_PRIVATE\""
170           + "      },"
171           + "      \"status\": \"ENABLED\","
172           + "      \"keyId\": 1885000158,"
173           + "      \"outputPrefixType\": \"TINK\""
174           + "    },"
175           + "    {"
176           + "      \"keyData\": {"
177           + "        \"typeUrl\":"
178           + "\"type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey\","
179           + "        \"value\": \"GiAGLU3EgraobyU/aOJalcfR2jUUwK/ubd5mTYHIzLHBnBKiASIgJDF8fcN"
180           + "yDS6BcgYpeVPkJ2/ZBG+Mum30OId4D4CzDuQaIP9J2qo487Shr+MxMIkE3VvMro1r4Z+VFoTP3QWVTpz"
181           + "iElwYARJSElAYARISEggQIAoEEBAIAwoGEBAKAggQCjh0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5"
182           + "jcnlwdG8udGluay5BZXNDdHJIbWFjQWVhZEtleQoEEAMIAg==\","
183           + "        \"keyMaterialType\": \"ASYMMETRIC_PRIVATE\""
184           + "      },"
185           + "      \"status\": \"ENABLED\","
186           + "      \"keyId\": 405658073,"
187           + "      \"outputPrefixType\": \"RAW\""
188           + "    },"
189           + "    {"
190           + "      \"keyData\": {"
191           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HpkePrivateKey\","
192           + "        \"value\": \"GiAnd0VLE8exo149gJ49nkifg03YQLNnRMKfna0AdfYjnBIqGiABOUjRp8F"
193           + "QgppUbZlHCkxRgxGc3jYiChCkm+pf9BL3YhIGGAIQAQgB\","
194           + "        \"keyMaterialType\": \"ASYMMETRIC_PRIVATE\""
195           + "      },"
196           + "      \"status\": \"ENABLED\","
197           + "      \"keyId\": 2085058073,"
198           + "      \"outputPrefixType\": \"LEGACY\""
199           + "    }"
200           + "  ]"
201           + "}";
202 
203   // Keyset with the public keys of the keys from JSON_PRIVATE_KEYSET_WITH_MULTIPLE_KEYS.
204   private static final String JSON_PUBLIC_KEYSET_WITH_MULTIPLE_KEYS =
205       ""
206           + "{"
207           + "  \"primaryKeyId\": 405658073,"
208           + "  \"key\": ["
209           + "    {"
210           + "      \"keyData\": {"
211           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HpkePublicKey\","
212           + "        \"value\": \"GiCwWh0y63GfObeWuYZcuLIiFz+15ElOFL7rhf9rbWxdBBIGGAEQAQgB\","
213           + "        \"keyMaterialType\": \"ASYMMETRIC_PUBLIC\""
214           + "      },"
215           + "      \"status\": \"ENABLED\","
216           + "      \"keyId\": 1885000158,"
217           + "      \"outputPrefixType\": \"TINK\""
218           + "    },"
219           + "    {"
220           + "      \"keyData\": {"
221           + "        \"typeUrl\":"
222           + "\"type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey\","
223           + "        \"value\": \"IiAkMXx9w3INLoFyBil5U+Qnb9kEb4y6bfQ4h3gPgLMO5Bog/0naqjjztKG"
224           + "v4zEwiQTdW8yujWvhn5UWhM/dBZVOnOISXBgBElISUBgBEhISCBAgCgQQEAgDCgYQEAoCCBAKOHR5cGU"
225           + "uZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLmNyeXB0by50aW5rLkFlc0N0ckhtYWNBZWFkS2V5CgQQAwgC\","
226           + "        \"keyMaterialType\": \"ASYMMETRIC_PUBLIC\""
227           + "      },"
228           + "      \"status\": \"ENABLED\","
229           + "      \"keyId\": 405658073,"
230           + "      \"outputPrefixType\": \"RAW\""
231           + "    },"
232           + "    {"
233           + "      \"keyData\": {"
234           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.HpkePublicKey\","
235           + "        \"value\": \"GiABOUjRp8FQgppUbZlHCkxRgxGc3jYiChCkm+pf9BL3YhIGGAIQAQgB\","
236           + "        \"keyMaterialType\": \"ASYMMETRIC_PUBLIC\""
237           + "      },"
238           + "      \"status\": \"ENABLED\","
239           + "      \"keyId\": 2085058073,"
240           + "      \"outputPrefixType\": \"LEGACY\""
241           + "    }"
242           + "  ]"
243           + "}";
244 
245   @Theory
multipleKeysReadKeysetWithEncryptDecrypt()246   public void multipleKeysReadKeysetWithEncryptDecrypt()
247       throws Exception {
248     KeysetHandle privateHandle =
249         TinkJsonProtoKeysetFormat.parseKeyset(
250             JSON_PRIVATE_KEYSET_WITH_MULTIPLE_KEYS, InsecureSecretKeyAccess.get());
251     KeysetHandle publicHandle =
252         TinkJsonProtoKeysetFormat.parseKeyset(
253             JSON_PUBLIC_KEYSET_WITH_MULTIPLE_KEYS, InsecureSecretKeyAccess.get());
254 
255     HybridEncrypt encrypter =
256         publicHandle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
257     HybridDecrypt decrypter =
258         privateHandle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
259 
260     byte[] plaintext = "plaintext".getBytes(UTF_8);
261     byte[] contextInfo = "contextInfo".getBytes(UTF_8);
262     byte[] ciphertext = encrypter.encrypt(plaintext, contextInfo);
263     assertThat(decrypter.decrypt(ciphertext, contextInfo)).isEqualTo(plaintext);
264 
265     // Also test that decrypter can decrypt ciphertext of a non-primary key. We use
266     // JSON_PUBLIC_KEYSET to create a ciphertext with the first key.
267     KeysetHandle publicHandle1 =
268         TinkJsonProtoKeysetFormat.parseKeyset(
269             JSON_PUBLIC_KEYSET, InsecureSecretKeyAccess.get());
270     HybridEncrypt encrypter1 =
271         publicHandle1.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
272     byte[] ciphertext1 = encrypter1.encrypt(plaintext, contextInfo);
273     assertThat(decrypter.decrypt(ciphertext1, contextInfo)).isEqualTo(plaintext);
274   }
275 
276   // A keyset with a valid DeterministicAead key. This keyset can't be used with the HybridEncrypt
277   // or HybridDecrypt.
278   private static final String JSON_DAEAD_KEYSET =
279       ""
280           + "{"
281           + "  \"primaryKeyId\": 961932622,"
282           + "  \"key\": ["
283           + "    {"
284           + "      \"keyData\": {"
285           + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesSivKey\","
286           + "        \"keyMaterialType\": \"SYMMETRIC\","
287           + "        \"value\": \"EkCJ9r5iwc5uxq5ugFyrHXh5dijTa7qalWUgZ8Gf08RxNd545FjtLMYL7ObcaFtCS"
288           + "kvV2+7u6F2DN+kqUjAfkf2W\""
289           + "      },"
290           + "      \"outputPrefixType\": \"TINK\","
291           + "      \"keyId\": 961932622,"
292           + "      \"status\": \"ENABLED\""
293           + "    }"
294           + "  ]"
295           + "}";
296 
297   @Theory
getPrimitiveFromNonSignatureKeyset_throws()298   public void getPrimitiveFromNonSignatureKeyset_throws()
299       throws Exception {
300     KeysetHandle handle =
301         TinkJsonProtoKeysetFormat.parseKeyset(JSON_DAEAD_KEYSET, InsecureSecretKeyAccess.get());
302     // Test that the keyset can create a DeterministicAead primitive, but neither HybridEncrypt
303     // nor HybridDecrypt primitives.
304     Object unused = handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class);
305     assertThrows(
306         GeneralSecurityException.class,
307         () -> handle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class));
308     assertThrows(
309         GeneralSecurityException.class,
310         () -> handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class));
311   }
312 }
313