/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <memory>
#include "nss.h"
#include "pk11pub.h"
#include "sechash.h"
#include "cryptohi.h"

#include "cpputil.h"
#include "json_reader.h"
#include "nss_scoped_ptrs.h"
#include "testvectors_base/test-structs.h"

#include "pk11_eddsa_vectors.h"
#include "pk11_signature_test.h"
#include "pk11_keygen.h"

namespace nss_test {
static const Pkcs11SignatureTestParams kEddsaVectors[] = {
    {DataBuffer(kEd25519Pkcs8_1, sizeof(kEd25519Pkcs8_1)),
     DataBuffer(kEd25519Spki_1, sizeof(kEd25519Spki_1)),
     DataBuffer(kEd25519Message_1, sizeof(kEd25519Message_1)),
     DataBuffer(kEd25519Signature_1, sizeof(kEd25519Signature_1))},

    {DataBuffer(kEd25519Pkcs8_2, sizeof(kEd25519Pkcs8_2)),
     DataBuffer(kEd25519Spki_2, sizeof(kEd25519Spki_2)),
     DataBuffer(kEd25519Message_2, sizeof(kEd25519Message_2)),
     DataBuffer(kEd25519Signature_2, sizeof(kEd25519Signature_2))},

    {DataBuffer(kEd25519Pkcs8_3, sizeof(kEd25519Pkcs8_3)),
     DataBuffer(kEd25519Spki_3, sizeof(kEd25519Spki_3)),
     DataBuffer(kEd25519Message_3, sizeof(kEd25519Message_3)),
     DataBuffer(kEd25519Signature_3, sizeof(kEd25519Signature_3))}};

class Pkcs11EddsaTest
    : public Pk11SignatureTest,
      public ::testing::WithParamInterface<Pkcs11SignatureTestParams> {
 protected:
  Pkcs11EddsaTest() : Pk11SignatureTest(CKM_EDDSA) {}
};

TEST_P(Pkcs11EddsaTest, SignAndVerify) { SignAndVerifyRaw(GetParam()); }

TEST_P(Pkcs11EddsaTest, ImportExport) { ImportExport(GetParam().pkcs8_); }

TEST_P(Pkcs11EddsaTest, ImportConvertToPublic) {
  ScopedSECKEYPrivateKey privKey(ImportPrivateKey(GetParam().pkcs8_));
  ASSERT_TRUE(privKey);

  ScopedSECKEYPublicKey pubKey(SECKEY_ConvertToPublicKey(privKey.get()));
  ASSERT_TRUE(pubKey);
}

TEST_P(Pkcs11EddsaTest, ImportPublicCreateSubjectPKInfo) {
  ScopedSECKEYPrivateKey privKey(ImportPrivateKey(GetParam().pkcs8_));
  ASSERT_TRUE(privKey);

  ScopedSECKEYPublicKey pubKey(
      (SECKEYPublicKey*)SECKEY_ConvertToPublicKey(privKey.get()));
  ASSERT_TRUE(pubKey);

  ScopedSECItem der_spki(SECKEY_EncodeDERSubjectPublicKeyInfo(pubKey.get()));
  ASSERT_TRUE(der_spki);
  ASSERT_EQ(der_spki->len, GetParam().spki_.len());
  ASSERT_EQ(0, memcmp(der_spki->data, GetParam().spki_.data(), der_spki->len));
}

INSTANTIATE_TEST_SUITE_P(EddsaSignVerify, Pkcs11EddsaTest,
                         ::testing::ValuesIn(kEddsaVectors));

class Pkcs11EddsaRoundtripTest
    : public Pk11SignatureTest,
      public ::testing::WithParamInterface<Pkcs11SignatureTestParams> {
 protected:
  Pkcs11EddsaRoundtripTest() : Pk11SignatureTest(CKM_EDDSA) {}

 protected:
  void GenerateExportImportSignVerify(Pkcs11SignatureTestParams params) {
    Pkcs11KeyPairGenerator generator(CKM_EC_EDWARDS_KEY_PAIR_GEN);
    ScopedSECKEYPrivateKey priv;
    ScopedSECKEYPublicKey pub;
    generator.GenerateKey(&priv, &pub, false);

    DataBuffer exported;
    ExportPrivateKey(&priv, exported);

    ScopedSECKEYPrivateKey privKey(ImportPrivateKey(exported));
    ASSERT_NE(privKey, nullptr);
    DataBuffer sig;

    SignRaw(privKey, params.data_, &sig);
    Verify(pub, params.data_, sig);
  }
};

TEST_P(Pkcs11EddsaRoundtripTest, GenerateExportImportSignVerify) {
  GenerateExportImportSignVerify(GetParam());
}

INSTANTIATE_TEST_SUITE_P(EddsaRound, Pkcs11EddsaRoundtripTest,
                         ::testing::ValuesIn(kEddsaVectors));

class Pkcs11EddsaWycheproofTest : public ::testing::Test {
 protected:
  void Run(const std::string& name) {
    WycheproofHeader(name, "EDDSA", "eddsa_verify_schema.json",
                     [this](JsonReader& r) { RunGroup(r); });
  }

 private:
  void RunGroup(JsonReader& r) {
    std::vector<EddsaTestVector> tests;
    std::vector<uint8_t> public_key;

    while (r.NextItem()) {
      std::string n = r.ReadLabel();
      if (n == "") {
        break;
      }

      if (n == "jwk" || n == "key" || n == "keyPem") {
        r.SkipValue();
      } else if (n == "keyDer") {
        public_key = r.ReadHex();
      } else if (n == "type") {
        ASSERT_EQ("EddsaVerify", r.ReadString());
      } else if (n == "tests") {
        WycheproofReadTests(r, &tests, ReadTestAttr);
      } else {
        FAIL() << "unknown label in group: " << n;
      }
    }

    for (auto& t : tests) {
      std::cout << "Running test " << t.id << std::endl;
      t.public_key = public_key;
      Derive(t);
    }
  }

  static void ReadTestAttr(EddsaTestVector& t, const std::string& n,
                           JsonReader& r) {
    if (n == "msg") {
      t.msg = r.ReadHex();
    } else if (n == "sig") {
      t.sig = r.ReadHex();
    } else {
      FAIL() << "unknown test key: " << n;
    }
  }

  void Derive(const EddsaTestVector& vec) {
    SECItem spki_item = {siBuffer, toUcharPtr(vec.public_key.data()),
                         static_cast<unsigned int>(vec.public_key.size())};
    SECItem sig_item = {siBuffer, toUcharPtr(vec.sig.data()),
                        static_cast<unsigned int>(vec.sig.size())};
    SECItem msg_item = {siBuffer, toUcharPtr(vec.msg.data()),
                        static_cast<unsigned int>(vec.msg.size())};

    ScopedCERTSubjectPublicKeyInfo cert_spki(
        SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_item));
    ASSERT_TRUE(cert_spki);

    ScopedSECKEYPublicKey pub_key(SECKEY_ExtractPublicKey(cert_spki.get()));
    ASSERT_TRUE(pub_key);

    SECStatus rv = PK11_VerifyWithMechanism(pub_key.get(), CKM_EDDSA, nullptr,
                                            &sig_item, &msg_item, nullptr);
    EXPECT_EQ(rv, vec.valid ? SECSuccess : SECFailure);
  };
};

TEST_F(Pkcs11EddsaWycheproofTest, Ed25519) { Run("eddsa"); }

}  // namespace nss_test
