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