blob: a68a7106e6569217a004a50833dec1444ba03002 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "crypto/unexportable_key_win.h"
#include <string>
#include <tuple>
#include <vector>
#include "base/base64.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/scoped_thread_priority.h"
#include "base/types/expected.h"
#include "base/types/optional_util.h"
#include "crypto/features.h"
#include "crypto/hash.h"
#include "crypto/random.h"
#include "crypto/unexportable_key.h"
#include "crypto/unexportable_key_metrics.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.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/ecdsa.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
namespace crypto {
namespace {
const char kMetricVirtualCreateKeyError[] = "Crypto.TpmError.VirtualCreateKey";
const char kMetricVirtualFinalizeKeyError[] =
"Crypto.TpmError.VirtualFinalizeKey";
const char kMetricVirtualOpenKeyError[] = "Crypto.TpmError.VirtualOpenKey";
const char kMetricVirtualOpenStorageError[] =
"Crypto.TpmError.VirtualOpenStorage";
enum class ProviderType {
// Keys will be backed by a TPM. Requires TPM support.
kTPM,
// Keys will be backed by software. Widely available.
kSoftware
};
LPCWSTR GetWindowsIdentifierForProvider(ProviderType type) {
switch (type) {
case ProviderType::kTPM:
return MS_PLATFORM_CRYPTO_PROVIDER;
case ProviderType::kSoftware:
return MS_KEY_STORAGE_PROVIDER;
}
}
std::u16string KeyIdToWindowsLabel(base::span<const uint8_t> key_id) {
return u"unexportable-key-" + base::UTF8ToUTF16(base::Base64Encode(key_id));
}
// Logs `status` and `selected_algorithm` to an error histogram capturing that
// `operation` failed for a TPM-backed key.
void LogTPMOperationError(
TPMOperation operation,
SECURITY_STATUS status,
std::optional<SignatureVerifier::SignatureAlgorithm> selected_algorithm,
bool open_storage_provider_error = false) {
static constexpr char kCreateKeyErrorStatusHistogramFormat[] =
"Crypto.TPMOperation.Win.%s%s.Error";
// There are two cases that can be recorded without a `selected_algorithm`:
// 1- OpenStorageProvider errors because these happen before an algorithm
// is chosen.
// 2- Errors during `kWrappedKeyCreation` TPM operation.
if (!open_storage_provider_error) {
CHECK_EQ(!selected_algorithm.has_value(),
operation == TPMOperation::kWrappedKeyCreation);
}
std::string algorithm_string =
selected_algorithm ? AlgorithmToString(*selected_algorithm) : "";
base::UmaHistogramSparse(
base::StringPrintf(kCreateKeyErrorStatusHistogramFormat,
OperationToString(operation).c_str(),
algorithm_string.c_str()),
status);
}
std::vector<uint8_t> CBBToVector(const CBB* cbb) {
return std::vector<uint8_t>(CBB_data(cbb), CBB_data(cbb) + CBB_len(cbb));
}
// BCryptAlgorithmFor returns the BCrypt algorithm ID for the given Chromium
// signing algorithm.
std::optional<LPCWSTR> BCryptAlgorithmFor(
SignatureVerifier::SignatureAlgorithm algo) {
switch (algo) {
case SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256:
return BCRYPT_RSA_ALGORITHM;
case SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256:
return BCRYPT_ECDSA_P256_ALGORITHM;
default:
return std::nullopt;
}
}
// GetBestSupported returns the first element of |acceptable_algorithms| that
// |provider| supports, or |nullopt| if there isn't any.
std::optional<SignatureVerifier::SignatureAlgorithm> GetBestSupported(
NCRYPT_PROV_HANDLE provider,
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms) {
for (auto algo : acceptable_algorithms) {
std::optional<LPCWSTR> bcrypto_algo_name = BCryptAlgorithmFor(algo);
if (!bcrypto_algo_name) {
continue;
}
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
if (!FAILED(NCryptIsAlgSupported(provider, *bcrypto_algo_name,
/*flags=*/0))) {
return algo;
}
}
return std::nullopt;
}
// GetKeyProperty returns the given NCrypt key property of |key|.
std::optional<std::vector<uint8_t>> GetKeyProperty(NCRYPT_KEY_HANDLE key,
LPCWSTR property) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
DWORD size;
if (FAILED(NCryptGetProperty(key, property, nullptr, 0, &size, 0))) {
return std::nullopt;
}
std::vector<uint8_t> ret(size);
if (FAILED(
NCryptGetProperty(key, property, ret.data(), ret.size(), &size, 0))) {
return std::nullopt;
}
CHECK_EQ(ret.size(), size);
return ret;
}
// ExportKey returns |key| exported in the given format or nullopt on error.
base::expected<std::vector<uint8_t>, SECURITY_STATUS> ExportKey(
NCRYPT_KEY_HANDLE key,
LPCWSTR format) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
DWORD output_size;
SECURITY_STATUS status =
NCryptExportKey(key, 0, format, nullptr, nullptr, 0, &output_size, 0);
if (FAILED(status)) {
return base::unexpected(status);
}
std::vector<uint8_t> output(output_size);
status = NCryptExportKey(key, 0, format, nullptr, output.data(),
output.size(), &output_size, 0);
if (FAILED(status)) {
return base::unexpected(status);
}
CHECK_EQ(output.size(), output_size);
return output;
}
std::optional<std::vector<uint8_t>> GetP256ECDSASPKI(NCRYPT_KEY_HANDLE key) {
const base::expected<std::vector<uint8_t>, SECURITY_STATUS> pub_key =
ExportKey(key, BCRYPT_ECCPUBLIC_BLOB);
if (!pub_key.has_value()) {
return std::nullopt;
}
// The exported key is a |BCRYPT_ECCKEY_BLOB| followed by the bytes of the
// public key itself.
// https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_ecckey_blob
BCRYPT_ECCKEY_BLOB header;
if (pub_key->size() < sizeof(header)) {
return std::nullopt;
}
memcpy(&header, pub_key->data(), sizeof(header));
// |cbKey| is documented[1] as "the length, in bytes, of the key". It is
// not. For ECDSA public keys it is the length of a field element.
if ((header.dwMagic != BCRYPT_ECDSA_PUBLIC_P256_MAGIC &&
header.dwMagic != BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC) ||
header.cbKey != 256 / 8 ||
pub_key->size() - sizeof(BCRYPT_ECCKEY_BLOB) != 64) {
return std::nullopt;
}
// Sometimes NCrypt will return a generic dwMagic even when asked for a P-256
// key. In that case, do extra validation to make sure that `key` is in fact
// a P-256 key.
if (header.dwMagic == BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC) {
const std::optional<std::vector<uint8_t>> curve_name =
GetKeyProperty(key, NCRYPT_ECC_CURVE_NAME_PROPERTY);
if (!curve_name) {
return std::nullopt;
}
if (curve_name->size() != sizeof(BCRYPT_ECC_CURVE_NISTP256) ||
memcmp(curve_name->data(), BCRYPT_ECC_CURVE_NISTP256,
sizeof(BCRYPT_ECC_CURVE_NISTP256)) != 0) {
return std::nullopt;
}
}
uint8_t x962[1 + 32 + 32];
x962[0] = POINT_CONVERSION_UNCOMPRESSED;
memcpy(&x962[1], pub_key->data() + sizeof(BCRYPT_ECCKEY_BLOB), 64);
bssl::UniquePtr<EC_GROUP> p256(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get()));
if (!EC_POINT_oct2point(p256.get(), point.get(), x962, sizeof(x962),
/*ctx=*/nullptr)) {
return std::nullopt;
}
bssl::UniquePtr<EC_KEY> ec_key(
EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
CHECK(EC_KEY_set_public_key(ec_key.get(), point.get()));
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
CHECK(EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get()));
bssl::ScopedCBB cbb;
CHECK(CBB_init(cbb.get(), /*initial_capacity=*/128) &&
EVP_marshal_public_key(cbb.get(), pkey.get()));
return CBBToVector(cbb.get());
}
std::optional<std::vector<uint8_t>> GetRSASPKI(NCRYPT_KEY_HANDLE key) {
const base::expected<std::vector<uint8_t>, SECURITY_STATUS> pub_key =
ExportKey(key, BCRYPT_RSAPUBLIC_BLOB);
if (!pub_key.has_value()) {
return std::nullopt;
}
// The exported key is a |BCRYPT_RSAKEY_BLOB| followed by the bytes of the
// key itself.
// https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob
BCRYPT_RSAKEY_BLOB header;
if (pub_key->size() < sizeof(header)) {
return std::nullopt;
}
memcpy(&header, pub_key->data(), sizeof(header));
if (header.Magic != static_cast<ULONG>(BCRYPT_RSAPUBLIC_MAGIC)) {
return std::nullopt;
}
size_t bytes_needed;
if (!base::CheckAdd(sizeof(BCRYPT_RSAKEY_BLOB),
base::CheckAdd(header.cbPublicExp, header.cbModulus))
.AssignIfValid(&bytes_needed) ||
pub_key->size() < bytes_needed) {
return std::nullopt;
}
bssl::UniquePtr<BIGNUM> e(
BN_bin2bn(&pub_key->data()[sizeof(BCRYPT_RSAKEY_BLOB)],
header.cbPublicExp, nullptr));
bssl::UniquePtr<BIGNUM> n(BN_bin2bn(
&pub_key->data()[sizeof(BCRYPT_RSAKEY_BLOB) + header.cbPublicExp],
header.cbModulus, nullptr));
bssl::UniquePtr<RSA> rsa(RSA_new());
CHECK(RSA_set0_key(rsa.get(), n.release(), e.release(), nullptr));
bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
CHECK(EVP_PKEY_set1_RSA(pkey.get(), rsa.get()));
bssl::ScopedCBB cbb;
CHECK(CBB_init(cbb.get(), /*initial_capacity=*/384) &&
EVP_marshal_public_key(cbb.get(), pkey.get()));
return CBBToVector(cbb.get());
}
base::expected<std::vector<uint8_t>, SECURITY_STATUS> SignECDSA(
NCRYPT_KEY_HANDLE key,
base::span<const uint8_t> data) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
std::array<uint8_t, hash::kSha256Size> digest = hash::Sha256(data);
// The signature is written as a pair of big-endian field elements for P-256
// ECDSA.
std::vector<uint8_t> sig(64);
DWORD sig_size;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS status =
NCryptSignHash(key, nullptr, digest.data(), digest.size(), sig.data(),
sig.size(), &sig_size, NCRYPT_SILENT_FLAG);
if (FAILED(status)) {
return base::unexpected(status);
}
}
CHECK_EQ(sig.size(), sig_size);
bssl::UniquePtr<BIGNUM> r(BN_bin2bn(sig.data(), 32, nullptr));
bssl::UniquePtr<BIGNUM> s(BN_bin2bn(sig.data() + 32, 32, nullptr));
ECDSA_SIG sig_st;
sig_st.r = r.get();
sig_st.s = s.get();
bssl::ScopedCBB cbb;
CHECK(CBB_init(cbb.get(), /*initial_capacity=*/72) &&
ECDSA_SIG_marshal(cbb.get(), &sig_st));
return CBBToVector(cbb.get());
}
base::expected<std::vector<uint8_t>, SECURITY_STATUS> SignRSA(
NCRYPT_KEY_HANDLE key,
base::span<const uint8_t> data) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
std::array<uint8_t, hash::kSha256Size> digest = hash::Sha256(data);
BCRYPT_PKCS1_PADDING_INFO padding_info = {0};
padding_info.pszAlgId = NCRYPT_SHA256_ALGORITHM;
DWORD sig_size;
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS status =
NCryptSignHash(key, &padding_info, digest.data(), digest.size(), nullptr,
0, &sig_size, NCRYPT_SILENT_FLAG | BCRYPT_PAD_PKCS1);
if (FAILED(status)) {
return base::unexpected(status);
}
std::vector<uint8_t> sig(sig_size);
status = NCryptSignHash(key, &padding_info, digest.data(), digest.size(),
sig.data(), sig.size(), &sig_size,
NCRYPT_SILENT_FLAG | BCRYPT_PAD_PKCS1);
if (FAILED(status)) {
return base::unexpected(status);
}
CHECK_EQ(sig.size(), sig_size);
return sig;
}
ScopedNCryptKey LoadWrappedKey(base::span<const uint8_t> wrapped,
ProviderType provider_type) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
ScopedNCryptProvider provider;
SECURITY_STATUS status =
NCryptOpenStorageProvider(ScopedNCryptProvider::Receiver(provider).get(),
GetWindowsIdentifierForProvider(provider_type),
/*flags=*/0);
if (FAILED(status)) {
LogTPMOperationError(TPMOperation::kWrappedKeyCreation, status,
std::nullopt, /*open_storage_provider_error=*/true);
return ScopedNCryptKey();
}
ScopedNCryptKey key;
SECURITY_STATUS import_status = -1;
if (provider_type == ProviderType::kSoftware) {
// Software keys are labelled with a random identifier. Attempt to obtain a
// handle from the identifier.
std::u16string key_label = KeyIdToWindowsLabel(wrapped);
import_status =
NCryptOpenKey(provider.get(), ScopedNCryptKey::Receiver(key).get(),
base::as_wcstr(key_label),
/*dwLegacyKeySpec=*/0, /*dwFlags=*/0);
} else {
// TPM keys use an undocumented Windows feature to export a wrapped key.
// Attempt to obtain a handle from the wrapped key.
import_status = NCryptImportKey(
provider.get(), /*hImportKey=*/NULL, BCRYPT_OPAQUE_KEY_BLOB,
/*pParameterList=*/nullptr, ScopedNCryptKey::Receiver(key).get(),
const_cast<PBYTE>(wrapped.data()), wrapped.size(),
/*dwFlags=*/NCRYPT_SILENT_FLAG);
}
if (FAILED(import_status)) {
LogTPMOperationError(TPMOperation::kWrappedKeyCreation, import_status,
std::nullopt);
return ScopedNCryptKey();
}
return key;
}
// ECDSAKey wraps a P-256 ECDSA key stored in the given provider.
class ECDSAKey : public UnexportableSigningKey {
public:
ECDSAKey(ProviderType provider_type,
ScopedNCryptKey key,
std::vector<uint8_t> key_id,
std::vector<uint8_t> spki)
: provider_type_(provider_type),
key_(std::move(key)),
key_id_(std::move(key_id)),
spki_(std::move(spki)) {}
SignatureVerifier::SignatureAlgorithm Algorithm() const override {
return SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256;
}
std::vector<uint8_t> GetSubjectPublicKeyInfo() const override {
return spki_;
}
std::vector<uint8_t> GetWrappedKey() const override { return key_id_; }
std::optional<std::vector<uint8_t>> SignSlowly(
base::span<const uint8_t> data) override {
base::expected<std::vector<uint8_t>, SECURITY_STATUS> signature =
SignECDSA(key_.get(), data);
if (!signature.has_value()) {
LogTPMOperationError(TPMOperation::kMessageSigning, signature.error(),
Algorithm());
}
return base::OptionalFromExpected(signature);
}
bool IsHardwareBacked() const override {
return base::FeatureList::IsEnabled(features::kIsHardwareBackedFixEnabled)
? provider_type_ == ProviderType::kTPM
: true;
}
private:
const ProviderType provider_type_;
ScopedNCryptKey key_;
const std::vector<uint8_t> key_id_;
const std::vector<uint8_t> spki_;
};
// RSAKey wraps a RSA key stored in the given provider.
class RSAKey : public UnexportableSigningKey {
public:
RSAKey(ProviderType provider_type,
ScopedNCryptKey key,
std::vector<uint8_t> wrapped,
std::vector<uint8_t> spki)
: provider_type_(provider_type),
key_(std::move(key)),
wrapped_(std::move(wrapped)),
spki_(std::move(spki)) {}
SignatureVerifier::SignatureAlgorithm Algorithm() const override {
return SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256;
}
std::vector<uint8_t> GetSubjectPublicKeyInfo() const override {
return spki_;
}
std::vector<uint8_t> GetWrappedKey() const override { return wrapped_; }
std::optional<std::vector<uint8_t>> SignSlowly(
base::span<const uint8_t> data) override {
base::expected<std::vector<uint8_t>, SECURITY_STATUS> signature =
SignRSA(key_.get(), data);
if (!signature.has_value()) {
LogTPMOperationError(TPMOperation::kMessageSigning, signature.error(),
Algorithm());
}
return base::OptionalFromExpected(signature);
}
bool IsHardwareBacked() const override {
return base::FeatureList::IsEnabled(features::kIsHardwareBackedFixEnabled)
? provider_type_ == ProviderType::kTPM
: true;
}
private:
const ProviderType provider_type_;
ScopedNCryptKey key_;
const std::vector<uint8_t> wrapped_;
const std::vector<uint8_t> spki_;
};
// UnexportableKeyProviderWin uses NCrypt and the Platform Crypto
// Provider to expose TPM-backed keys on Windows.
class UnexportableKeyProviderWin : public UnexportableKeyProvider {
public:
explicit UnexportableKeyProviderWin(ProviderType provider_type)
: provider_type_(provider_type) {}
~UnexportableKeyProviderWin() override = default;
std::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms) override {
ScopedNCryptProvider provider;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
if (FAILED(NCryptOpenStorageProvider(
ScopedNCryptProvider::Receiver(provider).get(),
GetWindowsIdentifierForProvider(provider_type_), /*flags=*/0))) {
return std::nullopt;
}
}
return GetBestSupported(provider.get(), acceptable_algorithms);
}
std::unique_ptr<UnexportableSigningKey> GenerateSigningKeySlowly(
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
ScopedNCryptProvider provider;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS status = NCryptOpenStorageProvider(
ScopedNCryptProvider::Receiver(provider).get(),
GetWindowsIdentifierForProvider(provider_type_), /*flags=*/0);
if (FAILED(status)) {
LogTPMOperationError(TPMOperation::kNewKeyCreation, status,
std::nullopt,
/*open_storage_provider_error=*/true);
return nullptr;
}
}
std::optional<SignatureVerifier::SignatureAlgorithm> algo =
GetBestSupported(provider.get(), acceptable_algorithms);
if (!algo) {
return nullptr;
}
std::vector<uint8_t> key_id;
ScopedNCryptKey key;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS creation_status;
if (provider_type_ == ProviderType::kSoftware) {
// Windows support for wrapped keys is undocumented, and doesn't seem to
// work for the software backend. The API wants Chrome to provide a
// label for the key, so we assign one randomly.
key_id = crypto::RandBytesAsVector(16);
std::u16string key_label = KeyIdToWindowsLabel(key_id);
creation_status = NCryptCreatePersistedKey(
provider.get(), ScopedNCryptKey::Receiver(key).get(),
BCryptAlgorithmFor(*algo).value(), base::as_wcstr(key_label),
/*dwLegacyKeySpec=*/0, /*dwFlags=*/0);
} else {
// An empty key name stops the key being persisted to disk.
// TODO(crbug.com/398125799): assign labels to these keys instead.
creation_status = NCryptCreatePersistedKey(
provider.get(), ScopedNCryptKey::Receiver(key).get(),
BCryptAlgorithmFor(*algo).value(),
/*pszKeyName=*/nullptr,
/*dwLegacyKeySpec=*/0, /*dwFlags=*/0);
}
if (FAILED(creation_status)) {
LogTPMOperationError(TPMOperation::kNewKeyCreation, creation_status,
algo);
return nullptr;
}
if (FAILED(NCryptFinalizeKey(key.get(), NCRYPT_SILENT_FLAG))) {
return nullptr;
}
}
if (provider_type_ == ProviderType::kTPM) {
base::expected<std::vector<uint8_t>, SECURITY_STATUS> wrapped_key =
ExportKey(key.get(), BCRYPT_OPAQUE_KEY_BLOB);
if (!wrapped_key.has_value()) {
LogTPMOperationError(TPMOperation::kWrappedKeyExport,
wrapped_key.error(), algo);
return nullptr;
}
key_id = std::move(wrapped_key.value());
}
std::optional<std::vector<uint8_t>> spki;
switch (*algo) {
case SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256:
spki = GetP256ECDSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<ECDSAKey>(provider_type_, std::move(key),
std::move(key_id),
std::move(spki.value()));
case SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256:
spki = GetRSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<RSAKey>(provider_type_, std::move(key),
std::move(key_id),
std::move(spki.value()));
default:
return nullptr;
}
}
std::unique_ptr<UnexportableSigningKey> FromWrappedSigningKeySlowly(
base::span<const uint8_t> wrapped) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
ScopedNCryptKey key = LoadWrappedKey(wrapped, provider_type_);
if (!key.is_valid()) {
return nullptr;
}
const std::optional<std::vector<uint8_t>> algo_bytes =
GetKeyProperty(key.get(), NCRYPT_ALGORITHM_PROPERTY);
if (!algo_bytes) {
return nullptr;
}
// The documentation suggests that |NCRYPT_ALGORITHM_PROPERTY| should return
// the original algorithm, i.e. |BCRYPT_ECDSA_P256_ALGORITHM| for ECDSA. But
// it actually returns just "ECDSA" for keys backed by the TPM.
// Note that these intentionally include the NUL terminator, since they're
// comparing against a c-style string that happens to be represented as an
// std::vector.
static constexpr wchar_t kECDSA[] = L"ECDSA";
static const base::span<const uint8_t> kECDSA_TPM =
base::as_byte_span(kECDSA);
static const base::span<const uint8_t> kECDSA_Software =
base::as_byte_span(BCRYPT_ECDSA_P256_ALGORITHM);
static const base::span<const uint8_t> kRSA =
base::as_byte_span(BCRYPT_RSA_ALGORITHM);
std::optional<std::vector<uint8_t>> spki;
if (algo_bytes == kECDSA_Software || algo_bytes == kECDSA_TPM) {
spki = GetP256ECDSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<ECDSAKey>(
provider_type_, std::move(key),
std::vector<uint8_t>(wrapped.begin(), wrapped.end()),
std::move(spki.value()));
} else if (algo_bytes == kRSA) {
spki = GetRSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<RSAKey>(
provider_type_, std::move(key),
std::vector<uint8_t>(wrapped.begin(), wrapped.end()),
std::move(spki.value()));
}
return nullptr;
}
StatefulUnexportableKeyProvider* AsStatefulUnexportableKeyProvider()
override {
// Unexportable keys are stateless on Windows.
return nullptr;
}
private:
ProviderType provider_type_;
};
// ECDSASoftwareKey wraps a Credential Guard stored P-256 ECDSA key.
class ECDSASoftwareKey : public VirtualUnexportableSigningKey {
public:
ECDSASoftwareKey(ScopedNCryptKey key,
std::string name,
std::vector<uint8_t> spki)
: key_(std::move(key)), name_(std::move(name)), spki_(std::move(spki)) {}
SignatureVerifier::SignatureAlgorithm Algorithm() const override {
return SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256;
}
std::vector<uint8_t> GetSubjectPublicKeyInfo() const override {
return spki_;
}
std::string GetKeyName() const override { return name_; }
std::optional<std::vector<uint8_t>> Sign(
base::span<const uint8_t> data) override {
if (!key_.is_valid()) {
return std::nullopt;
}
return base::OptionalFromExpected(SignECDSA(key_.get(), data));
}
void DeleteKey() override {
if (!key_.is_valid()) {
return;
}
// If key deletion succeeds, NCryptDeleteKey frees the key. To avoid double
// free, we need to release the key from the ScopedNCryptKey RAII object.
// Key deletion can fail in circumstances which are not under the
// application's control. For these cases, ScopedNCrypt key should free the
// key.
if (NCryptDeleteKey(key_.get(), NCRYPT_SILENT_FLAG) == ERROR_SUCCESS) {
static_cast<void>(key_.release());
}
}
private:
ScopedNCryptKey key_;
const std::string name_;
const std::vector<uint8_t> spki_;
};
// RSASoftwareKey wraps a Credential Guard stored RSA key.
class RSASoftwareKey : public VirtualUnexportableSigningKey {
public:
RSASoftwareKey(ScopedNCryptKey key,
std::string name,
std::vector<uint8_t> spki)
: key_(std::move(key)), name_(std::move(name)), spki_(std::move(spki)) {}
SignatureVerifier::SignatureAlgorithm Algorithm() const override {
return SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256;
}
std::vector<uint8_t> GetSubjectPublicKeyInfo() const override {
return spki_;
}
std::string GetKeyName() const override { return name_; }
std::optional<std::vector<uint8_t>> Sign(
base::span<const uint8_t> data) override {
if (!key_.is_valid()) {
return std::nullopt;
}
return base::OptionalFromExpected(SignRSA(key_.get(), data));
}
void DeleteKey() override {
if (!key_.is_valid()) {
return;
}
// If key deletion succeeds, NCryptDeleteKey frees the key. To avoid double
// free, we need to release the key from the ScopedNCryptKey RAII object.
// Key deletion can fail in circumstances which are not under the
// application's control. For these cases, ScopedNCrypt key should free the
// key.
if (NCryptDeleteKey(key_.get(), NCRYPT_SILENT_FLAG) == ERROR_SUCCESS) {
static_cast<void>(key_.release());
}
}
private:
ScopedNCryptKey key_;
std::string name_;
const std::vector<uint8_t> spki_;
};
// UnexportableKeyProviderWin uses NCrypt and the Platform Crypto
// Provider to expose Credential Guard backed keys on Windows.
class VirtualUnexportableKeyProviderWin
: public VirtualUnexportableKeyProvider {
public:
~VirtualUnexportableKeyProviderWin() override = default;
std::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms) override {
ScopedNCryptProvider provider;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS status = NCryptOpenStorageProvider(
ScopedNCryptProvider::Receiver(provider).get(),
MS_KEY_STORAGE_PROVIDER, /*dwFlags=*/0);
if (FAILED(status)) {
base::UmaHistogramSparse(kMetricVirtualOpenStorageError, status);
return std::nullopt;
}
}
return GetBestSupported(provider.get(), acceptable_algorithms);
}
std::unique_ptr<VirtualUnexportableSigningKey> GenerateSigningKey(
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms,
std::string name) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
ScopedNCryptProvider provider;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS status = NCryptOpenStorageProvider(
ScopedNCryptProvider::Receiver(provider).get(),
MS_KEY_STORAGE_PROVIDER, /*dwFlags=*/0);
if (FAILED(status)) {
base::UmaHistogramSparse(kMetricVirtualOpenStorageError, status);
return nullptr;
}
}
std::optional<SignatureVerifier::SignatureAlgorithm> algo =
GetBestSupported(provider.get(), acceptable_algorithms);
if (!algo) {
return nullptr;
}
ScopedNCryptKey key;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
// An empty key name stops the key being persisted to disk.
SECURITY_STATUS status = NCryptCreatePersistedKey(
provider.get(), ScopedNCryptKey::Receiver(key).get(),
BCryptAlgorithmFor(*algo).value(), base::SysUTF8ToWide(name).c_str(),
/*dwLegacyKeySpec=*/0,
/*dwFlags=*/NCRYPT_USE_VIRTUAL_ISOLATION_FLAG);
if (FAILED(status)) {
base::UmaHistogramSparse(kMetricVirtualCreateKeyError, status);
return nullptr;
}
status = NCryptFinalizeKey(
key.get(), NCRYPT_PROTECT_TO_LOCAL_SYSTEM | NCRYPT_SILENT_FLAG);
if (FAILED(status)) {
base::UmaHistogramSparse(kMetricVirtualFinalizeKeyError, status);
return nullptr;
}
}
std::optional<std::vector<uint8_t>> spki;
switch (*algo) {
case SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256:
spki = GetP256ECDSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<ECDSASoftwareKey>(std::move(key), name,
std::move(spki.value()));
case SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256:
spki = GetRSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<RSASoftwareKey>(std::move(key), name,
std::move(spki.value()));
default:
return nullptr;
}
}
std::unique_ptr<VirtualUnexportableSigningKey> FromKeyName(
std::string name) override {
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::WILL_BLOCK);
ScopedNCryptProvider provider;
ScopedNCryptKey key;
{
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
SECURITY_STATUS status = NCryptOpenStorageProvider(
ScopedNCryptProvider::Receiver(provider).get(),
MS_KEY_STORAGE_PROVIDER, /*dwFlags=*/0);
if (FAILED(status)) {
base::UmaHistogramSparse(kMetricVirtualOpenStorageError, status);
return nullptr;
}
status = NCryptOpenKey(
provider.get(), ScopedNCryptKey::Receiver(key).get(),
base::SysUTF8ToWide(name).c_str(), /*dwLegacyKeySpec=*/0,
/*dwFlags*/ 0);
if (FAILED(status)) {
base::UmaHistogramSparse(kMetricVirtualOpenKeyError, status);
return nullptr;
}
}
const std::optional<std::vector<uint8_t>> algo_bytes =
GetKeyProperty(key.get(), NCRYPT_ALGORITHM_PROPERTY);
// This is the expected behavior, but note it is different from TPM backed
// keys.
// Note that these intentionally include the NUL terminator, since they're
// comparing against a c-style string that happens to be represented as an
// std::vector.
static const base::span<const uint8_t> kECDSA_Software =
base::as_byte_span(BCRYPT_ECDSA_P256_ALGORITHM);
static const base::span<const uint8_t> kRSA =
base::as_byte_span(BCRYPT_RSA_ALGORITHM);
std::optional<std::vector<uint8_t>> spki;
if (algo_bytes == kECDSA_Software) {
spki = GetP256ECDSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<ECDSASoftwareKey>(std::move(key), name,
std::move(spki.value()));
} else if (algo_bytes == kRSA) {
spki = GetRSASPKI(key.get());
if (!spki) {
return nullptr;
}
return std::make_unique<RSASoftwareKey>(std::move(key), name,
std::move(spki.value()));
}
return nullptr;
}
};
} // namespace
ScopedNCryptKey DuplicatePlatformKeyHandle(const UnexportableSigningKey& key) {
return LoadWrappedKey(key.GetWrappedKey(), key.IsHardwareBacked()
? ProviderType::kTPM
: ProviderType::kSoftware);
}
std::unique_ptr<UnexportableKeyProvider> GetUnexportableKeyProviderWin() {
return std::make_unique<UnexportableKeyProviderWin>(ProviderType::kTPM);
}
std::unique_ptr<UnexportableKeyProvider>
GetMicrosoftSoftwareUnexportableKeyProviderWin() {
return std::make_unique<UnexportableKeyProviderWin>(ProviderType::kSoftware);
}
std::unique_ptr<VirtualUnexportableKeyProvider>
GetVirtualUnexportableKeyProviderWin() {
return std::make_unique<VirtualUnexportableKeyProviderWin>();
}
} // namespace crypto