| // Copyright 2018 The Chromium Authors |
| // 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 <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/no_destructor.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_piece_forward.h" |
| #include "base/strings/string_util_win.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/threading/scoped_thread_priority.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "device/fido/features.h" |
| #include "device/fido/win/logging.h" |
| #include "device/fido/win/type_conversions.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| // Time out all Windows API requests after 5 minutes. We maintain our own |
| // timeout and cancel the operation when it expires, so this value simply needs |
| // to be larger than the largest internal request timeout. |
| constexpr uint32_t kWinWebAuthnTimeoutMilliseconds = 1000 * 60 * 5; |
| |
| std::string HresultToHex(HRESULT hr) { |
| return base::StringPrintf("0x%0lX", hr); |
| } |
| |
| } // namespace |
| |
| class WinWebAuthnApiImpl : public WinWebAuthnApi { |
| public: |
| WinWebAuthnApiImpl() : WinWebAuthnApi(), is_bound_(false) { |
| if (!base::FeatureList::IsEnabled(device::kWebAuthUseNativeWinApi)) { |
| FIDO_LOG(DEBUG) << "Windows WebAuthn API deactivated via feature flag"; |
| return; |
| } |
| { |
| // Mitigate the issues caused by loading DLLs on a background thread |
| // (http://crbug/973868). |
| SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY(); |
| webauthn_dll_ = |
| LoadLibraryExA("webauthn.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); |
| } |
| if (!webauthn_dll_) { |
| FIDO_LOG(ERROR) << "Windows WebAuthn API failed to load"; |
| 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"); |
| |
| // The platform credential list set of functions was added in version 4. |
| BIND_FN(get_platform_credential_list_, webauthn_dll_, |
| "WebAuthNGetPlatformCredentialList"); |
| if (get_platform_credential_list_) { |
| BIND_FN_OR_RETURN(free_platform_credential_list_, webauthn_dll_, |
| "WebAuthNFreePlatformCredentialList"); |
| BIND_FN_OR_RETURN(delete_platform_credential_, webauthn_dll_, |
| "WebAuthNDeletePlatformCredential"); |
| } |
| |
| 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; |
| |
| FIDO_LOG(DEBUG) << "webauthn.dll version " << api_version_; |
| } |
| |
| ~WinWebAuthnApiImpl() override {} |
| |
| // WinWebAuthnApi: |
| bool IsAvailable() const override { |
| return is_bound_ && (api_version_ >= WEBAUTHN_API_VERSION_1); |
| } |
| |
| bool SupportsSilentDiscovery() const override { |
| return get_platform_credential_list_; |
| } |
| |
| HRESULT IsUserVerifyingPlatformAuthenticatorAvailable( |
| BOOL* available) override { |
| DCHECK(is_bound_); |
| // Mitigate the issues caused by loading DLLs on a background thread |
| // (http://crbug/973868). |
| SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY(); |
| base::ScopedBlockingCall scoped_blocking_call( |
| FROM_HERE, base::BlockingType::MAY_BLOCK); |
| return is_user_verifying_platform_authenticator_available_(available); |
| } |
| |
| HRESULT AuthenticatorMakeCredential( |
| HWND h_wnd, |
| PCWEBAUTHN_RP_ENTITY_INFORMATION rp, |
| PCWEBAUTHN_USER_ENTITY_INFORMATION user, |
| PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose_credential_parameters, |
| PCWEBAUTHN_CLIENT_DATA client_data, |
| PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options, |
| PWEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation_ptr) override { |
| DCHECK(is_bound_); |
| base::ScopedBlockingCall scoped_blocking_call( |
| FROM_HERE, base::BlockingType::MAY_BLOCK); |
| return authenticator_make_credential_( |
| h_wnd, rp, user, cose_credential_parameters, client_data, options, |
| credential_attestation_ptr); |
| } |
| |
| HRESULT AuthenticatorGetAssertion( |
| HWND h_wnd, |
| LPCWSTR rp_id, |
| PCWEBAUTHN_CLIENT_DATA client_data, |
| PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options, |
| PWEBAUTHN_ASSERTION* assertion_ptr) override { |
| DCHECK(is_bound_); |
| base::ScopedBlockingCall scoped_blocking_call( |
| FROM_HERE, base::BlockingType::MAY_BLOCK); |
| return authenticator_get_assertion_(h_wnd, rp_id, client_data, options, |
| assertion_ptr); |
| } |
| |
| HRESULT CancelCurrentOperation(GUID* cancellation_id) override { |
| DCHECK(is_bound_); |
| return cancel_current_operation_(cancellation_id); |
| } |
| |
| HRESULT GetPlatformCredentialList( |
| PCWEBAUTHN_GET_CREDENTIALS_OPTIONS options, |
| PWEBAUTHN_CREDENTIAL_DETAILS_LIST* credentials) override { |
| DCHECK(is_bound_ && get_platform_credential_list_); |
| return get_platform_credential_list_(options, credentials); |
| } |
| |
| HRESULT DeletePlatformCredential( |
| base::span<const uint8_t> credential_id) override { |
| return delete_platform_credential_(credential_id.size(), |
| credential_id.data()); |
| } |
| |
| PCWSTR GetErrorName(HRESULT hr) override { |
| DCHECK(is_bound_); |
| return get_error_name_(hr); |
| } |
| |
| void FreeCredentialAttestation( |
| PWEBAUTHN_CREDENTIAL_ATTESTATION attestation_ptr) override { |
| DCHECK(is_bound_); |
| return free_credential_attestation_(attestation_ptr); |
| } |
| |
| void FreeAssertion(PWEBAUTHN_ASSERTION assertion_ptr) override { |
| DCHECK(is_bound_); |
| return free_assertion_(assertion_ptr); |
| } |
| |
| void FreePlatformCredentialList( |
| PWEBAUTHN_CREDENTIAL_DETAILS_LIST credentials) override { |
| DCHECK(is_bound_ && free_platform_credential_list_); |
| free_platform_credential_list_(credentials); |
| } |
| |
| int Version() override { return api_version_; } |
| |
| private: |
| bool is_bound_ = false; |
| uint32_t api_version_ = 0; |
| HMODULE webauthn_dll_; |
| |
| 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(&WebAuthNGetPlatformCredentialList) get_platform_credential_list_ = |
| nullptr; |
| decltype(&WebAuthNDeletePlatformCredential) delete_platform_credential_ = |
| nullptr; |
| decltype(&WebAuthNGetErrorName) get_error_name_ = nullptr; |
| decltype(&WebAuthNFreeCredentialAttestation) free_credential_attestation_ = |
| nullptr; |
| decltype(&WebAuthNFreeAssertion) free_assertion_ = nullptr; |
| decltype(&WebAuthNFreePlatformCredentialList) free_platform_credential_list_ = |
| nullptr; |
| |
| // This method is not available in all versions of webauthn.dll. |
| decltype(&WebAuthNGetApiVersionNumber) get_api_version_number_ = nullptr; |
| }; |
| |
| // static |
| WinWebAuthnApi* WinWebAuthnApi::GetDefault() { |
| static base::NoDestructor<WinWebAuthnApiImpl> api; |
| return api.get(); |
| } |
| |
| WinWebAuthnApi::WinWebAuthnApi() = default; |
| |
| WinWebAuthnApi::~WinWebAuthnApi() = default; |
| |
| std::pair<CtapDeviceResponseCode, |
| absl::optional<AuthenticatorMakeCredentialResponse>> |
| AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api, |
| HWND h_wnd, |
| GUID cancellation_id, |
| CtapMakeCredentialRequest request, |
| MakeCredentialOptions request_options) { |
| DCHECK(webauthn_api->IsAvailable()); |
| const int api_version = webauthn_api->Version(); |
| |
| std::u16string rp_id = base::UTF8ToUTF16(request.rp.id); |
| std::u16string rp_name = base::UTF8ToUTF16(request.rp.name.value_or("")); |
| WEBAUTHN_RP_ENTITY_INFORMATION rp_info{ |
| WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, base::as_wcstr(rp_id), |
| base::as_wcstr(rp_name), |
| /*pwszIcon=*/base::as_wcstr(base::EmptyString16())}; |
| |
| std::u16string user_name = base::UTF8ToUTF16(request.user.name.value_or("")); |
| std::u16string user_display_name = |
| base::UTF8ToUTF16(request.user.display_name.value_or("")); |
| std::vector<uint8_t> user_id = request.user.id; |
| WEBAUTHN_USER_ENTITY_INFORMATION user_info{ |
| WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION, |
| base::checked_cast<DWORD>(user_id.size()), |
| const_cast<unsigned char*>(user_id.data()), |
| base::as_wcstr(user_name), |
| /*pwszIcon=*/base::as_wcstr(base::EmptyString16()), |
| base::as_wcstr(user_display_name), |
| }; |
| |
| std::vector<WEBAUTHN_COSE_CREDENTIAL_PARAMETER> |
| cose_credential_parameter_values; |
| for (const PublicKeyCredentialParams::CredentialInfo& credential_info : |
| request.public_key_credential_params.public_key_credential_params()) { |
| if (credential_info.type != CredentialType::kPublicKey) { |
| continue; |
| } |
| cose_credential_parameter_values.push_back( |
| {WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION, |
| WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY, credential_info.algorithm}); |
| } |
| WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose_credential_parameters{ |
| base::checked_cast<DWORD>(cose_credential_parameter_values.size()), |
| cose_credential_parameter_values.data()}; |
| |
| std::string client_data_json = request.client_data_json; |
| WEBAUTHN_CLIENT_DATA client_data{ |
| WEBAUTHN_CLIENT_DATA_CURRENT_VERSION, |
| base::checked_cast<DWORD>(client_data_json.size()), |
| const_cast<unsigned char*>( |
| reinterpret_cast<const unsigned char*>(client_data_json.data())), |
| WEBAUTHN_HASH_ALGORITHM_SHA_256}; |
| |
| std::vector<WEBAUTHN_EXTENSION> extensions; |
| if (request.hmac_secret) { |
| static BOOL kHMACSecretTrue = TRUE; |
| extensions.emplace_back( |
| WEBAUTHN_EXTENSION{WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET, |
| sizeof(BOOL), static_cast<void*>(&kHMACSecretTrue)}); |
| } |
| |
| WEBAUTHN_CRED_PROTECT_EXTENSION_IN maybe_cred_protect_extension; |
| if (request.cred_protect) { |
| // MakeCredentialRequestHandler rejects a request with credProtect |
| // enforced=true if webauthn.dll does not support credProtect. |
| if (request.cred_protect_enforce && api_version < WEBAUTHN_API_VERSION_2) { |
| NOTREACHED(); |
| return {CtapDeviceResponseCode::kCtap2ErrNotAllowed, absl::nullopt}; |
| } |
| // Windows doesn't support the concept of |
| // CredProtectRequest::kUVOrCredIDRequiredOrBetter. So an authenticators |
| // that defaults to credProtect level three will only use level two when |
| // Chrome is setting the credProtect level for discoverable credentials. |
| maybe_cred_protect_extension = WEBAUTHN_CRED_PROTECT_EXTENSION_IN{ |
| /*dwCredProtect=*/static_cast<uint8_t>(*request.cred_protect), |
| /*bRequireCredProtect=*/request.cred_protect_enforce, |
| }; |
| extensions.emplace_back(WEBAUTHN_EXTENSION{ |
| /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT, |
| /*cbExtension=*/sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN), |
| /*pvExtension=*/&maybe_cred_protect_extension, |
| }); |
| } |
| |
| uint32_t authenticator_attachment; |
| if (request_options.is_off_the_record_context && |
| api_version < WEBAUTHN_API_VERSION_4) { |
| // API versions before `WEBAUTHN_API_VERSION_4` don't have support for |
| // showing a warning message that platform credentials will out last the |
| // Incognito session. Thus, in this case, only external authenticators are |
| // enabled. |
| authenticator_attachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM; |
| } else { |
| authenticator_attachment = |
| ToWinAuthenticatorAttachment(request.authenticator_attachment); |
| } |
| |
| WEBAUTHN_CRED_BLOB_EXTENSION cred_blob_ext; |
| if (request.cred_blob && api_version >= WEBAUTHN_API_VERSION_3 && |
| request.cred_blob->size() <= |
| std::numeric_limits<decltype(cred_blob_ext.cbCredBlob)>::max()) { |
| cred_blob_ext = { |
| /*cbCredBlob=*/base::checked_cast<decltype(cred_blob_ext.cbCredBlob)>( |
| request.cred_blob->size()), |
| /*pbCredBlob=*/request.cred_blob->data(), |
| }; |
| extensions.emplace_back(WEBAUTHN_EXTENSION{ |
| /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB, |
| /*cbExtension=*/sizeof(cred_blob_ext), |
| /*pvExtension=*/&cred_blob_ext, |
| }); |
| } |
| |
| if (request.min_pin_length_requested && |
| api_version >= WEBAUTHN_API_VERSION_3) { |
| static const BOOL kRequestMinPINLength = TRUE; |
| extensions.emplace_back(WEBAUTHN_EXTENSION{ |
| /*pwszExtensionIdentifier=*/ |
| WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH, |
| /*cbExtension=*/sizeof(kRequestMinPINLength), |
| /*pvExtension=*/const_cast<BOOL*>(&kRequestMinPINLength), |
| }); |
| } |
| |
| DWORD enterprise_attestation = WEBAUTHN_ENTERPRISE_ATTESTATION_NONE; |
| switch (request.attestation_preference) { |
| case AttestationConveyancePreference::kEnterpriseIfRPListedOnAuthenticator: |
| enterprise_attestation = |
| WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED; |
| break; |
| case AttestationConveyancePreference::kEnterpriseApprovedByBrowser: |
| enterprise_attestation = WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED; |
| break; |
| default: |
| break; |
| } |
| |
| // Note that entries in |exclude_list_credentials| hold pointers |
| // into request.exclude_list. |
| std::vector<WEBAUTHN_CREDENTIAL_EX> exclude_list_credentials = |
| ToWinCredentialExVector(&request.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{ |
| base::checked_cast<DWORD>(exclude_list_ptrs.size()), |
| exclude_list_ptrs.data()}; |
| |
| WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options{ |
| WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5, |
| kWinWebAuthnTimeoutMilliseconds, |
| WEBAUTHN_CREDENTIALS{ |
| 0, nullptr}, // Ignored because pExcludeCredentialList is set. |
| WEBAUTHN_EXTENSIONS{base::checked_cast<DWORD>(extensions.size()), |
| extensions.data()}, |
| authenticator_attachment, |
| request.resident_key_required, |
| ToWinUserVerificationRequirement(request.user_verification), |
| ToWinAttestationConveyancePreference(request.attestation_preference, |
| api_version), |
| /*dwFlags=*/0, |
| &cancellation_id, |
| &exclude_credential_list, |
| enterprise_attestation, |
| WEBAUTHN_LARGE_BLOB_SUPPORT_NONE, |
| /*bPreferResidentKey=*/request_options.resident_key == |
| ResidentKeyRequirement::kPreferred, |
| request_options.is_off_the_record_context, |
| }; |
| |
| WEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation = nullptr; |
| |
| FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential(" |
| << "rp=" << rp_info << ", user=" << user_info |
| << ", cose_credential_parameters=" |
| << cose_credential_parameters |
| << ", client_data=" << client_data << ", options=" << options |
| << ")"; |
| HRESULT hresult = webauthn_api->AuthenticatorMakeCredential( |
| h_wnd, &rp_info, &user_info, &cose_credential_parameters, &client_data, |
| &options, &credential_attestation); |
| std::unique_ptr<WEBAUTHN_CREDENTIAL_ATTESTATION, |
| std::function<void(PWEBAUTHN_CREDENTIAL_ATTESTATION)>> |
| credential_attestation_deleter( |
| credential_attestation, |
| [webauthn_api](PWEBAUTHN_CREDENTIAL_ATTESTATION ptr) { |
| webauthn_api->FreeCredentialAttestation(ptr); |
| }); |
| |
| if (hresult != S_OK) { |
| FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential()=" |
| << HresultToHex(hresult) << " (" |
| << webauthn_api->GetErrorName(hresult) << ")"; |
| return {WinErrorNameToCtapDeviceResponseCode( |
| base::as_u16cstr(webauthn_api->GetErrorName(hresult))), |
| absl::nullopt}; |
| } |
| FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential()=" |
| << *credential_attestation; |
| return {CtapDeviceResponseCode::kSuccess, |
| ToAuthenticatorMakeCredentialResponse(*credential_attestation)}; |
| } |
| |
| std::pair<CtapDeviceResponseCode, |
| absl::optional<AuthenticatorGetAssertionResponse>> |
| AuthenticatorGetAssertionBlocking(WinWebAuthnApi* webauthn_api, |
| HWND h_wnd, |
| GUID cancellation_id, |
| CtapGetAssertionRequest request, |
| CtapGetAssertionOptions request_options) { |
| DCHECK(webauthn_api->IsAvailable()); |
| const int api_version = webauthn_api->Version(); |
| |
| std::u16string rp_id16 = base::UTF8ToUTF16(request.rp_id); |
| std::string client_data_json = request.client_data_json; |
| WEBAUTHN_CLIENT_DATA client_data{ |
| WEBAUTHN_CLIENT_DATA_CURRENT_VERSION, |
| base::checked_cast<DWORD>(client_data_json.size()), |
| const_cast<unsigned char*>( |
| reinterpret_cast<const unsigned char*>(client_data_json.data())), |
| WEBAUTHN_HASH_ALGORITHM_SHA_256}; |
| |
| absl::optional<std::u16string> opt_app_id16 = absl::nullopt; |
| if (request.app_id) { |
| opt_app_id16 = base::UTF8ToUTF16( |
| base::StringPiece(reinterpret_cast<const char*>(request.app_id->data()), |
| request.app_id->size())); |
| } |
| |
| // Note that entries in |allow_list_credentials| hold pointers into |
| // request.allow_list. |
| std::vector<WEBAUTHN_CREDENTIAL_EX> allow_list_credentials = |
| ToWinCredentialExVector(&request.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{ |
| base::checked_cast<DWORD>(allow_list_ptrs.size()), |
| allow_list_ptrs.data()}; |
| |
| // Note that entries in |legacy_credentials| hold pointers into |
| // request.allow_list. |
| auto legacy_credentials = ToWinCredentialVector(&request.allow_list); |
| |
| std::vector<WEBAUTHN_EXTENSION> extensions; |
| if (api_version >= WEBAUTHN_API_VERSION_3 && request.get_cred_blob) { |
| static const BOOL kCredBlobTrue = TRUE; |
| extensions.emplace_back(WEBAUTHN_EXTENSION{ |
| /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB, |
| /*cbExtension=*/sizeof(kCredBlobTrue), |
| /*pvExtension=*/const_cast<BOOL*>(&kCredBlobTrue), |
| }); |
| } |
| |
| static BOOL kUseAppIdTrue = TRUE; // const |
| static BOOL kUseAppIdFalse = FALSE; // const |
| WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options{ |
| WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4, |
| kWinWebAuthnTimeoutMilliseconds, |
| // 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. |
| WEBAUTHN_CREDENTIALS{base::checked_cast<DWORD>(legacy_credentials.size()), |
| legacy_credentials.data()}, |
| WEBAUTHN_EXTENSIONS{base::checked_cast<DWORD>(extensions.size()), |
| extensions.data()}, |
| WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY, |
| ToWinUserVerificationRequirement(request.user_verification), |
| /*dwFlags=*/0, |
| opt_app_id16 ? base::as_wcstr(*opt_app_id16) : nullptr, |
| opt_app_id16 ? &kUseAppIdTrue : &kUseAppIdFalse, |
| &cancellation_id, |
| &allow_credential_list, |
| }; |
| |
| WEBAUTHN_ASSERTION* assertion = nullptr; |
| |
| FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion(" |
| << "rp_id=\"" << rp_id16 << "\", client_data=" << client_data |
| << ", options=" << options << ")"; |
| HRESULT hresult = webauthn_api->AuthenticatorGetAssertion( |
| h_wnd, base::as_wcstr(rp_id16), &client_data, &options, &assertion); |
| std::unique_ptr<WEBAUTHN_ASSERTION, std::function<void(PWEBAUTHN_ASSERTION)>> |
| assertion_deleter(assertion, [webauthn_api](PWEBAUTHN_ASSERTION ptr) { |
| webauthn_api->FreeAssertion(ptr); |
| }); |
| |
| if (hresult != S_OK) { |
| FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion()=" |
| << HresultToHex(hresult) << " (" |
| << webauthn_api->GetErrorName(hresult) << ")"; |
| return {WinErrorNameToCtapDeviceResponseCode( |
| base::as_u16cstr(webauthn_api->GetErrorName(hresult))), |
| absl::nullopt}; |
| } |
| FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion()=" << *assertion; |
| absl::optional<AuthenticatorGetAssertionResponse> response = |
| ToAuthenticatorGetAssertionResponse(*assertion, request.allow_list); |
| if (response && !request_options.prf_inputs.empty()) { |
| // Windows does not yet support passing in inputs for hmac_secret. |
| response->hmac_secret_not_evaluated = true; |
| } |
| return {response ? CtapDeviceResponseCode::kSuccess |
| : CtapDeviceResponseCode::kCtap2ErrOther, |
| std::move(response)}; |
| } |
| |
| bool SupportsCredProtectExtension(WinWebAuthnApi* api) { |
| return api->IsAvailable() && api->Version() >= WEBAUTHN_API_VERSION_2; |
| } |
| |
| } // namespace device |