• 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.daead;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertArrayEquals;
21 
22 import com.google.crypto.tink.DeterministicAead;
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.daead.internal.testing.LegacyAesSivTestKeyManager;
29 import com.google.crypto.tink.internal.EnumTypeProtoConverter;
30 import com.google.crypto.tink.proto.AesSivKey;
31 import com.google.crypto.tink.proto.KeyData;
32 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
33 import com.google.crypto.tink.proto.KeyStatusType;
34 import com.google.crypto.tink.proto.Keyset;
35 import com.google.crypto.tink.proto.OutputPrefixType;
36 import com.google.crypto.tink.subtle.AesSiv;
37 import com.google.crypto.tink.subtle.Random;
38 import com.google.crypto.tink.util.SecretBytes;
39 import com.google.protobuf.ByteString;
40 import com.google.protobuf.ExtensionRegistryLite;
41 import java.security.GeneralSecurityException;
42 import org.junit.BeforeClass;
43 import org.junit.Test;
44 import org.junit.experimental.theories.DataPoints;
45 import org.junit.experimental.theories.FromDataPoints;
46 import org.junit.experimental.theories.Theories;
47 import org.junit.experimental.theories.Theory;
48 import org.junit.runner.RunWith;
49 
50 /**
51  * This test attempts to test the case where a user registers their own key type with
52  * Registry.registerKeyManager() and then uses it.
53  */
54 @RunWith(Theories.class)
55 public final class KeyManagerIntegrationTest {
56   private static final String TYPE_URL = "type.googleapis.com/custom.AesSivKey";
57   private static final byte[] KEY_BYTES = Random.randBytes(64);
58   private static final int KEY_ID = 0x23456789;
59   private static final EnumTypeProtoConverter<OutputPrefixType, AesSivParameters.Variant>
60       OUTPUT_PREFIX_TYPE_CONVERTER =
61           EnumTypeProtoConverter.<OutputPrefixType, AesSivParameters.Variant>builder()
62               .add(OutputPrefixType.RAW, AesSivParameters.Variant.NO_PREFIX)
63               .add(OutputPrefixType.TINK, AesSivParameters.Variant.TINK)
64               .add(OutputPrefixType.CRUNCHY, AesSivParameters.Variant.CRUNCHY)
65               .add(OutputPrefixType.LEGACY, AesSivParameters.Variant.CRUNCHY)
66               .build();
67 
68   @BeforeClass
setUpClass()69   public static void setUpClass() throws Exception {
70     // Register Tink and the key manger, as a user would typically do if they add their own key
71     // type.
72     DeterministicAeadConfig.register();
73     // Register the key manager the user would register. This has the type url TYPE_URL and
74     // interprets the key as AesSivKey exactly as Tink would.
75     Registry.registerKeyManager(new LegacyAesSivTestKeyManager(), true);
76   }
77 
78   @Test
parseFromKeyset_works()79   public void parseFromKeyset_works() throws Exception {
80     AesSivKey protoKey =
81         AesSivKey.newBuilder().setVersion(0).setKeyValue(ByteString.copyFrom(KEY_BYTES)).build();
82     KeysetHandle handle = getKeysetHandleFromProtoKey(protoKey, OutputPrefixType.TINK);
83 
84     Keyset keyset =
85         Keyset.parseFrom(
86             TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get()),
87             ExtensionRegistryLite.getEmptyRegistry());
88 
89     assertThat(keyset.getPrimaryKeyId()).isEqualTo(KEY_ID);
90     assertThat(keyset.getKeyCount()).isEqualTo(1);
91     assertThat(keyset.getKey(0).getKeyId()).isEqualTo(KEY_ID);
92     assertThat(keyset.getKey(0).getStatus()).isEqualTo(KeyStatusType.ENABLED);
93     assertThat(keyset.getKey(0).getOutputPrefixType()).isEqualTo(OutputPrefixType.TINK);
94     assertThat(keyset.getKey(0).getKeyData().getTypeUrl()).isEqualTo(TYPE_URL);
95     assertThat(keyset.getKey(0).getKeyData().getKeyMaterialType())
96         .isEqualTo(KeyMaterialType.SYMMETRIC);
97     assertThat(
98             AesSivKey.parseFrom(
99                 keyset.getKey(0).getKeyData().getValue(), ExtensionRegistryLite.getEmptyRegistry()))
100         .isEqualTo(protoKey);
101   }
102 
103   @DataPoints("allOutputPrefixTypes")
104   public static final OutputPrefixType[] OUTPUT_PREFIX_TYPES =
105       new OutputPrefixType[] {
106         OutputPrefixType.CRUNCHY,
107         OutputPrefixType.TINK,
108         OutputPrefixType.RAW,
109         OutputPrefixType.LEGACY
110       };
111 
112   /**
113    * Encrypts using a keyset with one key, with the custom key manager and decrypts the ciphertext
114    * using normal Tink subtle decryptDeterministically.
115    */
116   @Theory
encryptCustom_decryptBuiltIn_works( @romDataPoints"allOutputPrefixTypes") OutputPrefixType outputPrefixType)117   public void encryptCustom_decryptBuiltIn_works(
118       @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception {
119     byte[] plaintext = Random.randBytes(20);
120     byte[] associatedData = Random.randBytes(20);
121     AesSivKey protoKey =
122         AesSivKey.newBuilder().setVersion(0).setKeyValue(ByteString.copyFrom(KEY_BYTES)).build();
123     KeysetHandle handle = getKeysetHandleFromProtoKey(protoKey, outputPrefixType);
124 
125     DeterministicAead customDaead =
126         handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class);
127     byte[] ciphertext = customDaead.encryptDeterministically(plaintext, associatedData);
128     byte[] ciphertext2 = customDaead.encryptDeterministically(plaintext, associatedData);
129     DeterministicAead tinkDaead = AesSiv.create(createKey(outputPrefixType));
130     byte[] decrypted = tinkDaead.decryptDeterministically(ciphertext, associatedData);
131     byte[] decrypted2 = tinkDaead.decryptDeterministically(ciphertext2, associatedData);
132 
133     assertArrayEquals(ciphertext, ciphertext2);
134     assertArrayEquals(plaintext, decrypted);
135     assertArrayEquals(plaintext, decrypted2);
136   }
137 
138   /**
139    * This encrypts using the subtle Tink API, then decrypts using the custom key manager with a
140    * keyset with a single key.
141    */
142   @Theory
encryptBuiltIn_decryptCustom_works( @romDataPoints"allOutputPrefixTypes") OutputPrefixType outputPrefixType)143   public void encryptBuiltIn_decryptCustom_works(
144       @FromDataPoints("allOutputPrefixTypes") OutputPrefixType outputPrefixType) throws Exception {
145     byte[] plaintext = Random.randBytes(20);
146     byte[] associatedData = Random.randBytes(20);
147 
148     DeterministicAead tinkDaead = AesSiv.create(createKey(outputPrefixType));
149     byte[] ciphertext = tinkDaead.encryptDeterministically(plaintext, associatedData);
150     byte[] ciphertext2 = tinkDaead.encryptDeterministically(plaintext, associatedData);
151     AesSivKey protoKey =
152         AesSivKey.newBuilder().setVersion(0).setKeyValue(ByteString.copyFrom(KEY_BYTES)).build();
153     KeysetHandle handle = getKeysetHandleFromProtoKey(protoKey, outputPrefixType);
154     DeterministicAead customDaead =
155         handle.getPrimitive(RegistryConfiguration.get(), DeterministicAead.class);
156     byte[] decrypted = customDaead.decryptDeterministically(ciphertext, associatedData);
157     byte[] decrypted2 = customDaead.decryptDeterministically(ciphertext2, associatedData);
158 
159     assertArrayEquals(ciphertext, ciphertext2);
160     assertArrayEquals(plaintext, decrypted);
161     assertArrayEquals(plaintext, decrypted2);
162   }
163 
getKeysetHandleFromProtoKey( AesSivKey protoKey, OutputPrefixType outputPrefixType)164   private static KeysetHandle getKeysetHandleFromProtoKey(
165       AesSivKey protoKey, OutputPrefixType outputPrefixType) throws GeneralSecurityException {
166     KeyData keyData =
167         KeyData.newBuilder()
168             .setTypeUrl(TYPE_URL)
169             .setValue(protoKey.toByteString())
170             .setKeyMaterialType(KeyMaterialType.SYMMETRIC)
171             .build();
172     Keyset keyset =
173         Keyset.newBuilder()
174             .addKey(
175                 Keyset.Key.newBuilder()
176                     .setKeyData(keyData)
177                     .setStatus(KeyStatusType.ENABLED)
178                     .setOutputPrefixType(outputPrefixType)
179                     .setKeyId(KEY_ID)
180                     .build())
181             .setPrimaryKeyId(KEY_ID)
182             .build();
183 
184     return TinkProtoKeysetFormat.parseKeyset(keyset.toByteArray(), InsecureSecretKeyAccess.get());
185   }
186 
createKey(OutputPrefixType outputPrefixType)187   private static com.google.crypto.tink.daead.AesSivKey createKey(OutputPrefixType outputPrefixType)
188       throws GeneralSecurityException {
189     return com.google.crypto.tink.daead.AesSivKey.builder()
190         .setParameters(
191             AesSivParameters.builder()
192                 .setKeySizeBytes(64)
193                 .setVariant(OUTPUT_PREFIX_TYPE_CONVERTER.fromProtoEnum(outputPrefixType))
194                 .build())
195         .setKeyBytes(SecretBytes.copyFrom(KEY_BYTES, InsecureSecretKeyAccess.get()))
196         .setIdRequirement(outputPrefixType == OutputPrefixType.RAW ? null : KEY_ID)
197         .build();
198   }
199 }
200