| // Copyright 2018 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 "device/fido/win/webauthn_api.h" |
| |
| #include "base/bind.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/native_library.h" |
| #include "base/no_destructor.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread.h" |
| #include "device/fido/features.h" |
| #include "device/fido/win/type_conversions.h" |
| |
| namespace device { |
| |
| // We do not integrate with older API versions of webauthn.dll because they |
| // don't support BLE and direct device access to USB and BLE FIDO devices is |
| // not yet blocked on those platforms. |
| constexpr uint32_t kMinWinWebAuthnApiVersion = WEBAUTHN_API_VERSION_1; |
| |
| namespace { |
| base::string16 OptionalGURLToUTF16(const base::Optional<GURL>& in) { |
| return in ? base::UTF8ToUTF16(in->spec()) : base::string16(); |
| } |
| } // namespace |
| |
| // WinWebAuthnApiImpl is the default implementation of WinWebAuthnApi, which |
| // attempts to load webauthn.dll on intialization. |
| class WinWebAuthnApiImpl : public WinWebAuthnApi { |
| public: |
| WinWebAuthnApiImpl() |
| : is_bound_(false), |
| thread_(std::make_unique<base::Thread>("WindowsWebAuthnAPIRequest")) { |
| static HMODULE webauthn_dll = LoadLibraryA("webauthn.dll"); |
| if (!webauthn_dll) { |
| return; |
| } |
| |
| #define BIND_FN(fn_pointer, lib_handle, fn_name) \ |
| DCHECK(!fn_pointer); \ |
| fn_pointer = reinterpret_cast<decltype(fn_pointer)>( \ |
| GetProcAddress(lib_handle, fn_name)); |
| |
| #define BIND_FN_OR_RETURN(fn_pointer, lib_handle, fn_name) \ |
| BIND_FN(fn_pointer, lib_handle, fn_name); \ |
| if (!fn_pointer) { \ |
| DLOG(ERROR) << "failed to bind " << fn_name; \ |
| return; \ |
| } |
| |
| BIND_FN_OR_RETURN(is_user_verifying_platform_authenticator_available_, |
| webauthn_dll, |
| "WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable"); |
| BIND_FN_OR_RETURN(authenticator_make_credential_, webauthn_dll, |
| "WebAuthNAuthenticatorMakeCredential"); |
| BIND_FN_OR_RETURN(authenticator_get_assertion_, webauthn_dll, |
| "WebAuthNAuthenticatorGetAssertion"); |
| BIND_FN_OR_RETURN(cancel_current_operation_, webauthn_dll, |
| "WebAuthNCancelCurrentOperation"); |
| BIND_FN_OR_RETURN(get_error_name_, webauthn_dll, "WebAuthNGetErrorName"); |
| BIND_FN_OR_RETURN(free_credential_attestation_, webauthn_dll, |
| "WebAuthNFreeCredentialAttestation"); |
| BIND_FN_OR_RETURN(free_assertion_, webauthn_dll, "WebAuthNFreeAssertion"); |
| |
| is_bound_ = true; |
| |
| // Determine the API version of webauthn.dll. There is a version currently |
| // shipping with Windows RS5 from before WebAuthNGetApiVersionNumber was |
| // added (i.e., before WEBAUTHN_API_VERSION_1). Therefore we allow this |
| // function to be missing. |
| BIND_FN(get_api_version_number_, webauthn_dll, |
| "WebAuthNGetApiVersionNumber"); |
| api_version_ = get_api_version_number_ ? get_api_version_number_() : 0; |
| thread_->Start(); |
| } |
| |
| ~WinWebAuthnApiImpl() override {} |
| |
| // WinWebAuthnApi: |
| bool IsAvailable() const override { |
| return is_bound_ && (api_version_ >= kMinWinWebAuthnApiVersion || |
| base::FeatureList::IsEnabled( |
| kWebAuthDisableWinApiVersionCheckForTesting)); |
| } |
| |
| HRESULT IsUserVerifyingPlatformAuthenticatorAvailable(BOOL* result) override { |
| DCHECK(is_bound_); |
| return is_user_verifying_platform_authenticator_available_(result); |
| } |
| |
| void AuthenticatorMakeCredential( |
| HWND h_wnd, |
| GUID cancellation_id, |
| PublicKeyCredentialRpEntity rp, |
| PublicKeyCredentialUserEntity user, |
| std::vector<WEBAUTHN_COSE_CREDENTIAL_PARAMETER> |
| cose_credential_parameter_values, |
| std::string client_data_json, |
| std::vector<WEBAUTHN_EXTENSION> extensions, |
| base::Optional<std::vector<PublicKeyCredentialDescriptor>> exclude_list, |
| WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options, |
| AuthenticatorMakeCredentialCallback callback) override { |
| DCHECK(is_bound_); |
| base::PostTaskAndReplyWithResult( |
| thread_->task_runner().get(), FROM_HERE, |
| base::BindOnce(&WinWebAuthnApiImpl::AuthenticatorMakeCredentialBlocking, |
| base::Unretained(this), // |thread_| is owned by this. |
| h_wnd, cancellation_id, std::move(rp), std::move(user), |
| std::move(cose_credential_parameter_values), |
| std::move(client_data_json), std::move(extensions), |
| std::move(exclude_list), std::move(options)), |
| base::BindOnce(&WinWebAuthnApiImpl::AuthenticatorMakeCredentialDone, |
| base::Unretained(this), |
| base::SequencedTaskRunnerHandle::Get(), |
| std::move(callback))); |
| } |
| |
| void AuthenticatorGetAssertion( |
| HWND h_wnd, |
| GUID cancellation_id, |
| base::string16 rp_id, |
| base::Optional<base::string16> opt_app_id, |
| std::string client_data_json, |
| base::Optional<std::vector<PublicKeyCredentialDescriptor>> allow_list, |
| WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options, |
| AuthenticatorGetAssertionCallback callback) override { |
| DCHECK(is_bound_); |
| base::PostTaskAndReplyWithResult( |
| thread_->task_runner().get(), FROM_HERE, |
| base::BindOnce(&WinWebAuthnApiImpl::AuthenticatorGetAssertionBlocking, |
| base::Unretained(this), // |thread_| is owned by this. |
| h_wnd, cancellation_id, std::move(rp_id), |
| std::move(opt_app_id), std::move(client_data_json), |
| std::move(allow_list), std::move(options)), |
| base::BindOnce(&WinWebAuthnApiImpl::AuthenticatorGetAssertionDone, |
| base::Unretained(this), |
| base::SequencedTaskRunnerHandle::Get(), |
| std::move(callback))); |
| } |
| |
| HRESULT CancelCurrentOperation(GUID* cancellation_id) override { |
| return cancel_current_operation_(cancellation_id); |
| } |
| |
| const wchar_t* GetErrorName(HRESULT hr) override { |
| DCHECK(is_bound_); |
| return get_error_name_(hr); |
| }; |
| |
| private: |
| std::pair<HRESULT, ScopedCredentialAttestation> |
| AuthenticatorMakeCredentialBlocking( |
| HWND h_wnd, |
| GUID cancellation_id, |
| PublicKeyCredentialRpEntity rp, |
| PublicKeyCredentialUserEntity user, |
| std::vector<WEBAUTHN_COSE_CREDENTIAL_PARAMETER> |
| cose_credential_parameter_values, |
| std::string client_data_json, |
| std::vector<WEBAUTHN_EXTENSION> extensions, |
| base::Optional<std::vector<PublicKeyCredentialDescriptor>> exclude_list, |
| WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options) { |
| base::string16 rp_id = base::UTF8ToUTF16(rp.rp_id()); |
| base::string16 rp_name = base::UTF8ToUTF16(rp.rp_name().value_or("")); |
| base::string16 rp_icon_url = OptionalGURLToUTF16(rp.rp_icon_url()); |
| base::string16 user_name = base::UTF8ToUTF16(user.user_name().value_or("")); |
| base::string16 user_icon_url = OptionalGURLToUTF16(user.user_icon_url()); |
| WEBAUTHN_RP_ENTITY_INFORMATION rp_info{ |
| WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, rp_id.c_str(), |
| rp_name.c_str(), rp_icon_url.c_str()}; |
| |
| base::string16 user_display_name = |
| base::UTF8ToUTF16(user.user_display_name().value_or("")); |
| WEBAUTHN_USER_ENTITY_INFORMATION user_info{ |
| WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION, |
| user.user_id().size(), |
| const_cast<unsigned char*>(user.user_id().data()), |
| user_name.c_str(), |
| user_icon_url.c_str(), |
| user_display_name.c_str(), // This appears to be ignored. |
| }; |
| |
| WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose_credential_parameters{ |
| cose_credential_parameter_values.size(), |
| cose_credential_parameter_values.data()}; |
| |
| WEBAUTHN_CLIENT_DATA client_data{ |
| WEBAUTHN_CLIENT_DATA_CURRENT_VERSION, client_data_json.size(), |
| const_cast<unsigned char*>( |
| reinterpret_cast<const unsigned char*>(client_data_json.data())), |
| WEBAUTHN_HASH_ALGORITHM_SHA_256}; |
| |
| options.Extensions = |
| WEBAUTHN_EXTENSIONS{extensions.size(), extensions.data()}; |
| options.pCancellationId = &cancellation_id; |
| |
| std::vector<WEBAUTHN_CREDENTIAL_EX> exclude_list_credentials = |
| ToWinCredentialExVector(exclude_list); |
| std::vector<WEBAUTHN_CREDENTIAL_EX*> exclude_list_ptrs; |
| std::transform(exclude_list_credentials.begin(), |
| exclude_list_credentials.end(), |
| std::back_inserter(exclude_list_ptrs), |
| [](auto& cred) { return &cred; }); |
| WEBAUTHN_CREDENTIAL_LIST exclude_credential_list{exclude_list_ptrs.size(), |
| exclude_list_ptrs.data()}; |
| options.pExcludeCredentialList = &exclude_credential_list; |
| |
| WEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation_ptr = nullptr; |
| HRESULT hresult = authenticator_make_credential_( |
| h_wnd, &rp_info, &user_info, &cose_credential_parameters, &client_data, |
| &options, &credential_attestation_ptr); |
| return std::make_pair( |
| hresult, ScopedCredentialAttestation(credential_attestation_ptr, |
| free_credential_attestation_)); |
| } |
| |
| std::pair<HRESULT, ScopedAssertion> AuthenticatorGetAssertionBlocking( |
| HWND h_wnd, |
| GUID cancellation_id, |
| base::string16 rp_id, |
| base::Optional<base::string16> opt_app_id, |
| std::string client_data_json, |
| base::Optional<std::vector<PublicKeyCredentialDescriptor>> allow_list, |
| WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options) { |
| if (opt_app_id) { |
| options.pwszU2fAppId = opt_app_id->data(); |
| static BOOL kUseAppIdTrue = TRUE; // const |
| options.pbU2fAppId = &kUseAppIdTrue; |
| } else { |
| static BOOL kUseAppIdFalse = FALSE; // const |
| options.pbU2fAppId = &kUseAppIdFalse; |
| } |
| options.pCancellationId = &cancellation_id; |
| |
| std::vector<WEBAUTHN_CREDENTIAL_EX> allow_list_credentials = |
| ToWinCredentialExVector(allow_list); |
| std::vector<WEBAUTHN_CREDENTIAL_EX*> allow_list_ptrs; |
| std::transform(allow_list_credentials.begin(), allow_list_credentials.end(), |
| std::back_inserter(allow_list_ptrs), |
| [](auto& cred) { return &cred; }); |
| WEBAUTHN_CREDENTIAL_LIST allow_credential_list{allow_list_ptrs.size(), |
| allow_list_ptrs.data()}; |
| options.pAllowCredentialList = &allow_credential_list; |
| |
| // As of Nov 2018, the WebAuthNAuthenticatorGetAssertion method will fail |
| // to challenge credentials via CTAP1 if the allowList is passed in the |
| // extended form in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS (i.e. |
| // pAllowCredentialList instead of CredentialList). The legacy |
| // CredentialList field works fine, but does not support setting |
| // transport restrictions on the credential descriptor. |
| // |
| // As a workaround, MS tells us to also set the CredentialList parameter |
| // with an accurate cCredentials count and some arbitrary pCredentials |
| // data. |
| auto legacy_credentials = ToWinCredentialVector(allow_list); |
| options.CredentialList = WEBAUTHN_CREDENTIALS{legacy_credentials.size(), |
| legacy_credentials.data()}; |
| |
| WEBAUTHN_CLIENT_DATA client_data{ |
| WEBAUTHN_CLIENT_DATA_CURRENT_VERSION, client_data_json.size(), |
| const_cast<unsigned char*>( |
| reinterpret_cast<const unsigned char*>(client_data_json.data())), |
| WEBAUTHN_HASH_ALGORITHM_SHA_256}; |
| |
| WEBAUTHN_ASSERTION* assertion_ptr = nullptr; |
| HRESULT hresult = authenticator_get_assertion_( |
| h_wnd, rp_id.data(), &client_data, &options, &assertion_ptr); |
| return std::make_pair(hresult, |
| ScopedAssertion(assertion_ptr, free_assertion_)); |
| } |
| |
| void AuthenticatorMakeCredentialDone( |
| scoped_refptr<base::SequencedTaskRunner> callback_runner, |
| AuthenticatorMakeCredentialCallback callback, |
| std::pair<HRESULT, ScopedCredentialAttestation> result) { |
| callback_runner->PostTask(FROM_HERE, |
| base::BindOnce(std::move(callback), result.first, |
| std::move(result.second))); |
| } |
| |
| void AuthenticatorGetAssertionDone( |
| scoped_refptr<base::SequencedTaskRunner> callback_runner, |
| AuthenticatorGetAssertionCallback callback, |
| std::pair<HRESULT, ScopedAssertion> result) { |
| callback_runner->PostTask(FROM_HERE, |
| base::BindOnce(std::move(callback), result.first, |
| std::move(result.second))); |
| } |
| |
| decltype(&WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable) |
| is_user_verifying_platform_authenticator_available_ = nullptr; |
| decltype( |
| &WebAuthNAuthenticatorMakeCredential) authenticator_make_credential_ = |
| nullptr; |
| decltype(&WebAuthNAuthenticatorGetAssertion) authenticator_get_assertion_ = |
| nullptr; |
| decltype(&WebAuthNCancelCurrentOperation) cancel_current_operation_ = nullptr; |
| decltype(&WebAuthNGetErrorName) get_error_name_ = nullptr; |
| decltype(&WebAuthNFreeCredentialAttestation) free_credential_attestation_ = |
| nullptr; |
| decltype(&WebAuthNFreeAssertion) free_assertion_ = nullptr; |
| |
| // This method is not available in all versions of webauthn.dll. |
| decltype(&WebAuthNGetApiVersionNumber) get_api_version_number_ = nullptr; |
| |
| bool is_bound_ = false; |
| uint32_t api_version_ = 0; |
| |
| std::unique_ptr<base::Thread> thread_; |
| }; |
| |
| static WinWebAuthnApi* kDefaultForTesting = nullptr; |
| |
| // static |
| WinWebAuthnApi* WinWebAuthnApi::GetDefault() { |
| if (kDefaultForTesting) { |
| return kDefaultForTesting; |
| } |
| |
| static base::NoDestructor<WinWebAuthnApiImpl> api; |
| return api.get(); |
| } |
| |
| // static |
| void WinWebAuthnApi::SetDefaultForTesting(WinWebAuthnApi* api) { |
| DCHECK(!kDefaultForTesting); |
| kDefaultForTesting = api; |
| } |
| |
| // static |
| void WinWebAuthnApi::ClearDefaultForTesting() { |
| DCHECK(kDefaultForTesting); |
| kDefaultForTesting = nullptr; |
| } |
| |
| WinWebAuthnApi::~WinWebAuthnApi() = default; |
| |
| } // namespace device |