mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-23 22:06:12 +03:00
501 lines
17 KiB
C++
501 lines
17 KiB
C++
// Copyright (c) 2021 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include <windows.h>
|
|
|
|
#include <ncrypt.h>
|
|
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include "base/logging.h"
|
|
#include "base/numerics/checked_math.h"
|
|
#include "base/scoped_generic.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/strings/sys_string_conversions.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "base/threading/scoped_blocking_call.h"
|
|
#include "crypto/random.h"
|
|
#include "crypto/sha2.h"
|
|
#include "crypto/unexportable_key.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 {
|
|
|
|
// NCrypt has a style of returning handles by writing opaque pointers to
|
|
// caller-provided locations. These pointers must be passed to
|
|
// |NCryptFreeObject| when no longer needed.
|
|
template <typename T>
|
|
struct NCryptObjectTraits {
|
|
// In practice a value of zero makes |NCryptFreeObject| a no-op, but this
|
|
// isn't specified by the documentation so the code below avoids depending on
|
|
// this by releasing() values that were never initialised.
|
|
static T InvalidValue() { return 0; }
|
|
static void Free(T handle) { NCryptFreeObject(handle); }
|
|
};
|
|
|
|
using ScopedProvider =
|
|
base::ScopedGeneric<NCRYPT_PROV_HANDLE,
|
|
NCryptObjectTraits<NCRYPT_PROV_HANDLE>>;
|
|
using ScopedKey = base::ScopedGeneric<NCRYPT_KEY_HANDLE,
|
|
NCryptObjectTraits<NCRYPT_KEY_HANDLE>>;
|
|
|
|
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.
|
|
absl::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 absl::nullopt;
|
|
}
|
|
}
|
|
|
|
// GetBestSupported returns the first element of |acceptable_algorithms| that
|
|
// |provider| supports, or |nullopt| if there isn't any.
|
|
absl::optional<SignatureVerifier::SignatureAlgorithm> GetBestSupported(
|
|
NCRYPT_PROV_HANDLE provider,
|
|
base::span<const SignatureVerifier::SignatureAlgorithm>
|
|
acceptable_algorithms) {
|
|
for (auto algo : acceptable_algorithms) {
|
|
absl::optional<LPCWSTR> bcrypto_algo_name = BCryptAlgorithmFor(algo);
|
|
if (!bcrypto_algo_name) {
|
|
continue;
|
|
}
|
|
|
|
if (!FAILED(NCryptIsAlgSupported(provider, *bcrypto_algo_name,
|
|
/*flags=*/0))) {
|
|
return algo;
|
|
}
|
|
}
|
|
|
|
return absl::nullopt;
|
|
}
|
|
|
|
// GetKeyProperty returns the given NCrypt key property of |key|.
|
|
absl::optional<std::vector<uint8_t>> GetKeyProperty(NCRYPT_KEY_HANDLE key,
|
|
LPCWSTR property) {
|
|
DWORD size;
|
|
if (FAILED(NCryptGetProperty(key, property, nullptr, 0, &size, 0))) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
std::vector<uint8_t> ret(size);
|
|
if (FAILED(
|
|
NCryptGetProperty(key, property, ret.data(), ret.size(), &size, 0))) {
|
|
return absl::nullopt;
|
|
}
|
|
CHECK_EQ(ret.size(), size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// ExportKey returns |key| exported in the given format or nullopt on error.
|
|
absl::optional<std::vector<uint8_t>> ExportKey(NCRYPT_KEY_HANDLE key,
|
|
LPCWSTR format) {
|
|
DWORD output_size;
|
|
if (FAILED(NCryptExportKey(key, 0, format, nullptr, nullptr, 0, &output_size,
|
|
0))) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
std::vector<uint8_t> output(output_size);
|
|
if (FAILED(NCryptExportKey(key, 0, format, nullptr, output.data(),
|
|
output.size(), &output_size, 0))) {
|
|
return absl::nullopt;
|
|
}
|
|
CHECK_EQ(output.size(), output_size);
|
|
|
|
return output;
|
|
}
|
|
|
|
absl::optional<std::vector<uint8_t>> GetP256ECDSASPKI(NCRYPT_KEY_HANDLE key) {
|
|
const absl::optional<std::vector<uint8_t>> pub_key =
|
|
ExportKey(key, BCRYPT_ECCPUBLIC_BLOB);
|
|
if (!pub_key) {
|
|
return absl::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 absl::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.cbKey != 256 / 8 ||
|
|
pub_key->size() - sizeof(BCRYPT_ECCKEY_BLOB) != 64) {
|
|
return absl::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 absl::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());
|
|
}
|
|
|
|
absl::optional<std::vector<uint8_t>> GetRSASPKI(NCRYPT_KEY_HANDLE key) {
|
|
const absl::optional<std::vector<uint8_t>> pub_key =
|
|
ExportKey(key, BCRYPT_RSAPUBLIC_BLOB);
|
|
if (!pub_key) {
|
|
return absl::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 absl::nullopt;
|
|
}
|
|
memcpy(&header, pub_key->data(), sizeof(header));
|
|
if (header.Magic != static_cast<ULONG>(BCRYPT_RSAPUBLIC_MAGIC)) {
|
|
return absl::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 absl::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());
|
|
}
|
|
|
|
// ECDSAKey wraps a TPM-stored P-256 ECDSA key.
|
|
class ECDSAKey : public UnexportableSigningKey {
|
|
public:
|
|
ECDSAKey(ScopedProvider provider,
|
|
ScopedKey key,
|
|
std::vector<uint8_t> wrapped,
|
|
std::vector<uint8_t> spki)
|
|
: provider_(std::move(provider)),
|
|
key_(std::move(key)),
|
|
wrapped_(std::move(wrapped)),
|
|
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 wrapped_; }
|
|
|
|
absl::optional<std::vector<uint8_t>> SignSlowly(
|
|
base::span<const uint8_t> data) override {
|
|
base::ScopedBlockingCall scoped_blocking_call(
|
|
FROM_HERE, base::BlockingType::WILL_BLOCK);
|
|
|
|
std::array<uint8_t, kSHA256Length> digest = SHA256Hash(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;
|
|
if (FAILED(NCryptSignHash(key_.get(), nullptr, digest.data(), digest.size(),
|
|
sig.data(), sig.size(), &sig_size,
|
|
NCRYPT_SILENT_FLAG))) {
|
|
return absl::nullopt;
|
|
}
|
|
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());
|
|
}
|
|
|
|
private:
|
|
ScopedProvider provider_;
|
|
ScopedKey key_;
|
|
const std::vector<uint8_t> wrapped_;
|
|
const std::vector<uint8_t> spki_;
|
|
};
|
|
|
|
// RSAKey wraps a TPM-stored RSA key.
|
|
class RSAKey : public UnexportableSigningKey {
|
|
public:
|
|
RSAKey(ScopedProvider provider,
|
|
ScopedKey key,
|
|
std::vector<uint8_t> wrapped,
|
|
std::vector<uint8_t> spki)
|
|
: provider_(std::move(provider)),
|
|
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_; }
|
|
|
|
absl::optional<std::vector<uint8_t>> SignSlowly(
|
|
base::span<const uint8_t> data) override {
|
|
base::ScopedBlockingCall scoped_blocking_call(
|
|
FROM_HERE, base::BlockingType::WILL_BLOCK);
|
|
|
|
std::array<uint8_t, kSHA256Length> digest = SHA256Hash(data);
|
|
BCRYPT_PKCS1_PADDING_INFO padding_info = {0};
|
|
padding_info.pszAlgId = NCRYPT_SHA256_ALGORITHM;
|
|
|
|
DWORD sig_size;
|
|
if (FAILED(NCryptSignHash(key_.get(), &padding_info, digest.data(),
|
|
digest.size(), nullptr, 0, &sig_size,
|
|
NCRYPT_SILENT_FLAG | BCRYPT_PAD_PKCS1))) {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
std::vector<uint8_t> sig(sig_size);
|
|
if (FAILED(NCryptSignHash(key_.get(), &padding_info, digest.data(),
|
|
digest.size(), sig.data(), sig.size(), &sig_size,
|
|
NCRYPT_SILENT_FLAG | BCRYPT_PAD_PKCS1))) {
|
|
return absl::nullopt;
|
|
}
|
|
CHECK_EQ(sig.size(), sig_size);
|
|
|
|
return sig;
|
|
}
|
|
|
|
private:
|
|
ScopedProvider provider_;
|
|
ScopedKey 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:
|
|
~UnexportableKeyProviderWin() override = default;
|
|
|
|
absl::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
|
|
base::span<const SignatureVerifier::SignatureAlgorithm>
|
|
acceptable_algorithms) override {
|
|
ScopedProvider provider;
|
|
if (FAILED(NCryptOpenStorageProvider(
|
|
ScopedProvider::Receiver(provider).get(),
|
|
MS_PLATFORM_CRYPTO_PROVIDER, /*flags=*/0))) {
|
|
// If the operation failed then |provider| doesn't have a valid handle in
|
|
// it and we shouldn't try to free it.
|
|
std::ignore = provider.release();
|
|
return absl::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);
|
|
|
|
ScopedProvider provider;
|
|
if (FAILED(NCryptOpenStorageProvider(
|
|
ScopedProvider::Receiver(provider).get(),
|
|
MS_PLATFORM_CRYPTO_PROVIDER, /*flags=*/0))) {
|
|
// If the operation failed when |provider| doesn't have a valid handle in
|
|
// it and we shouldn't try to free it.
|
|
std::ignore = provider.release();
|
|
return nullptr;
|
|
}
|
|
|
|
absl::optional<SignatureVerifier::SignatureAlgorithm> algo =
|
|
GetBestSupported(provider.get(), acceptable_algorithms);
|
|
if (!algo) {
|
|
return nullptr;
|
|
}
|
|
|
|
ScopedKey key;
|
|
// An empty key name stops the key being persisted to disk.
|
|
if (FAILED(NCryptCreatePersistedKey(
|
|
provider.get(), ScopedKey::Receiver(key).get(),
|
|
BCryptAlgorithmFor(*algo).value(), /*pszKeyName=*/nullptr,
|
|
/*dwLegacyKeySpec=*/0, /*dwFlags=*/0))) {
|
|
// If the operation failed then |key| doesn't have a valid handle in it
|
|
// and we shouldn't try and free it.
|
|
std::ignore = key.release();
|
|
return nullptr;
|
|
}
|
|
|
|
if (FAILED(NCryptFinalizeKey(key.get(), NCRYPT_SILENT_FLAG))) {
|
|
return nullptr;
|
|
}
|
|
|
|
const absl::optional<std::vector<uint8_t>> wrapped_key =
|
|
ExportKey(key.get(), BCRYPT_OPAQUE_KEY_BLOB);
|
|
if (!wrapped_key) {
|
|
return nullptr;
|
|
}
|
|
|
|
absl::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>(std::move(provider), std::move(key),
|
|
std::move(*wrapped_key),
|
|
std::move(spki.value()));
|
|
case SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256:
|
|
spki = GetRSASPKI(key.get());
|
|
if (!spki) {
|
|
return nullptr;
|
|
}
|
|
return std::make_unique<RSAKey>(std::move(provider), std::move(key),
|
|
std::move(*wrapped_key),
|
|
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);
|
|
|
|
ScopedProvider provider;
|
|
if (FAILED(NCryptOpenStorageProvider(
|
|
ScopedProvider::Receiver(provider).get(),
|
|
MS_PLATFORM_CRYPTO_PROVIDER, /*flags=*/0))) {
|
|
// If the operation failed when |provider| doesn't have a valid handle in
|
|
// it and we shouldn't try to free it.
|
|
std::ignore = provider.release();
|
|
return nullptr;
|
|
}
|
|
|
|
ScopedKey key;
|
|
if (FAILED(NCryptImportKey(
|
|
provider.get(), /*hImportKey=*/NULL, BCRYPT_OPAQUE_KEY_BLOB,
|
|
/*pParameterList=*/nullptr, ScopedKey::Receiver(key).get(),
|
|
const_cast<PBYTE>(wrapped.data()), wrapped.size(),
|
|
/*dwFlags=*/NCRYPT_SILENT_FLAG))) {
|
|
// If the operation failed then |key| doesn't have a valid handle in it
|
|
// and we shouldn't try and free it.
|
|
std::ignore = key.release();
|
|
return nullptr;
|
|
}
|
|
|
|
const absl::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 that case.
|
|
static const wchar_t kECDSA[] = L"ECDSA";
|
|
static const wchar_t kRSA[] = BCRYPT_RSA_ALGORITHM;
|
|
|
|
absl::optional<std::vector<uint8_t>> spki;
|
|
if (algo_bytes->size() == sizeof(kECDSA) &&
|
|
memcmp(algo_bytes->data(), kECDSA, sizeof(kECDSA)) == 0) {
|
|
spki = GetP256ECDSASPKI(key.get());
|
|
if (!spki) {
|
|
return nullptr;
|
|
}
|
|
return std::make_unique<ECDSAKey>(
|
|
std::move(provider), std::move(key),
|
|
std::vector<uint8_t>(wrapped.begin(), wrapped.end()),
|
|
std::move(spki.value()));
|
|
} else if (algo_bytes->size() == sizeof(kRSA) &&
|
|
memcmp(algo_bytes->data(), kRSA, sizeof(kRSA)) == 0) {
|
|
spki = GetRSASPKI(key.get());
|
|
if (!spki) {
|
|
return nullptr;
|
|
}
|
|
return std::make_unique<RSAKey>(
|
|
std::move(provider), std::move(key),
|
|
std::vector<uint8_t>(wrapped.begin(), wrapped.end()),
|
|
std::move(spki.value()));
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<UnexportableKeyProvider> GetUnexportableKeyProviderWin() {
|
|
return std::make_unique<UnexportableKeyProviderWin>();
|
|
}
|
|
|
|
} // namespace crypto
|