blob: 0b71290a28fb8096c85b196d5811a0ccf6eb8ce7 [file] [log] [blame]
// 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/authenticator.h"
#include <Combaseapi.h>
#include <windows.h>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/win/type_conversions.h"
#include "third_party/microsoft_webauthn/webauthn.h"
namespace device {
// 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;
// static
const char WinWebAuthnApiAuthenticator::kAuthenticatorId[] =
"WinWebAuthnApiAuthenticator";
// static
bool WinWebAuthnApiAuthenticator::
IsUserVerifyingPlatformAuthenticatorAvailable() {
BOOL result;
return WinWebAuthnApi::GetDefault()->IsAvailable() &&
WinWebAuthnApi::GetDefault()
->IsUserVerifyingPlatformAuthenticatorAvailable(&result) ==
S_OK &&
result == TRUE;
}
WinWebAuthnApiAuthenticator::WinWebAuthnApiAuthenticator(
WinWebAuthnApi* win_api,
HWND current_window)
: FidoAuthenticator(),
win_api_(win_api),
current_window_(current_window),
weak_factory_(this) {
CHECK(win_api_->IsAvailable());
CoCreateGuid(&cancellation_id_);
}
WinWebAuthnApiAuthenticator::~WinWebAuthnApiAuthenticator() {
Cancel();
}
void WinWebAuthnApiAuthenticator::InitializeAuthenticator(
base::OnceClosure callback) {
std::move(callback).Run();
}
void WinWebAuthnApiAuthenticator::MakeCredential(
CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
DCHECK(!is_pending_);
if (is_pending_)
return;
is_pending_ = true;
auto rp = request.rp();
auto user = request.user();
std::string client_data_json = request.client_data_json();
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});
}
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)});
}
auto exclude_list = request.exclude_list();
uint32_t authenticator_attachment;
if (request.is_u2f_only()) {
authenticator_attachment =
WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2;
} else if (request.is_incognito_mode()) {
// Disable all platform authenticators in incognito mode. We are going to
// revisit this in crbug/908622.
authenticator_attachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
} else {
authenticator_attachment =
ToWinAuthenticatorAttachment(request.authenticator_attachment());
}
win_api_->AuthenticatorMakeCredential(
current_window_, 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),
WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS{
WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3,
kWinWebAuthnTimeoutMilliseconds,
WEBAUTHN_CREDENTIALS{
0, nullptr}, // Ignored because pExcludeCredentialList is set.
WEBAUTHN_EXTENSIONS{0, nullptr}, // will be set later
authenticator_attachment, request.resident_key_required(),
ToWinUserVerificationRequirement(request.user_verification()),
ToWinAttestationConveyancePreference(
request.attestation_preference()),
0 /* flags */,
nullptr, // pCancellationId -- will be set later
nullptr, // pExcludeCredentialList -- will be set later
},
base::BindOnce(&WinWebAuthnApiAuthenticator::MakeCredentialDone,
weak_factory_.GetWeakPtr(), std::move(request),
std::move(callback)));
}
void WinWebAuthnApiAuthenticator::MakeCredentialDone(
CtapMakeCredentialRequest request,
MakeCredentialCallback callback,
HRESULT hresult,
WinWebAuthnApi::ScopedCredentialAttestation credential_attestation) {
DCHECK(is_pending_);
is_pending_ = false;
const CtapDeviceResponseCode status =
hresult == S_OK ? CtapDeviceResponseCode::kSuccess
: WinErrorNameToCtapDeviceResponseCode(
base::string16(win_api_->GetErrorName(hresult)));
if (waiting_for_cancellation_) {
// Don't bother invoking the reply callback if the caller has already
// cancelled the operation.
waiting_for_cancellation_ = false;
return;
}
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, base::nullopt);
return;
}
base::Optional<AuthenticatorMakeCredentialResponse> response =
credential_attestation
? ToAuthenticatorMakeCredentialResponse(*credential_attestation)
: base::nullopt;
if (!response) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR,
base::nullopt);
return;
}
std::move(callback).Run(status, std::move(response));
}
void WinWebAuthnApiAuthenticator::GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) {
DCHECK(!is_pending_);
if (is_pending_)
return;
is_pending_ = true;
base::string16 rp_id16 = base::UTF8ToUTF16(request.rp_id());
base::Optional<base::string16> opt_app_id16 = base::nullopt;
if (request.app_id()) {
opt_app_id16 = base::UTF8ToUTF16(base::StringPiece(
reinterpret_cast<const char*>(request.app_id()->data()),
request.app_id()->size()));
}
std::string client_data_json = request.client_data_json();
auto allow_list = request.allow_list();
uint32_t authenticator_attachment;
if (opt_app_id16) {
authenticator_attachment =
WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2;
} else if (request.is_incognito_mode()) {
// Disable all platform authenticators in incognito mode. We are going to
// revisit this in crbug/908622.
authenticator_attachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM;
} else {
authenticator_attachment = WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY;
}
win_api_->AuthenticatorGetAssertion(
current_window_, cancellation_id_, std::move(rp_id16),
std::move(opt_app_id16), std::move(client_data_json),
std::move(allow_list),
WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS{
WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4,
kWinWebAuthnTimeoutMilliseconds,
WEBAUTHN_CREDENTIALS{
0, nullptr}, // Ignored because pAllowCredentialList is set.
WEBAUTHN_EXTENSIONS{0, nullptr}, // None supported.
authenticator_attachment,
ToWinUserVerificationRequirement(request.user_verification()),
0, // flags
nullptr, // pwszU2fAppId -- will be set later
nullptr, // pbU2fAppId -- will be set later
nullptr, // pCancellationId -- will be set later
nullptr, // pAllowCredentialList -- will be set later
},
base::BindOnce(&WinWebAuthnApiAuthenticator::GetAssertionDone,
weak_factory_.GetWeakPtr(), std::move(request),
std::move(callback)));
}
void WinWebAuthnApiAuthenticator::GetAssertionDone(
CtapGetAssertionRequest request,
GetAssertionCallback callback,
HRESULT hresult,
WinWebAuthnApi::ScopedAssertion assertion) {
DCHECK(is_pending_);
is_pending_ = false;
const CtapDeviceResponseCode status =
hresult == S_OK ? CtapDeviceResponseCode::kSuccess
: WinErrorNameToCtapDeviceResponseCode(
base::string16(win_api_->GetErrorName(hresult)));
if (waiting_for_cancellation_) {
// Don't bother invoking the reply callback if the caller has already
// cancelled the operation.
waiting_for_cancellation_ = false;
return;
}
base::Optional<AuthenticatorGetAssertionResponse> response =
(hresult == S_OK && assertion)
? ToAuthenticatorGetAssertionResponse(*assertion)
: base::nullopt;
std::move(callback).Run(status, std::move(response));
}
void WinWebAuthnApiAuthenticator::Cancel() {
if (!is_pending_ || waiting_for_cancellation_)
return;
waiting_for_cancellation_ = true;
// This returns immediately.
win_api_->CancelCurrentOperation(&cancellation_id_);
}
std::string WinWebAuthnApiAuthenticator::GetId() const {
return kAuthenticatorId;
}
base::string16 WinWebAuthnApiAuthenticator::GetDisplayName() const {
return base::UTF8ToUTF16(GetId());
}
bool WinWebAuthnApiAuthenticator::IsInPairingMode() const {
return false;
}
bool WinWebAuthnApiAuthenticator::IsPaired() const {
return false;
}
base::Optional<FidoTransportProtocol>
WinWebAuthnApiAuthenticator::AuthenticatorTransport() const {
// The Windows API could potentially use any external or
// platform authenticator.
return base::nullopt;
}
bool WinWebAuthnApiAuthenticator::IsWinNativeApiAuthenticator() const {
return true;
}
const base::Optional<AuthenticatorSupportedOptions>&
WinWebAuthnApiAuthenticator::Options() const {
// The request can potentially be fulfilled by any device that Windows
// communicates with, so returning AuthenticatorSupportedOptions really
// doesn't make much sense.
static const base::Optional<AuthenticatorSupportedOptions> no_options =
base::nullopt;
return no_options;
}
base::WeakPtr<FidoAuthenticator> WinWebAuthnApiAuthenticator::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace device