chromium/chrome/browser/ash/dbus/cryptohome_key_delegate_service_provider.cc

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

#include "chrome/browser/ash/dbus/cryptohome_key_delegate_service_provider.h"

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/cryptohome/key.pb.h"
#include "chromeos/ash/components/dbus/cryptohome/rpc.pb.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/common_types.h"
#include "components/user_manager/known_user.h"
#include "dbus/message.h"
#include "extensions/common/extension_id.h"
#include "net/base/net_errors.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "third_party/cros_system_api/dbus/cryptohome/dbus-constants.h"

namespace ash {

namespace {

// Converts the cryptohome challenge algorithm enum into the TLS 1.3
// SignatureScheme.
bool ChallengeSignatureAlgorithmToSslAlgorithm(
    cryptohome::ChallengeSignatureAlgorithm challenge_algorithm,
    uint16_t* ssl_algorithm) {
  switch (challenge_algorithm) {
    case cryptohome::CHALLENGE_RSASSA_PKCS1_V1_5_SHA1:
      *ssl_algorithm = SSL_SIGN_RSA_PKCS1_SHA1;
      return true;
    case cryptohome::CHALLENGE_RSASSA_PKCS1_V1_5_SHA256:
      *ssl_algorithm = SSL_SIGN_RSA_PKCS1_SHA256;
      return true;
    case cryptohome::CHALLENGE_RSASSA_PKCS1_V1_5_SHA384:
      *ssl_algorithm = SSL_SIGN_RSA_PKCS1_SHA384;
      return true;
    case cryptohome::CHALLENGE_RSASSA_PKCS1_V1_5_SHA512:
      *ssl_algorithm = SSL_SIGN_RSA_PKCS1_SHA512;
      return true;
    default:
      LOG(ERROR) << "Unknown cryptohome key challenge algorithm: "
                 << challenge_algorithm;
      return false;
  }
}

// Completes the "ChallengeKey" D-Bus call of the |CHALLENGE_TYPE_SIGNATURE|
// type with the given signature, or with an error if the signature wasn't
// successfully generated.
void CompleteSignatureKeyChallenge(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender,
    net::Error error,
    const std::vector<uint8_t>& signature) {
  if (error != net::OK || signature.empty()) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_FAILED,
            "Failed to generate the signature"));
    return;
  }

  cryptohome::KeyChallengeResponse challenge_response;
  challenge_response.mutable_signature_response_data()->set_signature(
      signature.data(), signature.size());

  std::unique_ptr<dbus::Response> response =
      dbus::Response::FromMethodCall(method_call);
  dbus::MessageWriter writer(response.get());
  writer.AppendProtoAsArrayOfBytes(challenge_response);

  std::move(response_sender).Run(std::move(response));
}

// Handles the "ChallengeKey" D-Bus call for the request of the
// |CHALLENGE_TYPE_SIGNATURE| type.
void HandleSignatureKeyChallenge(
    dbus::MethodCall* method_call,
    const cryptohome::SignatureKeyChallengeRequestData& challenge_request_data,
    const AccountId& account_id,
    dbus::ExportedObject::ResponseSender response_sender) {
  if (challenge_request_data.data_to_sign().empty()) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Missing data to sign"));
    return;
  }
  if (challenge_request_data.public_key_spki_der().empty()) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Missing public key"));
    return;
  }
  if (!challenge_request_data.has_signature_algorithm()) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS,
            "Missing signature algorithm"));
    return;
  }

  uint16_t ssl_algorithm = 0;
  if (!ChallengeSignatureAlgorithmToSslAlgorithm(
          challenge_request_data.signature_algorithm(), &ssl_algorithm)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_FAILED, "Unknown signature algorithm"));
    return;
  }

  // Handle the challenge request by delivering it to one of the sign-in
  // certificateProvider subscribers (e.g., smart card middleware extensions).
  // The sign-in profile is used since it's where the needed extensions are
  // installed (e.g., for the smart card based login they are force-installed
  // via the DeviceLoginScreenExtensions admin policy).
  Profile* signin_profile = ProfileHelper::GetSigninProfile();
  chromeos::CertificateProviderService* certificate_provider_service =
      chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
          signin_profile);
  if (!certificate_provider_service) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_FAILED,
            "Missing certificate provider service"));
    return;
  }

  std::vector<uint16_t> supported_ssl_algorithms;
  extensions::ExtensionId extension_id_ignored;
  if (!certificate_provider_service->LookUpSpki(
          challenge_request_data.public_key_spki_der(),
          &supported_ssl_algorithms, &extension_id_ignored)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
                                                 "Key is unavailable"));
    return;
  }
  if (!base::Contains(supported_ssl_algorithms, ssl_algorithm)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
                                                 "Unsupported algorithm"));
    return;
  }

  certificate_provider_service->RequestSignatureBySpki(
      challenge_request_data.public_key_spki_der(), ssl_algorithm,
      base::as_byte_span(challenge_request_data.data_to_sign()), account_id,
      base::BindOnce(&CompleteSignatureKeyChallenge,
                     base::Unretained(method_call),
                     std::move(response_sender)));
}

}  // namespace

CryptohomeKeyDelegateServiceProvider::CryptohomeKeyDelegateServiceProvider() =
    default;

CryptohomeKeyDelegateServiceProvider::~CryptohomeKeyDelegateServiceProvider() =
    default;

void CryptohomeKeyDelegateServiceProvider::Start(
    scoped_refptr<dbus::ExportedObject> exported_object) {
  exported_object->ExportMethod(
      cryptohome::kCryptohomeKeyDelegateInterface,
      cryptohome::kCryptohomeKeyDelegateChallengeKey,
      base::BindRepeating(
          &CryptohomeKeyDelegateServiceProvider::HandleChallengeKey,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce([](const std::string& interface_name,
                        const std::string& method_name, bool success) {
        LOG_IF(ERROR, !success)
            << "Failed to export " << interface_name << "." << method_name;
      }));
}

void CryptohomeKeyDelegateServiceProvider::HandleChallengeKey(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  dbus::MessageReader reader(method_call);

  cryptohome::AccountIdentifier account_identifier;
  if (!reader.PopArrayOfBytesAsProto(&account_identifier)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS,
            "Unable to parse AccountIdentifier from request"));
    return;
  }
  user_manager::KnownUser known_user(g_browser_process->local_state());
  user_manager::CryptohomeId cryptohome_id(account_identifier.account_id());
  const AccountId account_id =
      known_user.GetAccountIdByCryptohomeId(cryptohome_id);
  if (!account_id.is_valid() ||
      account_id.GetAccountType() == AccountType::UNKNOWN) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
                                                 "Unknown account"));
    return;
  }

  cryptohome::KeyChallengeRequest request;
  if (!reader.PopArrayOfBytesAsProto(&request)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS,
            "Unable to parse KeyChallengeRequest from request"));
    return;
  }

  if (!request.has_challenge_type()) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Missing challenge type"));
    return;
  }

  if (request.challenge_type() ==
      cryptohome::KeyChallengeRequest::CHALLENGE_TYPE_SIGNATURE) {
    if (!request.has_signature_request_data()) {
      std::move(response_sender)
          .Run(dbus::ErrorResponse::FromMethodCall(
              method_call, DBUS_ERROR_INVALID_ARGS,
              "Missing signature request data"));
      return;
    }
    HandleSignatureKeyChallenge(method_call, request.signature_request_data(),
                                account_id, std::move(response_sender));
    return;
  }

  std::move(response_sender)
      .Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
                                               "Unknown challenge type"));
}

}  // namespace ash