1 // Copyright 2017 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 java.nio.charset.StandardCharsets.UTF_8; 21 import static org.junit.Assert.assertThrows; 22 23 import com.google.crypto.tink.HybridDecrypt; 24 import com.google.crypto.tink.HybridEncrypt; 25 import com.google.crypto.tink.KeysetHandle; 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.internal.MonitoringAnnotations; 30 import com.google.crypto.tink.internal.MutableMonitoringRegistry; 31 import com.google.crypto.tink.internal.MutablePrimitiveRegistry; 32 import com.google.crypto.tink.internal.PrimitiveConstructor; 33 import com.google.crypto.tink.internal.PrimitiveRegistry; 34 import com.google.crypto.tink.internal.testing.FakeMonitoringClient; 35 import com.google.crypto.tink.proto.Keyset; 36 import com.google.protobuf.ExtensionRegistryLite; 37 import java.security.GeneralSecurityException; 38 import java.util.List; 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 import org.junit.runners.JUnit4; 43 44 /** Tests for HybridEncryptWrapper. */ 45 @RunWith(JUnit4.class) 46 public class HybridEncryptWrapperTest { 47 @Before setUp()48 public void setUp() throws Exception { 49 MutablePrimitiveRegistry.resetGlobalInstanceTestOnly(); 50 HybridConfig.register(); 51 } 52 53 @Test encryptNoPrefix_works()54 public void encryptNoPrefix_works() throws Exception { 55 HpkeParameters parameters = 56 HpkeParameters.builder() 57 .setVariant(HpkeParameters.Variant.NO_PREFIX) 58 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 59 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 60 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 61 .build(); 62 KeysetHandle handle = KeysetHandle.generateNew(parameters); 63 HybridEncrypt encrypter = 64 handle 65 .getPublicKeysetHandle() 66 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 67 HybridDecrypt decrypter = HpkeDecrypt.create((HpkePrivateKey) handle.getPrimary().getKey()); 68 69 byte[] message = "data".getBytes(UTF_8); 70 byte[] context = "context".getBytes(UTF_8); 71 assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message); 72 } 73 74 @Test encryptTinkPrefix_works()75 public void encryptTinkPrefix_works() throws Exception { 76 HpkeParameters parameters = 77 HpkeParameters.builder() 78 .setVariant(HpkeParameters.Variant.TINK) 79 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 80 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 81 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 82 .build(); 83 KeysetHandle handle = KeysetHandle.generateNew(parameters); 84 HybridEncrypt encrypter = 85 handle 86 .getPublicKeysetHandle() 87 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 88 HybridDecrypt decrypter = HpkeDecrypt.create((HpkePrivateKey) handle.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 encryptCrunchyPrefix_works()96 public void encryptCrunchyPrefix_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 HybridEncrypt encrypter = 106 handle 107 .getPublicKeysetHandle() 108 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 109 HybridDecrypt decrypter = HpkeDecrypt.create((HpkePrivateKey) handle.getPrimary().getKey()); 110 111 byte[] message = "data".getBytes(UTF_8); 112 byte[] context = "context".getBytes(UTF_8); 113 assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message); 114 } 115 116 @Test encryptEncryptsWithPrimary()117 public void encryptEncryptsWithPrimary() throws Exception { 118 HpkeParameters parameters = 119 HpkeParameters.builder() 120 .setVariant(HpkeParameters.Variant.NO_PREFIX) 121 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 122 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 123 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 124 .build(); 125 KeysetHandle handle = 126 KeysetHandle.newBuilder() 127 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 128 .addEntry( 129 KeysetHandle.generateEntryFromParameters(parameters).withRandomId().makePrimary()) 130 .addEntry(KeysetHandle.generateEntryFromParameters(parameters).withRandomId()) 131 .build(); 132 133 HybridEncrypt encrypter = 134 handle 135 .getPublicKeysetHandle() 136 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 137 HybridDecrypt decrypter = HpkeDecrypt.create((HpkePrivateKey) handle.getPrimary().getKey()); 138 139 byte[] message = "data".getBytes(UTF_8); 140 byte[] context = "context".getBytes(UTF_8); 141 assertThat(decrypter.decrypt(encrypter.encrypt(message, context), context)).isEqualTo(message); 142 } 143 144 @Test getPrimitiveNoPrimary_throwsNullPointerException()145 public void getPrimitiveNoPrimary_throwsNullPointerException() throws Exception { 146 HpkeParameters parameters = 147 HpkeParameters.builder() 148 .setVariant(HpkeParameters.Variant.NO_PREFIX) 149 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 150 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 151 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 152 .build(); 153 154 KeysetHandle handle = KeysetHandle.generateNew(parameters).getPublicKeysetHandle(); 155 Keyset keyset = 156 Keyset.parseFrom( 157 TinkProtoKeysetFormat.serializeKeysetWithoutSecret(handle), 158 ExtensionRegistryLite.getEmptyRegistry()); 159 Keyset keysetWithoutPrimary = keyset.toBuilder().clearPrimaryKeyId().build(); 160 // TODO(b/228140127) This should throw at primitive creation time. 161 assertThrows( 162 GeneralSecurityException.class, 163 () -> 164 TinkProtoKeysetFormat.parseKeysetWithoutSecret(keysetWithoutPrimary.toByteArray()) 165 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class) 166 .encrypt(new byte[0], new byte[0])); 167 } 168 169 @Test doesNotMonitorWithoutAnnotations()170 public void doesNotMonitorWithoutAnnotations() throws Exception { 171 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 172 MutableMonitoringRegistry.globalInstance().clear(); 173 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 174 HybridEncryptWrapper.register(); 175 176 HpkeParameters parameters = 177 HpkeParameters.builder() 178 .setVariant(HpkeParameters.Variant.NO_PREFIX) 179 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 180 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 181 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 182 .build(); 183 184 KeysetHandle handle = KeysetHandle.generateNew(parameters); 185 HybridEncrypt encrypt = 186 handle 187 .getPublicKeysetHandle() 188 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 189 190 byte[] message = "data".getBytes(UTF_8); 191 byte[] context = "context".getBytes(UTF_8); 192 Object unused = encrypt.encrypt(message, context); 193 194 assertThat(fakeMonitoringClient.getLogEntries()).isEmpty(); 195 assertThat(fakeMonitoringClient.getLogFailureEntries()).isEmpty(); 196 } 197 198 @Test monitorsWithAnnotations()199 public void monitorsWithAnnotations() throws Exception { 200 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 201 MutableMonitoringRegistry.globalInstance().clear(); 202 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 203 HybridEncryptWrapper.register(); 204 205 MonitoringAnnotations annotations = 206 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 207 208 HpkeParameters parameters = 209 HpkeParameters.builder() 210 .setVariant(HpkeParameters.Variant.NO_PREFIX) 211 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 212 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 213 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 214 .build(); 215 216 KeysetHandle privateHandle = 217 KeysetHandle.newBuilder() 218 .addEntry( 219 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary()) 220 .build(); 221 KeysetHandle publicHandle = 222 KeysetHandle.newBuilder(privateHandle.getPublicKeysetHandle()) 223 .setMonitoringAnnotations(annotations) 224 .build(); 225 226 HybridEncrypt encrypt = 227 publicHandle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 228 229 byte[] message = "data".getBytes(UTF_8); 230 byte[] context = "context".getBytes(UTF_8); 231 Object unused = encrypt.encrypt(message, context); 232 233 List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries(); 234 assertThat(logEntries).hasSize(1); 235 FakeMonitoringClient.LogEntry encryptEntry = logEntries.get(0); 236 assertThat(encryptEntry.getKeyId()).isEqualTo(123); 237 assertThat(encryptEntry.getPrimitive()).isEqualTo("hybrid_encrypt"); 238 assertThat(encryptEntry.getApi()).isEqualTo("encrypt"); 239 assertThat(encryptEntry.getNumBytesAsInput()).isEqualTo(message.length); 240 assertThat(encryptEntry.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 241 } 242 243 @Test monitorsWithAnnotations_multipleEncrypters_works()244 public void monitorsWithAnnotations_multipleEncrypters_works() throws Exception { 245 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 246 MutableMonitoringRegistry.globalInstance().clear(); 247 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 248 HybridEncryptWrapper.register(); 249 250 MonitoringAnnotations annotations = 251 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 252 253 HpkeParameters parameters = 254 HpkeParameters.builder() 255 .setVariant(HpkeParameters.Variant.NO_PREFIX) 256 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 257 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 258 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 259 .build(); 260 261 KeysetHandle privateHandle1 = 262 KeysetHandle.newBuilder() 263 .addEntry( 264 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary()) 265 .build(); 266 KeysetHandle publicHandle1 = 267 KeysetHandle.newBuilder(privateHandle1.getPublicKeysetHandle()) 268 .setMonitoringAnnotations(annotations) 269 .build(); 270 KeysetHandle privateHandle2 = 271 KeysetHandle.newBuilder() 272 .addEntry( 273 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(456).makePrimary()) 274 .build(); 275 KeysetHandle publicHandle2 = 276 KeysetHandle.newBuilder(privateHandle2.getPublicKeysetHandle()) 277 .setMonitoringAnnotations(annotations) 278 .build(); 279 280 HybridEncrypt encrypter1 = 281 publicHandle1.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 282 HybridEncrypt encrypter2 = 283 publicHandle2.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 284 byte[] message = "data".getBytes(UTF_8); 285 byte[] context = "context".getBytes(UTF_8); 286 Object unused = encrypter1.encrypt(message, context); 287 unused = encrypter2.encrypt(message, context); 288 289 List<FakeMonitoringClient.LogEntry> logEntries = fakeMonitoringClient.getLogEntries(); 290 assertThat(logEntries).hasSize(2); 291 FakeMonitoringClient.LogEntry encryptEntry1 = logEntries.get(0); 292 assertThat(encryptEntry1.getKeyId()).isEqualTo(123); 293 assertThat(encryptEntry1.getPrimitive()).isEqualTo("hybrid_encrypt"); 294 assertThat(encryptEntry1.getApi()).isEqualTo("encrypt"); 295 assertThat(encryptEntry1.getNumBytesAsInput()).isEqualTo(message.length); 296 assertThat(encryptEntry1.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 297 FakeMonitoringClient.LogEntry encryptEntry2 = logEntries.get(0); 298 assertThat(encryptEntry2.getKeyId()).isEqualTo(123); 299 assertThat(encryptEntry2.getPrimitive()).isEqualTo("hybrid_encrypt"); 300 assertThat(encryptEntry2.getApi()).isEqualTo("encrypt"); 301 assertThat(encryptEntry2.getNumBytesAsInput()).isEqualTo(message.length); 302 assertThat(encryptEntry2.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 303 } 304 305 private static class AlwaysFailingHybridEncrypt implements HybridEncrypt { AlwaysFailingHybridEncrypt(HpkePublicKey key)306 public AlwaysFailingHybridEncrypt(HpkePublicKey key) {} 307 308 @Override encrypt(byte[] message, byte[] contextInfo)309 public byte[] encrypt(byte[] message, byte[] contextInfo) throws GeneralSecurityException { 310 throw new GeneralSecurityException("AlwaysFailingHybridEncrypt always fails"); 311 } 312 } 313 314 @Test testAlwaysFailingPublicKeySignWithAnnotations_hasMonitoring()315 public void testAlwaysFailingPublicKeySignWithAnnotations_hasMonitoring() throws Exception { 316 FakeMonitoringClient fakeMonitoringClient = new FakeMonitoringClient(); 317 MutableMonitoringRegistry.globalInstance().clear(); 318 MutableMonitoringRegistry.globalInstance().registerMonitoringClient(fakeMonitoringClient); 319 MutablePrimitiveRegistry.resetGlobalInstanceTestOnly(); 320 MutablePrimitiveRegistry.globalInstance() 321 .registerPrimitiveConstructor( 322 PrimitiveConstructor.create( 323 AlwaysFailingHybridEncrypt::new, HpkePublicKey.class, HybridEncrypt.class)); 324 HybridEncryptWrapper.register(); 325 326 MonitoringAnnotations annotations = 327 MonitoringAnnotations.newBuilder().add("annotation_name", "annotation_value").build(); 328 HpkeParameters parameters = 329 HpkeParameters.builder() 330 .setVariant(HpkeParameters.Variant.NO_PREFIX) 331 .setKemId(HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) 332 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 333 .setAeadId(HpkeParameters.AeadId.AES_256_GCM) 334 .build(); 335 KeysetHandle privateHandle = 336 KeysetHandle.newBuilder() 337 .addEntry( 338 KeysetHandle.generateEntryFromParameters(parameters).withFixedId(123).makePrimary()) 339 .build(); 340 KeysetHandle publicHandle = 341 KeysetHandle.newBuilder(privateHandle.getPublicKeysetHandle()) 342 .setMonitoringAnnotations(annotations) 343 .build(); 344 HybridEncrypt encrypt = 345 publicHandle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 346 347 byte[] data = "data".getBytes(UTF_8); 348 byte[] context = "context".getBytes(UTF_8); 349 assertThrows(GeneralSecurityException.class, () -> encrypt.encrypt(data, context)); 350 351 assertThat(fakeMonitoringClient.getLogEntries()).isEmpty(); 352 353 List<FakeMonitoringClient.LogFailureEntry> failures = 354 fakeMonitoringClient.getLogFailureEntries(); 355 assertThat(failures).hasSize(1); 356 FakeMonitoringClient.LogFailureEntry signFailure = failures.get(0); 357 assertThat(signFailure.getPrimitive()).isEqualTo("hybrid_encrypt"); 358 assertThat(signFailure.getApi()).isEqualTo("encrypt"); 359 assertThat(signFailure.getKeysetInfo().getPrimaryKeyId()).isEqualTo(123); 360 assertThat(signFailure.getKeysetInfo().getAnnotations()).isEqualTo(annotations); 361 } 362 363 @Test registerToInternalPrimitiveRegistry_works()364 public void registerToInternalPrimitiveRegistry_works() throws Exception { 365 PrimitiveRegistry.Builder initialBuilder = PrimitiveRegistry.builder(); 366 PrimitiveRegistry initialRegistry = initialBuilder.build(); 367 PrimitiveRegistry.Builder processedBuilder = PrimitiveRegistry.builder(initialRegistry); 368 369 HybridEncryptWrapper.registerToInternalPrimitiveRegistry(processedBuilder); 370 PrimitiveRegistry processedRegistry = processedBuilder.build(); 371 372 assertThrows( 373 GeneralSecurityException.class, 374 () -> initialRegistry.getInputPrimitiveClass(HybridEncrypt.class)); 375 assertThat(processedRegistry.getInputPrimitiveClass(HybridEncrypt.class)) 376 .isEqualTo(HybridEncrypt.class); 377 } 378 } 379