chromium/chromeos/components/kcer/chaps/high_level_chaps_client.cc

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

#include "chromeos/components/kcer/chaps/high_level_chaps_client.h"

#include "base/sequence_checker.h"
#include "chromeos/components/kcer/chaps/session_chaps_client.h"
#include "chromeos/components/kcer/key_permissions.pb.h"
#include "chromeos/constants/pkcs11_definitions.h"
#include "third_party/cros_system_api/dbus/chaps/dbus-constants.h"

namespace kcer {

void AddAttribute(chaps::AttributeList& attr_list,
                  chromeos::PKCS11_CK_ATTRIBUTE_TYPE type,
                  base::span<const uint8_t> data) {
  chaps::Attribute* new_attr = attr_list.add_attributes();
  new_attr->set_type(type);
  new_attr->set_value(std::string(data.begin(), data.end()));
  new_attr->set_length(data.size());
}

namespace {

using AttributeId = HighLevelChapsClient::AttributeId;

constexpr int kDefaultAttempts = 5;

// Returns the expected size of an attribute with `attribute_id`. In case the
// actual attribute is bigger, the retrieval should still succeed, but will take
// two additional D-Bus calls.
int GetDefaultLength(AttributeId attribute_id) {
  switch (attribute_id) {
    case AttributeId::kModulus:
      // The size of the modulus for a 2048 RSA public key. All other supported
      // keys are expected to be smaller or equal to this.
      return 256;
    case AttributeId::kPublicExponent:
      return 3;
    case AttributeId::kEcPoint:
      return 67;
    case AttributeId::kPkcs11Id:
      // The size of a SHA-1 hash, a typical size for CKA_ID.
      return 20;
    case AttributeId::kLabel:
      // An arbitrary length, label is just a user readable string. In same
      // cases it contains a GUID (38 characters).
      return 40;
    case AttributeId::kKeyType:
      return sizeof(chromeos::PKCS11_CK_KEY_TYPE);
    case AttributeId::kValue:
      return 800;
    case AttributeId::kKeyInSoftware:
      return sizeof(chromeos::PKCS11_CK_BBOOL);
    case AttributeId::kKeyPermissions:
      return sizeof(chaps::KeyPermissions);
    case AttributeId::kCertProvisioningId:
      // An arbitrary length, the id is just a user readable string. In same
      // cases it contains a GUID (38 characters).
      return 40;
  }
}

}  // namespace

HighLevelChapsClientImpl::HighLevelChapsClientImpl(
    SessionChapsClient* session_chaps_client)
    : session_chaps_client_(session_chaps_client) {}
HighLevelChapsClientImpl::~HighLevelChapsClientImpl() = default;

//==============================================================================

void HighLevelChapsClientImpl::GetMechanismList(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::GetMechanismListCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_chaps_client_->GetMechanismList(slot_id, std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::CreateObject(
    SessionChapsClient::SlotId slot_id,
    const chaps::AttributeList& attributes,
    SessionChapsClient::CreateObjectCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_chaps_client_->CreateObject(
      slot_id, SessionChapsClient::SerializeToBytes(attributes),
      kDefaultAttempts, std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::DestroyObject(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::ObjectHandle object_handle,
    SessionChapsClient::DestroyObjectCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_chaps_client_->DestroyObject(slot_id, object_handle, kDefaultAttempts,
                                       std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::DestroyObjectsWithRetries(
    SessionChapsClient::SlotId slot_id,
    std::vector<SessionChapsClient::ObjectHandle> object_handles,
    SessionChapsClient::DestroyObjectCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DestroyObjectsWithRetriesImpl(slot_id, std::move(object_handles),
                                /*failed_handles=*/{},
                                /*last_error=*/chromeos::PKCS11_CKR_OK,
                                /*retries_left=*/2, std::move(callback));
}

void HighLevelChapsClientImpl::DestroyObjectsWithRetriesImpl(
    SessionChapsClient::SlotId slot_id,
    std::vector<SessionChapsClient::ObjectHandle> object_handles,
    std::vector<SessionChapsClient::ObjectHandle> failed_handles,
    uint32_t last_error,
    int retries_left,
    SessionChapsClient::DestroyObjectCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (!object_handles.empty()) {
    SessionChapsClient::ObjectHandle next_handle = object_handles.back();
    auto chaps_callback = base::BindOnce(
        &HighLevelChapsClientImpl::DestroyObjectsWithRetriesHandleOneResult,
        weak_factory_.GetWeakPtr(), slot_id, object_handles, failed_handles,
        last_error, retries_left, std::move(callback));
    return DestroyObject(slot_id, next_handle, std::move(chaps_callback));
  }

  if (!failed_handles.empty() && (retries_left > 0)) {
    return DestroyObjectsWithRetriesImpl(
        slot_id, /*object_handles=*/std::move(failed_handles),
        /*failed_handles=*/{}, last_error, retries_left - 1,
        std::move(callback));
  }

  uint32_t final_result =
      failed_handles.empty() ? chromeos::PKCS11_CKR_OK : last_error;
  return std::move(callback).Run(final_result);
}

void HighLevelChapsClientImpl::DestroyObjectsWithRetriesHandleOneResult(
    SessionChapsClient::SlotId slot_id,
    std::vector<SessionChapsClient::ObjectHandle> object_handles,
    std::vector<SessionChapsClient::ObjectHandle> failed_handles,
    uint32_t last_error,
    int retries_left,
    SessionChapsClient::DestroyObjectCallback callback,
    uint32_t result_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (SessionChapsClient::IsSessionError(result_code)) {
    return std::move(callback).Run(result_code);
  }

  uint32_t new_last_error = last_error;
  if (result_code != chromeos::PKCS11_CKR_OK) {
    failed_handles.push_back(object_handles.back());
    new_last_error = result_code;
  }
  object_handles.pop_back();

  return DestroyObjectsWithRetriesImpl(
      slot_id, std::move(object_handles), std::move(failed_handles),
      new_last_error, retries_left, std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::GetAttributeValue(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::ObjectHandle object_handle,
    std::vector<AttributeId> attribute_ids,
    HighLevelChapsClient::GetAttributeValueCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  chaps::AttributeList attributes_query;
  for (AttributeId attr_id : attribute_ids) {
    chaps::Attribute* attr = attributes_query.add_attributes();
    attr->set_type(static_cast<uint32_t>(attr_id));

    int length = GetDefaultLength(attr_id);
    attr->set_length(length);
    // The "value" represents the buffer from the PKCS#11 interface. The buffer
    // needs to be big enough to store the attribute value. While communicating
    // over D-Bus this actual buffer won't be used and the reply will be sent in
    // a separate message, but Chaps looks at the size of it to allocate the
    // memory for the reply.
    // TODO(miersh): Since Chaps only needs the size, it could just looks at the
    // "length" field. Investigate why it's not doing that and optimize the
    // protocol.
    attr->set_value(std::string(/*count=*/length, /*ch=*/'\0'));
  }

  auto chaps_callback = base::BindOnce(
      &HighLevelChapsClientImpl::DidGetAttributeValue,
      weak_factory_.GetWeakPtr(), slot_id, object_handle, std::move(callback));
  session_chaps_client_->GetAttributeValue(
      slot_id, object_handle,
      SessionChapsClient::SerializeToBytes(attributes_query), kDefaultAttempts,
      std::move(chaps_callback));
}

void HighLevelChapsClientImpl::DidGetAttributeValue(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::ObjectHandle object_handle,
    HighLevelChapsClient::GetAttributeValueCallback callback,
    std::vector<uint8_t> attributes,
    uint32_t result_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  chaps::AttributeList decoded_attributes;
  if (!decoded_attributes.ParseFromArray(attributes.data(),
                                         attributes.size())) {
    return std::move(callback).Run({}, chaps::CKR_DBUS_DECODING_ERROR);
  }

  if (result_code != chromeos::PKCS11_CKR_BUFFER_TOO_SMALL) {
    // If `result_code` is ok, then `decoded_attributes` should contain the
    // result. If `result_code` is an error, just forward it to the caller
    // together with all attributes that chaps managed to find (if any).
    return std::move(callback).Run(std::move(decoded_attributes), result_code);
  }

  // One or more provided lengths for the attributes were too small, Chaps
  // returned only some of the attributes. In theory only such attributes need
  // to be processed specially, but for simplicity this class requests correct
  // lengths for all attributes and then queries the attributes again.
  for (int i = 0; i < decoded_attributes.attributes_size(); i++) {
    // Clearing the value requests the size of the stored attribute.
    decoded_attributes.mutable_attributes(i)->clear_value();
    decoded_attributes.mutable_attributes(i)->set_length(0);
  }

  auto chaps_callback = base::BindOnce(
      &HighLevelChapsClientImpl::DidGetAttributeLength,
      weak_factory_.GetWeakPtr(), slot_id, object_handle, std::move(callback));
  session_chaps_client_->GetAttributeValue(
      slot_id, object_handle,
      SessionChapsClient::SerializeToBytes(decoded_attributes),
      kDefaultAttempts, std::move(chaps_callback));
}

void HighLevelChapsClientImpl::DidGetAttributeLength(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::ObjectHandle object_handle,
    HighLevelChapsClient::GetAttributeValueCallback callback,
    std::vector<uint8_t> attributes,
    uint32_t result_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (result_code != chromeos::PKCS11_CKR_OK) {
    return std::move(callback).Run({}, result_code);
  }

  chaps::AttributeList decoded_attributes;
  if (!decoded_attributes.ParseFromArray(attributes.data(),
                                         attributes.size())) {
    return std::move(callback).Run({}, chaps::CKR_DBUS_DECODING_ERROR);
  }
  // Set values to requests the attributes with the returned lengths.
  for (int i = 0; i < decoded_attributes.attributes_size(); i++) {
    decoded_attributes.mutable_attributes(i)->set_value(std::string(
        /*count=*/decoded_attributes.attributes(i).length(), /*ch=*/'\0'));
  }

  auto chaps_callback = base::BindOnce(
      &HighLevelChapsClientImpl::DidGetAttributeValueWithLength,
      weak_factory_.GetWeakPtr(), slot_id, object_handle, std::move(callback));
  session_chaps_client_->GetAttributeValue(
      slot_id, object_handle,
      SessionChapsClient::SerializeToBytes(decoded_attributes),
      kDefaultAttempts, std::move(chaps_callback));
}

void HighLevelChapsClientImpl::DidGetAttributeValueWithLength(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::ObjectHandle object_handle,
    HighLevelChapsClient::GetAttributeValueCallback callback,
    std::vector<uint8_t> attributes,
    uint32_t result_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  chaps::AttributeList decoded_attributes;
  if (!decoded_attributes.ParseFromArray(attributes.data(),
                                         attributes.size())) {
    return std::move(callback).Run({}, chaps::CKR_DBUS_DECODING_ERROR);
  }
  return std::move(callback).Run(std::move(decoded_attributes), result_code);
}

//==============================================================================

void HighLevelChapsClientImpl::SetAttributeValue(
    SessionChapsClient::SlotId slot_id,
    SessionChapsClient::ObjectHandle object_handle,
    const chaps::AttributeList& attributes,
    SessionChapsClient::SetAttributeValueCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_chaps_client_->SetAttributeValue(
      slot_id, object_handle, SessionChapsClient::SerializeToBytes(attributes),
      kDefaultAttempts, std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::SetAttributeValue(
    SessionChapsClient::SlotId slot_id,
    std::vector<SessionChapsClient::ObjectHandle> object_handles,
    const chaps::AttributeList& attributes,
    SessionChapsClient::SetAttributeValueCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  SetAttributeValueImpl(slot_id, object_handles, std::move(attributes),
                        std::move(callback),
                        /*last_error=*/chromeos::PKCS11_CKR_OK,
                        /*new_result_code=*/chromeos::PKCS11_CKR_OK);
}

void HighLevelChapsClientImpl::SetAttributeValueImpl(
    SessionChapsClient::SlotId slot_id,
    std::vector<SessionChapsClient::ObjectHandle> object_handles,
    const chaps::AttributeList& attributes,
    SessionChapsClient::SetAttributeValueCallback callback,
    uint32_t last_error,
    uint32_t new_result_code) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (new_result_code != chromeos::PKCS11_CKR_OK) {
    last_error = new_result_code;
  }

  if (object_handles.empty()) {
    return std::move(callback).Run(last_error);
  }

  SessionChapsClient::ObjectHandle next_handle = object_handles.back();
  chaps::AttributeList attributes_copy = attributes;
  object_handles.pop_back();
  auto chaps_callback =
      base::BindOnce(&HighLevelChapsClientImpl::SetAttributeValueImpl,
                     weak_factory_.GetWeakPtr(), slot_id, object_handles,
                     std::move(attributes), std::move(callback), last_error);
  SetAttributeValue(slot_id, next_handle, std::move(attributes_copy),
                    std::move(chaps_callback));
}

//==============================================================================

void HighLevelChapsClientImpl::FindObjects(
    SessionChapsClient::SlotId slot_id,
    const chaps::AttributeList& attributes,
    SessionChapsClient::FindObjectsCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_chaps_client_->FindObjects(
      slot_id, SessionChapsClient::SerializeToBytes(attributes),
      kDefaultAttempts, std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::Sign(
    SessionChapsClient::SlotId slot_id,
    uint64_t mechanism_type,
    const std::vector<uint8_t>& mechanism_parameter,
    SessionChapsClient::ObjectHandle key_handle,
    std::vector<uint8_t> data,
    SessionChapsClient::SignCallback callback) {
  session_chaps_client_->Sign(slot_id, mechanism_type, mechanism_parameter,
                              key_handle, std::move(data), kDefaultAttempts,
                              std::move(callback));
}

//==============================================================================

void HighLevelChapsClientImpl::GenerateKeyPair(
    SessionChapsClient::SlotId slot_id,
    uint64_t mechanism_type,
    const std::vector<uint8_t>& mechanism_parameter,
    const chaps::AttributeList& public_attributes,
    const chaps::AttributeList& private_attributes,
    SessionChapsClient::GenerateKeyPairCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  session_chaps_client_->GenerateKeyPair(
      slot_id, mechanism_type, mechanism_parameter,
      SessionChapsClient::SerializeToBytes(public_attributes),
      SessionChapsClient::SerializeToBytes(private_attributes),
      kDefaultAttempts, std::move(callback));
}

}  // namespace kcer