// 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/kcer_token_utils.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/sha1.h"
#include "chromeos/components/kcer/helpers/key_helper.h"
#include "chromeos/components/kcer/kcer_histograms.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/openssl_util.h"
namespace kcer::internal {
namespace {
// A helper method for error handling. When some method fails and should return
// the `error` through the `callback`, but also should clean up something first,
// this helper allows to bind the error to the callback and create a new
// callback for the clean up code.
template <typename T>
base::OnceCallback<void(uint32_t)> Bind(
base::OnceCallback<void(base::expected<T, Error>)> callback,
Error error) {
return base::IgnoreArgs<uint32_t>(
base::BindOnce(std::move(callback), base::unexpected(error)));
}
void RecordUmaImportSuccess(KeyType key_type, bool is_multiple_cert) {
if (key_type == kcer::KeyType::kRsa) {
RecordKcerPkcs12ImportUmaEvent(
kcer::internal::KcerPkcs12ImportEvent::SuccessRsaCertImportTask);
} else {
RecordKcerPkcs12ImportUmaEvent(
kcer::internal::KcerPkcs12ImportEvent::SuccessEcCertImportTask);
}
RecordKcerPkcs12ImportUmaEvent(
kcer::internal::KcerPkcs12ImportEvent::SuccessPkcs12ChapsImport);
if (is_multiple_cert) {
RecordKcerPkcs12ImportUmaEvent(
kcer::internal::KcerPkcs12ImportEvent::SuccessMultipleCertImport);
}
}
} // namespace
//==============================================================================
PublicKeySpki MakeRsaSpki(const base::span<const uint8_t>& modulus,
const base::span<const uint8_t>& exponent) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::UniquePtr<BIGNUM> modulus_bignum(
BN_bin2bn(modulus.data(), modulus.size(), nullptr));
bssl::UniquePtr<BIGNUM> exponent_bignum(
BN_bin2bn(exponent.data(), exponent.size(), nullptr));
if (!modulus_bignum || !exponent_bignum) {
return {};
}
bssl::UniquePtr<RSA> rsa(
RSA_new_public_key(modulus_bignum.get(), exponent_bignum.get()));
if (!rsa) {
return {};
}
bssl::UniquePtr<EVP_PKEY> ssl_public_key(EVP_PKEY_new());
if (!ssl_public_key || !EVP_PKEY_set1_RSA(ssl_public_key.get(), rsa.get())) {
return {};
}
bssl::ScopedCBB cbb;
uint8_t* der = nullptr;
size_t der_len = 0;
if (!CBB_init(cbb.get(), 0) ||
!EVP_marshal_public_key(cbb.get(), ssl_public_key.get()) ||
!CBB_finish(cbb.get(), &der, &der_len)) {
return {};
}
bssl::UniquePtr<uint8_t> der_deleter(der);
return PublicKeySpki(std::vector<uint8_t>(der, der + der_len));
}
PublicKeySpki MakeEcSpki(const base::span<const uint8_t>& ec_point) {
bssl::UniquePtr<EC_KEY> ec(EC_KEY_new());
if (!ec) {
return {};
}
if (!EC_KEY_set_group(ec.get(), EC_group_p256())) {
return {};
}
EC_KEY* ec_ptr = ec.get();
const uint8_t* data_2 = ec_point.data();
size_t data_2_len = ec_point.size();
if (!o2i_ECPublicKey(&ec_ptr, &data_2, data_2_len)) {
return {};
}
bssl::UniquePtr<EVP_PKEY> ssl_public_key(EVP_PKEY_new());
if (!ssl_public_key ||
!EVP_PKEY_set1_EC_KEY(ssl_public_key.get(), ec.get())) {
return {};
}
bssl::ScopedCBB cbb;
uint8_t* der = nullptr;
size_t der_len = 0;
if (!CBB_init(cbb.get(), 0) ||
!EVP_marshal_public_key(cbb.get(), ssl_public_key.get()) ||
!CBB_finish(cbb.get(), &der, &der_len)) {
return {};
}
bssl::UniquePtr<uint8_t> der_deleter(der);
return PublicKeySpki(std::vector<uint8_t>(der, der + der_len));
}
Pkcs11Id MakePkcs11Id(base::span<const uint8_t> public_key_data) {
if (public_key_data.size() <= base::kSHA1Length) {
return Pkcs11Id(
std::vector<uint8_t>(public_key_data.begin(), public_key_data.end()));
}
base::SHA1Digest hash = base::SHA1Hash(public_key_data);
return Pkcs11Id(std::vector<uint8_t>(hash.begin(), hash.end()));
}
base::expected<PublicKey, Error> MakeRsaPublicKey(
Token token,
base::span<const uint8_t> modulus,
base::span<const uint8_t> public_exponent) {
if (modulus.empty() || public_exponent.empty()) {
return base::unexpected(Error::kFailedToReadAttribute);
}
PublicKeySpki spki = MakeRsaSpki(modulus, public_exponent);
if (spki->empty()) {
return base::unexpected(Error::kFailedToCreateSpki);
}
Pkcs11Id pkcs11_id = MakePkcs11Id(modulus);
if (pkcs11_id->empty()) {
return base::unexpected(Error::kFailedToGetPkcs11Id);
}
return PublicKey(token, pkcs11_id, std::move(spki));
}
base::expected<PublicKey, Error> MakeEcPublicKey(
Token token,
base::span<const uint8_t> ec_point) {
if (ec_point.empty()) {
return base::unexpected(Error::kFailedToReadAttribute);
}
PublicKeySpki spki = MakeEcSpki(ec_point);
if (spki->empty()) {
return base::unexpected(Error::kFailedToCreateSpki);
}
Pkcs11Id pkcs11_id = MakePkcs11Id(ec_point);
if (pkcs11_id->empty()) {
return base::unexpected(Error::kFailedToGetPkcs11Id);
}
return PublicKey(token, pkcs11_id, std::move(spki));
}
//==============================================================================
KcerTokenUtils::KcerTokenUtils(Token token, HighLevelChapsClient* chaps_client)
: token_(token), chaps_client_(chaps_client) {}
KcerTokenUtils::~KcerTokenUtils() = default;
void KcerTokenUtils::Initialize(SessionChapsClient::SlotId pkcs_11_slot_id) {
pkcs_11_slot_id_ = pkcs_11_slot_id;
}
void KcerTokenUtils::FindPrivateKey(
Pkcs11Id id,
base::OnceCallback<void(std::vector<ObjectHandle>, uint32_t)> callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const chromeos::PKCS11_CK_OBJECT_CLASS kPrivKeyClass =
chromeos::PKCS11_CKO_PRIVATE_KEY;
chaps::AttributeList private_key_attrs;
AddAttribute(private_key_attrs, chromeos::PKCS11_CKA_CLASS,
MakeSpan(&kPrivKeyClass));
AddAttribute(private_key_attrs, chromeos::PKCS11_CKA_ID, id.value());
chaps_client_->FindObjects(pkcs_11_slot_id_, std::move(private_key_attrs),
std::move(callback));
}
//==============================================================================
void KcerTokenUtils::ImportCert(
const bssl::UniquePtr<X509>& cert,
const Pkcs11Id& pkcs11_id,
const std::string& nickname,
const CertDer& cert_der,
bool is_hardware_backed,
bool mark_as_migrated,
base::OnceCallback<void(std::optional<Error> kcer_error,
ObjectHandle cert_handle,
uint32_t result_code)> callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Pkcs12Reader reader;
base::span<const uint8_t> issuer_name_der;
Pkcs12ReaderStatusCode status =
reader.GetIssuerNameDer(cert.get(), issuer_name_der);
if (status != Pkcs12ReaderStatusCode::kSuccess) {
return std::move(callback).Run(Error::kFailedToGetIssuerName,
ObjectHandle(0),
chromeos::PKCS11_CKR_GENERAL_ERROR);
}
base::span<const uint8_t> subject_name_der;
status = reader.GetSubjectNameDer(cert.get(), subject_name_der);
if (status != Pkcs12ReaderStatusCode::kSuccess) {
return std::move(callback).Run(Error::kFailedToGetSubjectName,
ObjectHandle(0),
chromeos::PKCS11_CKR_GENERAL_ERROR);
}
bssl::UniquePtr<uint8_t> serial_number_der;
int serial_number_der_size = 0;
status = reader.GetSerialNumberDer(cert.get(), serial_number_der,
serial_number_der_size);
if (status != Pkcs12ReaderStatusCode::kSuccess) {
return std::move(callback).Run(Error::kFailedToGetSerialNumber,
ObjectHandle(0),
chromeos::PKCS11_CKR_GENERAL_ERROR);
}
if (issuer_name_der.empty() || subject_name_der.empty() ||
!serial_number_der || (serial_number_der_size <= 0)) {
return std::move(callback).Run(Error::kInvalidCertificate, ObjectHandle(0),
chromeos::PKCS11_CKR_GENERAL_ERROR);
}
chromeos::PKCS11_CK_OBJECT_CLASS cert_class =
chromeos::PKCS11_CKO_CERTIFICATE;
chromeos::PKCS11_CK_CERTIFICATE_TYPE cert_type = chromeos::PKCS11_CKC_X_509;
chromeos::PKCS11_CK_BBOOL kTrue = chromeos::PKCS11_CK_TRUE;
chaps::AttributeList cert_attrs;
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_CLASS, MakeSpan(&cert_class));
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_CERTIFICATE_TYPE,
MakeSpan(&cert_type));
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_TOKEN, MakeSpan(&kTrue));
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_ID, pkcs11_id.value());
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_LABEL,
base::as_byte_span(nickname));
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_VALUE, cert_der.value());
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_ISSUER, issuer_name_der);
AddAttribute(cert_attrs, chromeos::PKCS11_CKA_SUBJECT, subject_name_der);
AddAttribute(
cert_attrs, chromeos::PKCS11_CKA_SERIAL_NUMBER,
base::make_span(serial_number_der.get(), size_t(serial_number_der_size)));
if (!is_hardware_backed) {
AddAttribute(cert_attrs, chaps::kForceSoftwareAttribute, MakeSpan(&kTrue));
}
if (mark_as_migrated) {
AddAttribute(cert_attrs,
pkcs11_custom_attributes::kCkaChromeOsMigratedFromNss,
MakeSpan(&kTrue));
}
chaps_client_->CreateObject(
pkcs_11_slot_id_, cert_attrs,
base::BindOnce(std::move(callback), /*kcer_error*/ std::nullopt));
}
//==============================================================================
KcerTokenUtils::ImportKeyTask::ImportKeyTask(
KeyData in_key_data,
bool in_hardware_backed,
bool in_mark_as_migrated,
Kcer::GenerateKeyCallback in_callback)
: key_data(std::move(in_key_data)),
hardware_backed(in_hardware_backed),
mark_as_migrated(in_mark_as_migrated),
callback(std::move(in_callback)) {}
KcerTokenUtils::ImportKeyTask::ImportKeyTask(ImportKeyTask&& other) = default;
KcerTokenUtils::ImportKeyTask::~ImportKeyTask() = default;
void KcerTokenUtils::ImportKey(ImportKeyTask task) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
task.attemps_left--;
if (task.attemps_left < 0) {
return std::move(task.callback)
.Run(base::unexpected(Error::kPkcs11SessionFailure));
}
if (IsKeyRsaType(task.key_data.key)) {
ImportRsaKey(std::move(task));
} else if (IsKeyEcType(task.key_data.key)) {
ImportEcKey(std::move(task));
} else {
return std::move(task.callback).Run(base::unexpected(Error::kNotSupported));
}
}
void KcerTokenUtils::ImportRsaKey(ImportKeyTask task) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bssl::UniquePtr<RSA> rsa_key(EVP_PKEY_get1_RSA(task.key_data.key.get()));
if (!rsa_key) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToParseKey));
}
Pkcs12Reader pkcs12_reader;
std::vector<uint8_t> public_modulus_bytes =
pkcs12_reader.BignumToBytes(RSA_get0_n(rsa_key.get()));
std::vector<uint8_t> public_exponent_bytes =
pkcs12_reader.BignumToBytes(RSA_get0_e(rsa_key.get()));
base::expected<PublicKey, Error> kcer_public_key =
MakeRsaPublicKey(token_, public_modulus_bytes, public_exponent_bytes);
if (!kcer_public_key.has_value()) {
return std::move(task.callback)
.Run(base::unexpected(kcer_public_key.error()));
}
Pkcs11Id key_id = kcer_public_key->GetPkcs11Id();
FindPrivateKey(
std::move(key_id),
base::BindOnce(&KcerTokenUtils::ImportRsaKeyWithExistingKey,
weak_factory_.GetWeakPtr(), std::move(task),
std::move(rsa_key), std::move(kcer_public_key).value()));
}
// Creates a private key object in Chaps.
void KcerTokenUtils::ImportRsaKeyWithExistingKey(
ImportKeyTask task,
bssl::UniquePtr<RSA> rsa_key,
PublicKey kcer_public_key,
std::vector<ObjectHandle> handles,
uint32_t result_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (SessionChapsClient::IsSessionError(result_code)) {
return ImportKey(std::move(task));
}
if (result_code != chromeos::PKCS11_CKR_OK) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToSearchForObjects));
}
// The key was already found in Chaps and will not be imported again. Return
// success.
if (!handles.empty()) {
return std::move(task.callback).Run(std::move(kcer_public_key));
}
Pkcs12Reader pkcs12_reader;
std::vector<uint8_t> public_modulus_bytes =
pkcs12_reader.BignumToBytes(RSA_get0_n(rsa_key.get()));
std::vector<uint8_t> public_exponent_bytes =
pkcs12_reader.BignumToBytes(RSA_get0_e(rsa_key.get()));
std::vector<uint8_t> private_exponent_bytes =
pkcs12_reader.BignumToBytes(RSA_get0_d(rsa_key.get()));
std::vector<uint8_t> prime_factor_1 =
pkcs12_reader.BignumToBytes(RSA_get0_p(rsa_key.get()));
std::vector<uint8_t> prime_factor_2 =
pkcs12_reader.BignumToBytes(RSA_get0_q(rsa_key.get()));
std::vector<uint8_t> exponent_1 =
pkcs12_reader.BignumToBytes(RSA_get0_dmp1(rsa_key.get()));
std::vector<uint8_t> exponent_2 =
pkcs12_reader.BignumToBytes(RSA_get0_dmq1(rsa_key.get()));
std::vector<uint8_t> coefficient =
pkcs12_reader.BignumToBytes(RSA_get0_iqmp(rsa_key.get()));
if (public_modulus_bytes.empty() || public_exponent_bytes.empty() ||
private_exponent_bytes.empty() || prime_factor_1.empty() ||
prime_factor_2.empty() || exponent_1.empty() || exponent_2.empty() ||
coefficient.empty()) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToParseKey));
}
constexpr chromeos::PKCS11_CK_BBOOL kTrue = chromeos::PKCS11_CK_TRUE;
chromeos::PKCS11_CK_BBOOL is_software_backed = task.hardware_backed
? chromeos::PKCS11_CK_FALSE
: chromeos::PKCS11_CK_TRUE;
chromeos::PKCS11_CK_OBJECT_CLASS key_class = chromeos::PKCS11_CKO_PRIVATE_KEY;
chromeos::PKCS11_CK_KEY_TYPE key_type = chromeos::PKCS11_CKK_RSA;
chaps::AttributeList attrs;
AddAttribute(attrs, chromeos::PKCS11_CKA_CLASS, MakeSpan(&key_class));
AddAttribute(attrs, chromeos::PKCS11_CKA_KEY_TYPE, MakeSpan(&key_type));
AddAttribute(attrs, chromeos::PKCS11_CKA_TOKEN, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_SENSITIVE, MakeSpan(&kTrue));
if (!task.hardware_backed) {
AddAttribute(attrs, chaps::kForceSoftwareAttribute,
MakeSpan(&is_software_backed));
}
AddAttribute(attrs, chromeos::PKCS11_CKA_EXTRACTABLE,
MakeSpan(&is_software_backed));
AddAttribute(attrs, chromeos::PKCS11_CKA_PRIVATE, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_UNWRAP, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_DECRYPT, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_SIGN, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_SIGN_RECOVER, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_MODULUS, public_modulus_bytes);
AddAttribute(attrs, chromeos::PKCS11_CKA_ID,
kcer_public_key.GetPkcs11Id().value());
AddAttribute(attrs, chromeos::PKCS11_CKA_PUBLIC_EXPONENT,
public_exponent_bytes);
AddAttribute(attrs, chromeos::PKCS11_CKA_PRIVATE_EXPONENT,
private_exponent_bytes);
AddAttribute(attrs, chromeos::PKCS11_CKA_PRIME_1, prime_factor_1);
AddAttribute(attrs, chromeos::PKCS11_CKA_PRIME_2, prime_factor_2);
AddAttribute(attrs, chromeos::PKCS11_CKA_EXPONENT_1, exponent_1);
AddAttribute(attrs, chromeos::PKCS11_CKA_EXPONENT_2, exponent_2);
AddAttribute(attrs, chromeos::PKCS11_CKA_COEFFICIENT, coefficient);
if (task.mark_as_migrated) {
AddAttribute(attrs, pkcs11_custom_attributes::kCkaChromeOsMigratedFromNss,
MakeSpan(&kTrue));
}
auto chaps_callback = base::BindOnce(
&KcerTokenUtils::DidImportRsaPrivateKey, weak_factory_.GetWeakPtr(),
std::move(task), std::move(kcer_public_key), public_modulus_bytes,
public_exponent_bytes);
chaps_client_->CreateObject(pkcs_11_slot_id_, attrs,
std::move(chaps_callback));
}
// Creates a corresponding public key object in Chaps.
void KcerTokenUtils::DidImportRsaPrivateKey(
ImportKeyTask task,
PublicKey kcer_public_key,
std::vector<uint8_t> public_modulus_bytes,
std::vector<uint8_t> public_exponent_bytes,
ObjectHandle priv_key_handle,
uint32_t result_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (SessionChapsClient::IsSessionError(result_code)) {
return ImportKey(std::move(task));
}
if (result_code != chromeos::PKCS11_CKR_OK) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToImportKey));
}
constexpr chromeos::PKCS11_CK_BBOOL kTrue = chromeos::PKCS11_CK_TRUE;
chromeos::PKCS11_CK_KEY_TYPE key_type = chromeos::PKCS11_CKK_RSA;
chromeos::PKCS11_CK_OBJECT_CLASS key_class = chromeos::PKCS11_CKO_PUBLIC_KEY;
chaps::AttributeList attrs;
AddAttribute(attrs, chromeos::PKCS11_CKA_CLASS, MakeSpan(&key_class));
AddAttribute(attrs, chromeos::PKCS11_CKA_KEY_TYPE, MakeSpan(&key_type));
AddAttribute(attrs, chromeos::PKCS11_CKA_TOKEN, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_WRAP, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_ENCRYPT, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_VERIFY, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_MODULUS, public_modulus_bytes);
AddAttribute(attrs, chromeos::PKCS11_CKA_ID,
kcer_public_key.GetPkcs11Id().value());
AddAttribute(attrs, chromeos::PKCS11_CKA_PUBLIC_EXPONENT,
public_exponent_bytes);
if (!task.hardware_backed) {
AddAttribute(attrs, chaps::kForceSoftwareAttribute, MakeSpan(&kTrue));
}
if (task.mark_as_migrated) {
AddAttribute(attrs, pkcs11_custom_attributes::kCkaChromeOsMigratedFromNss,
MakeSpan(&kTrue));
}
auto chaps_callback = base::BindOnce(
&KcerTokenUtils::DidImportKey, weak_factory_.GetWeakPtr(),
std::move(task), std::move(kcer_public_key), priv_key_handle);
chaps_client_->CreateObject(pkcs_11_slot_id_, attrs,
std::move(chaps_callback));
}
void KcerTokenUtils::ImportEcKey(ImportKeyTask task) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bssl::UniquePtr<EC_KEY> ec_key(EVP_PKEY_get1_EC_KEY(task.key_data.key.get()));
if (!ec_key) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToParseKey));
}
std::vector<uint8_t> ec_point_oct = GetEcPublicKeyBytes(ec_key.get());
base::expected<PublicKey, Error> kcer_public_key =
MakeEcPublicKey(token_, ec_point_oct);
if (!kcer_public_key.has_value()) {
return std::move(task.callback)
.Run(base::unexpected(kcer_public_key.error()));
}
Pkcs11Id key_id = kcer_public_key->GetPkcs11Id();
FindPrivateKey(
std::move(key_id),
base::BindOnce(&KcerTokenUtils::ImportEcKeyWithExistingKey,
weak_factory_.GetWeakPtr(), std::move(task),
std::move(ec_key), std::move(kcer_public_key).value(),
std::move(ec_point_oct)));
}
void KcerTokenUtils::ImportEcKeyWithExistingKey(
ImportKeyTask task,
bssl::UniquePtr<EC_KEY> ec_key,
PublicKey kcer_public_key,
std::vector<uint8_t> ec_point_oct,
std::vector<ObjectHandle> handles,
uint32_t result_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (SessionChapsClient::IsSessionError(result_code)) {
return ImportKey(std::move(task));
}
if (result_code != chromeos::PKCS11_CKR_OK) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToSearchForObjects));
}
if (!handles.empty()) {
return std::move(task.callback).Run(std::move(kcer_public_key));
}
std::vector<uint8_t> ec_point_der = DerEncodeAsn1OctetString(ec_point_oct);
std::vector<uint8_t> priv_key_bytes = GetEcPrivateKeyBytes(ec_key.get());
std::vector<uint8_t> ec_params_der = GetEcParamsDer(ec_key.get());
if (priv_key_bytes.empty() || ec_params_der.empty() || ec_point_der.empty()) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToParseKey));
}
constexpr chromeos::PKCS11_CK_BBOOL kTrue = chromeos::PKCS11_CK_TRUE;
chromeos::PKCS11_CK_BBOOL is_software_backed = task.hardware_backed
? chromeos::PKCS11_CK_FALSE
: chromeos::PKCS11_CK_TRUE;
chromeos::PKCS11_CK_OBJECT_CLASS key_class = chromeos::PKCS11_CKO_PRIVATE_KEY;
chromeos::PKCS11_CK_KEY_TYPE key_type = chromeos::PKCS11_CKK_EC;
chaps::AttributeList attrs;
AddAttribute(attrs, chromeos::PKCS11_CKA_CLASS, MakeSpan(&key_class));
AddAttribute(attrs, chromeos::PKCS11_CKA_KEY_TYPE, MakeSpan(&key_type));
AddAttribute(attrs, chromeos::PKCS11_CKA_TOKEN, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_SENSITIVE, MakeSpan(&kTrue));
if (!task.hardware_backed) {
AddAttribute(attrs, chaps::kForceSoftwareAttribute,
MakeSpan(&is_software_backed));
}
AddAttribute(attrs, chromeos::PKCS11_CKA_EXTRACTABLE,
MakeSpan(&is_software_backed));
AddAttribute(attrs, chromeos::PKCS11_CKA_SIGN, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_SIGN_RECOVER, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_DERIVE, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_ID,
kcer_public_key.GetPkcs11Id().value());
AddAttribute(attrs, chromeos::PKCS11_CKA_VALUE, priv_key_bytes);
AddAttribute(attrs, chromeos::PKCS11_CKA_EC_POINT, ec_point_der);
AddAttribute(attrs, chromeos::PKCS11_CKA_PRIVATE, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_EC_PARAMS, ec_params_der);
if (task.mark_as_migrated) {
AddAttribute(attrs, pkcs11_custom_attributes::kCkaChromeOsMigratedFromNss,
MakeSpan(&kTrue));
}
auto chaps_callback = base::BindOnce(
&KcerTokenUtils::DidImportEcPrivateKey, weak_factory_.GetWeakPtr(),
std::move(task), std::move(kcer_public_key), std::move(ec_point_der),
std::move(ec_params_der));
chaps_client_->CreateObject(pkcs_11_slot_id_, attrs,
std::move(chaps_callback));
}
void KcerTokenUtils::DidImportEcPrivateKey(ImportKeyTask task,
PublicKey kcer_public_key,
std::vector<uint8_t> ec_point_der,
std::vector<uint8_t> ec_params_der,
ObjectHandle priv_key_handle,
uint32_t result_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (SessionChapsClient::IsSessionError(result_code)) {
return ImportKey(std::move(task));
}
if (result_code != chromeos::PKCS11_CKR_OK) {
return std::move(task.callback)
.Run(base::unexpected(Error::kFailedToImportKey));
}
constexpr chromeos::PKCS11_CK_BBOOL kTrue = chromeos::PKCS11_CK_TRUE;
chromeos::PKCS11_CK_KEY_TYPE key_type = chromeos::PKCS11_CKK_EC;
chromeos::PKCS11_CK_OBJECT_CLASS key_class = chromeos::PKCS11_CKO_PUBLIC_KEY;
chaps::AttributeList attrs;
AddAttribute(attrs, chromeos::PKCS11_CKA_CLASS, MakeSpan(&key_class));
AddAttribute(attrs, chromeos::PKCS11_CKA_KEY_TYPE, MakeSpan(&key_type));
AddAttribute(attrs, chromeos::PKCS11_CKA_TOKEN, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_VERIFY, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_DERIVE, MakeSpan(&kTrue));
AddAttribute(attrs, chromeos::PKCS11_CKA_EC_PARAMS, ec_params_der);
AddAttribute(attrs, chromeos::PKCS11_CKA_EC_POINT, ec_point_der);
AddAttribute(attrs, chromeos::PKCS11_CKA_ID,
kcer_public_key.GetPkcs11Id().value());
if (!task.hardware_backed) {
AddAttribute(attrs, chaps::kForceSoftwareAttribute, MakeSpan(&kTrue));
}
if (task.mark_as_migrated) {
AddAttribute(attrs, pkcs11_custom_attributes::kCkaChromeOsMigratedFromNss,
MakeSpan(&kTrue));
}
auto chaps_callback = base::BindOnce(
&KcerTokenUtils::DidImportKey, weak_factory_.GetWeakPtr(),
std::move(task), std::move(kcer_public_key), priv_key_handle);
chaps_client_->CreateObject(pkcs_11_slot_id_, attrs,
std::move(chaps_callback));
}
void KcerTokenUtils::DidImportKey(ImportKeyTask task,
PublicKey kcer_public_key,
ObjectHandle priv_key_handle,
ObjectHandle pub_key_handle,
uint32_t result_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (SessionChapsClient::IsSessionError(result_code)) {
return ImportKey(std::move(task));
}
if (result_code != chromeos::PKCS11_CKR_OK) {
return chaps_client_->DestroyObjectsWithRetries(
pkcs_11_slot_id_, {priv_key_handle},
Bind(std::move(task.callback), Error::kFailedToImportKey));
}
return std::move(task.callback).Run(kcer_public_key);
}
//==============================================================================
void KcerTokenUtils::ImportPkcs12(KeyData key_data,
std::vector<CertData> certs_data,
bool hardware_backed,
bool mark_as_migrated,
ImportPkcs12Callback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
kcer::KeyType key_type;
if (IsKeyRsaType(key_data.key)) {
RecordKcerPkcs12ImportUmaEvent(
KcerPkcs12ImportEvent::AttemptedRsaKeyImportTask);
key_type = KeyType::kRsa;
} else if (IsKeyEcType(key_data.key)) {
RecordKcerPkcs12ImportUmaEvent(
KcerPkcs12ImportEvent::AttemptedEcKeyImportTask);
key_type = KeyType::kEcc;
} else {
LOG(ERROR) << "Unexpected key type";
return std::move(callback).Run(/*did_modify=*/false,
base::unexpected(Error::kUnknownKeyType));
}
auto import_callback = base::BindOnce(
&KcerTokenUtils::ImportPkc12DidImportKey, weak_factory_.GetWeakPtr(),
key_type, Pkcs11Id(key_data.cka_id_value), std::move(certs_data),
hardware_backed, mark_as_migrated, std::move(callback));
ImportKey(ImportKeyTask(std::move(key_data), hardware_backed,
mark_as_migrated, std::move(import_callback)));
}
void KcerTokenUtils::ImportPkc12DidImportKey(
kcer::KeyType key_type,
Pkcs11Id pkcs11_id,
std::vector<CertData> certs_data,
bool hardware_backed,
bool mark_as_migrated,
ImportPkcs12Callback callback,
base::expected<PublicKey, Error> imported_key) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!imported_key.has_value()) {
return std::move(callback).Run(/*did_modify=*/false,
base::unexpected(imported_key.error()));
}
if (key_type == kcer::KeyType::kRsa) {
RecordKcerPkcs12ImportUmaEvent(
KcerPkcs12ImportEvent::SuccessRsaKeyImportTask);
} else {
RecordKcerPkcs12ImportUmaEvent(
KcerPkcs12ImportEvent::SuccessEcKeyImportTask);
}
bool is_multi_cert_import = (certs_data.size() > 1);
if (is_multi_cert_import) {
RecordKcerPkcs12ImportUmaEvent(
KcerPkcs12ImportEvent::AttemptedMultipleCertImport);
}
ImportAllCerts(ImportAllCertsTask(
std::move(pkcs11_id), std::move(certs_data), hardware_backed,
mark_as_migrated, is_multi_cert_import, key_type, std::move(callback)));
}
//==============================================================================
KcerTokenUtils::ImportAllCertsTask::ImportAllCertsTask(
Pkcs11Id in_pkcs11_id,
std::vector<CertData> in_certs_data,
bool in_hardware_backed,
bool in_mark_as_migrated,
bool in_multi_cert_import,
KeyType in_key_type,
ImportPkcs12Callback in_callback)
: pkcs11_id(std::move(in_pkcs11_id)),
certs_data(std::move(in_certs_data)),
hardware_backed(in_hardware_backed),
mark_as_migrated(in_mark_as_migrated),
multi_cert_import(in_multi_cert_import),
key_type(in_key_type),
callback(std::move(in_callback)) {}
KcerTokenUtils::ImportAllCertsTask::ImportAllCertsTask(
ImportAllCertsTask&& other) = default;
KcerTokenUtils::ImportAllCertsTask::~ImportAllCertsTask() = default;
void KcerTokenUtils::ImportAllCerts(ImportAllCertsTask task) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
RecordKcerPkcs12ImportUmaEvent(
KcerPkcs12ImportEvent::AttemptedPkcs12ChapsImportTask);
task.attemps_left--;
if (task.attemps_left < 0) {
return std::move(task.callback)
.Run(/*did_modify=*/false,
base::unexpected(Error::kPkcs11SessionFailure));
}
// The original vector of certs has to stay unchanged in case a retry is
// required. Create an additional vector to track which certs still need to be
// processed in the current attempt. Raw pointers must not outlive the
// originals stored in the `task`.
std::vector<const CertData*> certs_data;
certs_data.reserve(task.certs_data.size());
for (const CertData& data : task.certs_data) {
certs_data.push_back(&data);
}
return ImportAllCertsImpl(std::move(task), std::move(certs_data),
/*imports_failed=*/0);
}
void KcerTokenUtils::ImportAllCertsImpl(ImportAllCertsTask task,
std::vector<const CertData*> certs_data,
int imports_failed) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (certs_data.empty()) {
base::expected<void, Error> result;
if (imports_failed != 0) {
result = base::unexpected(Error::kFailedToImportCertificate);
} else {
RecordUmaImportSuccess(task.key_type, task.multi_cert_import);
}
return std::move(task.callback).Run(/*did_modify=*/true, std::move(result));
}
const CertData* cur_cert = certs_data.back();
certs_data.pop_back();
Pkcs11Id pkcs11_id = task.pkcs11_id;
bool hardware_backed = task.hardware_backed;
bool mark_as_migrated = task.mark_as_migrated;
auto callback =
base::BindOnce(&KcerTokenUtils::ImportAllCertsDidImportOneCert,
weak_factory_.GetWeakPtr(), std::move(task),
std::move(certs_data), imports_failed);
return ImportCert(cur_cert->x509, pkcs11_id, cur_cert->nickname,
cur_cert->cert_der, hardware_backed, mark_as_migrated,
std::move(callback));
}
void KcerTokenUtils::ImportAllCertsDidImportOneCert(
ImportAllCertsTask task,
std::vector<const CertData*> certs_data,
int imports_failed,
std::optional<Error> kcer_error,
SessionChapsClient::ObjectHandle cert_handle,
uint32_t result_code) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (SessionChapsClient::IsSessionError(result_code)) {
// Try again from the beginning. If some keys or certs were imported before
// the session got closed, they will be found and skipped on the next
// attempt.
return ImportAllCerts(std::move(task));
}
if (kcer_error.has_value() || (result_code != chromeos::PKCS11_CKR_OK)) {
++imports_failed;
}
return ImportAllCertsImpl(std::move(task), std::move(certs_data),
imports_failed);
}
} // namespace kcer::internal