blob: df92f92ce9a6882fd7c40476c68207f715539f89 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atomic>
#include <functional>
#include <utility>
#include <windows.foundation.h>
#include <windows.security.credentials.h>
#include <windows.security.cryptography.core.h>
#include <windows.storage.streams.h>
#include "base/base64.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_thread_priority.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"
#include "base/win/scoped_hstring.h"
#include "base/win/winrt_storage_util.h"
#include "crypto/random.h"
#include "crypto/user_verifying_key.h"
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Security::Credentials::IKeyCredential;
using ABI::Windows::Security::Credentials::IKeyCredentialManagerStatics;
using ABI::Windows::Security::Credentials::IKeyCredentialOperationResult;
using ABI::Windows::Security::Credentials::IKeyCredentialRetrievalResult;
using ABI::Windows::Security::Credentials::KeyCredentialCreationOption;
using ABI::Windows::Security::Credentials::KeyCredentialOperationResult;
using ABI::Windows::Security::Credentials::KeyCredentialRetrievalResult;
using ABI::Windows::Security::Credentials::KeyCredentialStatus;
using ABI::Windows::Security::Credentials::KeyCredentialStatus_Success;
using ABI::Windows::Security::Cryptography::Core::
CryptographicPublicKeyBlobType_X509SubjectPublicKeyInfo;
using ABI::Windows::Storage::Streams::IBuffer;
using Microsoft::WRL::ComPtr;
namespace crypto {
namespace {
enum KeyCredentialManagerAvailability {
kUnknown = 0,
kAvailable = 1,
kUnavailable = 2,
};
std::string FormatError(std::string message, HRESULT hr) {
return base::StrCat(
{message, " (hr = ", logging::SystemErrorCodeToString(hr), ")"});
}
// This helper splits OnceCallback three ways for use with `PostAsyncHandlers`,
// which has three separate paths to outcomes: Invoke a success callback, invoke
// an error callback, or return an error.
template <typename... Args>
std::tuple<base::OnceCallback<void(Args...)>,
base::OnceCallback<void(Args...)>,
base::OnceCallback<void(Args...)>>
SplitOnceCallbackIntoThree(base::OnceCallback<void(Args...)> callback) {
auto first_split = base::SplitOnceCallback(std::move(callback));
auto second_split = base::SplitOnceCallback(std::move(first_split.first));
return {std::move(first_split.second), std::move(second_split.first),
std::move(second_split.second)};
}
void OnSigningSuccess(
base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> callback,
ComPtr<IKeyCredentialOperationResult> sign_result) {
KeyCredentialStatus status;
HRESULT hr = sign_result->get_Status(&status);
if (FAILED(hr) || status != KeyCredentialStatus_Success) {
LOG(ERROR) << FormatError(
"Failed to obtain Status from IKeyCredentialOperationResult", hr);
std::move(callback).Run(std::nullopt);
return;
}
ComPtr<IBuffer> signature_buffer;
hr = sign_result->get_Result(&signature_buffer);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"Failed to obtain Result from IKeyCredentialOperationResult", hr);
std::move(callback).Run(std::nullopt);
return;
}
uint8_t* signature_data = nullptr;
uint32_t signature_length = 0;
hr = base::win::GetPointerToBufferData(signature_buffer.Get(),
&signature_data, &signature_length);
if (FAILED(hr)) {
LOG(ERROR) << FormatError("Failed to obtain data from signature buffer",
hr);
std::move(callback).Run(std::nullopt);
return;
}
std::move(callback).Run(
std::vector<uint8_t>(signature_data, signature_data + signature_length));
}
void OnSigningError(
base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> callback,
HRESULT hr) {
LOG(ERROR) << FormatError("Failed to sign with user-verifying signature", hr);
std::move(callback).Run(std::nullopt);
}
void SignInternal(
std::vector<uint8_t> data,
ComPtr<IKeyCredential> credential,
base::OnceCallback<void(std::optional<std::vector<uint8_t>>)> callback) {
Microsoft::WRL::ComPtr<IBuffer> signing_buf;
HRESULT hr =
base::win::CreateIBufferFromData(data.data(), data.size(), &signing_buf);
if (FAILED(hr)) {
LOG(ERROR) << FormatError("SignInternal: IBuffer creation failed", hr);
std::move(callback).Run(std::nullopt);
return;
}
ComPtr<IAsyncOperation<KeyCredentialOperationResult*>> sign_result;
hr = credential->RequestSignAsync(signing_buf.Get(), &sign_result);
if (FAILED(hr)) {
LOG(ERROR) << FormatError("SignInternal: Call to RequestSignAsync failed",
hr);
std::move(callback).Run(std::nullopt);
return;
}
auto callback_splits = SplitOnceCallbackIntoThree(std::move(callback));
hr = base::win::PostAsyncHandlers(
sign_result.Get(),
base::BindOnce(&OnSigningSuccess,
std::move(std::get<0>(callback_splits))),
base::BindOnce(&OnSigningError, std::move(std::get<1>(callback_splits))));
if (FAILED(hr)) {
LOG(ERROR) << FormatError("SignInternal: Call to PostAsyncHandlers failed",
hr);
std::move(std::get<2>(callback_splits)).Run(std::nullopt);
return;
}
}
class UserVerifyingSigningKeyWin : public UserVerifyingSigningKey {
public:
UserVerifyingSigningKeyWin(std::string key_name,
ComPtr<IKeyCredential> credential)
: key_name_(std::move(key_name)), credential_(std::move(credential)) {}
~UserVerifyingSigningKeyWin() override = default;
void Sign(base::span<const uint8_t> data,
base::OnceCallback<void(std::optional<std::vector<uint8_t>>)>
callback) override {
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING});
std::vector<uint8_t> vec_data(data.begin(), data.end());
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&SignInternal, std::move(vec_data), credential_,
base::BindPostTaskToCurrentDefault(std::move(callback))));
}
std::vector<uint8_t> GetPublicKey() const override {
ComPtr<IBuffer> key_buf;
HRESULT hr = credential_->RetrievePublicKeyWithBlobType(
CryptographicPublicKeyBlobType_X509SubjectPublicKeyInfo, &key_buf);
CHECK(SUCCEEDED(hr)) << FormatError(
"Failed to obtain public key from KeyCredential", hr);
uint8_t* pub_key_data = nullptr;
uint32_t pub_key_length = 0;
hr = base::win::GetPointerToBufferData(key_buf.Get(), &pub_key_data,
&pub_key_length);
CHECK(SUCCEEDED(hr)) << FormatError(
"Failed to access public key buffer data", hr);
return std::vector<uint8_t>(pub_key_data, pub_key_data + pub_key_length);
}
const UserVerifyingKeyLabel& GetKeyLabel() const override {
return key_name_;
}
private:
std::string key_name_;
ComPtr<IKeyCredential> credential_;
};
void OnKeyCreationCompletionSuccess(
base::OnceCallback<void(std::unique_ptr<UserVerifyingSigningKey>)> callback,
std::string key_name,
ComPtr<IKeyCredentialRetrievalResult> key_result) {
KeyCredentialStatus status;
HRESULT hr = key_result->get_Status(&status);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"Failed to obtain Status from IKeyCredentialRetrievalResult", hr);
std::move(callback).Run(nullptr);
return;
} else if (status != KeyCredentialStatus_Success) {
LOG(ERROR) << "IKeyCredentialRetrievalResult failed with status "
<< static_cast<uint32_t>(status);
std::move(callback).Run(nullptr);
return;
}
ComPtr<IKeyCredential> credential;
hr = key_result->get_Credential(&credential);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"Failed to obtain KeyCredential from KeyCredentialRetrievalResult", hr);
std::move(callback).Run(nullptr);
return;
}
auto key = std::make_unique<UserVerifyingSigningKeyWin>(
std::move(key_name), std::move(credential));
std::move(callback).Run(std::move(key));
}
void OnKeyCreationCompletionError(
base::OnceCallback<void(std::unique_ptr<UserVerifyingSigningKey>)> callback,
HRESULT hr) {
LOG(ERROR) << FormatError("Failed to obtain user-verifying key from system",
hr);
std::move(callback).Run(nullptr);
}
void GenerateUserVerifyingSigningKeyInternal(
std::string key_label,
base::OnceCallback<void(std::unique_ptr<UserVerifyingSigningKey>)>
callback) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
auto key_name = base::win::ScopedHString::Create(key_label);
ComPtr<IKeyCredentialManagerStatics> factory;
HRESULT hr = base::win::GetActivationFactory<
IKeyCredentialManagerStatics,
RuntimeClass_Windows_Security_Credentials_KeyCredentialManager>(&factory);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"GenerateUserVerifyingSigningKeyInternal: Failed to obtain activation "
"factory for KeyCredentialManager",
hr);
std::move(callback).Run(nullptr);
return;
}
ComPtr<IAsyncOperation<KeyCredentialRetrievalResult*>> create_result;
hr = factory->RequestCreateAsync(
key_name.get(),
KeyCredentialCreationOption::KeyCredentialCreationOption_ReplaceExisting,
&create_result);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"GenerateUserVerifyingSigningKeyInternal: Call to RequestCreateAsync "
"failed",
hr);
std::move(callback).Run(nullptr);
return;
}
auto callback_splits = SplitOnceCallbackIntoThree(std::move(callback));
hr = base::win::PostAsyncHandlers(
create_result.Get(),
base::BindOnce(&OnKeyCreationCompletionSuccess,
std::move(std::get<0>(callback_splits)),
std::move(key_label)),
base::BindOnce(&OnKeyCreationCompletionError,
std::move(std::get<1>(callback_splits))));
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"GenerateUserVerifyingSigningKeyInternal: Call to PostAsyncHandlers "
"failed",
hr);
std::move(std::get<2>(callback_splits)).Run(nullptr);
return;
}
}
void GetUserVerifyingSigningKeyInternal(
std::string key_label,
base::OnceCallback<void(std::unique_ptr<UserVerifyingSigningKey>)>
callback) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
auto key_name = base::win::ScopedHString::Create(key_label);
ComPtr<IKeyCredentialManagerStatics> factory;
HRESULT hr = base::win::GetActivationFactory<
IKeyCredentialManagerStatics,
RuntimeClass_Windows_Security_Credentials_KeyCredentialManager>(&factory);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"GetUserVerifyingSigningKeyInternal: Failed to obtain activation "
"factory for KeyCredentialManager",
hr);
std::move(callback).Run(nullptr);
return;
}
ComPtr<IAsyncOperation<KeyCredentialRetrievalResult*>> open_result;
hr = factory->OpenAsync(key_name.get(), &open_result);
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"GetUserVerifyingSigningKeyInternal: Call to OpenAsync failed", hr);
std::move(callback).Run(nullptr);
return;
}
auto callback_splits = SplitOnceCallbackIntoThree(std::move(callback));
hr = base::win::PostAsyncHandlers(
open_result.Get(),
base::BindOnce(&OnKeyCreationCompletionSuccess,
std::move(std::get<0>(callback_splits)),
std::move(key_label)),
base::BindOnce(&OnKeyCreationCompletionError,
std::move(std::get<1>(callback_splits))));
if (FAILED(hr)) {
LOG(ERROR) << FormatError(
"GetUserVerifyingSigningKeyInternal: Call to PostAsyncHandlers failed",
hr);
std::move(std::get<2>(callback_splits)).Run(nullptr);
return;
}
}
std::optional<SignatureVerifier::SignatureAlgorithm> SelectAlgorithm(
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms) {
// Windows keys come in any algorithm you want, as long as it's RSA 2048.
for (auto algorithm : acceptable_algorithms) {
if (algorithm == SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256) {
return algorithm;
}
}
return std::nullopt;
}
class UserVerifyingKeyProviderWin : public UserVerifyingKeyProvider {
public:
UserVerifyingKeyProviderWin() = default;
~UserVerifyingKeyProviderWin() override = default;
void GenerateUserVerifyingSigningKey(
base::span<const SignatureVerifier::SignatureAlgorithm>
acceptable_algorithms,
base::OnceCallback<void(std::unique_ptr<UserVerifyingSigningKey>)>
callback) override {
// Ignore the non-empty return value of `SelectAlgorithm` unless in the
// future Windows supports more algorithms.
if (!SelectAlgorithm(acceptable_algorithms)) {
LOG(ERROR) << "Key generation does not include a supported algorithm.";
std::move(callback).Run(nullptr);
return;
}
std::vector<uint8_t> random(16);
crypto::RandBytes(random);
UserVerifyingKeyLabel key_label =
base::StrCat({"uvkey-", base::Base64Encode(random)});
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING});
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&GenerateUserVerifyingSigningKeyInternal, std::move(key_label),
base::BindPostTaskToCurrentDefault(std::move(callback))));
}
void GetUserVerifyingSigningKey(
UserVerifyingKeyLabel key_label,
base::OnceCallback<void(std::unique_ptr<UserVerifyingSigningKey>)>
callback) override {
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING});
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&GetUserVerifyingSigningKeyInternal, key_label,
base::BindPostTaskToCurrentDefault(std::move(callback))));
}
void DeleteUserVerifyingKey(
UserVerifyingKeyLabel key_label,
base::OnceCallback<void(bool)> callback) override {
// TODO(crbug.com/40274370): implement.
std::move(callback).Run(false);
}
};
void IsKeyCredentialManagerAvailableInternal(
base::OnceCallback<void(bool)> callback) {
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
// Lookup requires an asynchronous system API call, so cache the value.
static std::atomic<KeyCredentialManagerAvailability> availability =
KeyCredentialManagerAvailability::kUnknown;
// Read once to ensure consistency.
const KeyCredentialManagerAvailability current_availability = availability;
if (current_availability != KeyCredentialManagerAvailability::kUnknown) {
std::move(callback).Run(current_availability ==
KeyCredentialManagerAvailability::kAvailable);
return;
}
ComPtr<IKeyCredentialManagerStatics> factory;
HRESULT hr = base::win::GetActivationFactory<
IKeyCredentialManagerStatics,
RuntimeClass_Windows_Security_Credentials_KeyCredentialManager>(&factory);
if (FAILED(hr)) {
// Don't cache API call failures, allowing the possibility of trying again
// if this was a one-time failure.
std::move(callback).Run(false);
return;
}
ComPtr<IAsyncOperation<bool>> is_supported_operation;
hr = factory->IsSupportedAsync(&is_supported_operation);
if (FAILED(hr)) {
std::move(callback).Run(false);
return;
}
auto callback_splits = SplitOnceCallbackIntoThree(std::move(callback));
hr = base::win::PostAsyncHandlers(
is_supported_operation.Get(),
base::BindOnce(
[](base::OnceCallback<void(bool)> callback,
std::atomic<KeyCredentialManagerAvailability>& availability,
boolean result) {
availability = result
? KeyCredentialManagerAvailability::kAvailable
: KeyCredentialManagerAvailability::kUnavailable;
std::move(callback).Run(result);
},
std::move(std::get<0>(callback_splits)), std::ref(availability)),
base::BindOnce([](base::OnceCallback<void(bool)> callback,
HRESULT) { std::move(callback).Run(false); },
std::move(std::get<1>(callback_splits))));
if (FAILED(hr)) {
std::move(std::get<2>(callback_splits)).Run(false);
return;
}
}
} // namespace
std::unique_ptr<UserVerifyingKeyProvider> GetUserVerifyingKeyProviderWin() {
return std::make_unique<UserVerifyingKeyProviderWin>();
}
void IsKeyCredentialManagerAvailable(base::OnceCallback<void(bool)> callback) {
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING});
task_runner->PostTask(
FROM_HERE,
base::BindOnce(&IsKeyCredentialManagerAvailableInternal,
base::BindPostTaskToCurrentDefault(std::move(callback))));
}
} // namespace crypto