1 // Copyright 2023 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.internal.BigIntegerEncoding; 23 import com.google.crypto.tink.subtle.EllipticCurves; 24 import com.google.crypto.tink.subtle.EllipticCurves.PointFormatType; 25 import com.google.crypto.tink.subtle.X25519; 26 import com.google.crypto.tink.util.Bytes; 27 import java.math.BigInteger; 28 import java.security.GeneralSecurityException; 29 import java.security.interfaces.ECPublicKey; 30 import java.security.spec.ECPoint; 31 import java.security.spec.EllipticCurve; 32 import org.junit.Test; 33 import org.junit.experimental.theories.DataPoints; 34 import org.junit.experimental.theories.FromDataPoints; 35 import org.junit.experimental.theories.Theories; 36 import org.junit.experimental.theories.Theory; 37 import org.junit.runner.RunWith; 38 39 @RunWith(Theories.class) 40 public final class HpkePublicKeyTest { 41 private static final class NistKemTuple { 42 final HpkeParameters.KemId kemId; 43 final EllipticCurves.CurveType curve; 44 NistKemTuple(HpkeParameters.KemId kemId, EllipticCurves.CurveType curve)45 NistKemTuple(HpkeParameters.KemId kemId, EllipticCurves.CurveType curve) { 46 this.kemId = kemId; 47 this.curve = curve; 48 } 49 } 50 51 @DataPoints("nistKemTuples") 52 public static final NistKemTuple[] KEMS = 53 new NistKemTuple[] { 54 new NistKemTuple( 55 HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256, EllipticCurves.CurveType.NIST_P256), 56 new NistKemTuple( 57 HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384, EllipticCurves.CurveType.NIST_P384), 58 new NistKemTuple( 59 HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512, EllipticCurves.CurveType.NIST_P521), 60 }; 61 62 @Theory createNistCurvePublicKey(@romDataPoints"nistKemTuples") NistKemTuple tuple)63 public void createNistCurvePublicKey(@FromDataPoints("nistKemTuples") NistKemTuple tuple) 64 throws Exception { 65 HpkeParameters params = 66 HpkeParameters.builder() 67 .setVariant(HpkeParameters.Variant.NO_PREFIX) 68 .setKemId(tuple.kemId) 69 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 70 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 71 .build(); 72 ECPublicKey ecPublicKey = (ECPublicKey) EllipticCurves.generateKeyPair(tuple.curve).getPublic(); 73 Bytes publicKeyBytes = 74 Bytes.copyFrom( 75 EllipticCurves.pointEncode( 76 tuple.curve, PointFormatType.UNCOMPRESSED, ecPublicKey.getW())); 77 78 HpkePublicKey publicKey = 79 HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null); 80 81 assertThat(publicKey.getPublicKeyBytes()).isEqualTo(publicKeyBytes); 82 assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); 83 assertThat(publicKey.getParameters()).isEqualTo(params); 84 assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null); 85 } 86 87 @Test createX25519PublicKey()88 public void createX25519PublicKey() throws Exception { 89 HpkeParameters params = 90 HpkeParameters.builder() 91 .setVariant(HpkeParameters.Variant.NO_PREFIX) 92 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 93 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 94 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 95 .build(); 96 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 97 98 HpkePublicKey publicKey = 99 HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null); 100 101 assertThat(publicKey.getPublicKeyBytes()).isEqualTo(publicKeyBytes); 102 assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); 103 assertThat(publicKey.getParameters()).isEqualTo(params); 104 assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null); 105 } 106 107 @Theory createNistCurvePublicKey_failsWithWrongKeyLength( @romDataPoints"nistKemTuples") NistKemTuple tuple)108 public void createNistCurvePublicKey_failsWithWrongKeyLength( 109 @FromDataPoints("nistKemTuples") NistKemTuple tuple) throws Exception { 110 HpkeParameters params = 111 HpkeParameters.builder() 112 .setVariant(HpkeParameters.Variant.NO_PREFIX) 113 .setKemId(tuple.kemId) 114 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 115 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 116 .build(); 117 ECPublicKey ecPublicKey = (ECPublicKey) EllipticCurves.generateKeyPair(tuple.curve).getPublic(); 118 Bytes publicKeyBytes = 119 Bytes.copyFrom( 120 EllipticCurves.pointEncode( 121 tuple.curve, PointFormatType.UNCOMPRESSED, ecPublicKey.getW())); 122 Bytes tooShort = Bytes.copyFrom(publicKeyBytes.toByteArray(), 0, publicKeyBytes.size() - 1); 123 byte[] tooLongBytes = new byte[publicKeyBytes.size() + 1]; 124 System.arraycopy(publicKeyBytes.toByteArray(), 0, tooLongBytes, 0, publicKeyBytes.size()); 125 Bytes tooLong = Bytes.copyFrom(tooLongBytes); 126 127 assertThrows( 128 GeneralSecurityException.class, 129 () -> HpkePublicKey.create(params, tooShort, /* idRequirement= */ null)); 130 131 assertThrows( 132 GeneralSecurityException.class, 133 () -> HpkePublicKey.create(params, tooLong, /* idRequirement= */ null)); 134 } 135 136 @Test createX25519PublicKey_failsWithWrongKeyLength()137 public void createX25519PublicKey_failsWithWrongKeyLength() throws Exception { 138 HpkeParameters params = 139 HpkeParameters.builder() 140 .setVariant(HpkeParameters.Variant.NO_PREFIX) 141 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 142 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 143 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 144 .build(); 145 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 146 Bytes tooShort = Bytes.copyFrom(publicKeyBytes.toByteArray(), 0, publicKeyBytes.size() - 1); 147 byte[] tooLongBytes = new byte[publicKeyBytes.size() + 1]; 148 System.arraycopy(publicKeyBytes.toByteArray(), 0, tooLongBytes, 0, publicKeyBytes.size()); 149 Bytes tooLong = Bytes.copyFrom(tooLongBytes); 150 151 assertThrows( 152 GeneralSecurityException.class, 153 () -> HpkePublicKey.create(params, tooShort, /* idRequirement= */ null)); 154 155 assertThrows( 156 GeneralSecurityException.class, 157 () -> HpkePublicKey.create(params, tooLong, /* idRequirement= */ null)); 158 } 159 160 /** Copied from {@link EllipticCurves#pointEncode} to bypass point validation. */ encodeUncompressedPoint(EllipticCurve curve, ECPoint point)161 private static byte[] encodeUncompressedPoint(EllipticCurve curve, ECPoint point) 162 throws GeneralSecurityException { 163 int coordinateSize = EllipticCurves.fieldSizeInBytes(curve); 164 byte[] encoded = new byte[2 * coordinateSize + 1]; 165 byte[] x = BigIntegerEncoding.toBigEndianBytes(point.getAffineX()); 166 byte[] y = BigIntegerEncoding.toBigEndianBytes(point.getAffineY()); 167 // Order of System.arraycopy is important because x,y can have leading 0's. 168 System.arraycopy(y, 0, encoded, 1 + 2 * coordinateSize - y.length, y.length); 169 System.arraycopy(x, 0, encoded, 1 + coordinateSize - x.length, x.length); 170 encoded[0] = 4; 171 return encoded; 172 } 173 174 @Theory createNistCurvePublicKey_failsIfPointNotOnCurve( @romDataPoints"nistKemTuples") NistKemTuple tuple)175 public void createNistCurvePublicKey_failsIfPointNotOnCurve( 176 @FromDataPoints("nistKemTuples") NistKemTuple tuple) throws Exception { 177 HpkeParameters params = 178 HpkeParameters.builder() 179 .setVariant(HpkeParameters.Variant.NO_PREFIX) 180 .setKemId(tuple.kemId) 181 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 182 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 183 .build(); 184 ECPublicKey ecPublicKey = (ECPublicKey) EllipticCurves.generateKeyPair(tuple.curve).getPublic(); 185 ECPoint point = ecPublicKey.getW(); 186 ECPoint badPoint = new ECPoint(point.getAffineX(), point.getAffineY().subtract(BigInteger.ONE)); 187 188 Bytes publicKeyBytes = 189 Bytes.copyFrom( 190 encodeUncompressedPoint(EllipticCurves.getCurveSpec(tuple.curve).getCurve(), badPoint)); 191 192 assertThrows( 193 GeneralSecurityException.class, 194 () -> HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null)); 195 } 196 197 @Test createPublicKey_failsWithMismatchedIdRequirement()198 public void createPublicKey_failsWithMismatchedIdRequirement() throws Exception { 199 HpkeParameters.Builder paramsBuilder = 200 HpkeParameters.builder() 201 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 202 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 203 .setAeadId(HpkeParameters.AeadId.AES_128_GCM); 204 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 205 206 HpkeParameters noPrefixParams = 207 paramsBuilder.setVariant(HpkeParameters.Variant.NO_PREFIX).build(); 208 assertThrows( 209 GeneralSecurityException.class, 210 () -> HpkePublicKey.create(noPrefixParams, publicKeyBytes, /* idRequirement= */ 123)); 211 212 HpkeParameters tinkParams = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build(); 213 assertThrows( 214 GeneralSecurityException.class, 215 () -> HpkePublicKey.create(tinkParams, publicKeyBytes, /* idRequirement= */ null)); 216 217 HpkeParameters crunchyParams = paramsBuilder.setVariant(HpkeParameters.Variant.CRUNCHY).build(); 218 assertThrows( 219 GeneralSecurityException.class, 220 () -> HpkePublicKey.create(crunchyParams, publicKeyBytes, /* idRequirement= */ null)); 221 } 222 223 @Test getOutputPrefix()224 public void getOutputPrefix() throws Exception { 225 HpkeParameters.Builder paramsBuilder = 226 HpkeParameters.builder() 227 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 228 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 229 .setAeadId(HpkeParameters.AeadId.AES_128_GCM); 230 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 231 232 HpkeParameters noPrefixParams = 233 paramsBuilder.setVariant(HpkeParameters.Variant.NO_PREFIX).build(); 234 HpkePublicKey noPrefixPublicKey = 235 HpkePublicKey.create(noPrefixParams, publicKeyBytes, /* idRequirement= */ null); 236 assertThat(noPrefixPublicKey.getIdRequirementOrNull()).isEqualTo(null); 237 assertThat(noPrefixPublicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); 238 239 HpkeParameters tinkParams = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build(); 240 HpkePublicKey tinkPublicKey = 241 HpkePublicKey.create(tinkParams, publicKeyBytes, /* idRequirement= */ 0x02030405); 242 assertThat(tinkPublicKey.getIdRequirementOrNull()).isEqualTo(0x02030405); 243 assertThat(tinkPublicKey.getOutputPrefix()) 244 .isEqualTo(Bytes.copyFrom(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05})); 245 246 HpkeParameters crunchyParams = paramsBuilder.setVariant(HpkeParameters.Variant.CRUNCHY).build(); 247 HpkePublicKey crunchyPublicKey = 248 HpkePublicKey.create(crunchyParams, publicKeyBytes, /* idRequirement= */ 0x01020304); 249 assertThat(crunchyPublicKey.getIdRequirementOrNull()).isEqualTo(0x01020304); 250 assertThat(crunchyPublicKey.getOutputPrefix()) 251 .isEqualTo(Bytes.copyFrom(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04})); 252 } 253 254 @Test sameKeysAreEqual()255 public void sameKeysAreEqual() throws Exception { 256 HpkeParameters params = 257 HpkeParameters.builder() 258 .setVariant(HpkeParameters.Variant.NO_PREFIX) 259 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 260 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 261 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 262 .build(); 263 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 264 265 HpkePublicKey publicKey1 = 266 HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null); 267 HpkePublicKey publicKey2 = 268 HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null); 269 270 assertThat(publicKey1.equalsKey(publicKey2)).isTrue(); 271 } 272 273 @Test differentParamsAreNotEqual()274 public void differentParamsAreNotEqual() throws Exception { 275 HpkeParameters.Builder paramsBuilder = 276 HpkeParameters.builder() 277 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 278 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 279 .setAeadId(HpkeParameters.AeadId.AES_128_GCM); 280 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 281 282 HpkeParameters params1 = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build(); 283 HpkePublicKey publicKey1 = 284 HpkePublicKey.create(params1, publicKeyBytes, /* idRequirement= */ 123); 285 HpkeParameters params2 = paramsBuilder.setVariant(HpkeParameters.Variant.CRUNCHY).build(); 286 HpkePublicKey publicKey2 = 287 HpkePublicKey.create(params2, publicKeyBytes, /* idRequirement= */ 123); 288 289 assertThat(publicKey1.equalsKey(publicKey2)).isFalse(); 290 } 291 292 @Test differentKeyBytesAreNotEqual()293 public void differentKeyBytesAreNotEqual() throws Exception { 294 HpkeParameters params = 295 HpkeParameters.builder() 296 .setVariant(HpkeParameters.Variant.NO_PREFIX) 297 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 298 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 299 .setAeadId(HpkeParameters.AeadId.AES_128_GCM) 300 .build(); 301 Bytes publicKeyBytes1 = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 302 byte[] buf2 = publicKeyBytes1.toByteArray(); 303 buf2[0] = (byte) (buf2[0] ^ 0x01); 304 Bytes publicKeyBytes2 = Bytes.copyFrom(buf2); 305 306 HpkePublicKey publicKey1 = 307 HpkePublicKey.create(params, publicKeyBytes1, /* idRequirement= */ null); 308 HpkePublicKey publicKey2 = 309 HpkePublicKey.create(params, publicKeyBytes2, /* idRequirement= */ null); 310 311 assertThat(publicKey1.equalsKey(publicKey2)).isFalse(); 312 } 313 314 @Test differentIdsAreNotEqual()315 public void differentIdsAreNotEqual() throws Exception { 316 HpkeParameters.Builder paramsBuilder = 317 HpkeParameters.builder() 318 .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) 319 .setKdfId(HpkeParameters.KdfId.HKDF_SHA256) 320 .setAeadId(HpkeParameters.AeadId.AES_128_GCM); 321 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 322 323 HpkeParameters params1 = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build(); 324 HpkePublicKey publicKey1 = 325 HpkePublicKey.create(params1, publicKeyBytes, /* idRequirement= */ 123); 326 HpkeParameters params2 = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build(); 327 HpkePublicKey publicKey2 = 328 HpkePublicKey.create(params2, publicKeyBytes, /* idRequirement= */ 456); 329 330 assertThat(publicKey1.equalsKey(publicKey2)).isFalse(); 331 } 332 } 333