Commit e122308b authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

fido: populate TransportAvailabilityInfo.has_recognized_mac_touch_id_credential

This introduces PlatformAuthenticatorInfo as a parameter object to
FidoRequestHandlerBase::SetPlatformAuthenticatorOrMarkUnavailable. The
struct holds the existing std::unique_ptr<FidoAuthenticator>, which is
the platform authenticator itself, and an additional bool indicating
whether the authenticator has a matching credential for
GetAssertionRequest. The bool is plumbed through to
TransportAvailabilityInfo for consideration by the UI.

Bug: 847985
Change-Id: Ice46685eef31eb29cb8fdcb0d471e4b40bfb97b3
Reviewed-on: https://chromium-review.googlesource.com/1179088
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarJun Choi <hongjunchoi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584580}
parent ae5bd514
......@@ -568,11 +568,14 @@ void AuthenticatorImpl::GetAssertion(
client_data_json_ = SerializeCollectedClientDataToJson(
client_data::kGetType, caller_origin, std::move(options->challenge));
auto ctap_request = CreateCtapGetAssertionRequest(
ConstructClientDataHash(client_data_json_), std::move(options),
alternative_application_parameter_);
auto opt_platform_authenticator_info =
CreatePlatformAuthenticatorIfAvailableAndCheckIfCredentialExists(
ctap_request);
request_ = std::make_unique<device::GetAssertionRequestHandler>(
connector_, protocols_,
CreateCtapGetAssertionRequest(ConstructClientDataHash(client_data_json_),
std::move(options),
alternative_application_parameter_),
connector_, protocols_, std::move(ctap_request),
base::BindOnce(&AuthenticatorImpl::OnSignResponse,
weak_factory_.GetWeakPtr()));
......@@ -585,7 +588,7 @@ void AuthenticatorImpl::GetAssertion(
request_->set_observer(request_delegate_.get());
request_->SetPlatformAuthenticatorOrMarkUnavailable(
CreatePlatformAuthenticatorIfAvailable());
std::move(opt_platform_authenticator_info));
}
void AuthenticatorImpl::IsUserVerifyingPlatformAuthenticatorAvailable(
......@@ -852,26 +855,56 @@ BrowserContext* AuthenticatorImpl::browser_context() const {
->GetBrowserContext();
}
std::unique_ptr<device::FidoAuthenticator>
AuthenticatorImpl::CreatePlatformAuthenticatorIfAvailable() {
#if defined(OS_MACOSX)
// Incognito mode disables platform authenticators, so check for availability
// first.
if (!IsUserVerifyingPlatformAuthenticatorAvailableImpl()) {
return nullptr;
}
namespace {
std::unique_ptr<device::fido::mac::TouchIdAuthenticator>
CreateTouchIdAuthenticatorIfAvailable(
const AuthenticatorRequestClientDelegate* request_delegate) {
// Not all embedders may provide an authenticator config.
auto opt_authenticator_config =
request_delegate_->GetTouchIdAuthenticatorConfig();
request_delegate->GetTouchIdAuthenticatorConfig();
if (!opt_authenticator_config) {
return nullptr;
}
return device::fido::mac::TouchIdAuthenticator::CreateIfAvailable(
std::move(opt_authenticator_config->keychain_access_group),
std::move(opt_authenticator_config->metadata_secret));
}
} // namespace
#endif
base::Optional<device::PlatformAuthenticatorInfo>
AuthenticatorImpl::CreatePlatformAuthenticatorIfAvailable() {
// Incognito mode disables platform authenticators, so check for availability
// first.
if (!IsUserVerifyingPlatformAuthenticatorAvailableImpl()) {
return base::nullopt;
}
#if defined(OS_MACOSX)
return device::PlatformAuthenticatorInfo(
CreateTouchIdAuthenticatorIfAvailable(request_delegate_.get()), false);
#else
return nullptr;
return base::nullopt;
#endif
}
base::Optional<device::PlatformAuthenticatorInfo> AuthenticatorImpl::
CreatePlatformAuthenticatorIfAvailableAndCheckIfCredentialExists(
const device::CtapGetAssertionRequest& request) {
// Incognito mode disables platform authenticators, so check for availability
// first.
if (!IsUserVerifyingPlatformAuthenticatorAvailableImpl()) {
return base::nullopt;
}
#if defined(OS_MACOSX)
std::unique_ptr<device::fido::mac::TouchIdAuthenticator> authenticator =
CreateTouchIdAuthenticatorIfAvailable(request_delegate_.get());
const bool has_credential =
authenticator->HasCredentialForGetAssertionRequest(request);
return device::PlatformAuthenticatorInfo(std::move(authenticator),
has_credential);
#else
return base::nullopt;
#endif
}
......
......@@ -31,7 +31,8 @@ class OneShotTimer;
namespace device {
class FidoAuthenticator;
struct PlatformAuthenticatorInfo;
class CtapGetAssertionRequest;
class FidoRequestHandlerBase;
enum class FidoReturnCode : uint8_t;
......@@ -155,8 +156,11 @@ class CONTENT_EXPORT AuthenticatorImpl : public blink::mojom::Authenticator,
blink::mojom::GetAssertionAuthenticatorResponsePtr response);
void Cleanup();
std::unique_ptr<device::FidoAuthenticator>
base::Optional<device::PlatformAuthenticatorInfo>
CreatePlatformAuthenticatorIfAvailable();
base::Optional<device::PlatformAuthenticatorInfo>
CreatePlatformAuthenticatorIfAvailableAndCheckIfCredentialExists(
const device::CtapGetAssertionRequest& request);
BrowserContext* browser_context() const;
......
......@@ -19,6 +19,20 @@
namespace device {
// PlatformAuthenticatorInfo --------------------------
PlatformAuthenticatorInfo::PlatformAuthenticatorInfo(
std::unique_ptr<FidoAuthenticator> authenticator_,
bool has_recognized_mac_touch_id_credential_)
: authenticator(std::move(authenticator_)),
has_recognized_mac_touch_id_credential(
has_recognized_mac_touch_id_credential_) {}
PlatformAuthenticatorInfo::PlatformAuthenticatorInfo(
PlatformAuthenticatorInfo&&) = default;
PlatformAuthenticatorInfo& PlatformAuthenticatorInfo::operator=(
PlatformAuthenticatorInfo&&) = default;
PlatformAuthenticatorInfo::~PlatformAuthenticatorInfo() = default;
// FidoRequestHandlerBase::TransportAvailabilityInfo --------------------------
FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() =
......@@ -208,13 +222,17 @@ void FidoRequestHandlerBase::AddAuthenticator(
}
void FidoRequestHandlerBase::SetPlatformAuthenticatorOrMarkUnavailable(
std::unique_ptr<FidoAuthenticator> authenticator) {
if (authenticator &&
base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info) {
if (platform_authenticator_info &&
base::ContainsKey(transport_availability_info_.available_transports,
FidoTransportProtocol::kInternal)) {
DCHECK(authenticator->AuthenticatorTransport() ==
FidoTransportProtocol::kInternal);
AddAuthenticator(std::move(authenticator));
DCHECK(platform_authenticator_info->authenticator);
DCHECK(
(platform_authenticator_info->authenticator->AuthenticatorTransport() ==
FidoTransportProtocol::kInternal));
transport_availability_info_.has_recognized_mac_touch_id_credential =
platform_authenticator_info->has_recognized_mac_touch_id_credential;
AddAuthenticator(std::move(platform_authenticator_info->authenticator));
} else {
transport_availability_info_.available_transports.erase(
FidoTransportProtocol::kInternal);
......
......@@ -33,6 +33,17 @@ class FidoAuthenticator;
class FidoDevice;
class FidoTask;
struct COMPONENT_EXPORT(DEVICE_FIDO) PlatformAuthenticatorInfo {
PlatformAuthenticatorInfo(std::unique_ptr<FidoAuthenticator> authenticator,
bool has_recognized_mac_touch_id_credential);
PlatformAuthenticatorInfo(PlatformAuthenticatorInfo&&);
PlatformAuthenticatorInfo& operator=(PlatformAuthenticatorInfo&& other);
~PlatformAuthenticatorInfo();
std::unique_ptr<FidoAuthenticator> authenticator;
bool has_recognized_mac_touch_id_credential;
};
// Base class that handles device discovery/removal. Each FidoRequestHandlerBase
// is owned by FidoRequestManager and its lifetime is equivalent to that of a
// single WebAuthn request. For each authenticator, the per-device work is
......@@ -126,7 +137,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
// |AuthenticatorImpl| must call this method after invoking |set_oberver| even
// if no platform authenticator is available, in which case it passes nullptr.
void SetPlatformAuthenticatorOrMarkUnavailable(
std::unique_ptr<FidoAuthenticator> authenticator);
base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info);
TransportAvailabilityInfo& transport_availability_info() {
return transport_availability_info_;
......
......@@ -55,12 +55,18 @@ class TestTransportAvailabilityObserver
~TestTransportAvailabilityObserver() override {}
void WaitForAndExpectAvailableTransportsAre(
base::flat_set<FidoTransportProtocol> expected_transports) {
base::flat_set<FidoTransportProtocol> expected_transports,
base::Optional<bool> has_recognized_mac_touch_id_credential =
base::nullopt) {
transport_availability_notification_receiver_.WaitForCallback();
auto result =
std::get<0>(*transport_availability_notification_receiver_.result());
EXPECT_THAT(result.available_transports,
::testing::UnorderedElementsAreArray(expected_transports));
if (has_recognized_mac_touch_id_credential) {
EXPECT_EQ(*has_recognized_mac_touch_id_credential,
result.has_recognized_mac_touch_id_credential);
}
}
protected:
......@@ -192,7 +198,7 @@ class FidoRequestHandlerTest : public ::testing::Test {
{FidoTransportProtocol::kUsbHumanInterfaceDevice,
FidoTransportProtocol::kBluetoothLowEnergy}),
cb_.callback());
handler->SetPlatformAuthenticatorOrMarkUnavailable(nullptr);
handler->SetPlatformAuthenticatorOrMarkUnavailable(base::nullopt);
return handler;
}
......@@ -403,10 +409,43 @@ TEST_F(FidoRequestHandlerTest, TestSetPlatformAuthenticator) {
callback().callback());
request_handler->set_observer(&observer);
request_handler->SetPlatformAuthenticatorOrMarkUnavailable(
std::move(authenticator));
PlatformAuthenticatorInfo(std::move(authenticator), false));
observer.WaitForAndExpectAvailableTransportsAre(
{FidoTransportProtocol::kInternal},
false /* has_recognized_mac_touch_id_credential */);
callback().WaitForCallback();
EXPECT_TRUE(request_handler->is_complete());
EXPECT_EQ(FidoReturnCode::kSuccess, callback().status());
}
// SetPlatformAuthenticatorOrMarkUnavailable should propagate the
// has_recognized_mac_touch_id_credential field.
TEST_F(FidoRequestHandlerTest,
TestSetPlatformAuthenticatorHasTouchIdCredential) {
// A platform authenticator usually wouldn't usually use a FidoDevice, but
// that's not the point of the test here. The test is only trying to ensure
// the authenticator gets injected and used.
auto device = MockFidoDevice::MakeCtap();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
// Device returns success response.
device->ExpectRequestAndRespondWith(std::vector<uint8_t>(),
CreateFakeSuccessDeviceResponse());
device->SetDeviceTransport(FidoTransportProtocol::kInternal);
auto authenticator = std::make_unique<FakeFidoAuthenticator>(device.get());
TestTransportAvailabilityObserver observer;
auto request_handler = std::make_unique<FakeFidoRequestHandler>(
base::flat_set<FidoTransportProtocol>({FidoTransportProtocol::kInternal}),
callback().callback());
request_handler->set_observer(&observer);
request_handler->SetPlatformAuthenticatorOrMarkUnavailable(
PlatformAuthenticatorInfo(std::move(authenticator), true));
observer.WaitForAndExpectAvailableTransportsAre(
{FidoTransportProtocol::kInternal});
{FidoTransportProtocol::kInternal},
true /* has_recognized_mac_touch_id_credential */);
callback().WaitForCallback();
EXPECT_TRUE(request_handler->is_complete());
......@@ -419,7 +458,7 @@ TEST_F(FidoRequestHandlerTest, InternalTransportDisallowedIfMarkedUnavailable) {
base::flat_set<FidoTransportProtocol>({FidoTransportProtocol::kInternal}),
callback().callback());
request_handler->set_observer(&observer);
request_handler->SetPlatformAuthenticatorOrMarkUnavailable(nullptr);
request_handler->SetPlatformAuthenticatorOrMarkUnavailable(base::nullopt);
observer.WaitForAndExpectAvailableTransportsAre({});
}
......
......@@ -142,11 +142,12 @@ class FidoGetAssertionHandlerTest : public ::testing::Test {
}
protected:
std::unique_ptr<FidoAuthenticator> CreatePlatformAuthenticator() {
base::Optional<PlatformAuthenticatorInfo> CreatePlatformAuthenticator() {
if (!mock_platform_device_)
return nullptr;
return std::make_unique<FidoDeviceAuthenticator>(
mock_platform_device_.get());
return base::nullopt;
return PlatformAuthenticatorInfo(
std::make_unique<FidoDeviceAuthenticator>(mock_platform_device_.get()),
false /* has_recognized_mac_touch_id_credential_available */);
}
base::test::ScopedTaskEnvironment scoped_task_environment_{
......
......@@ -44,6 +44,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) TouchIdAuthenticator
~TouchIdAuthenticator() override;
bool HasCredentialForGetAssertionRequest(
const CtapGetAssertionRequest& request);
// FidoAuthenticator
void MakeCredential(CtapMakeCredentialRequest request,
MakeCredentialCallback callback) override;
......
......@@ -4,12 +4,15 @@
#include "device/fido/mac/authenticator.h"
#include <algorithm>
#import <LocalAuthentication/LocalAuthentication.h>
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/strings/string_piece.h"
#include "device/base/features.h"
#include "device/fido/authenticator_supported_options.h"
......@@ -55,6 +58,35 @@ std::unique_ptr<TouchIdAuthenticator> TouchIdAuthenticator::CreateForTesting(
TouchIdAuthenticator::~TouchIdAuthenticator() = default;
bool TouchIdAuthenticator::HasCredentialForGetAssertionRequest(
const CtapGetAssertionRequest& request) {
if (__builtin_available(macOS 10.12.2, *)) {
std::set<std::vector<uint8_t>> allow_list_credential_ids;
// Extract applicable credential IDs from the allowList, if the request has
// one. If not, any credential matching the RP works.
if (request.allow_list()) {
for (const auto& credential_descriptor : *request.allow_list()) {
if (credential_descriptor.credential_type() !=
CredentialType::kPublicKey)
continue;
if (!credential_descriptor.transports().empty() &&
!base::ContainsKey(credential_descriptor.transports(),
FidoTransportProtocol::kInternal))
continue;
allow_list_credential_ids.insert(credential_descriptor.id());
}
}
return FindCredentialInKeychain(keychain_access_group_, metadata_secret_,
request.rp_id(), allow_list_credential_ids,
nullptr /* LAContext */) != base::nullopt;
}
NOTREACHED();
return false;
}
void TouchIdAuthenticator::MakeCredential(CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
if (__builtin_available(macOS 10.12.2, *)) {
......
......@@ -12,6 +12,7 @@
#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "device/fido/fido_constants.h"
......@@ -55,9 +56,14 @@ void GetAssertionOperation::Run() {
std::set<std::vector<uint8_t>> allowed_credential_ids;
if (request().allow_list()) {
for (const PublicKeyCredentialDescriptor& desc : *request().allow_list()) {
if (desc.credential_type() != CredentialType::kPublicKey) {
if (desc.credential_type() != CredentialType::kPublicKey)
continue;
}
if (!desc.transports().empty() &&
!base::ContainsKey(desc.transports(),
FidoTransportProtocol::kInternal))
continue;
allowed_credential_ids.insert(desc.id());
}
}
......
......@@ -8,6 +8,7 @@
#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h"
#include "base/stl_util.h"
#include "base/strings/sys_string_conversions.h"
#include "device/fido/mac/credential_metadata.h"
......@@ -135,7 +136,7 @@ base::Optional<Credential> FindCredentialInKeychain(
CFDataGetBytePtr(application_label) +
CFDataGetLength(application_label));
if (credential_id_filter.empty() ||
credential_id_filter.find(cid) != credential_id_filter.end()) {
base::ContainsKey(credential_id_filter, cid)) {
private_key.reset(key, base::scoped_policy::RETAIN);
credential_id = std::move(cid);
break;
......
......@@ -141,11 +141,12 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test {
}
protected:
std::unique_ptr<FidoAuthenticator> CreatePlatformAuthenticator() {
base::Optional<PlatformAuthenticatorInfo> CreatePlatformAuthenticator() {
if (!mock_platform_device_)
return nullptr;
return std::make_unique<FidoDeviceAuthenticator>(
mock_platform_device_.get());
return base::nullopt;
return PlatformAuthenticatorInfo(
std::make_unique<FidoDeviceAuthenticator>(mock_platform_device_.get()),
false /* has_recognized_mac_touch_id_credential_available */);
}
base::test::ScopedFeatureList scoped_feature_list_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment