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