chromium/chromeos/components/kcer/helpers/pkcs12_validator_unittest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chromeos/components/kcer/helpers/pkcs12_validator.h"

#include "chromeos/components/kcer/kcer_nss/test_utils.h"
#include "net/cert/x509_certificate.h"
#include "net/test/cert_builder.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace kcer::internal {
namespace {

std::string GetPassword(const std::string& file_name) {
  if (file_name == "client.p12") {
    return "12345";
  }
  if (file_name == "client_with_ec_key.p12") {
    return "123456";
  }
  ADD_FAILURE() << "GetPassword() is called with an unexpected file name";
  return "";
}

scoped_refptr<const Cert> MakeKcerCert(
    const std::string& nickname,
    scoped_refptr<net::X509Certificate> cert) {
  // CertCache only cares about the `cert`, other fields are can be anything.
  return base::MakeRefCounted<Cert>(Token::kUser, Pkcs11Id(), nickname,
                                    std::move(cert));
}

scoped_refptr<const Cert> MakeKcerCertFromBsslCert(const std::string& nickname,
                                                   X509* cert) {
  int cert_der_size = 0;
  bssl::UniquePtr<uint8_t> cert_der;
  Pkcs12Reader pkcs12_reader;
  Pkcs12ReaderStatusCode get_cert_der_result =
      pkcs12_reader.GetDerEncodedCert(cert, cert_der, cert_der_size);
  if (get_cert_der_result != Pkcs12ReaderStatusCode::kSuccess) {
    ADD_FAILURE() << "GetDerEncodedCert failed";
    return nullptr;
  }

  scoped_refptr<net::X509Certificate> x509_cert =
      net::X509Certificate::CreateFromBytes(
          base::make_span(cert_der.get(), size_t(cert_der_size)));
  return MakeKcerCert("name", x509_cert);
}

class KcerPkcs12ValidatorTest : public ::testing::Test {
 public:
  void GetData(const char* file_name,
               KeyData& key_data,
               bssl::UniquePtr<STACK_OF(X509)>& certs) {
    Pkcs12Blob pkcs12_blob(ReadTestFile(file_name));
    std::string password(GetPassword(file_name));

    Pkcs12ReaderStatusCode get_key_and_cert_status =
        pkcs12_reader_.GetPkcs12KeyAndCerts(pkcs12_blob.value(), password,
                                            key_data.key, certs);
    if (get_key_and_cert_status != Pkcs12ReaderStatusCode::kSuccess) {
      ADD_FAILURE() << "GetPkcs12KeyAndCerts failed";
      return;
    }

    if (pkcs12_reader_.EnrichKeyData(key_data) !=
        Pkcs12ReaderStatusCode::kSuccess) {
      ADD_FAILURE() << "EnrichKeyData failed";
      return;
    }
  }

  Pkcs12Reader pkcs12_reader_;
  CertCache cert_cache_;
};

// Test that ValidateAndPrepareCertData() returns success on validating a
// correct RSA PKCS#12 data and chooses the correct nickname.
TEST_F(KcerPkcs12ValidatorTest, RsaSuccess) {
  KeyData key_data;
  bssl::UniquePtr<STACK_OF(X509)> certs;
  ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs));

  std::vector<CertData> certs_data;
  Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data);
  EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess);
  ASSERT_EQ(certs_data.size(), 1u);
  // The file doesn't have the nickname set and there are no other certs to copy
  // it from, so the subject name should be used.
  EXPECT_EQ(certs_data[0].nickname, "testusercert");
}

// Test that ValidateAndPrepareCertData() returns success on validating a
// correct EC PKCS#12 data and chooses the correct nickname.
TEST_F(KcerPkcs12ValidatorTest, EcSuccess) {
  KeyData key_data;
  bssl::UniquePtr<STACK_OF(X509)> certs;
  ASSERT_NO_FATAL_FAILURE(GetData("client_with_ec_key.p12", key_data, certs));

  std::vector<CertData> certs_data;
  Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data);
  EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess);
  ASSERT_EQ(certs_data.size(), 1u);
  // There are no other certs to copy the nickname from, the file has the
  // nickname set and it should be used.
  EXPECT_EQ(certs_data[0].nickname, "serverkey");
}

// Test that ValidateAndPrepareCertData() correctly fails when the key and the
// certs are not related.
TEST_F(KcerPkcs12ValidatorTest, UnrelatedCert) {
  KeyData key_data_1;
  bssl::UniquePtr<STACK_OF(X509)> certs_1;
  ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data_1, certs_1));

  KeyData key_data_2;
  bssl::UniquePtr<STACK_OF(X509)> certs_2;
  ASSERT_NO_FATAL_FAILURE(
      GetData("client_with_ec_key.p12", key_data_2, certs_2));

  std::vector<CertData> certs_data;
  Pkcs12ReaderStatusCode prepare_certs_status;

  // `certs_1` and `key_data_2` don't match and should fail.
  prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs_1), key_data_2, certs_data);
  EXPECT_EQ(prepare_certs_status,
            Pkcs12ReaderStatusCode::kPkcs12NoValidCertificatesFound);

  // `certs_2` and `key_data_1` don't match and should fail.
  prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs_2), key_data_1, certs_data);
  EXPECT_EQ(prepare_certs_status,
            Pkcs12ReaderStatusCode::kPkcs12NoValidCertificatesFound);
}

// Test that ValidateAndPrepareCertData() skips already imported keys and
// correctly fails if there's nothing new to import.
TEST_F(KcerPkcs12ValidatorTest, CertExists) {
  KeyData key_data;
  bssl::UniquePtr<STACK_OF(X509)> certs;
  ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs));

  X509* cert = sk_X509_value(certs.get(), 0);
  ASSERT_TRUE(cert);

  scoped_refptr<const Cert> kcer_cert = MakeKcerCertFromBsslCert("name", cert);
  cert_cache_ = CertCache(std::vector{kcer_cert});

  std::vector<CertData> certs_data;
  Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data);
  EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kAlreadyExists);
}

// Test that ValidateAndPrepareCertData() takes the nickname from an existing
// cert when it has the same subject name as the one for import. (This is
// implemented for backwards compatibility with NSS, potentially doesn't have to
// stay this way long term.)
TEST_F(KcerPkcs12ValidatorTest, NicknameFromExistingCert) {
  KeyData key_data;
  bssl::UniquePtr<STACK_OF(X509)> certs;
  ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs));

  X509* cert = sk_X509_value(certs.get(), 0);
  ASSERT_TRUE(cert);

  base::span<const uint8_t> subject_der;
  Pkcs12ReaderStatusCode get_subject_der_result =
      pkcs12_reader_.GetSubjectNameDer(cert, subject_der);
  ASSERT_EQ(get_subject_der_result, Pkcs12ReaderStatusCode::kSuccess);

  std::vector<std::unique_ptr<net::CertBuilder>> cert_builders =
      net::CertBuilder::CreateSimpleChain(/*chain_length=*/1);
  cert_builders[0]->SetSubjectTLV(subject_der);

  const char kNickname[] = "nickname123";
  scoped_refptr<const Cert> kcer_cert =
      MakeKcerCert(kNickname, cert_builders[0]->GetX509Certificate());
  cert_cache_ = CertCache(std::vector{kcer_cert});

  std::vector<CertData> certs_data;
  Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data);
  EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess);
  ASSERT_EQ(certs_data.size(), 1u);
  EXPECT_EQ(certs_data[0].nickname, kNickname);
}

// Test that ValidateAndPrepareCertData() will generate a unique nickname when
// the default option is already taken.
TEST_F(KcerPkcs12ValidatorTest, UniqueNickname) {
  KeyData key_data;
  bssl::UniquePtr<STACK_OF(X509)> certs;
  ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs));

  X509* cert = sk_X509_value(certs.get(), 0);
  ASSERT_TRUE(cert);

  std::vector<std::unique_ptr<net::CertBuilder>> cert_builders =
      net::CertBuilder::CreateSimpleChain(/*chain_length=*/1);

  const char kNickname[] = "testusercert";
  scoped_refptr<const Cert> kcer_cert =
      MakeKcerCert(kNickname, cert_builders[0]->GetX509Certificate());
  cert_cache_ = CertCache(std::vector{kcer_cert});

  std::vector<CertData> certs_data;
  Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData(
      cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data);
  EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess);
  ASSERT_EQ(certs_data.size(), 1u);
  EXPECT_EQ(certs_data[0].nickname, std::string(kNickname) + " 1");
}

}  // namespace
}  // namespace kcer::internal