• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 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 
21 import com.google.crypto.tink.HybridDecrypt;
22 import com.google.crypto.tink.HybridEncrypt;
23 import com.google.crypto.tink.InsecureSecretKeyAccess;
24 import com.google.crypto.tink.KeysetHandle;
25 import com.google.crypto.tink.Registry;
26 import com.google.crypto.tink.RegistryConfiguration;
27 import com.google.crypto.tink.TinkProtoKeysetFormat;
28 import com.google.crypto.tink.hybrid.internal.HpkeDecrypt;
29 import com.google.crypto.tink.hybrid.internal.HpkeEncrypt;
30 import com.google.crypto.tink.hybrid.internal.testing.LegacyHybridDecryptKeyManager;
31 import com.google.crypto.tink.hybrid.internal.testing.LegacyHybridEncryptKeyManager;
32 import com.google.crypto.tink.proto.HpkeAead;
33 import com.google.crypto.tink.proto.HpkeKdf;
34 import com.google.crypto.tink.proto.HpkeKem;
35 import com.google.crypto.tink.proto.HpkeParams;
36 import com.google.crypto.tink.proto.HpkePrivateKey;
37 import com.google.crypto.tink.proto.HpkePublicKey;
38 import com.google.crypto.tink.proto.KeyData;
39 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
40 import com.google.crypto.tink.proto.KeyStatusType;
41 import com.google.crypto.tink.proto.Keyset;
42 import com.google.crypto.tink.proto.OutputPrefixType;
43 import com.google.crypto.tink.subtle.Hex;
44 import com.google.crypto.tink.util.Bytes;
45 import com.google.crypto.tink.util.SecretBytes;
46 import com.google.protobuf.ByteString;
47 import com.google.protobuf.ExtensionRegistryLite;
48 import java.security.GeneralSecurityException;
49 import javax.annotation.Nullable;
50 import org.junit.BeforeClass;
51 import org.junit.Test;
52 import org.junit.experimental.theories.DataPoints;
53 import org.junit.experimental.theories.FromDataPoints;
54 import org.junit.experimental.theories.Theories;
55 import org.junit.experimental.theories.Theory;
56 import org.junit.runner.RunWith;
57 
58 /**
59  * This test attempts to test the case where a user registers their own key type with
60  * Registry.registerKeyManager() and then uses it.
61  */
62 @RunWith(Theories.class)
63 public final class KeyManagerIntegrationTest {
64   private static final String PRIVATE_TYPE_URL = "type.googleapis.com/custom.HpkePrivateKey";
65   private static final String PUBLIC_TYPE_URL = "type.googleapis.com/custom.HpkePublicKey";
66 
67   private static byte[] publicKeyByteArray;
68   private static byte[] privateKeyByteArray;
69 
70   @BeforeClass
setUpClass()71   public static void setUpClass() throws Exception {
72     // We register Tink and key manger, as a user would typically do if they add their own key type.
73     HybridConfig.register();
74     // Register the key managers the user would register. These have type URLs PRIVATE_TYPE_URL and
75     // PUBLIC_TYPE_URL, and interpret the keys as HpkePrivateKey and HpkePublicKey exactly
76     // as Tink would. However, the parameters need to be:
77     //  * DHKEM_X25519_HKDF_SHA256
78     //  * HKDF_SHA256
79     //  * AES_128_GCM
80     Registry.registerKeyManager(new LegacyHybridDecryptKeyManager(), true);
81     Registry.registerKeyManager(new LegacyHybridEncryptKeyManager(), false);
82 
83     publicKeyByteArray =
84         Hex.decode("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431");
85     privateKeyByteArray =
86         Hex.decode("52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736");
87   }
88 
getHpkeParams()89   private static HpkeParams getHpkeParams() {
90     return HpkeParams.newBuilder()
91         .setKem(HpkeKem.DHKEM_X25519_HKDF_SHA256)
92         .setKdf(HpkeKdf.HKDF_SHA256)
93         .setAead(HpkeAead.AES_128_GCM)
94         .build();
95   }
96 
getParameters(HpkeParameters.Variant variant)97   private static HpkeParameters getParameters(HpkeParameters.Variant variant)
98       throws GeneralSecurityException {
99     return HpkeParameters.builder()
100         .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
101         .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
102         .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
103         .setVariant(variant)
104         .build();
105   }
106 
107   @Test
testGetPublicKeyset_works()108   public void testGetPublicKeyset_works() throws Exception {
109     HpkePublicKey protoPublicKey =
110         HpkePublicKey.newBuilder()
111             .setVersion(0)
112             .setParams(getHpkeParams())
113             .setPublicKey(ByteString.copyFrom(publicKeyByteArray))
114             .build();
115     HpkePrivateKey protoPrivateKey =
116         HpkePrivateKey.newBuilder()
117             .setVersion(0)
118             .setPublicKey(protoPublicKey)
119             .setPrivateKey(ByteString.copyFrom(privateKeyByteArray))
120             .build();
121     KeyData keyData =
122         KeyData.newBuilder()
123             .setTypeUrl(PRIVATE_TYPE_URL)
124             .setValue(protoPrivateKey.toByteString())
125             .setKeyMaterialType(KeyMaterialType.ASYMMETRIC_PRIVATE)
126             .build();
127     Keyset keyset =
128         Keyset.newBuilder()
129             .addKey(
130                 Keyset.Key.newBuilder()
131                     .setKeyData(keyData)
132                     .setStatus(KeyStatusType.ENABLED)
133                     .setOutputPrefixType(OutputPrefixType.TINK)
134                     .setKeyId(0x23456789)
135                     .build())
136             .setPrimaryKeyId(0x23456789)
137             .build();
138 
139     KeysetHandle handle =
140         TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get());
141     KeysetHandle publicHandle = handle.getPublicKeysetHandle();
142 
143     Keyset publicKeyset =
144         Keyset.parseFrom(
145             TinkProtoKeysetFormat.serializeKeysetWithoutSecret(publicHandle),
146             ExtensionRegistryLite.getEmptyRegistry());
147 
148     assertThat(publicKeyset.getPrimaryKeyId()).isEqualTo(0x23456789);
149     assertThat(publicKeyset.getKeyCount()).isEqualTo(1);
150     assertThat(publicKeyset.getKey(0).getKeyId()).isEqualTo(0x23456789);
151     assertThat(publicKeyset.getKey(0).getStatus()).isEqualTo(KeyStatusType.ENABLED);
152     assertThat(publicKeyset.getKey(0).getOutputPrefixType()).isEqualTo(OutputPrefixType.TINK);
153     assertThat(publicKeyset.getKey(0).getKeyData().getTypeUrl()).isEqualTo(PUBLIC_TYPE_URL);
154     assertThat(publicKeyset.getKey(0).getKeyData().getKeyMaterialType())
155         .isEqualTo(KeyMaterialType.ASYMMETRIC_PUBLIC);
156     assertThat(
157             HpkePublicKey.parseFrom(
158                 publicKeyset.getKey(0).getKeyData().getValue(),
159                 ExtensionRegistryLite.getEmptyRegistry()))
160         .isEqualTo(protoPublicKey);
161   }
162 
163   @DataPoints("allOutputPrefixTypes")
164   public static final OutputPrefixType[] OUTPUT_PREFIX_TYPES =
165       new OutputPrefixType[] {
166         OutputPrefixType.LEGACY,
167         OutputPrefixType.CRUNCHY,
168         OutputPrefixType.TINK,
169         OutputPrefixType.RAW
170       };
171 
variantForOutputPrefix(OutputPrefixType outputPrefixType)172   private static HpkeParameters.Variant variantForOutputPrefix(OutputPrefixType outputPrefixType)
173       throws GeneralSecurityException {
174     switch (outputPrefixType) {
175       case LEGACY:
176       case CRUNCHY:
177         return HpkeParameters.Variant.CRUNCHY;
178       case TINK:
179         return HpkeParameters.Variant.TINK;
180       case RAW:
181         return HpkeParameters.Variant.NO_PREFIX;
182       default:
183         throw new GeneralSecurityException("Unknown output prefix type: " + outputPrefixType);
184     }
185   }
186 
187   /**
188    * This test encrypts using a keyset with one key, with the custom key manager. It then decrypts
189    * the ciphertext using normal Tink subtle HpkeDecrypt.
190    */
191   @Theory
testEncryptCustom_decryptBuiltIn_works( @romDataPoints"allOutputPrefixTypes") OutputPrefixType outputPrefixType)192   public void testEncryptCustom_decryptBuiltIn_works(
193       @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception {
194     HpkePublicKey protoPublicKey =
195         HpkePublicKey.newBuilder()
196             .setVersion(0)
197             .setParams(getHpkeParams())
198             .setPublicKey(ByteString.copyFrom(publicKeyByteArray))
199             .build();
200     KeyData keyData =
201         KeyData.newBuilder()
202             .setTypeUrl(PUBLIC_TYPE_URL)
203             .setValue(protoPublicKey.toByteString())
204             .setKeyMaterialType(KeyMaterialType.ASYMMETRIC_PUBLIC)
205             .build();
206     Keyset keyset =
207         Keyset.newBuilder()
208             .addKey(
209                 Keyset.Key.newBuilder()
210                     .setKeyData(keyData)
211                     .setStatus(KeyStatusType.ENABLED)
212                     .setOutputPrefixType(outputPrefixType)
213                     .setKeyId(0x23456789)
214                     .build())
215             .setPrimaryKeyId(0x23456789)
216             .build();
217 
218     KeysetHandle handle = TinkProtoKeysetFormat.parseKeysetWithoutSecret(keyset.toByteArray());
219     HybridEncrypt customEncrypter =
220         handle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
221 
222     byte[] message = new byte[] {1, 2, 3};
223     byte[] context = new byte[] {4};
224     byte[] ciphertext = customEncrypter.encrypt(message, context);
225 
226     @Nullable Integer idRequirement = outputPrefixType == OutputPrefixType.RAW ? null : 0x23456789;
227     HybridDecrypt tinkDecrypter =
228         HpkeDecrypt.create(
229             com.google.crypto.tink.hybrid.HpkePrivateKey.create(
230                 com.google.crypto.tink.hybrid.HpkePublicKey.create(
231                     getParameters(variantForOutputPrefix(outputPrefixType)),
232                     Bytes.copyFrom(publicKeyByteArray),
233                     idRequirement),
234                 SecretBytes.copyFrom(privateKeyByteArray, InsecureSecretKeyAccess.get())));
235     assertThat(tinkDecrypter.decrypt(ciphertext, context)).isEqualTo(message);
236   }
237 
238   /**
239    * This encrypts using the subtle Tink API, then decrypts using the custom key manager with a
240    * keyset with a single key.
241    */
242   @Theory
testDecryptCustom_encryptBuiltIn_works( @romDataPoints"allOutputPrefixTypes") OutputPrefixType outputPrefixType)243   public void testDecryptCustom_encryptBuiltIn_works(
244       @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception {
245     HpkePublicKey protoPublicKey =
246         HpkePublicKey.newBuilder()
247             .setVersion(0)
248             .setParams(getHpkeParams())
249             .setPublicKey(ByteString.copyFrom(publicKeyByteArray))
250             .build();
251     HpkePrivateKey protoPrivateKey =
252         HpkePrivateKey.newBuilder()
253             .setVersion(0)
254             .setPublicKey(protoPublicKey)
255             .setPrivateKey(ByteString.copyFrom(privateKeyByteArray))
256             .build();
257     KeyData keyData =
258         KeyData.newBuilder()
259             .setTypeUrl(PRIVATE_TYPE_URL)
260             .setValue(protoPrivateKey.toByteString())
261             .setKeyMaterialType(KeyMaterialType.ASYMMETRIC_PRIVATE)
262             .build();
263     Keyset keyset =
264         Keyset.newBuilder()
265             .addKey(
266                 Keyset.Key.newBuilder()
267                     .setKeyData(keyData)
268                     .setStatus(KeyStatusType.ENABLED)
269                     .setOutputPrefixType(outputPrefixType)
270                     .setKeyId(0x23456789)
271                     .build())
272             .setPrimaryKeyId(0x23456789)
273             .build();
274 
275     KeysetHandle handle =
276         TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get());
277     HybridDecrypt customDecrypter =
278         handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class);
279 
280     byte[] message = new byte[] {1, 2, 3};
281     byte[] context = new byte[] {4};
282 
283     @Nullable Integer idRequirement = outputPrefixType == OutputPrefixType.RAW ? null : 0x23456789;
284     HybridEncrypt tinkEncrypter =
285         HpkeEncrypt.create(
286             com.google.crypto.tink.hybrid.HpkePublicKey.create(
287                 getParameters(variantForOutputPrefix(outputPrefixType)),
288                 Bytes.copyFrom(publicKeyByteArray),
289                 idRequirement));
290     byte[] ciphertext = tinkEncrypter.encrypt(message, context);
291     assertThat(customDecrypter.decrypt(ciphertext, context)).isEqualTo(message);
292   }
293 }
294