diff options
Diffstat (limited to 'java_src')
5 files changed, 706 insertions, 0 deletions
diff --git a/java_src/BUILD.bazel b/java_src/BUILD.bazel index d7bba6372..8e9957487 100644 --- a/java_src/BUILD.bazel +++ b/java_src/BUILD.bazel @@ -135,6 +135,7 @@ gen_maven_jar_rules( "//src/main/java/com/google/crypto/tink/hybrid:ecies_aead_hkdf_private_key_manager", "//src/main/java/com/google/crypto/tink/hybrid:ecies_aead_hkdf_public_key_manager", "//src/main/java/com/google/crypto/tink/hybrid:ecies_parameters", + "//src/main/java/com/google/crypto/tink/hybrid:ecies_private_key", "//src/main/java/com/google/crypto/tink/hybrid:ecies_proto_serialization", "//src/main/java/com/google/crypto/tink/hybrid:ecies_public_key", "//src/main/java/com/google/crypto/tink/hybrid:hpke_parameters", @@ -564,6 +565,7 @@ gen_maven_jar_rules( "//src/main/java/com/google/crypto/tink/hybrid:ecies_aead_hkdf_private_key_manager-android", "//src/main/java/com/google/crypto/tink/hybrid:ecies_aead_hkdf_public_key_manager-android", "//src/main/java/com/google/crypto/tink/hybrid:ecies_parameters-android", + "//src/main/java/com/google/crypto/tink/hybrid:ecies_private_key-android", "//src/main/java/com/google/crypto/tink/hybrid:ecies_proto_serialization-android", "//src/main/java/com/google/crypto/tink/hybrid:ecies_public_key-android", "//src/main/java/com/google/crypto/tink/hybrid:hpke_parameters-android", diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel index 90ff37cfa..5f9edeb76 100644 --- a/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel +++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel @@ -353,6 +353,26 @@ java_library( ], ) +java_library( + name = "ecies_private_key", + srcs = ["EciesPrivateKey.java"], + deps = [ + ":ecies_parameters", + ":ecies_public_key", + ":hybrid_private_key", + "//src/main/java/com/google/crypto/tink:accesses_partial_key", + "//src/main/java/com/google/crypto/tink:insecure_secret_key_access", + "//src/main/java/com/google/crypto/tink:key", + "//src/main/java/com/google/crypto/tink/internal:elliptic_curves_util", + "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves", + "//src/main/java/com/google/crypto/tink/subtle:x25519", + "//src/main/java/com/google/crypto/tink/util:secret_big_integer", + "//src/main/java/com/google/crypto/tink/util:secret_bytes", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + # Android libraries android_library( @@ -703,3 +723,23 @@ android_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) + +android_library( + name = "ecies_private_key-android", + srcs = ["EciesPrivateKey.java"], + deps = [ + ":ecies_parameters-android", + ":ecies_public_key-android", + ":hybrid_private_key-android", + "//src/main/java/com/google/crypto/tink:accesses_partial_key-android", + "//src/main/java/com/google/crypto/tink:insecure_secret_key_access-android", + "//src/main/java/com/google/crypto/tink:key-android", + "//src/main/java/com/google/crypto/tink/internal:elliptic_curves_util-android", + "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves-android", + "//src/main/java/com/google/crypto/tink/subtle:x25519-android", + "//src/main/java/com/google/crypto/tink/util:secret_big_integer-android", + "//src/main/java/com/google/crypto/tink/util:secret_bytes-android", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesPrivateKey.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesPrivateKey.java new file mode 100644 index 000000000..ca199f847 --- /dev/null +++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesPrivateKey.java @@ -0,0 +1,204 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.hybrid; + +import com.google.crypto.tink.AccessesPartialKey; +import com.google.crypto.tink.InsecureSecretKeyAccess; +import com.google.crypto.tink.Key; +import com.google.crypto.tink.internal.EllipticCurvesUtil; +import com.google.crypto.tink.subtle.EllipticCurves; +import com.google.crypto.tink.subtle.X25519; +import com.google.crypto.tink.util.SecretBigInteger; +import com.google.crypto.tink.util.SecretBytes; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.RestrictedApi; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.util.Arrays; +import javax.annotation.Nullable; + +/** Representation of the decryption function for an ECIES hybrid encryption primitive. */ +@Immutable +public final class EciesPrivateKey extends HybridPrivateKey { + private final EciesPublicKey publicKey; + + /** Exactly one of nistPrivateKeyValue and x25519PrivateKeyBytes is non-null. */ + @Nullable private final SecretBigInteger nistPrivateKeyValue; + + @Nullable private final SecretBytes x25519PrivateKeyBytes; + + private EciesPrivateKey( + EciesPublicKey publicKey, + @Nullable SecretBigInteger nistPrivateKeyValue, + @Nullable SecretBytes x25519PrivateKeyBytes) { + this.publicKey = publicKey; + this.nistPrivateKeyValue = nistPrivateKeyValue; + this.x25519PrivateKeyBytes = x25519PrivateKeyBytes; + } + + private static ECParameterSpec toParameterSpecNistCurve(EciesParameters.CurveType curveType) { + if (curveType == EciesParameters.CurveType.NIST_P256) { + return EllipticCurves.getNistP256Params(); + } + if (curveType == EciesParameters.CurveType.NIST_P384) { + return EllipticCurves.getNistP384Params(); + } + if (curveType == EciesParameters.CurveType.NIST_P521) { + return EllipticCurves.getNistP521Params(); + } + throw new IllegalArgumentException("Unable to determine NIST curve type for " + curveType); + } + + private static void validateNistPrivateKeyValue( + BigInteger privateValue, ECPoint publicPoint, EciesParameters.CurveType curveType) + throws GeneralSecurityException { + BigInteger order = toParameterSpecNistCurve(curveType).getOrder(); + if ((privateValue.signum() <= 0) || (privateValue.compareTo(order) >= 0)) { + throw new GeneralSecurityException("Invalid private value"); + } + ECPoint p = + EllipticCurvesUtil.multiplyByGenerator(privateValue, toParameterSpecNistCurve(curveType)); + if (!p.equals(publicPoint)) { + throw new GeneralSecurityException("Invalid private value"); + } + } + + private static void validateX25519PrivateKeyBytes(byte[] privateKeyBytes, byte[] publicKeyBytes) + throws GeneralSecurityException { + if (privateKeyBytes.length != 32) { + throw new GeneralSecurityException("Private key bytes length for X25519 curve must be 32"); + } + byte[] expectedPublicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + if (!Arrays.equals(expectedPublicKeyBytes, publicKeyBytes)) { + throw new GeneralSecurityException("Invalid private key for public key."); + } + } + + /** + * Creates a new ECIES private key using Curve25519. + * + * @param publicKey Corresponding ECIES public key for this private key + * @param x25519PrivateKeyBytes private key bytes + */ + @AccessesPartialKey + @RestrictedApi( + explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", + link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", + allowedOnPath = ".*Test\\.java", + allowlistAnnotations = {AccessesPartialKey.class}) + public static EciesPrivateKey createForCurveX25519( + EciesPublicKey publicKey, SecretBytes x25519PrivateKeyBytes) throws GeneralSecurityException { + if (publicKey == null) { + throw new GeneralSecurityException( + "ECIES private key cannot be constructed without an ECIES public key"); + } + if (publicKey.getX25519CurvePointBytes() == null) { + throw new GeneralSecurityException( + "ECIES private key for X25519 curve cannot be constructed with NIST-curve public key"); + } + if (x25519PrivateKeyBytes == null) { + throw new GeneralSecurityException("ECIES private key cannot be constructed without secret"); + } + validateX25519PrivateKeyBytes( + x25519PrivateKeyBytes.toByteArray(InsecureSecretKeyAccess.get()), + publicKey.getX25519CurvePointBytes().toByteArray()); + + return new EciesPrivateKey(publicKey, null, x25519PrivateKeyBytes); + } + + /** + * Creates a new ECIES private key using NIST Curves. + * + * @param publicKey Corresponding ECIES public key for this private key + * @param nistPrivateKeyValue private big integer value in bigendian representation + */ + @AccessesPartialKey + @RestrictedApi( + explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", + link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", + allowedOnPath = ".*Test\\.java", + allowlistAnnotations = {AccessesPartialKey.class}) + public static EciesPrivateKey createForNistCurve( + EciesPublicKey publicKey, SecretBigInteger nistPrivateKeyValue) + throws GeneralSecurityException { + if (publicKey == null) { + throw new GeneralSecurityException( + "ECIES private key cannot be constructed without an ECIES public key"); + } + if (publicKey.getNistCurvePoint() == null) { + throw new GeneralSecurityException( + "ECIES private key for NIST curve cannot be constructed with X25519-curve public key"); + } + if (nistPrivateKeyValue == null) { + throw new GeneralSecurityException("ECIES private key cannot be constructed without secret"); + } + validateNistPrivateKeyValue( + nistPrivateKeyValue.getBigInteger(InsecureSecretKeyAccess.get()), + publicKey.getNistCurvePoint(), + publicKey.getParameters().getCurveType()); + + return new EciesPrivateKey(publicKey, nistPrivateKeyValue, null); + } + + @RestrictedApi( + explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", + link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", + allowedOnPath = ".*Test\\.java", + allowlistAnnotations = {AccessesPartialKey.class}) + @Nullable + public SecretBytes getX25519PrivateKeyBytes() { + return x25519PrivateKeyBytes; + } + + @RestrictedApi( + explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", + link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", + allowedOnPath = ".*Test\\.java", + allowlistAnnotations = {AccessesPartialKey.class}) + @Nullable + public SecretBigInteger getNistPrivateKeyValue() { + return nistPrivateKeyValue; + } + + @Override + public EciesParameters getParameters() { + return publicKey.getParameters(); + } + + @Override + public EciesPublicKey getPublicKey() { + return publicKey; + } + + @Override + public boolean equalsKey(Key o) { + if (!(o instanceof EciesPrivateKey)) { + return false; + } + EciesPrivateKey other = (EciesPrivateKey) o; + if (!publicKey.equalsKey(other.publicKey)) { + return false; + } + if (x25519PrivateKeyBytes == null && other.x25519PrivateKeyBytes == null) { + return nistPrivateKeyValue.equalsSecretBigInteger(other.nistPrivateKeyValue); + } + + return x25519PrivateKeyBytes.equalsSecretBytes(other.x25519PrivateKeyBytes); + } +} diff --git a/java_src/src/test/java/com/google/crypto/tink/hybrid/BUILD.bazel b/java_src/src/test/java/com/google/crypto/tink/hybrid/BUILD.bazel index c8e4506e5..5ce9ce2dd 100644 --- a/java_src/src/test/java/com/google/crypto/tink/hybrid/BUILD.bazel +++ b/java_src/src/test/java/com/google/crypto/tink/hybrid/BUILD.bazel @@ -427,3 +427,23 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "EciesPrivateKeyTest", + size = "small", + srcs = ["EciesPrivateKeyTest.java"], + deps = [ + "//src/main/java/com/google/crypto/tink:insecure_secret_key_access", + "//src/main/java/com/google/crypto/tink/aead:x_cha_cha20_poly1305_parameters", + "//src/main/java/com/google/crypto/tink/hybrid:ecies_parameters", + "//src/main/java/com/google/crypto/tink/hybrid:ecies_private_key", + "//src/main/java/com/google/crypto/tink/hybrid:ecies_public_key", + "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves", + "//src/main/java/com/google/crypto/tink/subtle:x25519", + "//src/main/java/com/google/crypto/tink/util:bytes", + "//src/main/java/com/google/crypto/tink/util:secret_big_integer", + "//src/main/java/com/google/crypto/tink/util:secret_bytes", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesPrivateKeyTest.java b/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesPrivateKeyTest.java new file mode 100644 index 000000000..f5311b2a5 --- /dev/null +++ b/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesPrivateKeyTest.java @@ -0,0 +1,440 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.hybrid; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.crypto.tink.InsecureSecretKeyAccess; +import com.google.crypto.tink.aead.XChaCha20Poly1305Parameters; +import com.google.crypto.tink.subtle.EllipticCurves; +import com.google.crypto.tink.subtle.X25519; +import com.google.crypto.tink.util.Bytes; +import com.google.crypto.tink.util.SecretBigInteger; +import com.google.crypto.tink.util.SecretBytes; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECPrivateKeySpec; +import java.util.Arrays; +import org.junit.Test; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.FromDataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +@RunWith(Theories.class) +public final class EciesPrivateKeyTest { + private static final class NistCurveMapping { + final EciesParameters.CurveType curveType; + final EllipticCurves.CurveType ecNistCurve; + + NistCurveMapping(EciesParameters.CurveType curveType, EllipticCurves.CurveType ecNistCurve) { + this.curveType = curveType; + this.ecNistCurve = ecNistCurve; + } + } + + @DataPoints("nistCurvesMapping") + public static final NistCurveMapping[] NIST_CURVES = + new NistCurveMapping[] { + new NistCurveMapping( + EciesParameters.CurveType.NIST_P256, EllipticCurves.CurveType.NIST_P256), + new NistCurveMapping( + EciesParameters.CurveType.NIST_P384, EllipticCurves.CurveType.NIST_P384), + new NistCurveMapping( + EciesParameters.CurveType.NIST_P521, EllipticCurves.CurveType.NIST_P521) + }; + + @DataPoints("pointFormats") + public static final EciesParameters.PointFormat[] POINT_FORMATS = + new EciesParameters.PointFormat[] { + EciesParameters.PointFormat.UNCOMPRESSED, + EciesParameters.PointFormat.COMPRESSED, + EciesParameters.PointFormat.LEGACY_UNCOMPRESSED, + }; + + @Test + public void convertToAndFromJavaECPrivateKey() throws Exception { + // Create an elliptic curve key pair using Java's KeyPairGenerator and get the public key. + KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256); + ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); + + // Before conversion, check that the spec of the ecPrivateKey are what we expect. + assertThat(ecPrivateKey.getParams().getCurve()) + .isEqualTo(EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256).getCurve()); + assertThat(ecPublicKey.getParams().getCurve()) + .isEqualTo(EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256).getCurve()); + + // Create EciesParameters that match the curve type. + EciesParameters parameters = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.NIST_P256) + .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + // Create EciesPublicKey and EciesPrivateKey using using ecPublicKey and ecPrivateKey. + EciesPublicKey publicKey = + EciesPublicKey.createForNistCurve( + parameters, ecPublicKey.getW(), /* idRequirement= */ null); + EciesPrivateKey privateKey = + EciesPrivateKey.createForNistCurve( + publicKey, + SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), InsecureSecretKeyAccess.get())); + + // Convert EciesPrivateKey back into a ECPrivateKey. + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + ECPrivateKey ecPrivateKey2 = + (ECPrivateKey) + keyFactory.generatePrivate( + new ECPrivateKeySpec( + privateKey + .getNistPrivateKeyValue() + .getBigInteger(InsecureSecretKeyAccess.get()), + EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256))); + assertThat(ecPrivateKey2.getS()).isEqualTo(ecPrivateKey.getS()); + assertThat(ecPrivateKey2.getParams().getCurve()).isEqualTo(ecPrivateKey.getParams().getCurve()); + } + + @Theory + public void createNistCurvePrivateKey_hasCorrectParameters( + @FromDataPoints("nistCurvesMapping") NistCurveMapping nistCurveMapping, + @FromDataPoints("pointFormats") EciesParameters.PointFormat pointFormat) + throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(nistCurveMapping.curveType) + .setNistCurvePointFormat(pointFormat) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + KeyPair keyPair = EllipticCurves.generateKeyPair(nistCurveMapping.ecNistCurve); + ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); + + EciesPublicKey publicKey = + EciesPublicKey.createForNistCurve(params, ecPublicKey.getW(), /* idRequirement= */ null); + EciesPrivateKey privateKey = + EciesPrivateKey.createForNistCurve( + publicKey, + SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), InsecureSecretKeyAccess.get())); + + assertThat(privateKey.getParameters()).isEqualTo(params); + assertThat(privateKey.getPublicKey()).isEqualTo(publicKey); + assertThat(privateKey.getX25519PrivateKeyBytes()).isEqualTo(null); + assertThat(privateKey.getNistPrivateKeyValue().getBigInteger(InsecureSecretKeyAccess.get())) + .isEqualTo(ecPrivateKey.getS()); + assertThat(privateKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); + assertThat(privateKey.getIdRequirementOrNull()).isNull(); + } + + @Test + public void createX25519PrivateKey_hasCorrectParameters() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + byte[] privateKeyBytes = X25519.generatePrivateKey(); + byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ null); + EciesPrivateKey privateKey = + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); + + assertThat(privateKey.getParameters()).isEqualTo(params); + assertThat(privateKey.getPublicKey()).isEqualTo(publicKey); + assertThat(privateKey.getX25519PrivateKeyBytes().toByteArray(InsecureSecretKeyAccess.get())) + .isEqualTo(privateKeyBytes); + assertThat(privateKey.getNistPrivateKeyValue()).isNull(); + assertThat(privateKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {})); + assertThat(privateKey.getIdRequirementOrNull()).isNull(); + } + + @Test + public void callCreateForNistCurveWithX25519PublicKey_throws() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519(params, publicKeyBytes, /* idRequirement= */ null); + + ECPrivateKey ecPrivateKey = + (ECPrivateKey) + EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256).getPrivate(); + + assertThrows( + GeneralSecurityException.class, + () -> + EciesPrivateKey.createForNistCurve( + publicKey, + SecretBigInteger.fromBigInteger( + ecPrivateKey.getS(), InsecureSecretKeyAccess.get()))); + } + + @Test + public void callCreateForCurve25519WithNistPublicKey_throws() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.NIST_P256) + .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + ECPublicKey ecPublicKey = + (ECPublicKey) + EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256).getPublic(); + EciesPublicKey publicKey = + EciesPublicKey.createForNistCurve(params, ecPublicKey.getW(), /* idRequirement= */ null); + + byte[] privateKeyBytes = X25519.generatePrivateKey(); + + assertThrows( + GeneralSecurityException.class, + () -> + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get()))); + } + + @Theory + public void createNistCurvePrivateKey_failsWithMismatchedPublicKey() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.NIST_P256) + .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + ECPublicKey ecPublicKey = + (ECPublicKey) + EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256).getPublic(); + ECPrivateKey ecPrivateKey = + (ECPrivateKey) + EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256).getPrivate(); + + EciesPublicKey publicKey = + EciesPublicKey.createForNistCurve(params, ecPublicKey.getW(), /* idRequirement= */ null); + + assertThrows( + GeneralSecurityException.class, + () -> + EciesPrivateKey.createForNistCurve( + publicKey, + SecretBigInteger.fromBigInteger( + ecPrivateKey.getS(), InsecureSecretKeyAccess.get()))); + } + + @Test + public void createX25519PrivateKey_withTooShortKey_fails() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + byte[] privateKeyBytes = X25519.generatePrivateKey(); + byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ null); + byte[] tooShort = Arrays.copyOf(privateKeyBytes, privateKeyBytes.length - 1); + + assertThrows( + GeneralSecurityException.class, + () -> + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(tooShort, InsecureSecretKeyAccess.get()))); + } + + @Test + public void createX25519PrivateKey_withTooLongKey_fails() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + byte[] privateKeyBytes = X25519.generatePrivateKey(); + byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ null); + byte[] tooLong = Arrays.copyOf(privateKeyBytes, privateKeyBytes.length + 1); + + assertThrows( + GeneralSecurityException.class, + () -> + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(tooLong, InsecureSecretKeyAccess.get()))); + } + + @Test + public void createX25519PrivateKey_failsWithMismatchedPublicKey() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + + byte[] privateKeyBytes = X25519.generatePrivateKey(); + Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey())); + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519(params, publicKeyBytes, /* idRequirement= */ null); + + assertThrows( + GeneralSecurityException.class, + () -> + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get()))); + } + + @Test + public void sameX25519Keys_areEqual() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + byte[] privateKeyBytes = X25519.generatePrivateKey(); + byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ null); + + EciesPrivateKey privateKey1 = + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); + EciesPrivateKey privateKey2 = + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); + + assertThat(privateKey1.equalsKey(privateKey2)).isTrue(); + } + + @Theory + public void sameNistKeys_areEqual( + @FromDataPoints("nistCurvesMapping") NistCurveMapping nistCurveMapping, + @FromDataPoints("pointFormats") EciesParameters.PointFormat pointFormat) + throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(nistCurveMapping.curveType) + .setNistCurvePointFormat(pointFormat) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + KeyPair keyPair = EllipticCurves.generateKeyPair(nistCurveMapping.ecNistCurve); + ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic(); + EciesPublicKey publicKey = + EciesPublicKey.createForNistCurve(params, ecPublicKey.getW(), /* idRequirement= */ null); + + EciesPrivateKey privateKey1 = + EciesPrivateKey.createForNistCurve( + publicKey, + SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), InsecureSecretKeyAccess.get())); + EciesPrivateKey privateKey2 = + EciesPrivateKey.createForNistCurve( + publicKey, + SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), InsecureSecretKeyAccess.get())); + + assertThat(privateKey1.equalsKey(privateKey2)).isTrue(); + } + + @Test + public void testDifferentPublicKeyParams_areNotEqual() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.TINK) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + byte[] privateKeyBytes = X25519.generatePrivateKey(); + byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + EciesPublicKey publicKey1 = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ 123); + EciesPublicKey publicKey2 = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ 456); + + EciesPrivateKey privateKey1 = + EciesPrivateKey.createForCurveX25519( + publicKey1, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); + EciesPrivateKey privateKey2 = + EciesPrivateKey.createForCurveX25519( + publicKey2, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); + + assertThat(privateKey1.equalsKey(privateKey2)).isFalse(); + } + + @Test + public void differentKeyTypesAreNotEqual() throws Exception { + EciesParameters params = + EciesParameters.builder() + .setHashType(EciesParameters.HashType.SHA256) + .setCurveType(EciesParameters.CurveType.X25519) + .setVariant(EciesParameters.Variant.NO_PREFIX) + .setDemParameters(XChaCha20Poly1305Parameters.create()) + .build(); + byte[] privateKeyBytes = X25519.generatePrivateKey(); + byte[] publicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); + + EciesPublicKey publicKey = + EciesPublicKey.createForCurveX25519( + params, Bytes.copyFrom(publicKeyBytes), /* idRequirement= */ null); + EciesPrivateKey privateKey = + EciesPrivateKey.createForCurveX25519( + publicKey, SecretBytes.copyFrom(privateKeyBytes, InsecureSecretKeyAccess.get())); + + assertThat(publicKey.equalsKey(privateKey)).isFalse(); + } +} |