Kcer-without-NSS: Implement ImportCertFromBytes
Implement the KcerTokenImpl::ImportCertFromBytes method.
Computation of the correct nickname for the cert will be
added separately.
Bug: b:287015636
Test: *Kcer* chromeos_components_unittests
Change-Id: Ica808435d87ef070d837b8e18edcef37ba463337
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5277059
Commit-Queue: Michael Ershov <miersh@google.com>
Reviewed-by: Pavol Marko <pmarko@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1259721}
diff --git a/chromeos/components/kcer/kcer.h b/chromeos/components/kcer/kcer.h
index 33c18eb..66d3b5cb 100644
--- a/chromeos/components/kcer/kcer.h
+++ b/chromeos/components/kcer/kcer.h
@@ -88,6 +88,9 @@
kFailedToDecodeKeyAttributes = 37,
kFailedToRetrieveMechanismList = 38,
kFailedToParseKey = 39,
+ kFailedToGetIssuerName = 40,
+ kFailedToGetSubjectName = 41,
+ kFailedToGetSerialNumber = 42,
};
// Handles for tokens on ChromeOS.
diff --git a/chromeos/components/kcer/kcer_token_impl.cc b/chromeos/components/kcer/kcer_token_impl.cc
index a7f0200..53078f09 100644
--- a/chromeos/components/kcer/kcer_token_impl.cc
+++ b/chromeos/components/kcer/kcer_token_impl.cc
@@ -23,6 +23,7 @@
#include "chromeos/constants/pkcs11_definitions.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/openssl_util.h"
+#include "net/cert/asn1_util.h"
#include "net/cert/cert_database.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
@@ -42,6 +43,11 @@
namespace kcer::internal {
namespace {
+std::string_view AsString(base::span<const uint8_t> bytes) {
+ return std::string_view(reinterpret_cast<const char*>(bytes.data()),
+ bytes.size());
+}
+
const chaps::Attribute* GetAttribute(const chaps::AttributeList& attr_list,
AttributeId attribute_id) {
for (int i = 0; i < attr_list.attributes_size(); i++) {
@@ -794,9 +800,149 @@
//==============================================================================
+KcerTokenImpl::ImportCertFromBytesTask::ImportCertFromBytesTask(
+ CertDer in_cert_der,
+ Kcer::StatusCallback in_callback)
+ : cert_der(std::move(in_cert_der)), callback(std::move(in_callback)) {}
+KcerTokenImpl::ImportCertFromBytesTask::ImportCertFromBytesTask(
+ ImportCertFromBytesTask&& other) = default;
+KcerTokenImpl::ImportCertFromBytesTask::~ImportCertFromBytesTask() = default;
+
void KcerTokenImpl::ImportCertFromBytes(CertDer cert_der,
Kcer::StatusCallback callback) {
- // TODO(244409232): Implement.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (is_blocked_) {
+ return task_queue_.push_back(base::BindOnce(
+ &KcerTokenImpl::ImportCertFromBytes, weak_factory_.GetWeakPtr(),
+ std::move(cert_der), std::move(callback)));
+ }
+
+ // Block task queue, attach unblocking task to the callback.
+ auto unblocking_callback = BlockQueueGetUnblocker(std::move(callback));
+
+ ImportCertFromBytesImpl(ImportCertFromBytesTask(
+ std::move(cert_der), std::move(unblocking_callback)));
+}
+
+void KcerTokenImpl::ImportCertFromBytesImpl(ImportCertFromBytesTask 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));
+ }
+
+ chaps::AttributeList attributes;
+ AddAttribute(attributes, chromeos::PKCS11_CKA_VALUE, task.cert_der.value());
+
+ // Check whether the cert is already imported.
+ chaps_client_->FindObjects(
+ pkcs_11_slot_id_, std::move(attributes),
+ base::BindOnce(&KcerTokenImpl::ImportCertFromBytesWithExistingCerts,
+ weak_factory_.GetWeakPtr(), std::move(task)));
+}
+
+// Checks whether the private key for the cert exists.
+void KcerTokenImpl::ImportCertFromBytesWithExistingCerts(
+ ImportCertFromBytesTask task,
+ std::vector<ObjectHandle> existing_certs,
+ uint32_t result_code) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (SessionChapsClient::IsSessionError(result_code)) {
+ return ImportCertFromBytesImpl(std::move(task));
+ }
+ if (result_code != chromeos::PKCS11_CKR_OK) {
+ return std::move(task.callback)
+ .Run(base::unexpected(Error::kFailedToSearchForObjects));
+ }
+ if (!existing_certs.empty()) {
+ // The cert already exists, no need to import, return success.
+ return std::move(task.callback).Run({});
+ }
+
+ std::string_view spki_string_piece;
+ if (!net::asn1::ExtractSPKIFromDERCert(AsString(task.cert_der.value()),
+ &spki_string_piece)) {
+ return std::move(task.callback)
+ .Run(base::unexpected(Error::kInvalidCertificate));
+ }
+ PublicKeySpki spki(std::vector<uint8_t>(
+ spki_string_piece.data(),
+ spki_string_piece.data() + spki_string_piece.size()));
+
+ Pkcs11Id key_id = GetPkcs11IdFromSpki(spki);
+ if (key_id->empty()) {
+ return std::move(task.callback)
+ .Run(base::unexpected(Error::kInvalidCertificate));
+ }
+
+ Pkcs11Id key_id_copy = key_id;
+ kcer_utils_.FindPrivateKey(
+ std::move(key_id),
+ base::BindOnce(&KcerTokenImpl::ImportCertFromBytesWithKeyHandle,
+ weak_factory_.GetWeakPtr(), std::move(task),
+ std::move(key_id_copy)));
+}
+
+// Parses and imports the cert into Chaps.
+void KcerTokenImpl::ImportCertFromBytesWithKeyHandle(
+ ImportCertFromBytesTask task,
+ Pkcs11Id pkcs11_id,
+ std::vector<ObjectHandle> key_handles,
+ uint32_t result_code) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (SessionChapsClient::IsSessionError(result_code)) {
+ return ImportCertFromBytesImpl(std::move(task));
+ }
+ if (result_code != chromeos::PKCS11_CKR_OK) {
+ return std::move(task.callback)
+ .Run(base::unexpected(Error::kFailedToSearchForObjects));
+ }
+ if (key_handles.empty()) {
+ return std::move(task.callback).Run(base::unexpected(Error::kKeyNotFound));
+ }
+
+ Pkcs12Reader reader;
+ bssl::UniquePtr<X509> cert;
+ Pkcs12ReaderStatusCode status = reader.GetCertFromDerData(
+ task.cert_der.value().data(), task.cert_der.value().size(), cert);
+ if (status != Pkcs12ReaderStatusCode::kSuccess) {
+ return std::move(task.callback)
+ .Run(base::unexpected(Error::kInvalidCertificate));
+ }
+
+ // TODO(b/244409232): Assign the correct nickname.
+ std::string label = "";
+ CertDer cert_der = task.cert_der;
+ auto import_callback =
+ base::BindOnce(&KcerTokenImpl::DidImportCertFromBytes,
+ weak_factory_.GetWeakPtr(), std::move(task));
+ kcer_utils_.ImportCert(std::move(cert), std::move(pkcs11_id),
+ std::move(label), std::move(cert_der),
+ std::move(import_callback));
+}
+
+void KcerTokenImpl::DidImportCertFromBytes(ImportCertFromBytesTask task,
+ std::optional<Error> kcer_error,
+ ObjectHandle cert_handle,
+ uint32_t result_code) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ if (kcer_error.has_value()) {
+ return std::move(task.callback).Run(base::unexpected(kcer_error.value()));
+ }
+ if (SessionChapsClient::IsSessionError(result_code)) {
+ return ImportCertFromBytesImpl(std::move(task));
+ }
+ if (result_code != chromeos::PKCS11_CKR_OK) {
+ return std::move(task.callback)
+ .Run(base::unexpected(Error::kFailedToImportCertificate));
+ }
+ return std::move(task.callback).Run({});
}
//==============================================================================
diff --git a/chromeos/components/kcer/kcer_token_impl.h b/chromeos/components/kcer/kcer_token_impl.h
index 7722f8e7..25b8b164 100644
--- a/chromeos/components/kcer/kcer_token_impl.h
+++ b/chromeos/components/kcer/kcer_token_impl.h
@@ -163,6 +163,30 @@
PublicKey kcer_public_key,
uint32_t result_code);
+ struct ImportCertFromBytesTask {
+ ImportCertFromBytesTask(CertDer in_cert_der,
+ Kcer::StatusCallback in_callback);
+ ImportCertFromBytesTask(ImportCertFromBytesTask&& other);
+ ~ImportCertFromBytesTask();
+
+ const CertDer cert_der;
+ Kcer::StatusCallback callback;
+ int attemps_left = kDefaultAttempts;
+ };
+ void ImportCertFromBytesImpl(ImportCertFromBytesTask task);
+ void ImportCertFromBytesWithExistingCerts(
+ ImportCertFromBytesTask task,
+ std::vector<ObjectHandle> existing_certs,
+ uint32_t result_code);
+ void ImportCertFromBytesWithKeyHandle(ImportCertFromBytesTask task,
+ Pkcs11Id pkcs11_id,
+ std::vector<ObjectHandle> key_handles,
+ uint32_t result_code);
+ void DidImportCertFromBytes(ImportCertFromBytesTask task,
+ std::optional<Error> kcer_error,
+ ObjectHandle cert_handle,
+ uint32_t result_code);
+
struct RemoveKeyAndCertsTask {
RemoveKeyAndCertsTask(PrivateKeyHandle in_key,
Kcer::StatusCallback in_callback);
diff --git a/chromeos/components/kcer/kcer_token_impl_unittest.cc b/chromeos/components/kcer/kcer_token_impl_unittest.cc
index 11a3edf5..e98b587 100644
--- a/chromeos/components/kcer/kcer_token_impl_unittest.cc
+++ b/chromeos/components/kcer/kcer_token_impl_unittest.cc
@@ -1342,6 +1342,304 @@
EXPECT_EQ(import_key_waiter.Get().error(), Error::kPkcs11SessionFailure);
}
+// Test that ImportCertFromBytes can successfully import a cert.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesSuccess) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ std::vector<ObjectHandle> result_handles{ObjectHandle(10)};
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ // Return empty list of existing certs.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK))
+ // Simulate that a key for the cert exists.
+ .WillOnce(RunOnceCallback<2>(result_handles, chromeos::PKCS11_CKR_OK));
+
+ chaps::AttributeList cert_attrs;
+ EXPECT_CALL(chaps_client_, CreateObject(pkcs11_slot_id_, _, _))
+ .WillOnce(
+ DoAll(MoveArg<1>(&cert_attrs),
+ RunOnceCallback<2>(ObjectHandle(1), chromeos::PKCS11_CKR_OK)));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ EXPECT_TRUE(waiter.Get().has_value());
+
+ 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;
+ std::string expected_label = "";
+
+ // Contains "CN=B CA".
+ std::vector<uint8_t> issuer_name_der =
+ base::Base64Decode("MA8xDTALBgNVBAMMBEIgQ0E=").value();
+ // Contains "CN=Client Cert A".
+ std::vector<uint8_t> subject_name_der =
+ base::Base64Decode("MBgxFjAUBgNVBAMMDUNsaWVudCBDZXJ0IEE=").value();
+ // Contains 4096.
+ std::vector<uint8_t> serial_number_der =
+ base::Base64Decode("AgIQAA==").value();
+
+ EXPECT_TRUE(FindAttribute(cert_attrs, chromeos::PKCS11_CKA_CLASS,
+ MakeSpan(&cert_class)));
+ EXPECT_TRUE(FindAttribute(cert_attrs, chromeos::PKCS11_CKA_CERTIFICATE_TYPE,
+ MakeSpan(&cert_type)));
+ EXPECT_TRUE(
+ FindAttribute(cert_attrs, chromeos::PKCS11_CKA_TOKEN, MakeSpan(&kTrue)));
+ EXPECT_TRUE(FindAttribute(cert_attrs, chromeos::PKCS11_CKA_ID,
+ rsa_pkcs11_id_.value()));
+ EXPECT_TRUE(FindAttribute(cert_attrs, chromeos::PKCS11_CKA_LABEL,
+ base::as_byte_span(expected_label)));
+ EXPECT_TRUE(
+ FindAttribute(cert_attrs, chromeos::PKCS11_CKA_VALUE, cert.value()));
+ EXPECT_TRUE(
+ FindAttribute(cert_attrs, chromeos::PKCS11_CKA_ISSUER, issuer_name_der));
+ EXPECT_TRUE(FindAttribute(cert_attrs, chromeos::PKCS11_CKA_SUBJECT,
+ subject_name_der));
+ EXPECT_TRUE(FindAttribute(cert_attrs, chromeos::PKCS11_CKA_SERIAL_NUMBER,
+ serial_number_der));
+}
+
+// Test that ImportCertFromBytes correctly fails when the key for the cert is
+// not present.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesKeyNotFound) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ // Return empty list of existing certs.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK))
+ // Return empty list of found keys.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kKeyNotFound);
+}
+
+// Test that ImportCertFromBytes returns success without importing a cert when
+// it already exists.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesAlreadyExists) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ // Return some handles to simulate that an existing cert was found.
+ std::vector<ObjectHandle> result_handles{ObjectHandle(10)};
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ .WillOnce(RunOnceCallback<2>(result_handles, chromeos::PKCS11_CKR_OK));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ EXPECT_TRUE(waiter.Get().has_value());
+}
+
+// Test that ImportCertFromBytes correctly fails when the cert is invalid.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesBadCert) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+ // Corrupt the data.
+ cert.value().pop_back();
+
+ std::vector<ObjectHandle> result_handles{ObjectHandle(10)};
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ // Return empty list of existing certs.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK));
+
+ EXPECT_CALL(chaps_client_, CreateObject).Times(0);
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kInvalidCertificate);
+}
+
+// Test that ImportCertFromBytes correctly fails when it fails to fetch search
+// for the existing cert.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesFailToSearchForCert) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_GENERAL_ERROR));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kFailedToSearchForObjects);
+}
+
+// Test that ImportCertFromBytes correctly fails when it fails to fetch search
+// for the existing key.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesFailToSearchForKey) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ // Return some handles to simulate that an existing cert was found.
+ std::vector<ObjectHandle> result_handles{ObjectHandle(10)};
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ // Return empty list of existing certs.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK))
+ // Return an error when searching for the key.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_GENERAL_ERROR));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kFailedToSearchForObjects);
+}
+
+// Test that ImportCertFromBytes correctly fails when it fails to create a cert
+// object.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesFailToCreate) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ std::vector<ObjectHandle> result_handles{ObjectHandle(10)};
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ // Return empty list of existing certs.
+ .WillOnce(RunOnceCallback<2>(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK))
+ // Simulate that a key for the cert exists.
+ .WillOnce(RunOnceCallback<2>(result_handles, chromeos::PKCS11_CKR_OK));
+
+ EXPECT_CALL(chaps_client_, CreateObject)
+ .WillOnce(RunOnceCallback<2>(ObjectHandle(0),
+ chromeos::PKCS11_CKR_GENERAL_ERROR));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kFailedToImportCertificate);
+}
+
+// Test that ImportCertFromBytes retries several times when Chaps fails to
+// search for the cert with a session error.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesRetryToSearchForCert) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ EXPECT_CALL(chaps_client_, FindObjects(pkcs11_slot_id_, _, _))
+ .Times(kDefaultAttempts)
+ .WillRepeatedly(RunOnceCallbackRepeatedly<2>(
+ std::vector<ObjectHandle>(), chromeos::PKCS11_CKR_SESSION_CLOSED));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kPkcs11SessionFailure);
+}
+
+// Test that ImportCertFromBytes retries several times when Chaps fails to
+// search for the key with a session error.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesRetryToSearchForKey) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ // Alternates between replying with OK and SESSION_CLOSED to handle
+ // alternating calls for existing certs and keys.
+ auto fake_find_objects = [](auto slot_id, auto attrs, auto callback) {
+ static bool next_is_cert = true;
+ bool is_cert = std::exchange(next_is_cert, /*new_value=*/!next_is_cert);
+ if (is_cert) {
+ return std::move(callback).Run(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK);
+ } else {
+ return std::move(callback).Run(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_SESSION_CLOSED);
+ }
+ };
+
+ EXPECT_CALL(chaps_client_, FindObjects)
+ .Times(2 * kDefaultAttempts)
+ .WillRepeatedly(Invoke(fake_find_objects));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kPkcs11SessionFailure);
+}
+
+// Test that ImportCertFromBytes retries several times when Chaps fails to
+// create a new cert with a session error.
+TEST_F(KcerTokenImplTest, ImportCertFromBytesRetryToCreateCert) {
+ token_.InitializeWithoutNss(pkcs11_slot_id_);
+
+ std::optional<std::vector<uint8_t>> cert = ReadPemFileReturnDer(
+ net::GetTestCertsDirectory().AppendASCII("client_1.pem"));
+ ASSERT_TRUE(cert.has_value() && (cert->size() > 0));
+
+ // Alternates between replying with empty and non-empty handles to process
+ // alternating calls for existing certs and keys.
+ auto fake_find_objects = [](auto slot_id, auto attrs, auto callback) {
+ static bool next_is_cert = true;
+ bool is_cert = std::exchange(next_is_cert, /*new_value=*/!next_is_cert);
+ if (is_cert) {
+ return std::move(callback).Run(std::vector<ObjectHandle>(),
+ chromeos::PKCS11_CKR_OK);
+ } else {
+ return std::move(callback).Run(std::vector<ObjectHandle>{ObjectHandle(1)},
+ chromeos::PKCS11_CKR_OK);
+ }
+ };
+
+ EXPECT_CALL(chaps_client_, FindObjects)
+ .Times(2 * kDefaultAttempts)
+ .WillRepeatedly(Invoke(fake_find_objects));
+ EXPECT_CALL(chaps_client_, CreateObject)
+ .WillRepeatedly(RunOnceCallbackRepeatedly<2>(
+ ObjectHandle(0), chromeos::PKCS11_CKR_SESSION_CLOSED));
+
+ base::test::TestFuture<base::expected<void, Error>> waiter;
+ token_.ImportCertFromBytes(CertDer(cert.value()), waiter.GetCallback());
+
+ ASSERT_FALSE(waiter.Get().has_value());
+ EXPECT_EQ(waiter.Get().error(), Error::kPkcs11SessionFailure);
+}
+
// Test that RemoveKeyAndCerts can successfully remove a key pair and certs by
// PKCS#11 id.
TEST_F(KcerTokenImplTest, RemoveKeyAndCertsByIdSuccess) {
diff --git a/chromeos/components/kcer/kcer_token_utils.cc b/chromeos/components/kcer/kcer_token_utils.cc
index 1f10f3120..3102fc6d 100644
--- a/chromeos/components/kcer/kcer_token_utils.cc
+++ b/chromeos/components/kcer/kcer_token_utils.cc
@@ -189,6 +189,78 @@
//==============================================================================
+void KcerTokenUtils::ImportCert(
+ bssl::UniquePtr<X509> cert,
+ Pkcs11Id pkcs11_id,
+ std::string nickname,
+ CertDer cert_der,
+ 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)));
+
+ 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,
Kcer::GenerateKeyCallback in_callback)
diff --git a/chromeos/components/kcer/kcer_token_utils.h b/chromeos/components/kcer/kcer_token_utils.h
index 8c168a1c..b63e62f 100644
--- a/chromeos/components/kcer/kcer_token_utils.h
+++ b/chromeos/components/kcer/kcer_token_utils.h
@@ -72,6 +72,17 @@
base::OnceCallback<void(std::vector<ObjectHandle>,
uint32_t result_code)> callback);
+ // Creates a certificate object in Chaps. Does not check whether such an
+ // object already exists. If `kcer_error` is not empty - import failed without
+ // talking with Chaps. Otherwise returns the result from Chaps.
+ void ImportCert(bssl::UniquePtr<X509> cert,
+ Pkcs11Id pkcs11_id,
+ std::string nickname,
+ CertDer cert_der,
+ base::OnceCallback<void(std::optional<Error> kcer_error,
+ ObjectHandle cert_handle,
+ uint32_t result_code)> callback);
+
// Imports an EVP_KEY into Chaps as a pair of public and private objects.
// Skips the actual import if the key already exists.
struct ImportKeyTask {
@@ -83,7 +94,6 @@
Kcer::GenerateKeyCallback callback;
int attemps_left = 5;
};
-
void ImportKey(ImportKeyTask task);
private:
diff --git a/chromeos/constants/pkcs11_definitions.h b/chromeos/constants/pkcs11_definitions.h
index 6083dfb..40af761 100644
--- a/chromeos/constants/pkcs11_definitions.h
+++ b/chromeos/constants/pkcs11_definitions.h
@@ -21,6 +21,7 @@
using PKCS11_CK_OBJECT_CLASS = PKCS11_CK_ULONG;
using PKCS11_CK_KEY_TYPE = PKCS11_CK_ULONG;
using PKCS11_CK_ATTRIBUTE_TYPE = PKCS11_CK_ULONG;
+using PKCS11_CK_CERTIFICATE_TYPE = PKCS11_CK_ULONG;
// PKCS #11 v2.20 section 9.5 page 52.
using PKCS11_CK_MECHANISM_TYPE = PKCS11_CK_ULONG;
// PKCS #11 v2.20 section 12.1.6 page 198.
@@ -57,6 +58,9 @@
inline constexpr uint32_t PKCS11_CKK_RSA = 0x00000000;
inline constexpr uint32_t PKCS11_CKK_EC = 0x00000003;
+// PKCS #11 v2.20 section A Manifest constants page 376.
+inline constexpr uint32_t PKCS11_CKC_X_509 = 0x00000000;
+
// PKCS #11 v2.20 section A Manifest constants pages 376-377.
inline constexpr uint32_t PKCS11_CKA_CLASS = 0x00000000;
inline constexpr uint32_t PKCS11_CKA_TOKEN = 0x00000001;