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 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.KeysetHandle; 26 import com.google.crypto.tink.aead.AesGcmParameters; 27 import com.google.crypto.tink.aead.internal.AesCtrHmacAeadProtoSerialization; 28 import com.google.crypto.tink.aead.internal.AesGcmProtoSerialization; 29 import com.google.crypto.tink.config.internal.TinkFipsUtil; 30 import com.google.crypto.tink.daead.internal.AesSivProtoSerialization; 31 import com.google.crypto.tink.hybrid.EciesParameters.CurveType; 32 import com.google.crypto.tink.hybrid.EciesParameters.PointFormat; 33 import com.google.crypto.tink.hybrid.HpkeParameters.AeadId; 34 import com.google.crypto.tink.hybrid.HpkeParameters.KdfId; 35 import com.google.crypto.tink.hybrid.HpkeParameters.KemId; 36 import com.google.crypto.tink.hybrid.internal.EciesProtoSerialization; 37 import com.google.crypto.tink.hybrid.internal.testing.EciesAeadHkdfTestUtil; 38 import com.google.crypto.tink.hybrid.internal.testing.HpkeTestUtil; 39 import com.google.crypto.tink.hybrid.internal.testing.HybridTestVector; 40 import com.google.crypto.tink.internal.BigIntegerEncoding; 41 import com.google.crypto.tink.subtle.EllipticCurves; 42 import com.google.crypto.tink.subtle.EllipticCurves.PointFormatType; 43 import com.google.crypto.tink.subtle.Hex; 44 import com.google.crypto.tink.util.Bytes; 45 import com.google.crypto.tink.util.SecretBigInteger; 46 import com.google.crypto.tink.util.SecretBytes; 47 import java.security.GeneralSecurityException; 48 import java.security.KeyPair; 49 import java.security.interfaces.ECPrivateKey; 50 import java.security.interfaces.ECPublicKey; 51 import java.util.Arrays; 52 import java.util.stream.Stream; 53 import javax.annotation.Nullable; 54 import org.junit.Assume; 55 import org.junit.BeforeClass; 56 import org.junit.Test; 57 import org.junit.experimental.theories.DataPoints; 58 import org.junit.experimental.theories.FromDataPoints; 59 import org.junit.experimental.theories.Theories; 60 import org.junit.experimental.theories.Theory; 61 import org.junit.runner.RunWith; 62 63 @RunWith(Theories.class) 64 public class HybridConfigurationV0Test { 65 @BeforeClass setUp()66 public static void setUp() throws Exception { 67 EciesProtoSerialization.register(); 68 AesGcmProtoSerialization.register(); 69 AesCtrHmacAeadProtoSerialization.register(); 70 AesSivProtoSerialization.register(); 71 72 HpkeProtoSerialization.register(); 73 } 74 75 @Test config_throwsIfInFipsMode()76 public void config_throwsIfInFipsMode() throws Exception { 77 Assume.assumeTrue(TinkFipsUtil.useOnlyFips()); 78 79 assertThrows(GeneralSecurityException.class, HybridConfigurationV0::get); 80 } 81 82 @Test config_containsEciesAeadHkdfForHybridEncrypt()83 public void config_containsEciesAeadHkdfForHybridEncrypt() throws Exception { 84 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 85 86 AesGcmParameters demParameters = 87 AesGcmParameters.builder() 88 .setIvSizeBytes(12) 89 .setKeySizeBytes(32) 90 .setTagSizeBytes(16) 91 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 92 .build(); 93 EciesParameters parameters = 94 EciesParameters.builder() 95 .setCurveType(CurveType.NIST_P384) 96 .setHashType(EciesParameters.HashType.SHA256) 97 .setNistCurvePointFormat(PointFormat.UNCOMPRESSED) 98 .setDemParameters(demParameters) 99 .setVariant(EciesParameters.Variant.TINK) 100 .build(); 101 KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P384); 102 EciesPublicKey publicKey = 103 EciesPublicKey.createForNistCurve( 104 parameters, ((ECPublicKey) keyPair.getPublic()).getW(), /* idRequirement= */ 42); 105 KeysetHandle keysetHandle = 106 KeysetHandle.newBuilder().addEntry(KeysetHandle.importKey(publicKey).makePrimary()).build(); 107 108 assertThat(keysetHandle.getPrimitive(HybridConfigurationV0.get(), HybridEncrypt.class)) 109 .isNotNull(); 110 } 111 112 @Test config_containsEciesAeadHkdfForHybridDecrypt()113 public void config_containsEciesAeadHkdfForHybridDecrypt() throws Exception { 114 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 115 116 AesGcmParameters demParameters = 117 AesGcmParameters.builder() 118 .setIvSizeBytes(12) 119 .setKeySizeBytes(32) 120 .setTagSizeBytes(16) 121 .setVariant(AesGcmParameters.Variant.NO_PREFIX) 122 .build(); 123 EciesParameters parameters = 124 EciesParameters.builder() 125 .setCurveType(CurveType.NIST_P384) 126 .setHashType(EciesParameters.HashType.SHA256) 127 .setNistCurvePointFormat(PointFormat.UNCOMPRESSED) 128 .setDemParameters(demParameters) 129 .setVariant(EciesParameters.Variant.TINK) 130 .build(); 131 KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P384); 132 EciesPublicKey publicKey = 133 EciesPublicKey.createForNistCurve( 134 parameters, ((ECPublicKey) keyPair.getPublic()).getW(), /* idRequirement= */ 42); 135 EciesPrivateKey privateKey = 136 EciesPrivateKey.createForNistCurve( 137 publicKey, 138 SecretBigInteger.fromBigInteger( 139 ((ECPrivateKey) keyPair.getPrivate()).getS(), InsecureSecretKeyAccess.get())); 140 KeysetHandle keysetHandle = 141 KeysetHandle.newBuilder() 142 .addEntry(KeysetHandle.importKey(privateKey).makePrimary()) 143 .build(); 144 145 assertThat(keysetHandle.getPrimitive(HybridConfigurationV0.get(), HybridDecrypt.class)) 146 .isNotNull(); 147 } 148 149 @Test config_containsHpkeForHybridEncrypt()150 public void config_containsHpkeForHybridEncrypt() throws Exception { 151 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 152 153 HpkeParameters parameters = 154 HpkeParameters.builder() 155 .setKemId(KemId.DHKEM_P384_HKDF_SHA384) 156 .setKdfId(KdfId.HKDF_SHA384) 157 .setAeadId(AeadId.AES_256_GCM) 158 .setVariant(HpkeParameters.Variant.TINK) 159 .build(); 160 KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P384); 161 Bytes publicKeyBytes = 162 Bytes.copyFrom( 163 EllipticCurves.pointEncode( 164 EllipticCurves.CurveType.NIST_P384, 165 PointFormatType.UNCOMPRESSED, 166 ((ECPublicKey) keyPair.getPublic()).getW())); 167 HpkePublicKey publicKey = HpkePublicKey.create(parameters, publicKeyBytes, 42); 168 KeysetHandle keysetHandle = 169 KeysetHandle.newBuilder().addEntry(KeysetHandle.importKey(publicKey).makePrimary()).build(); 170 171 assertThat(keysetHandle.getPrimitive(HybridConfigurationV0.get(), HybridEncrypt.class)) 172 .isNotNull(); 173 } 174 175 @Test config_containsHpkeForHybridDecrypt()176 public void config_containsHpkeForHybridDecrypt() throws Exception { 177 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 178 179 HpkeParameters parameters = 180 HpkeParameters.builder() 181 .setKemId(KemId.DHKEM_P384_HKDF_SHA384) 182 .setKdfId(KdfId.HKDF_SHA384) 183 .setAeadId(AeadId.AES_256_GCM) 184 .setVariant(HpkeParameters.Variant.TINK) 185 .build(); 186 KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P384); 187 Bytes publicKeyBytes = 188 Bytes.copyFrom( 189 EllipticCurves.pointEncode( 190 EllipticCurves.CurveType.NIST_P384, 191 PointFormatType.UNCOMPRESSED, 192 ((ECPublicKey) keyPair.getPublic()).getW())); 193 HpkePublicKey publicKey = HpkePublicKey.create(parameters, publicKeyBytes, 42); 194 byte[] privateKeyBytes = 195 BigIntegerEncoding.toBigEndianBytesOfFixedLength( 196 ((ECPrivateKey) keyPair.getPrivate()).getS(), 48); 197 HpkePrivateKey privateKey = 198 HpkePrivateKey.create( 199 publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); 200 KeysetHandle keysetHandle = 201 KeysetHandle.newBuilder() 202 .addEntry(KeysetHandle.importKey(privateKey).makePrimary()) 203 .build(); 204 205 assertThat(keysetHandle.getPrimitive(HybridConfigurationV0.get(), HybridDecrypt.class)) 206 .isNotNull(); 207 } 208 209 @Theory decryptCiphertextWorks(@romDataPoints"hybridTests") HybridTestVector v)210 public void decryptCiphertextWorks(@FromDataPoints("hybridTests") HybridTestVector v) 211 throws Exception { 212 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 213 214 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getPrivateKey()).makePrimary(); 215 @Nullable Integer id = v.getPrivateKey().getIdRequirementOrNull(); 216 if (id == null) { 217 entry.withRandomId(); 218 } else { 219 entry.withFixedId(id); 220 } 221 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 222 223 HybridDecrypt hybridDecrypt = 224 handle.getPrimitive(HybridConfigurationV0.get(), HybridDecrypt.class); 225 byte[] plaintext = hybridDecrypt.decrypt(v.getCiphertext(), v.getContextInfo()); 226 227 assertThat(Hex.encode(plaintext)).isEqualTo(Hex.encode(v.getPlaintext())); 228 } 229 230 @Theory decryptWrongContextInfoThrows(@romDataPoints"hybridTests") HybridTestVector v)231 public void decryptWrongContextInfoThrows(@FromDataPoints("hybridTests") HybridTestVector v) 232 throws Exception { 233 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 234 235 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getPrivateKey()).makePrimary(); 236 @Nullable Integer id = v.getPrivateKey().getIdRequirementOrNull(); 237 if (id == null) { 238 entry.withRandomId(); 239 } else { 240 entry.withFixedId(id); 241 } 242 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 243 244 byte[] contextInfo = v.getContextInfo(); 245 if (contextInfo.length > 0) { 246 contextInfo[0] ^= 1; 247 } else { 248 contextInfo = new byte[] {1}; 249 } 250 // local variables referenced from a lambda expression must be final or effectively final 251 final byte[] contextInfoCopy = Arrays.copyOf(contextInfo, contextInfo.length); 252 253 HybridDecrypt hybridDecrypt = 254 handle.getPrimitive(HybridConfigurationV0.get(), HybridDecrypt.class); 255 256 assertThrows( 257 GeneralSecurityException.class, 258 () -> hybridDecrypt.decrypt(v.getCiphertext(), contextInfoCopy)); 259 } 260 261 @Theory encryptThenDecryptMessageWorks(@romDataPoints"hybridTests") HybridTestVector v)262 public void encryptThenDecryptMessageWorks(@FromDataPoints("hybridTests") HybridTestVector v) 263 throws Exception { 264 Assume.assumeFalse(TinkFipsUtil.useOnlyFips()); 265 266 KeysetHandle.Builder.Entry entry = KeysetHandle.importKey(v.getPrivateKey()).makePrimary(); 267 @Nullable Integer id = v.getPrivateKey().getIdRequirementOrNull(); 268 if (id == null) { 269 entry.withRandomId(); 270 } else { 271 entry.withFixedId(id); 272 } 273 KeysetHandle handle = KeysetHandle.newBuilder().addEntry(entry).build(); 274 275 HybridDecrypt hybridDecrypt = 276 handle.getPrimitive(HybridConfigurationV0.get(), HybridDecrypt.class); 277 HybridEncrypt hybridEncrypt = 278 handle 279 .getPublicKeysetHandle() 280 .getPrimitive(HybridConfigurationV0.get(), HybridEncrypt.class); 281 byte[] ciphertext = hybridEncrypt.encrypt(v.getPlaintext(), v.getContextInfo()); 282 byte[] plaintext = hybridDecrypt.decrypt(ciphertext, v.getContextInfo()); 283 284 assertThat(Hex.encode(plaintext)).isEqualTo(Hex.encode(v.getPlaintext())); 285 } 286 287 @DataPoints("hybridTests") 288 public static final HybridTestVector[] hybridTestVectors = 289 Stream.concat( 290 Arrays.stream(HpkeTestUtil.createHpkeTestVectors()), 291 Arrays.stream(EciesAeadHkdfTestUtil.createEciesTestVectors())) 292 .toArray(HybridTestVector[]::new); 293 } 294