// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/kcer/kcer_nss/test_utils.h"
#include <pk11pub.h>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_future.h"
#include "chromeos/components/kcer/kcer_token.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/signature_verifier.h"
#include "net/test/cert_builder.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/pki/pem.h"
using SignatureAlgorithm = crypto::SignatureVerifier::SignatureAlgorithm;
namespace kcer {
namespace {
std::string ToString(const std::optional<chaps::KeyPermissions>& val) {
if (!val.has_value()) {
return "<empty>";
}
// Should be updated if `KeyPermissions` struct is changed.
return base::StringPrintf("[arc:%d corp:%d]", val->key_usages().arc(),
val->key_usages().corporate());
}
crypto::ScopedPK11Slot CopySlotPtr(PK11SlotInfo* slot) {
return crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot));
}
} // namespace
TokenHolder::TokenHolder(Token token,
HighLevelChapsClient* chaps_client,
bool initialize_token) {
Initialize(token, chaps_client, initialize_token,
crypto::ScopedPK11Slot(PK11_ReferenceSlot(nss_db_.slot())));
}
TokenHolder::TokenHolder(Token token,
HighLevelChapsClient* chaps_client,
bool initialize_token,
crypto::ScopedPK11Slot nss_slot) {
Initialize(token, chaps_client, initialize_token, std::move(nss_slot));
}
void TokenHolder::Initialize(Token token,
HighLevelChapsClient* chaps_client,
bool initialize,
crypto::ScopedPK11Slot nss_slot) {
if (!nss_slot) {
return;
}
io_token_ = std::make_unique<internal::KcerTokenImplNss>(token, chaps_client);
io_token_->SetAttributeTranslationForTesting(/*is_enabled=*/true);
weak_ptr_ = io_token_->GetWeakPtr();
// After this point `io_token_` should only be used on the IO thread.
nss_slot_ = std::move(nss_slot);
if (initialize) {
InitializeToken();
}
}
TokenHolder::~TokenHolder() {
weak_ptr_.reset();
content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
std::move(io_token_));
}
void TokenHolder::InitializeToken() {
CHECK(!is_initialized_);
is_initialized_ = true;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&internal::KcerToken::InitializeForNss, weak_ptr_,
crypto::ScopedPK11Slot(PK11_ReferenceSlot(nss_slot_.get()))));
}
void TokenHolder::FailTokenInitialization() {
CHECK(!is_initialized_);
is_initialized_ = true;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&internal::KcerToken::InitializeForNss, weak_ptr_,
/*nss_slot=*/nullptr));
}
uint32_t TokenHolder::GetSlotId() {
return PK11_GetSlotID(nss_slot_.get());
}
//==============================================================================
KeyAndCert::KeyAndCert(PublicKey key, scoped_refptr<const Cert> cert)
: key(key), cert(cert) {}
KeyAndCert::KeyAndCert(KeyAndCert&&) = default;
KeyAndCert& KeyAndCert::operator=(KeyAndCert&&) = default;
KeyAndCert::~KeyAndCert() = default;
//==============================================================================
TestKcerHolder::TestKcerHolder(PK11SlotInfo* user_slot,
PK11SlotInfo* device_slot)
: user_token_(Token::kUser,
/*chaps_client=*/nullptr,
true,
user_slot ? CopySlotPtr(user_slot) : nullptr),
device_token_(Token::kDevice,
/*chaps_client=*/nullptr,
true,
device_slot ? CopySlotPtr(device_slot) : nullptr) {
kcer_ = std::make_unique<kcer::internal::KcerImpl>();
kcer_->Initialize(content::GetIOThreadTaskRunner({}),
user_token_.GetWeakPtr(), device_token_.GetWeakPtr());
}
TestKcerHolder::~TestKcerHolder() = default;
base::WeakPtr<Kcer> TestKcerHolder::GetKcer() {
return kcer_->GetWeakPtr();
}
//==============================================================================
[[nodiscard]] bool ExpectKeyPermissionsEqual(
const std::optional<chaps::KeyPermissions>& a,
const std::optional<chaps::KeyPermissions>& b) {
bool result = true;
if (!a.has_value() || !b.has_value()) {
result = (a.has_value() == b.has_value());
} else {
result = (a->SerializeAsString() == b->SerializeAsString());
}
if (!result) {
LOG(ERROR) << "ERROR: key_permissions: a: " << ToString(a)
<< ", b: " << ToString(b);
}
return result;
}
bool VerifySignature(SigningScheme signing_scheme,
PublicKeySpki spki,
DataToSign data_to_sign,
Signature signature,
bool strict) {
SignatureAlgorithm signature_algo = SignatureAlgorithm::RSA_PKCS1_SHA1;
switch (signing_scheme) {
case SigningScheme::kRsaPkcs1Sha1:
signature_algo = SignatureAlgorithm::RSA_PKCS1_SHA1;
break;
case SigningScheme::kRsaPkcs1Sha256:
signature_algo = SignatureAlgorithm::RSA_PKCS1_SHA256;
break;
case SigningScheme::kRsaPssRsaeSha256:
signature_algo = SignatureAlgorithm::RSA_PSS_SHA256;
break;
case SigningScheme::kEcdsaSecp256r1Sha256:
signature_algo = SignatureAlgorithm::ECDSA_SHA256;
break;
default:
return !strict;
}
crypto::SignatureVerifier signature_verifier;
if (!signature_verifier.VerifyInit(signature_algo, signature.value(),
spki.value())) {
LOG(ERROR) << "Failed to initialize signature verifier";
return false;
}
signature_verifier.VerifyUpdate(data_to_sign.value());
return signature_verifier.VerifyFinal();
}
std::vector<uint8_t> PrependSHA256DigestInfo(base::span<const uint8_t> hash) {
// DER-encoded PKCS#1 DigestInfo "prefix" with
// AlgorithmIdentifier=id-sha256.
// The encoding is taken from https://tools.ietf.org/html/rfc3447#page-43
const std::vector<uint8_t> kDigestInfoSha256DerData = {
0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
std::vector<uint8_t> result;
result.reserve(kDigestInfoSha256DerData.size() + hash.size());
result.insert(result.end(), kDigestInfoSha256DerData.begin(),
kDigestInfoSha256DerData.end());
result.insert(result.end(), hash.begin(), hash.end());
return result;
}
std::optional<std::vector<uint8_t>> ReadPemFileReturnDer(
const base::FilePath& path) {
std::string pem_data;
if (!base::ReadFileToString(path, &pem_data)) {
return std::nullopt;
}
bssl::PEMTokenizer tokenizer(pem_data, {"CERTIFICATE", "PRIVATE KEY"});
if (!tokenizer.GetNext()) {
return std::nullopt;
}
return std::vector<uint8_t>(tokenizer.data().begin(), tokenizer.data().end());
}
std::unique_ptr<net::CertBuilder> MakeCertIssuer() {
auto issuer = std::make_unique<net::CertBuilder>(/*orig_cert=*/nullptr,
/*issuer=*/nullptr);
issuer->SetSubjectCommonName("IssuerSubjectCommonName");
issuer->GenerateRSAKey();
return issuer;
}
// Creates a certificate builder that can generate a self-signed certificate for
// the `public_key`.
std::unique_ptr<net::CertBuilder> MakeCertBuilder(
net::CertBuilder* issuer,
const std::vector<uint8_t>& public_key) {
std::unique_ptr<net::CertBuilder> cert_builder =
net::CertBuilder::FromSubjectPublicKeyInfo(public_key, issuer);
cert_builder->SetSignatureAlgorithm(
bssl::SignatureAlgorithm::kRsaPkcs1Sha256);
auto now = base::Time::Now();
cert_builder->SetValidity(now, now + base::Days(30));
cert_builder->SetSubjectCommonName("SubjectCommonName");
return cert_builder;
}
std::vector<uint8_t> ReadTestFile(const std::string& file_name) {
base::FilePath file_path =
net::GetTestCertsDirectory().AppendASCII(file_name);
std::optional<std::vector<uint8_t>> file_data =
base::ReadFileToBytes(file_path);
if (!file_data.has_value()) {
ADD_FAILURE() << "Couldn't read " << file_path;
return {};
}
return file_data.value();
}
base::expected<KeyAndCert, Error> ImportTestKeyAndCert(
base::WeakPtr<Kcer> kcer,
Token token,
std::string_view key_filename,
std::string_view cert_filename) {
CHECK(kcer);
std::optional<std::vector<uint8_t>> key = ReadPemFileReturnDer(
net::GetTestCertsDirectory().AppendASCII(key_filename));
if (!key.has_value() || (key->size() == 0)) {
return base::unexpected(Error::kUnknownError);
}
std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
net::GetTestCertsDirectory().AppendASCII(cert_filename));
if (!cert.has_value() || (cert->size() == 0)) {
return base::unexpected(Error::kUnknownError);
}
base::test::TestFuture<base::expected<PublicKey, Error>> import_key_waiter;
kcer->ImportKey(Token::kUser, Pkcs8PrivateKeyInfoDer(std::move(key.value())),
import_key_waiter.GetCallback());
if (!import_key_waiter.Get().has_value()) {
return base::unexpected(import_key_waiter.Get().error());
}
base::test::TestFuture<base::expected<void, Error>> import_cert_waiter;
kcer->ImportCertFromBytes(Token::kUser, CertDer(std::move(cert.value())),
import_cert_waiter.GetCallback());
if (!import_cert_waiter.Get().has_value()) {
return base::unexpected(import_cert_waiter.Get().error());
}
base::test::TestFuture<std::vector<scoped_refptr<const Cert>>,
base::flat_map<Token, Error>>
certs_waiter;
kcer->ListCerts({Token::kUser}, certs_waiter.GetCallback());
EXPECT_TRUE(certs_waiter.Get<1>().empty()); // Error map is empty.
const auto& certs =
certs_waiter.Get<std::vector<scoped_refptr<const Cert>>>();
if (certs.size() != 1u) {
return base::unexpected(Error::kUnknownError);
}
return KeyAndCert{std::move(import_key_waiter.Get().value()), certs[0]};
}
} // namespace kcer