// 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 #include #include #include #include #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 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>; using ScopedKey = base::ScopedGeneric>; std::vector CBBToVector(const CBB* cbb) { return std::vector(CBB_data(cbb), CBB_data(cbb) + CBB_len(cbb)); } // BCryptAlgorithmFor returns the BCrypt algorithm ID for the given Chromium // signing algorithm. absl::optional 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 GetBestSupported( NCRYPT_PROV_HANDLE provider, base::span acceptable_algorithms) { for (auto algo : acceptable_algorithms) { absl::optional 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> GetKeyProperty(NCRYPT_KEY_HANDLE key, LPCWSTR property) { DWORD size; if (FAILED(NCryptGetProperty(key, property, nullptr, 0, &size, 0))) { return absl::nullopt; } std::vector 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> 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 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> GetP256ECDSASPKI(NCRYPT_KEY_HANDLE key) { const absl::optional> 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 p256( EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); bssl::UniquePtr 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_new_by_curve_name(NID_X9_62_prime256v1)); CHECK(EC_KEY_set_public_key(ec_key.get(), point.get())); bssl::UniquePtr 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> GetRSASPKI(NCRYPT_KEY_HANDLE key) { const absl::optional> 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(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 e( BN_bin2bn(&pub_key->data()[sizeof(BCRYPT_RSAKEY_BLOB)], header.cbPublicExp, nullptr)); bssl::UniquePtr n(BN_bin2bn( &pub_key->data()[sizeof(BCRYPT_RSAKEY_BLOB) + header.cbPublicExp], header.cbModulus, nullptr)); bssl::UniquePtr rsa(RSA_new()); CHECK(RSA_set0_key(rsa.get(), n.release(), e.release(), nullptr)); bssl::UniquePtr 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 wrapped, std::vector 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 GetSubjectPublicKeyInfo() const override { return spki_; } std::vector GetWrappedKey() const override { return wrapped_; } absl::optional> SignSlowly( base::span data) override { base::ScopedBlockingCall scoped_blocking_call( FROM_HERE, base::BlockingType::WILL_BLOCK); std::array digest = SHA256Hash(data); // The signature is written as a pair of big-endian field elements for P-256 // ECDSA. std::vector 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 r(BN_bin2bn(sig.data(), 32, nullptr)); bssl::UniquePtr 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 wrapped_; const std::vector spki_; }; // RSAKey wraps a TPM-stored RSA key. class RSAKey : public UnexportableSigningKey { public: RSAKey(ScopedProvider provider, ScopedKey key, std::vector wrapped, std::vector 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 GetSubjectPublicKeyInfo() const override { return spki_; } std::vector GetWrappedKey() const override { return wrapped_; } absl::optional> SignSlowly( base::span data) override { base::ScopedBlockingCall scoped_blocking_call( FROM_HERE, base::BlockingType::WILL_BLOCK); std::array 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 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 wrapped_; const std::vector 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 SelectAlgorithm( base::span 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 GenerateSigningKeySlowly( base::span 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 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> wrapped_key = ExportKey(key.get(), BCRYPT_OPAQUE_KEY_BLOB); if (!wrapped_key) { return nullptr; } absl::optional> spki; switch (*algo) { case SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256: spki = GetP256ECDSASPKI(key.get()); if (!spki) { return nullptr; } return std::make_unique(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(std::move(provider), std::move(key), std::move(*wrapped_key), std::move(spki.value())); default: return nullptr; } } std::unique_ptr FromWrappedSigningKeySlowly( base::span 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(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> 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> 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( std::move(provider), std::move(key), std::vector(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( std::move(provider), std::move(key), std::vector(wrapped.begin(), wrapped.end()), std::move(spki.value())); } return nullptr; } }; } // namespace std::unique_ptr GetUnexportableKeyProviderWin() { return std::make_unique(); } } // namespace crypto