// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/fido/u2f_command_constructor.h"

#include <utility>

#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_test_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {

namespace {

CtapMakeCredentialRequest ConstructMakeCredentialRequest() {
  PublicKeyCredentialRpEntity rp("acme.com");
  rp.name = "acme.com";

  PublicKeyCredentialUserEntity user(
      fido_parsing_utils::Materialize(test_data::kUserId));
  user.name = "johnpsmith@example.com";
  user.display_name = "John P. Smith";

  return CtapMakeCredentialRequest(
      test_data::kClientDataJson, std::move(rp), std::move(user),
      PublicKeyCredentialParams(PublicKeyCredentialParams(
          std::vector<PublicKeyCredentialParams::CredentialInfo>(1))));
}

CtapGetAssertionRequest ConstructGetAssertionRequest() {
  return CtapGetAssertionRequest("acme.com", test_data::kClientDataJson);
}

}  // namespace

TEST(U2fCommandConstructorTest, TestCreateU2fRegisterCommand) {
  const auto register_command_without_individual_attestation =
      ConstructU2fRegisterCommand(test_data::kApplicationParameter,
                                  test_data::kChallengeParameter,
                                  false /* is_individual_attestation */);

  EXPECT_THAT(register_command_without_individual_attestation,
              ::testing::ElementsAreArray(test_data::kU2fRegisterCommandApdu));

  const auto register_command_with_individual_attestation =
      ConstructU2fRegisterCommand(test_data::kApplicationParameter,
                                  test_data::kChallengeParameter,
                                  true /* is_individual_attestation */);

  EXPECT_THAT(register_command_with_individual_attestation,
              ::testing::ElementsAreArray(
                  test_data::kU2fRegisterCommandApduWithIndividualAttestation));
}

TEST(U2fCommandConstructorTest, TestConvertCtapMakeCredentialToU2fRegister) {
  const auto make_credential_param = ConstructMakeCredentialRequest();

  EXPECT_TRUE(IsConvertibleToU2fRegisterCommand(make_credential_param));

  const auto u2f_register_command =
      ConvertToU2fRegisterCommand(make_credential_param);
  ASSERT_TRUE(u2f_register_command);
  EXPECT_THAT(*u2f_register_command,
              ::testing::ElementsAreArray(test_data::kU2fRegisterCommandApdu));
}

TEST(U2fCommandConstructorTest, TestU2fRegisterCredentialAlgorithmRequirement) {
  PublicKeyCredentialRpEntity rp("acme.com");
  rp.name = "acme.com";

  PublicKeyCredentialUserEntity user(
      fido_parsing_utils::Materialize(test_data::kUserId));
  user.name = "johnpsmith@example.com";
  user.display_name = "John P. Smith";

  CtapMakeCredentialRequest make_credential_param(
      test_data::kClientDataJson, std::move(rp), std::move(user),
      PublicKeyCredentialParams({{CredentialType::kPublicKey, -257}}));

  EXPECT_FALSE(IsConvertibleToU2fRegisterCommand(make_credential_param));
}

TEST(U2fCommandConstructorTest, TestU2fRegisterUserVerificationRequirement) {
  auto make_credential_param = ConstructMakeCredentialRequest();
  make_credential_param.user_verification =
      UserVerificationRequirement::kRequired;

  EXPECT_FALSE(IsConvertibleToU2fRegisterCommand(make_credential_param));
}

TEST(U2fCommandConstructorTest, TestU2fRegisterResidentKeyRequirement) {
  auto make_credential_param = ConstructMakeCredentialRequest();
  make_credential_param.resident_key_required = true;

  EXPECT_FALSE(IsConvertibleToU2fRegisterCommand(make_credential_param));
}

TEST(U2fCommandConstructorTest, TestCreateSignApduCommand) {
  const auto& encoded_sign = ConstructU2fSignCommand(
      test_data::kApplicationParameter, test_data::kChallengeParameter,
      test_data::kU2fSignKeyHandle);
  ASSERT_TRUE(encoded_sign);
  EXPECT_THAT(*encoded_sign,
              ::testing::ElementsAreArray(test_data::kU2fSignCommandApdu));
}

TEST(U2fCommandConstructorTest, TestConvertCtapGetAssertionToU2fSignRequest) {
  auto get_assertion_req = ConstructGetAssertionRequest();
  std::vector<PublicKeyCredentialDescriptor> allowed_list;
  allowed_list.push_back(PublicKeyCredentialDescriptor(
      CredentialType::kPublicKey,
      fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
  get_assertion_req.allow_list = std::move(allowed_list);

  const auto u2f_sign_command = ConvertToU2fSignCommand(
      get_assertion_req, ApplicationParameterType::kPrimary,
      fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle));

  EXPECT_TRUE(IsConvertibleToU2fSignCommand(get_assertion_req));
  ASSERT_TRUE(u2f_sign_command);
  EXPECT_THAT(*u2f_sign_command,
              ::testing::ElementsAreArray(test_data::kU2fSignCommandApdu));
}

TEST(U2fCommandConstructorTest, TestU2fSignAllowListRequirement) {
  auto get_assertion_req = ConstructGetAssertionRequest();
  EXPECT_FALSE(IsConvertibleToU2fSignCommand(get_assertion_req));
}

TEST(U2fCommandConstructorTest, TestU2fSignUserVerificationRequirement) {
  auto get_assertion_req = ConstructGetAssertionRequest();
  std::vector<PublicKeyCredentialDescriptor> allowed_list;
  allowed_list.push_back(PublicKeyCredentialDescriptor(
      CredentialType::kPublicKey,
      fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
  get_assertion_req.allow_list = std::move(allowed_list);
  get_assertion_req.user_verification = UserVerificationRequirement::kRequired;

  EXPECT_FALSE(IsConvertibleToU2fSignCommand(get_assertion_req));
}

TEST(U2fCommandConstructorTest, TestCreateSignWithIncorrectKeyHandle) {
  std::array<uint8_t, kU2fApplicationParamLength> application_parameter = {
      0x01};
  std::array<uint8_t, kU2fChallengeParamLength> challenge_parameter = {0x02};
  std::vector<uint8_t> key_handle(kMaxKeyHandleLength, 0xff);

  const auto valid_sign_command = ConstructU2fSignCommand(
      application_parameter, challenge_parameter, key_handle);
  ASSERT_TRUE(valid_sign_command);

  key_handle.push_back(0xff);
  const auto invalid_sign_command = ConstructU2fSignCommand(
      application_parameter, challenge_parameter, key_handle);
  ASSERT_FALSE(invalid_sign_command);
}

}  // namespace device