// Copyright 2022 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.signature;

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.internal.KeyTester;
import com.google.crypto.tink.subtle.Hex;
import com.google.crypto.tink.util.Bytes;
import com.google.crypto.tink.util.SecretBigInteger;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class EcdsaPrivateKeyTest {

  // Test case from https://www.ietf.org/rfc/rfc6979.txt, A.2.5
  private static final ECPoint P256_PUBLIC_POINT =
      new ECPoint(
          new BigInteger("60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6", 16),
          new BigInteger("7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299", 16));
  private static final BigInteger P256_PRIVATE_VALUE =
      new BigInteger("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721", 16);

  // Test case from https://www.ietf.org/rfc/rfc6979.txt, A.2.5
  private static final ECPoint P521_PUBLIC_POINT =
      new ECPoint(
          new BigInteger(
              "1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD3"
                  + "71123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F502"
                  + "3A4",
              16),
          new BigInteger(
              "0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A2"
                  + "8A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDF"
                  + "CF5",
              16));
  private static final BigInteger P521_PRIVATE_VALUE =
      new BigInteger(
          "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75C"
              + "AA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83"
              + "538",
          16);

  @Test
  public void buildNoPrefixVariantAndGetProperties() throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    assertThat(parameters.hasIdRequirement()).isFalse();
    EcdsaPublicKey publicKey =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(P256_PUBLIC_POINT)
            .build();
    EcdsaPrivateKey privateKey =
        EcdsaPrivateKey.builder()
            .setPublicKey(publicKey)
            .setPrivateValue(
                SecretBigInteger.fromBigInteger(P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
            .build();
    assertThat(privateKey.getParameters()).isEqualTo(parameters);
    assertThat(privateKey.getPublicKey()).isEqualTo(publicKey);
    assertThat(privateKey.getPrivateValue().getBigInteger(InsecureSecretKeyAccess.get()))
        .isEqualTo(P256_PRIVATE_VALUE);
    assertThat(privateKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
    assertThat(privateKey.getIdRequirementOrNull()).isNull();
  }

  @Test
  public void buildTinkVariantAndGetProperties() throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.TINK)
            .build();
    assertThat(parameters.hasIdRequirement()).isTrue();
    EcdsaPublicKey publicKey =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(P256_PUBLIC_POINT)
            .setIdRequirement(0x66AABBCC)
            .build();
    EcdsaPrivateKey privateKey =
        EcdsaPrivateKey.builder()
            .setPublicKey(publicKey)
            .setPrivateValue(
                SecretBigInteger.fromBigInteger(P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
            .build();
    assertThat(privateKey.getParameters()).isEqualTo(parameters);
    assertThat(privateKey.getPublicKey()).isEqualTo(publicKey);
    assertThat(privateKey.getPrivateValue().getBigInteger(InsecureSecretKeyAccess.get()))
        .isEqualTo(P256_PRIVATE_VALUE);
    assertThat(privateKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(Hex.decode("0166AABBCC")));
    assertThat(privateKey.getIdRequirementOrNull()).isEqualTo(0x66AABBCC);
  }

  @Test
  public void convertToAndFromJavaECKeys() throws Exception {
    // Create an elliptic curve key pair using Java's KeyPairGenerator.
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
    keyGen.initialize(EcdsaParameters.CurveType.NIST_P384.toParameterSpec());
    KeyPair keyPair = keyGen.generateKeyPair();
    ECPrivateKey ecPrivateKey = (ECPrivateKey) keyPair.getPrivate();
    ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();

    // Before conversion, always check that the specs of ecPrivateKey and ecPublicKey are what
    // we expect.
    assertThat(EcdsaParameters.CurveType.fromParameterSpec(ecPrivateKey.getParams()))
        .isEqualTo(EcdsaParameters.CurveType.NIST_P384);
    assertThat(EcdsaParameters.CurveType.fromParameterSpec(ecPublicKey.getParams()))
        .isEqualTo(EcdsaParameters.CurveType.NIST_P384);

    // Create EcdsaParameters that match the curve type.
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P384)
            .setHashType(EcdsaParameters.HashType.SHA384)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();

    // Create EcdsaPublicKey and EcdsaPrivateKey using ecPublicKey and ecPrivateKey.
    EcdsaPublicKey publicKey =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(ecPublicKey.getW())
            .build();
    EcdsaPrivateKey privateKey =
        EcdsaPrivateKey.builder()
            .setPublicKey(publicKey)
            .setPrivateValue(
                SecretBigInteger.fromBigInteger(ecPrivateKey.getS(), InsecureSecretKeyAccess.get()))
            .build();

    // Convert EcdsaPublicKey and back into a ECPublicKey.
    KeyFactory keyFactory = KeyFactory.getInstance("EC");
    ECPublicKey ecPublicKey2 =
        (ECPublicKey)
            keyFactory.generatePublic(
                new ECPublicKeySpec(
                    publicKey.getPublicPoint(),
                    publicKey.getParameters().getCurveType().toParameterSpec()));
    assertThat(ecPublicKey2.getW()).isEqualTo(ecPublicKey.getW());
    assertThat(EcdsaParameters.CurveType.fromParameterSpec(ecPublicKey2.getParams()))
        .isEqualTo(EcdsaParameters.CurveType.NIST_P384);

    // Convert EcdsaPrivateKey back into a ECPrivateKey.
    ECPrivateKey ecPrivateKey2 =
        (ECPrivateKey)
            keyFactory.generatePrivate(
                new ECPrivateKeySpec(
                    privateKey.getPrivateValue().getBigInteger(InsecureSecretKeyAccess.get()),
                    privateKey.getParameters().getCurveType().toParameterSpec()));
    assertThat(ecPrivateKey2.getS()).isEqualTo(ecPrivateKey.getS());
    assertThat(EcdsaParameters.CurveType.fromParameterSpec(ecPrivateKey2.getParams()))
        .isEqualTo(EcdsaParameters.CurveType.NIST_P384);
  }

  @Test
  public void emptyBuild_fails() throws Exception {
    assertThrows(GeneralSecurityException.class, () -> EcdsaPublicKey.builder().build());
  }

  @Test
  public void buildWithoutPublicKey_fails() throws Exception {
    assertThrows(
        GeneralSecurityException.class,
        () ->
            EcdsaPrivateKey.builder()
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
                .build());
  }

  @Test
  public void build_validatesPrivateValue() throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey publicKey =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(P256_PUBLIC_POINT)
            .build();
    EcdsaPrivateKey valid =
        EcdsaPrivateKey.builder()
            .setPublicKey(publicKey)
            .setPrivateValue(
                SecretBigInteger.fromBigInteger(P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
            .build();
    assertThat(valid.getPrivateValue().getBigInteger(InsecureSecretKeyAccess.get()))
        .isEqualTo(P256_PRIVATE_VALUE);
    BigInteger invalidPrivateValue = P256_PRIVATE_VALUE.add(BigInteger.ONE);
    assertThrows(
        GeneralSecurityException.class,
        () ->
            EcdsaPrivateKey.builder()
                .setPublicKey(publicKey)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        invalidPrivateValue, InsecureSecretKeyAccess.get()))
                .build());
    assertThrows(
        GeneralSecurityException.class,
        () ->
            EcdsaPrivateKey.builder()
                .setPublicKey(publicKey)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(BigInteger.ZERO, InsecureSecretKeyAccess.get()))
                .build());
    assertThrows(
        GeneralSecurityException.class,
        () ->
            EcdsaPrivateKey.builder()
                .setPublicKey(publicKey)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        new BigInteger("-1"), InsecureSecretKeyAccess.get()))
                .build());
  }

  @Test
  public void build_rejectsPrivateValueThatIsLargerThanOrder() throws Exception {
    EcdsaParameters parameters =
        EcdsaParameters.builder()
            .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
            .setCurveType(EcdsaParameters.CurveType.NIST_P256)
            .setHashType(EcdsaParameters.HashType.SHA256)
            .setVariant(EcdsaParameters.Variant.NO_PREFIX)
            .build();
    EcdsaPublicKey publicKey =
        EcdsaPublicKey.builder()
            .setParameters(parameters)
            .setPublicPoint(P256_PUBLIC_POINT)
            .build();
    EcdsaPrivateKey valid =
        EcdsaPrivateKey.builder()
            .setPublicKey(publicKey)
            .setPrivateValue(
                SecretBigInteger.fromBigInteger(P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
            .build();
    assertThat(valid.getPrivateValue().getBigInteger(InsecureSecretKeyAccess.get()))
        .isEqualTo(P256_PRIVATE_VALUE);
    // Add the order of the generator to the private value.
    BigInteger tooLargePrivateValue =
        P256_PRIVATE_VALUE.add(EcdsaParameters.CurveType.NIST_P256.toParameterSpec().getOrder());
    assertThrows(
        GeneralSecurityException.class,
        () ->
            EcdsaPrivateKey.builder()
                .setPublicKey(publicKey)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        tooLargePrivateValue, InsecureSecretKeyAccess.get()))
                .build());
  }

  @Test
  public void testEqualities() throws Exception {
    EcdsaPublicKey noPrefixPublicKey =
        EcdsaPublicKey.builder()
            .setParameters(
                EcdsaParameters.builder()
                    .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
                    .setCurveType(EcdsaParameters.CurveType.NIST_P256)
                    .setHashType(EcdsaParameters.HashType.SHA256)
                    .setVariant(EcdsaParameters.Variant.NO_PREFIX)
                    .build())
            .setPublicPoint(P256_PUBLIC_POINT)
            .build();

    // Uses generator as public key, and private key as ONE
    EcdsaPublicKey noPrefixPublicKeyOne =
        EcdsaPublicKey.builder()
            .setParameters(
                EcdsaParameters.builder()
                    .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
                    .setCurveType(EcdsaParameters.CurveType.NIST_P256)
                    .setHashType(EcdsaParameters.HashType.SHA256)
                    .setVariant(EcdsaParameters.Variant.NO_PREFIX)
                    .build())
            .setPublicPoint(EcdsaParameters.CurveType.NIST_P256.toParameterSpec().getGenerator())
            .build();

    EcdsaPublicKey tinkPrefixPublicKey =
        EcdsaPublicKey.builder()
            .setParameters(
                EcdsaParameters.builder()
                    .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
                    .setCurveType(EcdsaParameters.CurveType.NIST_P256)
                    .setHashType(EcdsaParameters.HashType.SHA256)
                    .setVariant(EcdsaParameters.Variant.TINK)
                    .build())
            .setPublicPoint(P256_PUBLIC_POINT)
            .setIdRequirement(1907)
            .build();

    EcdsaPublicKey noPrefixPublicKeyP521 =
        EcdsaPublicKey.builder()
            .setParameters(
                EcdsaParameters.builder()
                    .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
                    .setCurveType(EcdsaParameters.CurveType.NIST_P521)
                    .setHashType(EcdsaParameters.HashType.SHA512)
                    .setVariant(EcdsaParameters.Variant.NO_PREFIX)
                    .build())
            .setPublicPoint(P521_PUBLIC_POINT)
            .build();

    new KeyTester()
        .addEqualityGroup(
            "No prefix",
            EcdsaPrivateKey.builder()
                .setPublicKey(noPrefixPublicKey)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
                .build(),
            // the same key built twice must be equal
            EcdsaPrivateKey.builder()
                .setPublicKey(
                    EcdsaPublicKey.builder()
                        .setParameters(
                            EcdsaParameters.builder()
                                .setSignatureEncoding(EcdsaParameters.SignatureEncoding.IEEE_P1363)
                                .setCurveType(EcdsaParameters.CurveType.NIST_P256)
                                .setHashType(EcdsaParameters.HashType.SHA256)
                                .setVariant(EcdsaParameters.Variant.NO_PREFIX)
                                .build())
                        .setPublicPoint(P256_PUBLIC_POINT)
                        .build())
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
                .build())
        // This group checks that keys with different key bytes are not equal
        .addEqualityGroup(
            "No prefix, ONE",
            EcdsaPrivateKey.builder()
                .setPublicKey(noPrefixPublicKeyOne)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(BigInteger.ONE, InsecureSecretKeyAccess.get()))
                .build())
        // This group checks that keys with different parameters are not equal
        .addEqualityGroup(
            "No prefix, P521",
            EcdsaPrivateKey.builder()
                .setPublicKey(noPrefixPublicKeyP521)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        P521_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
                .build())
        .addEqualityGroup(
            "Tink with key id 1907",
            EcdsaPrivateKey.builder()
                .setPublicKey(tinkPrefixPublicKey)
                .setPrivateValue(
                    SecretBigInteger.fromBigInteger(
                        P256_PRIVATE_VALUE, InsecureSecretKeyAccess.get()))
                .build())
        .doTests();
  }
}
