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 com.google.crypto.tink.internal.Util.isPrefix; 21 import static java.nio.charset.StandardCharsets.UTF_8; 22 import static org.junit.Assert.assertThrows; 23 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.KeysetHandle; 28 import com.google.crypto.tink.RegistryConfiguration; 29 import com.google.crypto.tink.aead.AesGcmParameters; 30 import com.google.crypto.tink.hybrid.internal.HpkeEncrypt; 31 import com.google.crypto.tink.internal.MonitoringAnnotations; 32 import com.google.crypto.tink.internal.MutableMonitoringRegistry; 33 import com.google.crypto.tink.internal.MutablePrimitiveRegistry; 34 import com.google.crypto.tink.internal.PrimitiveRegistry; 35 import com.google.crypto.tink.internal.testing.FakeMonitoringClient; 36 import com.google.crypto.tink.subtle.EciesAeadHkdfHybridDecrypt; 37 import com.google.crypto.tink.subtle.Hex; 38 import com.google.crypto.tink.util.SecretBigInteger; 39 import java.math.BigInteger; 40 import java.security.GeneralSecurityException; 41 import java.security.spec.ECPoint; 42 import java.util.List; 43 import org.junit.Before; 44 import org.junit.Test; 45 import org.junit.experimental.theories.Theories; 46 import org.junit.runner.RunWith; 47 48 /** Tests for {@link HybridDecryptWrapper}. */ 49 @RunWith(Theories.class) 50 public class HybridDecryptWrapperTest { 51 @Before setUp()52 public void setUp() throws Exception { 53 MutablePrimitiveRegistry.resetGlobalInstanceTestOnly(); 54 HybridConfig.register(); 55 } 56 57 @Test decryptNoPrefix_works()58 public void decryptNoPrefix_works() throws Exception { 59 HpkeParameters parameters = 60 HpkeParameters.builder() 61 .setVariant(HpkeParameters.Variant.NO_PREFIX) 62 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 63 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 64 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 65 .build(); 66 KeysetHandle handle = KeysetHandle.generateNew(parameters); 67 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 68 HybridEncrypt encrypter = 69 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey()); 70 71 byte[] message = "data".getBytes(UTF_8); 72 byte[] context = "context".getBytes(UTF_8); 73 assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message); 74 } 75 76 @Test decryptTink_works()77 public void decryptTink_works() throws Exception { 78 HpkeParameters parameters = 79 HpkeParameters.builder() 80 .setVariant(HpkeParameters.Variant.TINK) 81 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 82 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 83 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 84 .build(); 85 KeysetHandle handle = KeysetHandle.generateNew(parameters); 86 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 87 HybridEncrypt encrypter = 88 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey()); 89 90 byte[] message = "data".getBytes(UTF_8); 91 byte[] context = "context".getBytes(UTF_8); 92 assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message); 93 } 94 95 @Test decryptCrunchy_works()96 public void decryptCrunchy_works() throws Exception { 97 HpkeParameters parameters = 98 HpkeParameters.builder() 99 .setVariant(HpkeParameters.Variant.CRUNCHY) 100 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 101 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 102 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 103 .build(); 104 KeysetHandle handle = KeysetHandle.generateNew(parameters); 105 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 106 HybridEncrypt encrypter = 107 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey()); 108 109 byte[] message = "data".getBytes(UTF_8); 110 byte[] context = "context".getBytes(UTF_8); 111 assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message); 112 } 113 114 @Test decrypt_worksForEveryTinkKey()115 public void decrypt_worksForEveryTinkKey() throws Exception { 116 HpkeParameters parameters = 117 HpkeParameters.builder() 118 .setVariant(HpkeParameters.Variant.TINK) 119 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 120 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 121 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 122 .build(); 123 KeysetHandle handle = 124 KeysetHandle.newBuilder() 125 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 126 .addEntry( 127 KeysetHandle.generateEntryFromParameters(parameters).withRandomId().makePrimary()) 128 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 129 .build(); 130 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 131 HybridEncrypt encrypter0 = 132 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(0).getKey()); 133 HybridEncrypt encrypter1 = 134 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(1).getKey()); 135 HybridEncrypt encrypter2 = 136 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(2).getKey()); 137 138 byte[] message = "data".getBytes(UTF_8); 139 byte[] context = "context".getBytes(UTF_8); 140 assertThat(decrypter.decrypt(encrypter0.encrypt(message, context), context)).isEqualTo(message); 141 assertThat(decrypter.decrypt(encrypter1.encrypt(message, context), context)).isEqualTo(message); 142 assertThat(decrypter.decrypt(encrypter2.encrypt(message, context), context)).isEqualTo(message); 143 } 144 145 @Test decrypt_worksForEveryNoPrefixKey()146 public void decrypt_worksForEveryNoPrefixKey() throws Exception { 147 HpkeParameters parameters = 148 HpkeParameters.builder() 149 .setVariant(HpkeParameters.Variant.NO_PREFIX) 150 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 151 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 152 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 153 .build(); 154 KeysetHandle handle = 155 KeysetHandle.newBuilder() 156 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 157 .addEntry( 158 KeysetHandle.generateEntryFromParameters(parameters).withRandomId().makePrimary()) 159 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 160 .build(); 161 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 162 HybridEncrypt encrypter0 = 163 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(0).getKey()); 164 HybridEncrypt encrypter1 = 165 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(1).getKey()); 166 HybridEncrypt encrypter2 = 167 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(2).getKey()); 168 169 byte[] message = "data".getBytes(UTF_8); 170 byte[] context = "context".getBytes(UTF_8); 171 assertThat(decrypter.decrypt(encrypter0.encrypt(message, context), context)).isEqualTo(message); 172 assertThat(decrypter.decrypt(encrypter1.encrypt(message, context), context)).isEqualTo(message); 173 assertThat(decrypter.decrypt(encrypter2.encrypt(message, context), context)).isEqualTo(message); 174 } 175 176 /** 177 * This test checks that we decrypt ciphertext even if it is encrypted under a NO_PREFIX 178 * non-primary, the primary is a Tink prefix primary, and the ciphertext matches the prefix by a 179 * 2^(-40) probability coincidence. 180 */ 181 @Test decrypt_rawCiphertextLookingLikeTinkCiphertext_createTest()182 public void decrypt_rawCiphertextLookingLikeTinkCiphertext_createTest() throws Exception { 183 // We are using LEGACY_UNCOMPRESSED because we can make the ciphertext start with 0x01 for this. 184 EciesParameters parameters = 185 EciesParameters.builder() 186 .setCurveType(EciesParameters.CurveType.NIST_P256) 187 .setHashType(EciesParameters.HashType.SHA256) 188 .setNistCurvePointFormat(EciesParameters.PointFormat.LEGACY_UNCOMPRESSED) 189 .setVariant(EciesParameters.Variant.NO_PREFIX) 190 .setDemParameters( 191 AesGcmParameters.builder() 192 .setIvSizeBytes(12) 193 .setKeySizeBytes(16) 194 .setTagSizeBytes(16) 195 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 196 .build()) 197 .build(); 198 EciesPublicKey publicKey = 199 EciesPublicKey.createForNistCurve( 200 parameters, 201 new ECPoint( 202 new BigInteger( 203 "cc38c424b8c88e0d5726e0b05017b597e92c3dd8be412a458d12172180c6badd", 16), 204 new BigInteger( 205 "6ef995bf8e6a392dd038d0543b6f57f3e2283d0dc3a1c470faf6d4d0299ad80e", 16)), 206 /* idRequirement= */ null); 207 // This is now a private key for which we have a known ciphertext which starts with "0x01". 208 EciesPrivateKey privateKey = 209 EciesPrivateKey.createForNistCurve( 210 publicKey, 211 SecretBigInteger.fromBigInteger( 212 new BigInteger( 213 "57bd0131ccab56735932597e9414c4e9f6ed4a2d780f93d7d03573023100de5e", 16), 214 InsecureSecretKeyAccess.get())); 215 // We verify the above claim first to make sure the test is correct. 216 byte[] message = "data".getBytes(UTF_8); 217 byte[] context = "context".getBytes(UTF_8); 218 byte[] ciphertext = 219 Hex.decode( 220 "01ae4755bd" 221 + "66be54fdbfc60907e1ba0801dcc2f9a25c049f8fe1c2578d509019d048fd9dd2718b9b940711c0" 222 + "10b86ca28786eb5a7b93da42ccd2ac950ea2614295f1bd6b0ad91e0369044ecfdd2fae8f31811472" 223 + "426e4410fce68191f2cfe5aa"); 224 { 225 HybridDecrypt decrypt = EciesAeadHkdfHybridDecrypt.create(privateKey); 226 assertThat(decrypt.decrypt(ciphertext, context)).isEqualTo(message); 227 } 228 HpkeParameters tinkHpkeParameters = 229 HpkeParameters.builder() 230 .setVariant(HpkeParameters.Variant.TINK) 231 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 232 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 233 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 234 .build(); 235 236 KeysetHandle handle = 237 KeysetHandle.newBuilder() 238 .addEntry( 239 KeysetHandle.generateEntryFromParameters(tinkHpkeParameters) 240 .withFixedId(0xae4755bd) 241 .makePrimary()) 242 .addEntry(KeysetHandle.importKey(privateKey).withRandomId()) 243 .build(); 244 245 HybridDecrypt keysetHandleDecrypt = 246 handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 247 assertThat(keysetHandleDecrypt.decrypt(ciphertext, context)).isEqualTo(message); 248 249 HybridEncrypt encrypt = 250 handle 251 .getPublicKeysetHandle() 252 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 253 byte[] primaryCiphertext = encrypt.encrypt(message, context); 254 assertThat(keysetHandleDecrypt.decrypt(primaryCiphertext, context)).isEqualTo(message); 255 assertThat(isPrefix(Hex.decode("01ae4755bd"), primaryCiphertext)).isTrue(); 256 } 257 258 @Test monitorsWithAnnotations()259 public void monitorsWithAnnotations() throws Exception { 260 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 261 MutableMonitoringRegistry.globalInstance().clear(); 262 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 263 HybridDecryptWrapper.register(); 264 265 MonitoringAnnotations annotations = 266 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 267 HpkeParameters parameters = 268 HpkeParameters.builder() 269 .setVariant(HpkeParameters.Variant.TINK) 270 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 271 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 272 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 273 .build(); 274 275 KeysetHandle handle = 276 KeysetHandle.newBuilder() 277 .addEntry( 278 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary()) 279 .setMonitoringAnnotations(annotations) 280 .build(); 281 282 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 283 HybridEncrypt encrypter = 284 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getPrimary().getKey()); 285 byte[] data = "data".getBytes(UTF_8); 286 byte[] context = "context".getBytes(UTF_8); 287 byte[] ciphertext = encrypter.encrypt(data, context); 288 assertThat(decrypter.decrypt(ciphertext, context)).isEqualTo(data); 289 290 List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries(); 291 assertThat(logEntries).hasSize(1); 292 FakeMonitoringClient.LogEntry signEntry = logEntries.get(0); 293 assertThat(signEntry.getKeyId()).isEqualTo(123); 294 assertThat(signEntry.getPrimitive()).isEqualTo("hybrid_decrypt"); 295 assertThat(signEntry.getApi()).isEqualTo("decrypt"); 296 assertThat(signEntry.getNumBytesAsInput()).isEqualTo(ciphertext.length); 297 assertThat(signEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 298 } 299 300 @Test monitorsWithAnnotation_correctKeyIsAssociated()301 public void monitorsWithAnnotation_correctKeyIsAssociated() throws Exception { 302 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 303 MutableMonitoringRegistry.globalInstance().clear(); 304 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 305 HybridDecryptWrapper.register(); 306 307 MonitoringAnnotations annotations = 308 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 309 HpkeParameters parameters = 310 HpkeParameters.builder() 311 .setVariant(HpkeParameters.Variant.TINK) 312 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 313 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 314 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 315 .build(); 316 317 KeysetHandle handle = 318 KeysetHandle.newBuilder() 319 .addEntry( 320 KeysetHandle.generateEntryFromParameters( 321 HpkeParameters.builder() 322 .setVariant(HpkeParameters.Variant.NO_PREFIX) 323 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 324 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 325 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 326 .build()) 327 .withFixedId(100)) 328 .addEntry( 329 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(200).makePrimary()) 330 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withFixedId(300)) 331 .setMonitoringAnnotations(annotations) 332 .build(); 333 334 HybridDecrypt decrypter = handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 335 HybridEncrypt encrypter0 = 336 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(0).getKey()); 337 HybridEncrypt encrypter1 = 338 HpkeEncrypt.create((HpkePublicKey) handle.getPublicKeysetHandle().getAt(1).getKey()); 339 byte[] context = "context".getBytes(UTF_8); 340 byte[] ciphertext0 = encrypter0.encrypt(new byte[100], context); 341 Object unused = decrypter.decrypt(ciphertext0, context); 342 byte[] ciphertext1 = encrypter1.encrypt(new byte[200], context); 343 unused = decrypter.decrypt(ciphertext1, context); 344 345 List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries(); 346 assertThat(logEntries).hasSize(2); 347 FakeMonitoringClient.LogEntry signEntry0 = logEntries.get(0); 348 assertThat(signEntry0.getKeyId()).isEqualTo(100); 349 assertThat(signEntry0.getPrimitive()).isEqualTo("hybrid_decrypt"); 350 assertThat(signEntry0.getApi()).isEqualTo("decrypt"); 351 assertThat(signEntry0.getNumBytesAsInput()).isEqualTo(ciphertext0.length); 352 assertThat(signEntry0.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 353 354 FakeMonitoringClient.LogEntry signEntry1 = logEntries.get(1); 355 assertThat(signEntry1.getKeyId()).isEqualTo(200); 356 assertThat(signEntry1.getPrimitive()).isEqualTo("hybrid_decrypt"); 357 assertThat(signEntry1.getApi()).isEqualTo("decrypt"); 358 assertThat(signEntry1.getNumBytesAsInput()).isEqualTo(ciphertext1.length); 359 assertThat(signEntry1.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 360 } 361 362 @Test registerToInternalPrimitiveRegistry_works()363 public void registerToInternalPrimitiveRegistry_works() throws Exception { 364 PrimitiveRegistry.Builder initialBuilder = PrimitiveRegistry.builder(); 365 PrimitiveRegistry initialRegistry = initialBuilder.build(); 366 PrimitiveRegistry.Builder processedBuilder = PrimitiveRegistry.builder(initialRegistry); 367 368 HybridDecryptWrapper.registerToInternalPrimitiveRegistry(processedBuilder); 369 PrimitiveRegistry processedRegistry = processedBuilder.build(); 370 371 assertThrows( 372 GeneralSecurityException.class, 373 () -> initialRegistry.getInputPrimitiveClass(HybridDecrypt.class)); 374 assertThat(processedRegistry.getInputPrimitiveClass(HybridDecrypt.class)) 375 .isEqualTo(HybridDecrypt.class); 376 } 377 } 378