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;