aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorioannanedelcu <ioannanedelcu@google.com>2023-08-04 06:00:09 -0700
committerCopybara-Service <copybara-worker@google.com>2023-08-04 06:01:06 -0700
commit8e991c57cecf6794b774e32ce531a80fe6ad3110 (patch)
tree1fecf8960b845ee332b0ab6bec9f7c00d180b466
parent4fa7e0322848bcf34eb200510da0619ae0b3636b (diff)
downloadtink-8e991c57cecf6794b774e32ce531a80fe6ad3110.tar.gz
Add EciesPrivateKey class in Java
PiperOrigin-RevId: 553782644
-rw-r--r--java_src/BUILD.bazel2
-rw-r--r--java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel40
-rw-r--r--java_src/src/main/java/com/google/crypto/tink/hybrid/EciesPrivateKey.java204
-rw-r--r--java_src/src/test/java/com/google/crypto/tink/hybrid/BUILD.bazel20
-rw-r--r--java_src/src/test/java/com/google/crypto/tink/hybrid/EciesPrivateKeyTest.java440
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();
+ }
+}