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

#include "net/ssl/ssl_platform_key_mac.h"

#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecItem.h>
#include <Security/SecKey.h>

#include <string>
#include <string_view>

#include "base/apple/scoped_cftyperef.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/checked_math.h"
#include "base/test/task_environment.h"
#include "crypto/apple/scoped_fake_keychain_v2.h"
#include "crypto/apple/test_helpers.h"
#include "crypto/signature_verifier.h"
#include "net/ssl/ssl_private_key.h"
#include "net/ssl/ssl_private_key_test_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/evp.h"

namespace net {

namespace {

struct TestKey {
  const char* name;
  const char* cert_file;
  const char* key_file;
  int type;
};

const TestKey kTestKeys[] = {
    {"RSA", "client_1.pem", "client_1.pk8", EVP_PKEY_RSA},
    {"ECDSA_P256", "client_p256.pem", "client_p256.pk8", EVP_PKEY_EC},
    {"ECDSA_P384", "client_p384.pem", "client_p384.pk8", EVP_PKEY_EC},
    {"ECDSA_P521", "client_p521.pem", "client_p521.pk8", EVP_PKEY_EC},
};

std::string TestKeyToString(const testing::TestParamInfo<TestKey>& params) {
  return params.param.name;
}

}  // namespace

class SSLPlatformKeyMacTest : public testing::TestWithParam<TestKey> {};

TEST_P(SSLPlatformKeyMacTest, KeyMatches) {
  base::test::TaskEnvironment task_environment;

  const TestKey& test_key = GetParam();

  // Load test data.
  scoped_refptr<X509Certificate> cert =
      ImportCertFromFile(GetTestCertsDirectory(), test_key.cert_file);
  ASSERT_TRUE(cert);

  base::FilePath pkcs8_path =
      GetTestCertsDirectory().AppendASCII(test_key.key_file);
  std::optional<std::vector<uint8_t>> pkcs8 = base::ReadFileToBytes(pkcs8_path);
  ASSERT_TRUE(pkcs8);
  base::apple::ScopedCFTypeRef<SecKeyRef> sec_key =
      crypto::apple::SecKeyFromPKCS8(*pkcs8);
  ASSERT_TRUE(sec_key);

  // Make an `SSLPrivateKey` backed by `sec_key`.
  scoped_refptr<SSLPrivateKey> key =
      CreateSSLPrivateKeyForSecKey(cert.get(), sec_key.get());
  ASSERT_TRUE(key);

  // Mac keys from the default provider are expected to support all algorithms.
  EXPECT_EQ(SSLPrivateKey::DefaultAlgorithmPreferences(test_key.type, true),
            key->GetAlgorithmPreferences());

  TestSSLPrivateKeyMatches(key.get(), *pkcs8);
}

INSTANTIATE_TEST_SUITE_P(All,
                         SSLPlatformKeyMacTest,
                         testing::ValuesIn(kTestKeys),
                         TestKeyToString);

TEST(SSLPlatformKeyMacInvalidTest, UnsupportedKeyType) {
  base::test::TaskEnvironment task_environment;

  scoped_refptr<X509Certificate> cert =
      ImportCertFromFile(GetTestCertsDirectory(), "client_x25519.pem");
  ASSERT_TRUE(cert);

  // macOS does not support X25519 SecKeyRefs. However,
  // `CreateSSLPrivateKeyForSecKey` only inspects the certificate's public key
  // when checking the key type, so test rejection by pairing the certificate
  // with an arbitrary other key.
  base::FilePath pkcs8_path =
      GetTestCertsDirectory().AppendASCII("client_1.pk8");
  std::optional<std::vector<uint8_t>> pkcs8 = base::ReadFileToBytes(pkcs8_path);
  ASSERT_TRUE(pkcs8);
  base::apple::ScopedCFTypeRef<SecKeyRef> sec_key =
      crypto::apple::SecKeyFromPKCS8(*pkcs8);
  ASSERT_TRUE(sec_key);

  scoped_refptr<SSLPrivateKey> key =
      CreateSSLPrivateKeyForSecKey(cert.get(), sec_key.get());
  EXPECT_FALSE(key);
}

namespace {

constexpr char kTestKeychainAccessGroup[] = "test-keychain-access-group";
constexpr crypto::SignatureVerifier::SignatureAlgorithm kAcceptableAlgos[] = {
    crypto::SignatureVerifier::ECDSA_SHA256};

const crypto::UnexportableKeyProvider::Config config = {
    .keychain_access_group = kTestKeychainAccessGroup,
};

}  // namespace

// Tests that a SSLPrivateKey can be created from a
// crypto::UnexportableSigningKey.
TEST(UnexportableSSLPlatformKeyMacTest, Convert) {
  crypto::apple::ScopedFakeKeychainV2 scoped_fake_apple_keychain_{
      kTestKeychainAccessGroup};
  // Create a crypto::UnexportableSigningKey and verify preconditions.
  std::unique_ptr<crypto::UnexportableKeyProvider> provider =
      crypto::GetUnexportableKeyProvider(config);
  ASSERT_TRUE(provider);
  std::unique_ptr<crypto::UnexportableSigningKey> unexportable_key =
      provider->GenerateSigningKeySlowly(kAcceptableAlgos);
  ASSERT_TRUE(unexportable_key);
  SecKeyRef key_ref = unexportable_key->GetSecKeyRef();
  EXPECT_TRUE(key_ref);

  auto ssl_private_key = WrapUnexportableKey(*unexportable_key);
  EXPECT_TRUE(ssl_private_key);
}

}  // namespace net
