chromium/chrome/browser/chromeos/platform_keys/platform_keys.cc

// Copyright 2020 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/chromeos/platform_keys/platform_keys.h"

#include <stdint.h>

#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/functional/callback.h"
#include "base/notreached.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chromeos/crosapi/cpp/keystore_service_util.h"
#include "chromeos/crosapi/mojom/keystore_error.mojom.h"
#include "crypto/openssl_util.h"
#include "net/base/hash_value.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"

namespace {

using crosapi::keystore_service_util::kWebCryptoEcdsa;
using crosapi::keystore_service_util::kWebCryptoNamedCurveP256;
using crosapi::keystore_service_util::kWebCryptoRsassaPkcs1v15;

void IntersectOnWorkerThread(const net::CertificateList& certs1,
                             const net::CertificateList& certs2,
                             net::CertificateList* intersection) {
  std::map<net::SHA256HashValue, scoped_refptr<net::X509Certificate>>
      fingerprints2;

  // Fill the map with fingerprints of certs from |certs2|.
  for (const auto& cert2 : certs2) {
    fingerprints2[net::X509Certificate::CalculateFingerprint256(
        cert2->cert_buffer())] = cert2;
  }

  // Compare each cert from |certs1| with the entries of the map.
  for (const auto& cert1 : certs1) {
    const net::SHA256HashValue fingerprint1 =
        net::X509Certificate::CalculateFingerprint256(cert1->cert_buffer());
    const auto it = fingerprints2.find(fingerprint1);
    if (it == fingerprints2.end())
      continue;
    const auto& cert2 = it->second;
    DCHECK(cert1->EqualsExcludingChain(cert2.get()));
    intersection->push_back(cert1);
  }
}

}  // namespace

namespace chromeos::platform_keys {

std::string StatusToString(Status status) {
  switch (status) {
    case Status::kSuccess:
      return "The operation was successfully executed.";
    case Status::kErrorAlgorithmNotSupported:
      return "Algorithm not supported.";
    case Status::kErrorAlgorithmNotPermittedByCertificate:
      return "The requested Algorithm is not permitted by the certificate.";
    case Status::kErrorCertificateNotFound:
      return "Certificate could not be found.";
    case Status::kErrorCertificateInvalid:
      return "Certificate is not a valid X.509 certificate.";
    case Status::kErrorInputTooLong:
      return "Input too long.";
    case Status::kErrorGrantKeyPermissionForExtension:
      return "Tried to grant permission for a key although prohibited (either "
             "key is a corporate key or this account is managed).";
    case Status::kErrorInternal:
      return "Internal Error.";
    case Status::kErrorKeyAttributeRetrievalFailed:
      return "Key attribute value retrieval failed.";
    case Status::kErrorKeyAttributeSettingFailed:
      return "Setting key attribute value failed.";
    case Status::kErrorKeyNotAllowedForSigning:
      return "This key is not allowed for signing. Either it was used for "
             "signing before or it was not correctly generated.";
    case Status::kErrorKeyNotFound:
      return "Key not found.";
    case Status::kErrorShutDown:
      return "Delegate shut down.";
    case Status::kNetErrorAddUserCertFailed:
      return net::ErrorToString(net::ERR_ADD_USER_CERT_FAILED);
    case Status::kNetErrorCertificateDateInvalid:
      return net::ErrorToString(net::ERR_CERT_DATE_INVALID);
    case Status::kNetErrorCertificateInvalid:
      return net::ErrorToString(net::ERR_CERT_INVALID);
  }
}

crosapi::mojom::KeystoreError StatusToKeystoreError(Status status) {
  DCHECK(status != Status::kSuccess);
  using crosapi::mojom::KeystoreError;

  switch (status) {
    case Status::kSuccess:
      return KeystoreError::kUnknown;
    case Status::kErrorAlgorithmNotSupported:
      return KeystoreError::kAlgorithmNotSupported;
    case Status::kErrorAlgorithmNotPermittedByCertificate:
      return KeystoreError::kAlgorithmNotPermittedByCertificate;
    case Status::kErrorCertificateNotFound:
      return KeystoreError::kCertificateNotFound;
    case Status::kErrorCertificateInvalid:
      return KeystoreError::kCertificateInvalid;
    case Status::kErrorInputTooLong:
      return KeystoreError::kInputTooLong;
    case Status::kErrorGrantKeyPermissionForExtension:
      return KeystoreError::kGrantKeyPermissionForExtension;
    case Status::kErrorInternal:
      return KeystoreError::kInternal;
    case Status::kErrorKeyAttributeRetrievalFailed:
      return KeystoreError::kKeyAttributeRetrievalFailed;
    case Status::kErrorKeyAttributeSettingFailed:
      return KeystoreError::kKeyAttributeSettingFailed;
    case Status::kErrorKeyNotAllowedForSigning:
      return KeystoreError::kKeyNotAllowedForSigning;
    case Status::kErrorKeyNotFound:
      return KeystoreError::kKeyNotFound;
    case Status::kErrorShutDown:
      return KeystoreError::kShutDown;
    case Status::kNetErrorAddUserCertFailed:
      return KeystoreError::kNetAddUserCertFailed;
    case Status::kNetErrorCertificateDateInvalid:
      return KeystoreError::kNetCertificateDateInvalid;
    case Status::kNetErrorCertificateInvalid:
      return KeystoreError::kNetCertificateInvalid;
  }
  NOTREACHED_IN_MIGRATION();
}

Status StatusFromKeystoreError(crosapi::mojom::KeystoreError error) {
  using crosapi::mojom::KeystoreError;

  switch (error) {
    case KeystoreError::kUnknown:
    case KeystoreError::kUnsupportedKeystoreType:
    case KeystoreError::kUnsupportedAlgorithmType:
    case KeystoreError::kUnsupportedKeyTag:
    case KeystoreError::kMojoUnavailable:
    case KeystoreError::kUnsupportedKeyType:
      // Keystore specific errors shouldn't be passed here.
      NOTREACHED_IN_MIGRATION();
      return Status::kErrorInternal;

    case KeystoreError::kAlgorithmNotSupported:
      return Status::kErrorAlgorithmNotSupported;
    case KeystoreError::kAlgorithmNotPermittedByCertificate:
      return Status::kErrorAlgorithmNotPermittedByCertificate;
    case KeystoreError::kCertificateNotFound:
      return Status::kErrorCertificateNotFound;
    case KeystoreError::kCertificateInvalid:
      return Status::kErrorCertificateInvalid;
    case KeystoreError::kInputTooLong:
      return Status::kErrorInputTooLong;
    case KeystoreError::kGrantKeyPermissionForExtension:
      return Status::kErrorGrantKeyPermissionForExtension;
    case KeystoreError::kInternal:
      return Status::kErrorInternal;
    case KeystoreError::kKeyAttributeRetrievalFailed:
      return Status::kErrorKeyAttributeRetrievalFailed;
    case KeystoreError::kKeyAttributeSettingFailed:
      return Status::kErrorKeyAttributeSettingFailed;
    case KeystoreError::kKeyNotAllowedForSigning:
      return Status::kErrorKeyNotAllowedForSigning;
    case KeystoreError::kKeyNotFound:
      return Status::kErrorKeyNotFound;
    case KeystoreError::kShutDown:
      return Status::kErrorShutDown;
    case KeystoreError::kNetAddUserCertFailed:
      return Status::kNetErrorAddUserCertFailed;
    case KeystoreError::kNetCertificateDateInvalid:
      return Status::kNetErrorCertificateDateInvalid;
    case KeystoreError::kNetCertificateInvalid:
      return Status::kNetErrorCertificateInvalid;
  }

  NOTREACHED_IN_MIGRATION();
}

std::string KeystoreErrorToString(crosapi::mojom::KeystoreError error) {
  using crosapi::mojom::KeystoreError;

  // Handle Keystore specific errors.
  switch (error) {
    case KeystoreError::kUnknown:
      return "Unknown keystore error.";
    case KeystoreError::kUnsupportedKeystoreType:
      return "The token is not valid.";
    case KeystoreError::kUnsupportedAlgorithmType:
      return "Algorithm type is not supported.";
    case KeystoreError::kMojoUnavailable:
      return "The OS is too old.";
    case KeystoreError::kUnsupportedKeyType:
      return "Key type is not supported.";
    default:
      break;
  }
  // Handle platform_keys errors.
  return StatusToString(StatusFromKeystoreError(error));
}

std::string GetSubjectPublicKeyInfo(
    const scoped_refptr<net::X509Certificate>& certificate) {
  std::string_view spki_bytes;
  if (!net::asn1::ExtractSPKIFromDERCert(
          net::x509_util::CryptoBufferAsStringPiece(certificate->cert_buffer()),
          &spki_bytes))
    return {};
  return std::string(spki_bytes);
}

std::vector<uint8_t> GetSubjectPublicKeyInfoBlob(
    const scoped_refptr<net::X509Certificate>& certificate) {
  std::string_view spki_bytes;
  if (!net::asn1::ExtractSPKIFromDERCert(
          net::x509_util::CryptoBufferAsStringPiece(certificate->cert_buffer()),
          &spki_bytes))
    return {};
  return std::vector<uint8_t>(spki_bytes.begin(), spki_bytes.end());
}

// Extracts the public exponent out of an EVP_PKEY and verifies if it is equal
// to 65537 (Fermat number with n=4). This values is enforced by
// platform_keys::GetPublicKey() and platform_keys::GetPublicKeyBySpki().
// The caller of this function needs to have an OpenSSLErrStackTracer or
// otherwise clean up the error stack on failure.
bool VerifyRSAPublicExponent(EVP_PKEY* pkey) {
  RSA* rsa = EVP_PKEY_get0_RSA(pkey);
  if (!rsa) {
    LOG(WARNING) << "Could not get RSA from PKEY.";
    return false;
  }

  const BIGNUM* public_exponent = nullptr;
  RSA_get0_key(rsa, nullptr /* out_n */, &public_exponent, nullptr /* out_d */);
  if (BN_get_word(public_exponent) != 65537L) {
    LOG(ERROR) << "Rejecting RSA public exponent that is unequal 65537.";
    return false;
  }

  return true;
}

bool GetPublicKey(const scoped_refptr<net::X509Certificate>& certificate,
                  net::X509Certificate::PublicKeyType* key_type,
                  size_t* key_size_bits) {
  net::X509Certificate::PublicKeyType key_type_tmp =
      net::X509Certificate::kPublicKeyTypeUnknown;
  size_t key_size_bits_tmp = 0;
  net::X509Certificate::GetPublicKeyInfo(certificate->cert_buffer(),
                                         &key_size_bits_tmp, &key_type_tmp);

  if (key_type_tmp == net::X509Certificate::kPublicKeyTypeUnknown) {
    LOG(WARNING) << "Could not extract public key of certificate.";
    return false;
  }
  if (key_type_tmp != net::X509Certificate::kPublicKeyTypeRSA &&
      key_type_tmp != net::X509Certificate::kPublicKeyTypeECDSA) {
    LOG(WARNING) << "Keys of other types than RSA and EC are not supported.";
    return false;
  }

  std::string spki = GetSubjectPublicKeyInfo(certificate);
  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
  CBS cbs;
  CBS_init(&cbs, reinterpret_cast<const uint8_t*>(spki.data()), spki.size());
  bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_public_key(&cbs));
  if (!pkey) {
    LOG(WARNING) << "Could not extract public key of certificate.";
    return false;
  }

  switch (EVP_PKEY_id(pkey.get())) {
    case EVP_PKEY_RSA: {
      if (!VerifyRSAPublicExponent(pkey.get())) {
        return false;
      }
      break;
    }
    case EVP_PKEY_EC: {
      EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
      if (!ec) {
        LOG(WARNING) << "Could not get EC from PKEY.";
        return false;
      }

      if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)) !=
          NID_X9_62_prime256v1) {
        LOG(WARNING) << "Only P-256 named curve is supported.";
        return false;
      }
      break;
    }
    default: {
      LOG(WARNING) << "Only RSA and EC keys are supported.";
      return false;
    }
  }

  *key_type = key_type_tmp;
  *key_size_bits = key_size_bits_tmp;
  return true;
}

bool GetPublicKeyBySpki(const std::string& spki,
                        net::X509Certificate::PublicKeyType* key_type,
                        size_t* key_size_bits) {
  net::X509Certificate::PublicKeyType key_type_tmp =
      net::X509Certificate::kPublicKeyTypeUnknown;

  crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
  CBS cbs;
  CBS_init(&cbs, reinterpret_cast<const uint8_t*>(spki.data()), spki.size());
  bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_public_key(&cbs));
  if (!pkey) {
    LOG(WARNING) << "Could not extract public key from SPKI.";
    return false;
  }
  switch (EVP_PKEY_id(pkey.get())) {
    case EVP_PKEY_RSA: {
      if (!VerifyRSAPublicExponent(pkey.get())) {
        return false;
      }
      key_type_tmp = net::X509Certificate::kPublicKeyTypeRSA;
      break;
    }
    case EVP_PKEY_EC: {
      EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
      if (!ec) {
        LOG(WARNING) << "Could not get EC from PKEY.";
        return false;
      }
      if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)) !=
          NID_X9_62_prime256v1) {
        LOG(WARNING) << "Only P-256 named curve is supported.";
        return false;
      }
      key_type_tmp = net::X509Certificate::kPublicKeyTypeECDSA;
      break;
    }
    default: {
      LOG(WARNING) << "Only RSA and EC keys are supported.";
      return false;
    }
  }

  *key_type = key_type_tmp;
  *key_size_bits = base::saturated_cast<size_t>(EVP_PKEY_bits(pkey.get()));
  return true;
}

void IntersectCertificates(
    const net::CertificateList& certs1,
    const net::CertificateList& certs2,
    base::OnceCallback<void(std::unique_ptr<net::CertificateList>)> callback) {
  std::unique_ptr<net::CertificateList> intersection(new net::CertificateList);
  net::CertificateList* const intersection_ptr = intersection.get();

  // This is triggered by a call to the
  // chrome.platformKeys.selectClientCertificates extensions API. Completion
  // does not affect browser responsiveness, hence the BEST_EFFORT priority.
  base::ThreadPool::PostTaskAndReply(
      FROM_HERE,
      {base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&IntersectOnWorkerThread, certs1, certs2,
                     intersection_ptr),
      base::BindOnce(std::move(callback), std::move(intersection)));
}

GetPublicKeyAndAlgorithmOutput::GetPublicKeyAndAlgorithmOutput() = default;
GetPublicKeyAndAlgorithmOutput::GetPublicKeyAndAlgorithmOutput(
    GetPublicKeyAndAlgorithmOutput&&) = default;
GetPublicKeyAndAlgorithmOutput::~GetPublicKeyAndAlgorithmOutput() = default;

GetPublicKeyAndAlgorithmOutput GetPublicKeyAndAlgorithm(
    const std::vector<uint8_t>& possibly_invalid_cert_der,
    const std::string& algorithm_name) {
  GetPublicKeyAndAlgorithmOutput output;

  if (possibly_invalid_cert_der.empty()) {
    output.status = Status::kErrorCertificateInvalid;
    return output;
  }

  // Allow UTF-8 inside PrintableStrings in client certificates. See
  // crbug.com/770323 and crbug.com/788655.
  net::X509Certificate::UnsafeCreateOptions options;
  options.printable_string_is_utf8 = true;
  scoped_refptr<net::X509Certificate> cert_x509 =
      net::X509Certificate::CreateFromBytesUnsafeOptions(
          possibly_invalid_cert_der, options);
  if (!cert_x509) {
    output.status = Status::kErrorCertificateInvalid;
    return output;
  }

  PublicKeyInfo key_info;
  key_info.public_key_spki_der =
      chromeos::platform_keys::GetSubjectPublicKeyInfo(cert_x509);
  if (!chromeos::platform_keys::GetPublicKey(cert_x509, &key_info.key_type,
                                             &key_info.key_size_bits)) {
    output.status = Status::kErrorAlgorithmNotSupported;
    return output;
  }

  chromeos::platform_keys::Status check_result =
      chromeos::platform_keys::CheckKeyTypeAndAlgorithm(key_info.key_type,
                                                        algorithm_name);
  if (check_result != chromeos::platform_keys::Status::kSuccess) {
    output.status = check_result;
    return output;
  }

  std::optional<base::Value::Dict> algorithm =
      BuildWebCryptoAlgorithmDictionary(key_info);
  DCHECK(algorithm.has_value());
  output.algorithm = std::move(algorithm.value());

  output.public_key = std::vector<uint8_t>(key_info.public_key_spki_der.begin(),
                                           key_info.public_key_spki_der.end());
  output.status = Status::kSuccess;
  return output;
}

PublicKeyInfo::PublicKeyInfo() = default;
PublicKeyInfo::~PublicKeyInfo() = default;

Status CheckKeyTypeAndAlgorithm(net::X509Certificate::PublicKeyType key_type,
                                const std::string& algorithm_name) {
  if (key_type != net::X509Certificate::kPublicKeyTypeRSA &&
      key_type != net::X509Certificate::kPublicKeyTypeECDSA) {
    return Status::kErrorAlgorithmNotSupported;
  }

  if (algorithm_name != kWebCryptoRsassaPkcs1v15 &&
      algorithm_name != kWebCryptoEcdsa) {
    return Status::kErrorAlgorithmNotSupported;
  }

  if (key_type !=
      chromeos::platform_keys::GetKeyTypeForAlgorithm(algorithm_name)) {
    return Status::kErrorAlgorithmNotPermittedByCertificate;
  }

  return Status::kSuccess;
}

net::X509Certificate::PublicKeyType GetKeyTypeForAlgorithm(
    const std::string& algorithm_name) {
  // Currently, the only supported combinations are:
  // 1- A certificate declaring rsaEncryption in the SubjectPublicKeyInfo used
  // with the RSASSA-PKCS1-v1.5 algorithm.
  // 2- A certificate declaring id-ecPublicKey in the SubjectPublicKeyInfo used
  // with the ECDSA algorithm.
  if (algorithm_name == kWebCryptoRsassaPkcs1v15)
    return net::X509Certificate::kPublicKeyTypeRSA;
  if (algorithm_name == kWebCryptoEcdsa)
    return net::X509Certificate::kPublicKeyTypeECDSA;
  return net::X509Certificate::kPublicKeyTypeUnknown;
}

std::optional<base::Value::Dict> BuildWebCryptoAlgorithmDictionary(
    const PublicKeyInfo& key_info) {
  switch (key_info.key_type) {
    case net::X509Certificate::kPublicKeyTypeRSA: {
      base::Value::Dict result;
      BuildWebCryptoRSAAlgorithmDictionary(key_info, &result);
      return result;
    }
    case net::X509Certificate::kPublicKeyTypeECDSA: {
      base::Value::Dict result;
      BuildWebCryptoEcdsaAlgorithmDictionary(key_info, &result);
      return result;
    }
    default:
      return std::nullopt;
  }
}

void BuildWebCryptoRSAAlgorithmDictionary(const PublicKeyInfo& key_info,
                                          base::Value::Dict* algorithm) {
  CHECK_EQ(net::X509Certificate::kPublicKeyTypeRSA, key_info.key_type);
  algorithm->Set("name", kWebCryptoRsassaPkcs1v15);
  algorithm->Set("modulusLength", static_cast<int>(key_info.key_size_bits));

  // Equals 65537.
  static constexpr uint8_t kDefaultPublicExponent[] = {0x01, 0x00, 0x01};
  algorithm->Set("publicExponent",
                 base::Value::BlobStorage(std::begin(kDefaultPublicExponent),
                                          std::end(kDefaultPublicExponent)));
}

void BuildWebCryptoEcdsaAlgorithmDictionary(const PublicKeyInfo& key_info,
                                            base::Value::Dict* algorithm) {
  CHECK_EQ(net::X509Certificate::kPublicKeyTypeECDSA, key_info.key_type);
  algorithm->Set("name", kWebCryptoEcdsa);

  // Only P-256 named curve is supported.
  algorithm->Set("namedCurve", kWebCryptoNamedCurveP256);
}

ClientCertificateRequest::ClientCertificateRequest() = default;

ClientCertificateRequest::ClientCertificateRequest(
    const ClientCertificateRequest& other) = default;

ClientCertificateRequest::~ClientCertificateRequest() = default;

}  // namespace chromeos::platform_keys