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.aead.XChaCha20Poly1305Parameters; 23 import com.google.crypto.tink.subtle.EllipticCurves; 24 import com.google.crypto.tink.subtle.X25519; 25 import com.google.crypto.tink.util.Bytes; 26 import java.math.BigInteger; 27 import java.security.GeneralSecurityException; 28 import java.security.KeyFactory; 29 import java.security.KeyPair; 30 import java.security.interfaces.ECPublicKey; 31 import java.security.spec.ECPoint; 32 import java.security.spec.ECPublicKeySpec; 33 import org.junit.Test; 34 import org.junit.experimental.theories.DataPoints; 35 import org.junit.experimental.theories.FromDataPoints; 36 import org.junit.experimental.theories.Theories; 37 import org.junit.experimental.theories.Theory; 38 import org.junit.runner.RunWith; 39 40 @RunWith(Theories.class) 41 public final class EciesPublicKeyTest { 42 private static final class NistCurveMapping { 43 final EciesParameters.CurveType curveType; 44 final EllipticCurves.CurveType ecNistCurve; 45 NistCurveMapping(EciesParameters.CurveType curveType, EllipticCurves.CurveType ecNistCurve)46 NistCurveMapping(EciesParameters.CurveType curveType, EllipticCurves.CurveType ecNistCurve) { 47 this.curveType = curveType; 48 this.ecNistCurve = ecNistCurve; 49 } 50 } 51 52 @DataPoints("nistCurvesMapping") 53 public static final NistCurveMapping[] NIST_CURVES = 54 new NistCurveMapping[] { 55 new NistCurveMapping( 56 EciesParameters.CurveType.NIST_P256, EllipticCurves.CurveType.NIST_P256), 57 new NistCurveMapping( 58 EciesParameters.CurveType.NIST_P384, EllipticCurves.CurveType.NIST_P384), 59 new NistCurveMapping( 60 EciesParameters.CurveType.NIST_P521, EllipticCurves.CurveType.NIST_P521) 61 }; 62 63 @DataPoints("pointFormats") 64 public static final EciesParameters.PointFormat[] POINT_FORMATS = 65 new EciesParameters.PointFormat[] { 66 EciesParameters.PointFormat.UNCOMPRESSED, 67 EciesParameters.PointFormat.COMPRESSED, 68 EciesParameters.PointFormat.LEGACY_UNCOMPRESSED, 69 }; 70 71 @Test convertToAndFromJavaECPublicKey()72 public void convertToAndFromJavaECPublicKey() throws Exception { 73 // Create an elliptic curve key pair using Java's KeyPairGenerator and get the public key. 74 KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256); 75 ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); 76 77 // Before conversion, check that the spec of the ecPublicKey are what we expect. 78 assertThat(ecPublicKey.getParams().getCurve()) 79 .isEqualTo(EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256).getCurve()); 80 81 // Create EciesParameters that match the curve type. 82 EciesParameters parameters = 83 EciesParameters.builder() 84 .setHashType(EciesParameters.HashType.SHA256) 85 .setCurveType(EciesParameters.CurveType.NIST_P256) 86 .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) 87 .setVariant(EciesParameters.Variant.NO_PREFIX) 88 .setDemParameters(XChaCha20Poly1305Parameters.create()) 89 .build(); 90 91 // Create EciesPublicKey using the bytes from the ecPublicKey. 92 EciesPublicKey publicKey = 93 EciesPublicKey.createForNistCurve( 94 parameters, ecPublicKey.getW(), /* idRequirement= */ null); 95 96 // Convert EciesPublicKey back into a ECPublicKey. 97 KeyFactory keyFactory = KeyFactory.getInstance("EC"); 98 ECPublicKey ecPublicKey2 = 99 (ECPublicKey) 100 keyFactory.generatePublic( 101 new ECPublicKeySpec( 102 publicKey.getNistCurvePoint(), 103 EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256))); 104 assertThat(ecPublicKey2.getW()).isEqualTo(ecPublicKey.getW()); 105 assertThat(ecPublicKey2.getParams().getCurve()).isEqualTo(ecPublicKey.getParams().getCurve()); 106 } 107 108 @Theory createNistCurvePublicKey_hasCorrectParameters( @romDataPoints"nistCurvesMapping") NistCurveMapping nistCurveMapping, @FromDataPoints("pointFormats") EciesParameters.PointFormat pointFormat)109 public void createNistCurvePublicKey_hasCorrectParameters( 110 @FromDataPoints("nistCurvesMapping") NistCurveMapping nistCurveMapping, 111 @FromDataPoints("pointFormats") EciesParameters.PointFormat pointFormat) 112 throws Exception { 113 EciesParameters params = 114 EciesParameters.builder() 115 .setHashType(EciesParameters.HashType.SHA256) 116 .setCurveType(nistCurveMapping.curveType) 117 .setNistCurvePointFormat(pointFormat) 118 .setVariant(EciesParameters.Variant.NO_PREFIX) 119 .setDemParameters(XChaCha20Poly1305Parameters.create()) 120 .build(); 121 ECPublicKey ecPublicKey = 122 (ECPublicKey) EllipticCurves.generateKeyPair(nistCurveMapping.ecNistCurve).getPublic(); 123 124 EciesPublicKey publicKey = 125 EciesPublicKey.createForNistCurve(params, ecPublicKey.getW(), /* idRequirement= */ null); 126 127 assertThat(publicKey.getX25519CurvePointBytes()).isEqualTo(null); 128 assertThat(publicKey.getNistCurvePoint()).isEqualTo(ecPublicKey.getW()); 129 assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); 130 assertThat(publicKey.getParameters()).isEqualTo(params); 131 assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null); 132 } 133 134 @Test callCreateForNistCurveWithX25519Params_throws()135 public void callCreateForNistCurveWithX25519Params_throws() throws Exception { 136 EciesParameters parameters = 137 EciesParameters.builder() 138 .setHashType(EciesParameters.HashType.SHA256) 139 .setCurveType(EciesParameters.CurveType.X25519) 140 .setVariant(EciesParameters.Variant.NO_PREFIX) 141 .setDemParameters(XChaCha20Poly1305Parameters.create()) 142 .build(); 143 ECPublicKey ecPublicKey = 144 (ECPublicKey) 145 EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256).getPublic(); 146 147 assertThrows( 148 GeneralSecurityException.class, 149 () -> 150 EciesPublicKey.createForNistCurve( 151 parameters, ecPublicKey.getW(), /* idRequirement= */ null)); 152 } 153 154 @Test createX25519PublicKey_hasCorrectParameters()155 public void createX25519PublicKey_hasCorrectParameters() throws Exception { 156 EciesParameters params = 157 EciesParameters.builder() 158 .setHashType(EciesParameters.HashType.SHA256) 159 .setCurveType(EciesParameters.CurveType.X25519) 160 .setVariant(EciesParameters.Variant.NO_PREFIX) 161 .setDemParameters(XChaCha20Poly1305Parameters.create()) 162 .build(); 163 Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 164 165 EciesPublicKey publicKey = 166 EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null); 167 168 assertThat(publicKey.getX25519CurvePointBytes()).isEqualTo(publicPointBytes); 169 assertThat(publicKey.getNistCurvePoint()).isEqualTo(null); 170 assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); 171 assertThat(publicKey.getParameters()).isEqualTo(params); 172 assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null); 173 } 174 175 @Test callCreateForCurve25519WithNistParams_throws()176 public void callCreateForCurve25519WithNistParams_throws() throws Exception { 177 EciesParameters parameters = 178 EciesParameters.builder() 179 .setHashType(EciesParameters.HashType.SHA256) 180 .setCurveType(EciesParameters.CurveType.NIST_P256) 181 .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) 182 .setVariant(EciesParameters.Variant.NO_PREFIX) 183 .setDemParameters(XChaCha20Poly1305Parameters.create()) 184 .build(); 185 Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 186 187 assertThrows( 188 GeneralSecurityException.class, 189 () -> 190 EciesPublicKey.createForCurveX25519( 191 parameters, publicPointBytes, /* idRequirement= */ null)); 192 } 193 194 @Test createX25519PublicKey_withWrongKeyLength_fails()195 public void createX25519PublicKey_withWrongKeyLength_fails() throws Exception { 196 EciesParameters params = 197 EciesParameters.builder() 198 .setHashType(EciesParameters.HashType.SHA256) 199 .setCurveType(EciesParameters.CurveType.X25519) 200 .setVariant(EciesParameters.Variant.NO_PREFIX) 201 .setDemParameters(XChaCha20Poly1305Parameters.create()) 202 .build(); 203 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 204 Bytes tooShort = Bytes.copyFrom(publicKeyBytes.toByteArray(), 0, publicKeyBytes.size() - 1); 205 byte[] tooLongBytes = new byte[publicKeyBytes.size() + 1]; 206 System.arraycopy(publicKeyBytes.toByteArray(), 0, tooLongBytes, 0, publicKeyBytes.size()); 207 Bytes tooLong = Bytes.copyFrom(tooLongBytes); 208 209 assertThrows( 210 GeneralSecurityException.class, 211 () -> EciesPublicKey.createForCurveX25519(params, tooShort, /* idRequirement= */ null)); 212 213 assertThrows( 214 GeneralSecurityException.class, 215 () -> EciesPublicKey.createForCurveX25519(params, tooLong, /* idRequirement= */ null)); 216 } 217 218 @Theory createNistCurvePublicKey_withPointNotOnCurve_fails( @romDataPoints"nistCurvesMapping") NistCurveMapping nistCurveMapping)219 public void createNistCurvePublicKey_withPointNotOnCurve_fails( 220 @FromDataPoints("nistCurvesMapping") NistCurveMapping nistCurveMapping) throws Exception { 221 EciesParameters params = 222 EciesParameters.builder() 223 .setHashType(EciesParameters.HashType.SHA256) 224 .setCurveType(nistCurveMapping.curveType) 225 .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) 226 .setVariant(EciesParameters.Variant.NO_PREFIX) 227 .setDemParameters(XChaCha20Poly1305Parameters.create()) 228 .build(); 229 ECPublicKey ecPublicKey = 230 (ECPublicKey) EllipticCurves.generateKeyPair(nistCurveMapping.ecNistCurve).getPublic(); 231 ECPoint point = ecPublicKey.getW(); 232 ECPoint badPoint = new ECPoint(point.getAffineX(), point.getAffineY().subtract(BigInteger.ONE)); 233 234 assertThrows( 235 GeneralSecurityException.class, 236 () -> EciesPublicKey.createForNistCurve(params, badPoint, /* idRequirement= */ null)); 237 } 238 239 @Test createPublicKeyWithMismatchedIdRequirement_fails()240 public void createPublicKeyWithMismatchedIdRequirement_fails() throws Exception { 241 EciesParameters.Builder paramsBuilder = 242 EciesParameters.builder() 243 .setHashType(EciesParameters.HashType.SHA256) 244 .setCurveType(EciesParameters.CurveType.X25519) 245 .setDemParameters(XChaCha20Poly1305Parameters.create()); 246 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 247 248 EciesParameters noPrefixParams = 249 paramsBuilder.setVariant(EciesParameters.Variant.NO_PREFIX).build(); 250 assertThrows( 251 GeneralSecurityException.class, 252 () -> 253 EciesPublicKey.createForCurveX25519( 254 noPrefixParams, publicKeyBytes, /* idRequirement= */ 123)); 255 256 EciesParameters tinkParams = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build(); 257 assertThrows( 258 GeneralSecurityException.class, 259 () -> 260 EciesPublicKey.createForCurveX25519( 261 tinkParams, publicKeyBytes, /* idRequirement= */ null)); 262 263 EciesParameters crunchyParams = 264 paramsBuilder.setVariant(EciesParameters.Variant.CRUNCHY).build(); 265 assertThrows( 266 GeneralSecurityException.class, 267 () -> 268 EciesPublicKey.createForCurveX25519( 269 crunchyParams, publicKeyBytes, /* idRequirement= */ null)); 270 } 271 272 @Test getOutputPrefix_isCorrect()273 public void getOutputPrefix_isCorrect() throws Exception { 274 EciesParameters.Builder paramsBuilder = 275 EciesParameters.builder() 276 .setHashType(EciesParameters.HashType.SHA256) 277 .setCurveType(EciesParameters.CurveType.X25519) 278 .setDemParameters(XChaCha20Poly1305Parameters.create()); 279 Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 280 281 EciesParameters noPrefixParams = 282 paramsBuilder.setVariant(EciesParameters.Variant.NO_PREFIX).build(); 283 EciesPublicKey noPrefixPublicKey = 284 EciesPublicKey.createForCurveX25519( 285 noPrefixParams, publicPointBytes, /* idRequirement= */ null); 286 assertThat(noPrefixPublicKey.getIdRequirementOrNull()).isEqualTo(null); 287 assertThat(noPrefixPublicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); 288 289 EciesParameters tinkParams = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build(); 290 EciesPublicKey tinkPublicKey = 291 EciesPublicKey.createForCurveX25519( 292 tinkParams, publicPointBytes, /* idRequirement= */ 0x02030405); 293 assertThat(tinkPublicKey.getIdRequirementOrNull()).isEqualTo(0x02030405); 294 assertThat(tinkPublicKey.getOutputPrefix()) 295 .isEqualTo(Bytes.copyFrom(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05})); 296 297 EciesParameters crunchyParams = 298 paramsBuilder.setVariant(EciesParameters.Variant.CRUNCHY).build(); 299 EciesPublicKey crunchyPublicKey = 300 EciesPublicKey.createForCurveX25519( 301 crunchyParams, publicPointBytes, /* idRequirement= */ 0x01020304); 302 assertThat(crunchyPublicKey.getIdRequirementOrNull()).isEqualTo(0x01020304); 303 assertThat(crunchyPublicKey.getOutputPrefix()) 304 .isEqualTo(Bytes.copyFrom(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04})); 305 } 306 307 @Test sameKeys_areEqual()308 public void sameKeys_areEqual() throws Exception { 309 EciesParameters params = 310 EciesParameters.builder() 311 .setHashType(EciesParameters.HashType.SHA256) 312 .setCurveType(EciesParameters.CurveType.X25519) 313 .setVariant(EciesParameters.Variant.NO_PREFIX) 314 .setDemParameters(XChaCha20Poly1305Parameters.create()) 315 .build(); 316 Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 317 318 EciesPublicKey publicKey1 = 319 EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null); 320 EciesPublicKey publicKey2 = 321 EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null); 322 323 assertThat(publicKey1.equalsKey(publicKey2)).isTrue(); 324 } 325 326 @Test sameKeys_nist_areEqual()327 public void sameKeys_nist_areEqual() throws Exception { 328 EciesParameters params = 329 EciesParameters.builder() 330 .setHashType(EciesParameters.HashType.SHA256) 331 .setCurveType(EciesParameters.CurveType.X25519) 332 .setVariant(EciesParameters.Variant.NO_PREFIX) 333 .setDemParameters(XChaCha20Poly1305Parameters.create()) 334 .build(); 335 Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 336 337 EciesPublicKey publicKey1 = 338 EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null); 339 EciesPublicKey publicKey2 = 340 EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null); 341 342 assertThat(publicKey1.equalsKey(publicKey2)).isTrue(); 343 } 344 345 @Test keysWithDifferentParams_areNotEqual()346 public void keysWithDifferentParams_areNotEqual() throws Exception { 347 EciesParameters.Builder paramsBuilder = 348 EciesParameters.builder() 349 .setHashType(EciesParameters.HashType.SHA256) 350 .setCurveType(EciesParameters.CurveType.X25519) 351 .setDemParameters(XChaCha20Poly1305Parameters.create()); 352 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 353 354 EciesParameters params1 = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build(); 355 EciesPublicKey publicKey1 = 356 EciesPublicKey.createForCurveX25519(params1, publicKeyBytes, /* idRequirement= */ 123); 357 EciesParameters params2 = paramsBuilder.setVariant(EciesParameters.Variant.CRUNCHY).build(); 358 EciesPublicKey publicKey2 = 359 EciesPublicKey.createForCurveX25519(params2, publicKeyBytes, /* idRequirement= */ 123); 360 361 assertThat(publicKey1.equalsKey(publicKey2)).isFalse(); 362 } 363 364 @Test keysWithDifferentKeyBytes_areNotEqual()365 public void keysWithDifferentKeyBytes_areNotEqual() throws Exception { 366 EciesParameters params = 367 EciesParameters.builder() 368 .setHashType(EciesParameters.HashType.SHA256) 369 .setCurveType(EciesParameters.CurveType.X25519) 370 .setVariant(EciesParameters.Variant.NO_PREFIX) 371 .setDemParameters( 372 XChaCha20Poly1305Parameters.create(XChaCha20Poly1305Parameters.Variant.NO_PREFIX)) 373 .build(); 374 Bytes publicKeyBytes1 = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 375 byte[] buf2 = publicKeyBytes1.toByteArray(); 376 buf2[0] = (byte) (buf2[0] ^ 0x01); 377 Bytes publicKeyBytes2 = Bytes.copyFrom(buf2); 378 379 EciesPublicKey publicKey1 = 380 EciesPublicKey.createForCurveX25519(params, publicKeyBytes1, /* idRequirement= */ null); 381 EciesPublicKey publicKey2 = 382 EciesPublicKey.createForCurveX25519(params, publicKeyBytes2, /* idRequirement= */ null); 383 384 assertThat(publicKey1.equalsKey(publicKey2)).isFalse(); 385 } 386 387 @Test keysWithdifferentIds_areNotEqual()388 public void keysWithdifferentIds_areNotEqual() throws Exception { 389 EciesParameters.Builder paramsBuilder = 390 EciesParameters.builder() 391 .setHashType(EciesParameters.HashType.SHA256) 392 .setCurveType(EciesParameters.CurveType.X25519) 393 .setDemParameters( 394 XChaCha20Poly1305Parameters.create(XChaCha20Poly1305Parameters.Variant.NO_PREFIX)); 395 Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); 396 397 EciesParameters params1 = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build(); 398 EciesPublicKey publicKey1 = 399 EciesPublicKey.createForCurveX25519(params1, publicKeyBytes, /* idRequirement= */ 123); 400 EciesParameters params2 = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build(); 401 EciesPublicKey publicKey2 = 402 EciesPublicKey.createForCurveX25519(params2, publicKeyBytes, /* idRequirement= */ 456); 403 404 assertThat(publicKey1.equalsKey(publicKey2)).isFalse(); 405 } 406 } 407