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 org.junit.Assert.assertThrows; 21 22 import com.google.crypto.tink.HybridDecrypt; 23 import com.google.crypto.tink.HybridEncrypt; 24 import com.google.crypto.tink.InsecureSecretKeyAccess; 25 import com.google.crypto.tink.KeyTemplate; 26 import com.google.crypto.tink.KeyTemplates; 27 import com.google.crypto.tink.KeysetHandle; 28 import com.google.crypto.tink.Parameters; 29 import com.google.crypto.tink.RegistryConfiguration; 30 import com.google.crypto.tink.TinkProtoKeysetFormat; 31 import com.google.crypto.tink.aead.AeadConfig; 32 import com.google.crypto.tink.aead.AesCtrHmacAeadParameters; 33 import com.google.crypto.tink.aead.AesGcmParameters; 34 import com.google.crypto.tink.aead.XChaCha20Poly1305Parameters; 35 import com.google.crypto.tink.hybrid.internal.testing.EciesAeadHkdfTestUtil; 36 import com.google.crypto.tink.hybrid.internal.testing.HybridTestVector; 37 import com.google.crypto.tink.internal.KeyManagerRegistry; 38 import com.google.crypto.tink.subtle.Hex; 39 import com.google.crypto.tink.subtle.X25519; 40 import com.google.crypto.tink.util.Bytes; 41 import com.google.crypto.tink.util.SecretBytes; 42 import java.math.BigInteger; 43 import java.security.GeneralSecurityException; 44 import java.util.Arrays; 45 import java.util.Set; 46 import java.util.TreeSet; 47 import javax.annotation.Nullable; 48 import org.junit.BeforeClass; 49 import org.junit.Test; 50 import org.junit.experimental.theories.DataPoints; 51 import org.junit.experimental.theories.FromDataPoints; 52 import org.junit.experimental.theories.Theories; 53 import org.junit.experimental.theories.Theory; 54 import org.junit.runner.RunWith; 55 56 /** Tests for EciesAeadHkdfPrivateKeyManager. */ 57 @RunWith(Theories.class) 58 public class EciesAeadHkdfPrivateKeyManagerTest { 59 @BeforeClass setUp()60 public static void setUp() throws Exception { 61 AeadConfig.register(); 62 HybridConfig.register(); 63 } 64 65 @Test testEciesP256HkdfHmacSha256Aes128GcmTemplate()66 public void testEciesP256HkdfHmacSha256Aes128GcmTemplate() throws Exception { 67 KeyTemplate template = 68 EciesAeadHkdfPrivateKeyManager.eciesP256HkdfHmacSha256Aes128GcmTemplate(); 69 assertThat(template.toParameters()) 70 .isEqualTo( 71 EciesParameters.builder() 72 .setCurveType(EciesParameters.CurveType.NIST_P256) 73 .setHashType(EciesParameters.HashType.SHA256) 74 .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) 75 .setVariant(EciesParameters.Variant.TINK) 76 .setDemParameters( 77 AesGcmParameters.builder() 78 .setIvSizeBytes(12) 79 .setKeySizeBytes(16) 80 .setTagSizeBytes(16) 81 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 82 .build()) 83 .build()); 84 } 85 86 @Test testRawEciesP256HkdfHmacSha256Aes128GcmCompressedTemplate()87 public void testRawEciesP256HkdfHmacSha256Aes128GcmCompressedTemplate() throws Exception { 88 KeyTemplate template = 89 EciesAeadHkdfPrivateKeyManager.rawEciesP256HkdfHmacSha256Aes128GcmCompressedTemplate(); 90 assertThat(template.toParameters()) 91 .isEqualTo( 92 EciesParameters.builder() 93 .setCurveType(EciesParameters.CurveType.NIST_P256) 94 .setHashType(EciesParameters.HashType.SHA256) 95 .setNistCurvePointFormat(EciesParameters.PointFormat.COMPRESSED) 96 .setVariant(EciesParameters.Variant.NO_PREFIX) 97 .setDemParameters( 98 AesGcmParameters.builder() 99 .setIvSizeBytes(12) 100 .setKeySizeBytes(16) 101 .setTagSizeBytes(16) 102 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 103 .build()) 104 .build()); 105 } 106 107 @Test testEciesP256HkdfHmacSha256Aes128CtrHmacSha256Template()108 public void testEciesP256HkdfHmacSha256Aes128CtrHmacSha256Template() throws Exception { 109 KeyTemplate template = 110 EciesAeadHkdfPrivateKeyManager.eciesP256HkdfHmacSha256Aes128CtrHmacSha256Template(); 111 112 assertThat(template.toParameters()) 113 .isEqualTo( 114 EciesParameters.builder() 115 .setCurveType(EciesParameters.CurveType.NIST_P256) 116 .setHashType(EciesParameters.HashType.SHA256) 117 .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) 118 .setVariant(EciesParameters.Variant.TINK) 119 .setDemParameters( 120 AesCtrHmacAeadParameters.builder() 121 .setAesKeySizeBytes(16) 122 .setHmacKeySizeBytes(32) 123 .setTagSizeBytes(16) 124 .setIvSizeBytes(16) 125 .setHashType(AesCtrHmacAeadParameters.HashType.SHA256) 126 .setVariant(AesCtrHmacAeadParameters.Variant.NO_PREFIX) 127 .build()) 128 .build()); 129 } 130 131 @Test testRawEciesP256HkdfHmacSha256Aes128CtrHmacSha256CompressedTemplate()132 public void testRawEciesP256HkdfHmacSha256Aes128CtrHmacSha256CompressedTemplate() 133 throws Exception { 134 KeyTemplate template = 135 EciesAeadHkdfPrivateKeyManager 136 .rawEciesP256HkdfHmacSha256Aes128CtrHmacSha256CompressedTemplate(); 137 138 assertThat(template.toParameters()) 139 .isEqualTo( 140 EciesParameters.builder() 141 .setCurveType(EciesParameters.CurveType.NIST_P256) 142 .setHashType(EciesParameters.HashType.SHA256) 143 .setNistCurvePointFormat(EciesParameters.PointFormat.COMPRESSED) 144 .setVariant(EciesParameters.Variant.NO_PREFIX) 145 .setDemParameters( 146 AesCtrHmacAeadParameters.builder() 147 .setAesKeySizeBytes(16) 148 .setHmacKeySizeBytes(32) 149 .setTagSizeBytes(16) 150 .setIvSizeBytes(16) 151 .setHashType(AesCtrHmacAeadParameters.HashType.SHA256) 152 .setVariant(AesCtrHmacAeadParameters.Variant.NO_PREFIX) 153 .build()) 154 .build()); 155 } 156 157 @Test testKeyTemplatesWork()158 public void testKeyTemplatesWork() throws Exception { 159 Parameters p = 160 EciesAeadHkdfPrivateKeyManager.eciesP256HkdfHmacSha256Aes128GcmTemplate().toParameters(); 161 assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p); 162 163 p = 164 EciesAeadHkdfPrivateKeyManager.rawEciesP256HkdfHmacSha256Aes128GcmCompressedTemplate() 165 .toParameters(); 166 assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p); 167 168 p = 169 EciesAeadHkdfPrivateKeyManager.eciesP256HkdfHmacSha256Aes128CtrHmacSha256Template() 170 .toParameters(); 171 assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p); 172 173 p = 174 EciesAeadHkdfPrivateKeyManager 175 .rawEciesP256HkdfHmacSha256Aes128CtrHmacSha256CompressedTemplate() 176 .toParameters(); 177 assertThat(KeysetHandle.generateNew(p).getAt(0).getKey().getParameters()).isEqualTo(p); 178 } 179 180 @DataPoints("templateNames") 181 public static final String[] KEY_TEMPLATES = 182 new String[] { 183 "ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM", 184 "ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM_RAW", 185 "ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_GCM", 186 "ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_GCM_RAW", 187 "ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256", 188 "ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256_RAW", 189 "ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256", 190 "ECIES_P256_COMPRESSED_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256_RAW", 191 }; 192 193 @Theory testTemplates(@romDataPoints"templateNames") String templateName)194 public void testTemplates(@FromDataPoints("templateNames") String templateName) throws Exception { 195 KeysetHandle h = KeysetHandle.generateNew(KeyTemplates.get(templateName)); 196 assertThat(h.size()).isEqualTo(1); 197 assertThat(h.getAt(0).getKey().getParameters()) 198 .isEqualTo(KeyTemplates.get(templateName).toParameters()); 199 } 200 201 @Test createKey_nistCurve_alwaysDifferent()202 public void createKey_nistCurve_alwaysDifferent() throws Exception { 203 Parameters params = KeyTemplates.get("ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM").toParameters(); 204 205 int numKeys = 10; 206 Set<BigInteger> keys = new TreeSet<>(); 207 for (int i = 0; i < numKeys; i++) { 208 KeysetHandle handle = KeysetHandle.generateNew(params); 209 assertThat(handle.size()).isEqualTo(1); 210 EciesPrivateKey key = (EciesPrivateKey) handle.getPrimary().getKey(); 211 keys.add(key.getNistPrivateKeyValue().getBigInteger(InsecureSecretKeyAccess.get())); 212 } 213 assertThat(keys).hasSize(numKeys); 214 } 215 216 @Test createKey_x25519Curve_throws()217 public void createKey_x25519Curve_throws() throws Exception { 218 Parameters params = 219 EciesParameters.builder() 220 .setCurveType(EciesParameters.CurveType.X25519) 221 .setHashType(EciesParameters.HashType.SHA256) 222 .setVariant(EciesParameters.Variant.NO_PREFIX) 223 .setDemParameters( 224 AesGcmParameters.builder() 225 .setIvSizeBytes(12) 226 .setKeySizeBytes(16) 227 .setTagSizeBytes(16) 228 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 229 .build()) 230 .build(); 231 assertThrows(GeneralSecurityException.class, () -> KeysetHandle.generateNew(params)); 232 } 233 234 @Test createPrimitive_x25519Curve_throws()235 public void createPrimitive_x25519Curve_throws() throws Exception { 236 EciesParameters params = 237 EciesParameters.builder() 238 .setHashType(EciesParameters.HashType.SHA256) 239 .setCurveType(EciesParameters.CurveType.X25519) 240 .setVariant(EciesParameters.Variant.NO_PREFIX) 241 .setDemParameters(XChaCha20Poly1305Parameters.create()) 242 .build(); 243 244 byte[] privateKeyBytes = X25519.generatePrivateKey(); 245 byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); 246 247 EciesPublicKey publicKey = 248 EciesPublicKey.createForCurveX25519( 249 params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ null); 250 EciesPrivateKey privateKey = 251 EciesPrivateKey.createForCurveX25519( 252 publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); 253 254 KeysetHandle.Builder.Entry entry = 255 KeysetHandle.importKey(privateKey).makePrimary().withRandomId(); 256 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 257 assertThrows( 258 GeneralSecurityException.class, 259 () -> handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class)); 260 } 261 262 @DataPoints("testVectors") 263 public static final HybridTestVector[] HYBRID_TEST_VECTORS = 264 EciesAeadHkdfTestUtil.createEciesTestVectors(); 265 266 @Theory test_decryptCiphertext_works(@romDataPoints"testVectors") HybridTestVector v)267 public void test_decryptCiphertext_works(@FromDataPoints("testVectors") HybridTestVector v) 268 throws Exception { 269 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getPrivateKey()).makePrimary(); 270 @Nullable Integer id = v.getPrivateKey().getIdRequirementOrNull(); 271 if (id == null) { 272 entry.withRandomId(); 273 } else { 274 entry.withFixedId(id); 275 } 276 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 277 HybridDecrypt hybridDecrypt = 278 handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 279 byte[] plaintext = hybridDecrypt.decrypt(v.getCiphertext(), v.getContextInfo()); 280 assertThat(Hex.encode(plaintext)).isEqualTo(Hex.encode(v.getPlaintext())); 281 } 282 283 @Theory test_decryptWrongContextInfo_throws(@romDataPoints"testVectors") HybridTestVector v)284 public void test_decryptWrongContextInfo_throws(@FromDataPoints("testVectors") HybridTestVector v) 285 throws Exception { 286 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getPrivateKey()).makePrimary(); 287 @Nullable Integer id = v.getPrivateKey().getIdRequirementOrNull(); 288 if (id == null) { 289 entry.withRandomId(); 290 } else { 291 entry.withFixedId(id); 292 } 293 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 294 HybridDecrypt hybridDecrypt = 295 handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 296 byte[] contextInfo = v.getContextInfo(); 297 if (contextInfo.length > 0) { 298 contextInfo[0] ^= 1; 299 } else { 300 contextInfo = new byte[] {1}; 301 } 302 // local variables referenced from a lambda expression must be final or effectively final 303 final byte[] contextInfoCopy = Arrays.copyOf(contextInfo, contextInfo.length); 304 assertThrows( 305 GeneralSecurityException.class, 306 () -> hybridDecrypt.decrypt(v.getCiphertext(), contextInfoCopy)); 307 } 308 309 @Theory test_encryptThenDecryptMessage_works( @romDataPoints"testVectors") HybridTestVector v)310 public void test_encryptThenDecryptMessage_works( 311 @FromDataPoints("testVectors") HybridTestVector v) throws Exception { 312 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getPrivateKey()).makePrimary(); 313 @Nullable Integer id = v.getPrivateKey().getIdRequirementOrNull(); 314 if (id == null) { 315 entry.withRandomId(); 316 } else { 317 entry.withFixedId(id); 318 } 319 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 320 HybridDecrypt hybridDecrypt = 321 handle.getPrimitive(RegistryConfiguration.get(), HybridDecrypt.class); 322 HybridEncrypt hybridEncrypt = 323 handle 324 .getPublicKeysetHandle() 325 .getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class); 326 byte[] ciphertext = hybridEncrypt.encrypt(v.getPlaintext(), v.getContextInfo()); 327 byte[] plaintext = hybridDecrypt.decrypt(ciphertext, v.getContextInfo()); 328 assertThat(Hex.encode(plaintext)).isEqualTo(Hex.encode(v.getPlaintext())); 329 } 330 331 @Test test_serializeAndParse_works()332 public void test_serializeAndParse_works() throws Exception { 333 HybridTestVector testVector = HYBRID_TEST_VECTORS[0]; 334 EciesPrivateKey key = (EciesPrivateKey) testVector.getPrivateKey(); 335 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(key).withFixedId(1216).makePrimary(); 336 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 337 338 byte[] serializedHandle = 339 TinkProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get()); 340 KeysetHandle parsedHandle = 341 TinkProtoKeysetFormat.parseKeyset(serializedHandle, InsecureSecretKeyAccess.get()); 342 assertThat(parsedHandle.equalsKeyset(handle)).isTrue(); 343 } 344 345 @Test test_serializeAndParse_publicKey_works()346 public void test_serializeAndParse_publicKey_works() throws Exception { 347 HybridTestVector testVector = HYBRID_TEST_VECTORS[0]; 348 EciesPrivateKey key = (EciesPrivateKey) testVector.getPrivateKey(); 349 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(key).withFixedId(1216).makePrimary(); 350 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build().getPublicKeysetHandle(); 351 352 byte[] serializedHandle = TinkProtoKeysetFormat.serializeKeysetWithoutSecret(handle); 353 KeysetHandle parsedHandle = TinkProtoKeysetFormat.parseKeysetWithoutSecret(serializedHandle); 354 assertThat(parsedHandle.equalsKeyset(handle)).isTrue(); 355 } 356 357 @Test testKeyManagerRegistered()358 public void testKeyManagerRegistered() throws Exception { 359 assertThat( 360 KeyManagerRegistry.globalInstance() 361 .getKeyManager( 362 "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey", 363 HybridDecrypt.class)) 364 .isNotNull(); 365 assertThat( 366 KeyManagerRegistry.globalInstance() 367 .getKeyManager( 368 "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey", 369 HybridEncrypt.class)) 370 .isNotNull(); 371 } 372 } 373