Commit 2e9d8072 authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

WebAuthn: implement AuthenticatorSelectionCriteria.resident_key

WebAuthn Level 2 adds an enum-valued |resident_key| property to
AuthenticatorSelectionCriteria. When making a credential, the existing
boolean |require_resident_key| property indicates whether the browser
should create a client-side discoverable (resident) credential. The new
enum-valued alternative adds a middle value ("preferred") that yields a
resident credential if the user's authenticator supports it,
and a non-resident credential otherwise.

Web-facing changes in this CL, i.e. the addition of the
ResidentKeyRequirement enum as well as the resident_key property in
AuthenticatorSelectionCriteria, are guarded by a new Blink feature flag
(WebAuthenticationResidentKeyRequirement).

Bug: 1117630
Change-Id: I89656a10a977023a4ca60b59ad1de5cd03800f44
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2376570Reviewed-by: default avatarMatthias Körber <koerber@chromium.org>
Reviewed-by: default avatarMatthias Körber <koerber@google.com>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarNina Satragno <nsatragno@chromium.org>
Commit-Queue: Matthias Körber <koerber@google.com>
Cr-Commit-Position: refs/heads/master@{#803112}
parent 1bb404d5
......@@ -302,8 +302,8 @@ public class Fido2ApiTestHelper {
options.excludeCredentials = new PublicKeyCredentialDescriptor[] {descriptor};
options.authenticatorSelection = new AuthenticatorSelectionCriteria();
/* TODO add back UserVerificationRequirement when the FIDO2 API supports it */
options.authenticatorSelection.requireResidentKey = false;
/* TODO add UserVerificationRequirement and ResidentKeyRequirement when the FIDO2 API
* supports it */
options.authenticatorSelection.authenticatorAttachment =
AuthenticatorAttachment.CROSS_PLATFORM;
return options;
......
......@@ -44,7 +44,9 @@ specific_include_rules = {
"+components/ukm",
],
"(test_)?credit_card_fido_authenticator\.(cc|h)": [
"+device/fido/authenticator_selection_criteria.h",
"+device/fido/fido_constants.h",
"+device/fido/fido_types.h",
"+third_party/blink/public/mojom/webauthn/authenticator.mojom.h",
],
"(test_)?internal_authenticator\.h": [
......
......@@ -23,6 +23,8 @@
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/fido_types.h"
#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
......@@ -621,9 +623,10 @@ CreditCardFIDOAuthenticator::ParseCreationOptions(
}
// Only allow user-verifying platform authenticators.
options->authenticator_selection = AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kPlatform, /*require_resident_key=*/false,
UserVerificationRequirement::kRequired);
options->authenticator_selection = device::AuthenticatorSelectionCriteria(
device::AuthenticatorAttachment::kPlatform,
device::ResidentKeyRequirement::kDiscouraged,
device::UserVerificationRequirement::kRequired);
// List of keys that Payments already knows about, and so should not make a
// new credential.
......
......@@ -630,7 +630,8 @@ void AuthenticatorCommon::StartMakeCredentialRequest(
base::BindRepeating(
&device::FidoRequestHandlerBase::PowerOnBluetoothAdapter,
request_->GetWeakPtr()) /* bluetooth_adapter_power_on_callback */);
if (make_credential_options_->require_resident_key) {
if (make_credential_options_->resident_key !=
device::ResidentKeyRequirement::kDiscouraged) {
request_delegate_->SetMightCreateResidentCredential(true);
}
request_->set_observer(request_delegate_.get());
......@@ -777,21 +778,30 @@ void AuthenticatorCommon::MakeCredential(
return;
}
bool resident_key = options->authenticator_selection &&
options->authenticator_selection->require_resident_key();
if (resident_key && !request_delegate_->SupportsResidentKeys()) {
// Disallow the creation of resident credentials.
InvokeCallbackAndCleanup(
std::move(callback),
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
return;
const device::AuthenticatorSelectionCriteria
authenticator_selection_criteria =
options->authenticator_selection
? *options->authenticator_selection
: device::AuthenticatorSelectionCriteria();
make_credential_options_ = device::MakeCredentialRequestHandler::Options(
authenticator_selection_criteria);
const bool might_create_resident_key =
make_credential_options_->resident_key !=
device::ResidentKeyRequirement::kDiscouraged;
if (might_create_resident_key && !request_delegate_->SupportsResidentKeys()) {
if (make_credential_options_->resident_key ==
device::ResidentKeyRequirement::kRequired) {
InvokeCallbackAndCleanup(
std::move(callback),
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
return;
}
// Downgrade 'preferred' to 'discouraged'.
make_credential_options_->resident_key =
device::ResidentKeyRequirement::kDiscouraged;
}
device::AuthenticatorSelectionCriteria authenticator_selection_criteria =
options->authenticator_selection
? *options->authenticator_selection
: device::AuthenticatorSelectionCriteria();
// Reject any non-sensical credProtect extension values.
if ( // Can't require the default policy (or no policy).
(options->enforce_protection_policy &&
......@@ -802,7 +812,7 @@ void AuthenticatorCommon::MakeCredential(
// does because, with CTAP 2.0, just because a resident key isn't
// _required_ doesn't mean that one won't be created and an RP might want
// credProtect to take effect if that happens.)
(!resident_key &&
(!might_create_resident_key &&
options->protection_policy == blink::mojom::ProtectionPolicy::NONE) ||
// UV_REQUIRED only makes sense if UV is required overall.
(options->protection_policy ==
......@@ -818,9 +828,9 @@ void AuthenticatorCommon::MakeCredential(
base::Optional<device::CredProtectRequest> cred_protect_request;
switch (options->protection_policy) {
case blink::mojom::ProtectionPolicy::UNSPECIFIED:
if (resident_key) {
// If not specified, kUVOrCredIDRequired is made the default unless the
// authenticator defaults to something better.
if (might_create_resident_key) {
// If not specified, kUVOrCredIDRequired is made the default unless
// the authenticator defaults to something better.
cred_protect_request =
device::CredProtectRequest::kUVOrCredIDRequiredOrBetter;
}
......@@ -836,13 +846,10 @@ void AuthenticatorCommon::MakeCredential(
break;
}
make_credential_options_ =
cred_protect_request
? device::MakeCredentialRequestHandler::Options(
authenticator_selection_criteria, *cred_protect_request,
options->enforce_protection_policy)
: device::MakeCredentialRequestHandler::Options(
authenticator_selection_criteria);
if (cred_protect_request) {
make_credential_options_->cred_protect_request = {
{*cred_protect_request, options->enforce_protection_policy}};
}
DCHECK(make_credential_response_callback_.is_null());
make_credential_response_callback_ = std::move(callback);
......@@ -888,7 +895,9 @@ void AuthenticatorCommon::MakeCredential(
// On dual protocol CTAP2/U2F devices, force credential creation over U2F.
ctap_make_credential_request_->is_u2f_only = origin_is_crypto_token_extension;
if (resident_key && caller_origin.scheme() == "chrome-extension") {
if (make_credential_options_->resident_key ==
device::ResidentKeyRequirement::kRequired &&
caller_origin.scheme() == "chrome-extension") {
// The large blob key extension is set for every request since we cannot
// know in advance if the RP will attempt storing a blob on a future
// GetAssertion request.
......
......@@ -321,7 +321,8 @@ GetTestPublicKeyCredentialParameters(int32_t algorithm_identifier) {
device::AuthenticatorSelectionCriteria GetTestAuthenticatorSelectionCriteria() {
return device::AuthenticatorSelectionCriteria(
device::AuthenticatorAttachment::kAny, false,
device::AuthenticatorAttachment::kAny,
device::ResidentKeyRequirement::kDiscouraged,
device::UserVerificationRequirement::kPreferred);
}
......@@ -737,7 +738,8 @@ TEST_F(AuthenticatorImplTest, MakeCredentialResidentKeyUnsupported) {
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->authenticator_selection->SetRequireResidentKeyForTesting(true);
options->authenticator_selection->SetResidentKeyForTesting(
device::ResidentKeyRequirement::kRequired);
EXPECT_EQ(AuthenticatorMakeCredential(std::move(options)).status,
AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
......@@ -1646,8 +1648,10 @@ TEST_F(ExtensionAuthenticatorTest, MakeCredentialLargeBlobKeyExtension) {
virtual_device_factory_->SetCtap2Config(config);
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->authenticator_selection->SetRequireResidentKeyForTesting(
rk_enabled);
if (rk_enabled) {
options->authenticator_selection->SetResidentKeyForTesting(
device::ResidentKeyRequirement::kRequired);
}
options->user.id = {1, 2, 3, 4};
options->user.name = "name";
options->user.display_name = "displayName";
......@@ -4642,10 +4646,12 @@ class ResidentKeyAuthenticatorImplTest : public UVAuthenticatorImplTest {
protected:
ResidentKeyTestAuthenticatorContentBrowserClient test_client_;
static PublicKeyCredentialCreationOptionsPtr make_credential_options() {
static PublicKeyCredentialCreationOptionsPtr make_credential_options(
device::ResidentKeyRequirement resident_key =
device::ResidentKeyRequirement::kRequired) {
PublicKeyCredentialCreationOptionsPtr options =
UVAuthenticatorImplTest::make_credential_options();
options->authenticator_selection->SetRequireResidentKeyForTesting(true);
options->authenticator_selection->SetResidentKeyForTesting(resident_key);
options->user.id = {1, 2, 3, 4};
return options;
}
......@@ -4663,7 +4669,7 @@ class ResidentKeyAuthenticatorImplTest : public UVAuthenticatorImplTest {
DISALLOW_COPY_AND_ASSIGN(ResidentKeyAuthenticatorImplTest);
};
TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredential) {
TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkRequired) {
for (const bool internal_uv : {false, true}) {
SCOPED_TRACE(::testing::Message() << "internal_uv=" << internal_uv);
test_client_.might_create_resident_credential = false;
......@@ -4696,6 +4702,78 @@ TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredential) {
}
}
TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferred) {
for (const bool supports_rk : {false, true}) {
SCOPED_TRACE(::testing::Message() << "supports_rk=" << supports_rk);
ResetVirtualDevice();
test_client_.might_create_resident_credential = false;
device::VirtualCtap2Device::Config config;
config.internal_uv_support = true;
config.resident_key_support = supports_rk;
virtual_device_factory_->SetCtap2Config(config);
virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
MakeCredentialResult result = AuthenticatorMakeCredential(
make_credential_options(device::ResidentKeyRequirement::kPreferred));
ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
EXPECT_TRUE(test_client_.might_create_resident_credential);
EXPECT_TRUE(HasUV(result.response));
ASSERT_EQ(1u,
virtual_device_factory_->mutable_state()->registrations.size());
const device::VirtualFidoDevice::RegistrationData& registration =
virtual_device_factory_->mutable_state()->registrations.begin()->second;
EXPECT_EQ(registration.is_resident, supports_rk);
}
}
TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferredStorageFull) {
// Making a credential on an authenticator with full storage falls back to
// making a non-resident key.
ResetVirtualDevice();
test_client_.might_create_resident_credential = false;
device::VirtualCtap2Device::Config config;
config.internal_uv_support = true;
config.resident_key_support = true;
config.resident_credential_storage = 0;
virtual_device_factory_->SetCtap2Config(config);
virtual_device_factory_->mutable_state()->fingerprints_enrolled = true;
MakeCredentialResult result = AuthenticatorMakeCredential(
make_credential_options(device::ResidentKeyRequirement::kPreferred));
ASSERT_EQ(AuthenticatorStatus::SUCCESS, result.status);
EXPECT_TRUE(test_client_.might_create_resident_credential);
EXPECT_TRUE(HasUV(result.response));
ASSERT_EQ(1u, virtual_device_factory_->mutable_state()->registrations.size());
const device::VirtualFidoDevice::RegistrationData& registration =
virtual_device_factory_->mutable_state()->registrations.begin()->second;
EXPECT_EQ(registration.is_resident, false);
}
TEST_F(ResidentKeyAuthenticatorImplTest, MakeCredentialRkPreferredSetsPIN) {
device::VirtualCtap2Device::Config config;
config.pin_support = true;
config.internal_uv_support = false;
config.resident_key_support = true;
virtual_device_factory_->SetCtap2Config(config);
virtual_device_factory_->mutable_state()->pin = "";
MakeCredentialResult result = AuthenticatorMakeCredential(
make_credential_options(device::ResidentKeyRequirement::kPreferred));
EXPECT_EQ(AuthenticatorStatus::SUCCESS, result.status);
EXPECT_TRUE(test_client_.might_create_resident_credential);
EXPECT_TRUE(HasUV(result.response));
ASSERT_EQ(1u, virtual_device_factory_->mutable_state()->registrations.size());
const device::VirtualFidoDevice::RegistrationData& registration =
virtual_device_factory_->mutable_state()->registrations.begin()->second;
EXPECT_EQ(registration.is_resident, true);
EXPECT_EQ(virtual_device_factory_->mutable_state()->pin, kTestPIN);
}
TEST_F(ResidentKeyAuthenticatorImplTest, StorageFull) {
device::VirtualCtap2Device::Config config;
config.resident_key_support = true;
......@@ -4900,8 +4978,9 @@ TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) {
<< "support=" << test.supported_by_authenticator);
PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
options->authenticator_selection->SetRequireResidentKeyForTesting(
test.is_resident);
options->authenticator_selection->SetResidentKeyForTesting(
test.is_resident ? device::ResidentKeyRequirement::kRequired
: device::ResidentKeyRequirement::kDiscouraged);
options->protection_policy = test.protection;
options->enforce_protection_policy = test.enforce;
options->authenticator_selection->SetUserVerificationRequirementForTesting(
......@@ -4977,7 +5056,8 @@ TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorSetsCredProtect) {
virtual_device_factory_->mutable_state()->registrations.clear();
PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
options->authenticator_selection->SetRequireResidentKeyForTesting(true);
options->authenticator_selection->SetResidentKeyForTesting(
device::ResidentKeyRequirement::kRequired);
options->protection_policy = kMojoLevels[requested_level];
options->authenticator_selection
->SetUserVerificationRequirementForTesting(
......@@ -5067,7 +5147,8 @@ TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorDefaultCredProtect) {
<< ProtectionPolicyDescription(test.requested_level));
PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
options->authenticator_selection->SetRequireResidentKeyForTesting(true);
options->authenticator_selection->SetResidentKeyForTesting(
device::ResidentKeyRequirement::kRequired);
options->protection_policy = test.requested_level;
options->authenticator_selection->SetUserVerificationRequirementForTesting(
device::UserVerificationRequirement::kRequired);
......@@ -5160,7 +5241,8 @@ TEST_F(ResidentKeyAuthenticatorImplTest, WinCredProtectApiVersion) {
options->relying_party.name = "";
options->authenticator_selection->SetUserVerificationRequirementForTesting(
device::UserVerificationRequirement::kRequired);
options->authenticator_selection->SetRequireResidentKeyForTesting(true);
options->authenticator_selection->SetResidentKeyForTesting(
device::ResidentKeyRequirement::kRequired);
options->protection_policy =
blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED;
options->enforce_protection_policy = true;
......@@ -5191,8 +5273,9 @@ TEST_F(ResidentKeyAuthenticatorImplTest, PRFExtension) {
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->prf_enable = true;
options->authenticator_selection->SetRequireResidentKeyForTesting(
hmac_secret_supported);
options->authenticator_selection->SetResidentKeyForTesting(
hmac_secret_supported ? device::ResidentKeyRequirement::kRequired
: device::ResidentKeyRequirement::kDiscouraged);
options->user.id = {1, 2, 3, 4};
options->user.name = "name";
options->user.display_name = "displayName";
......
......@@ -150,6 +150,43 @@ bool EnumTraits<blink::mojom::AuthenticatorAttachment,
return false;
}
// static
blink::mojom::ResidentKeyRequirement EnumTraits<
blink::mojom::ResidentKeyRequirement,
device::ResidentKeyRequirement>::ToMojom(device::ResidentKeyRequirement
input) {
switch (input) {
case ::device::ResidentKeyRequirement::kDiscouraged:
return blink::mojom::ResidentKeyRequirement::DISCOURAGED;
case ::device::ResidentKeyRequirement::kPreferred:
return blink::mojom::ResidentKeyRequirement::PREFERRED;
case ::device::ResidentKeyRequirement::kRequired:
return blink::mojom::ResidentKeyRequirement::REQUIRED;
}
NOTREACHED();
return blink::mojom::ResidentKeyRequirement::DISCOURAGED;
}
// static
bool EnumTraits<blink::mojom::ResidentKeyRequirement,
device::ResidentKeyRequirement>::
FromMojom(blink::mojom::ResidentKeyRequirement input,
device::ResidentKeyRequirement* output) {
switch (input) {
case blink::mojom::ResidentKeyRequirement::DISCOURAGED:
*output = ::device::ResidentKeyRequirement::kDiscouraged;
return true;
case blink::mojom::ResidentKeyRequirement::PREFERRED:
*output = ::device::ResidentKeyRequirement::kPreferred;
return true;
case blink::mojom::ResidentKeyRequirement::REQUIRED:
*output = ::device::ResidentKeyRequirement::kRequired;
return true;
}
NOTREACHED();
return false;
}
// static
blink::mojom::UserVerificationRequirement
EnumTraits<blink::mojom::UserVerificationRequirement,
......@@ -193,16 +230,16 @@ bool StructTraits<blink::mojom::AuthenticatorSelectionCriteriaDataView,
Read(blink::mojom::AuthenticatorSelectionCriteriaDataView data,
device::AuthenticatorSelectionCriteria* out) {
device::AuthenticatorAttachment authenticator_attachment;
bool require_resident_key = data.require_resident_key();
device::UserVerificationRequirement user_verification_requirement;
device::ResidentKeyRequirement resident_key;
if (!data.ReadAuthenticatorAttachment(&authenticator_attachment) ||
!data.ReadUserVerification(&user_verification_requirement)) {
!data.ReadUserVerification(&user_verification_requirement) ||
!data.ReadResidentKey(&resident_key)) {
return false;
}
*out = device::AuthenticatorSelectionCriteria(authenticator_attachment,
require_resident_key,
user_verification_requirement);
*out = device::AuthenticatorSelectionCriteria(
authenticator_attachment, resident_key, user_verification_requirement);
return true;
}
......
......@@ -99,6 +99,15 @@ struct BLINK_COMMON_EXPORT EnumTraits<blink::mojom::AuthenticatorAttachment,
device::AuthenticatorAttachment* output);
};
template <>
struct BLINK_COMMON_EXPORT EnumTraits<blink::mojom::ResidentKeyRequirement,
device::ResidentKeyRequirement> {
static blink::mojom::ResidentKeyRequirement ToMojom(
device::ResidentKeyRequirement input);
static bool FromMojom(blink::mojom::ResidentKeyRequirement input,
device::ResidentKeyRequirement* output);
};
template <>
struct BLINK_COMMON_EXPORT EnumTraits<blink::mojom::UserVerificationRequirement,
device::UserVerificationRequirement> {
......@@ -117,9 +126,9 @@ struct BLINK_COMMON_EXPORT
return in.authenticator_attachment();
}
static bool require_resident_key(
static device::ResidentKeyRequirement resident_key(
const device::AuthenticatorSelectionCriteria& in) {
return in.require_resident_key();
return in.resident_key();
}
static device::UserVerificationRequirement user_verification(
......
......@@ -35,6 +35,7 @@ using device::PublicKeyCredentialDescriptor;
using device::PublicKeyCredentialParams;
using device::PublicKeyCredentialRpEntity;
using device::PublicKeyCredentialUserEntity;
using device::ResidentKeyRequirement;
using device::UserVerificationRequirement;
const std::vector<uint8_t> kDescriptorId = {'d', 'e', 's', 'c'};
......@@ -104,12 +105,18 @@ TEST(AuthenticatorMojomTraitsTest, SerializeCredentialDescriptors) {
// Verify serialization and deserialization of AuthenticatorSelectionCriteria.
TEST(AuthenticatorMojomTraitsTest, SerializeAuthenticatorSelectionCriteria) {
std::vector<AuthenticatorSelectionCriteria> success_cases = {
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kAny, true,
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kAny,
ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kRequired),
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform, false,
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform,
ResidentKeyRequirement::kPreferred,
UserVerificationRequirement::kPreferred),
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform,
ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred),
AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kCrossPlatform, true,
AuthenticatorAttachment::kCrossPlatform,
ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kDiscouraged)};
AssertSerializeAndDeserializeSucceeds<
......
......@@ -10,10 +10,10 @@ AuthenticatorSelectionCriteria::AuthenticatorSelectionCriteria() = default;
AuthenticatorSelectionCriteria::AuthenticatorSelectionCriteria(
AuthenticatorAttachment authenticator_attachment,
bool require_resident_key,
ResidentKeyRequirement resident_key,
UserVerificationRequirement user_verification_requirement)
: authenticator_attachment_(authenticator_attachment),
require_resident_key_(require_resident_key),
resident_key_(resident_key),
user_verification_requirement_(user_verification_requirement) {}
AuthenticatorSelectionCriteria::AuthenticatorSelectionCriteria(
......@@ -31,7 +31,7 @@ AuthenticatorSelectionCriteria& AuthenticatorSelectionCriteria::operator=(
bool AuthenticatorSelectionCriteria::operator==(
const AuthenticatorSelectionCriteria& other) const {
return authenticator_attachment_ == other.authenticator_attachment_ &&
require_resident_key_ == other.require_resident_key_ &&
resident_key_ == other.resident_key_ &&
user_verification_requirement_ == other.user_verification_requirement_;
}
......
......@@ -7,18 +7,20 @@
#include "base/component_export.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_types.h"
namespace device {
// Represents authenticator properties the relying party can specify to restrict
// the type of authenticator used in creating credentials.
//
// https://w3c.github.io/webauthn/#authenticatorSelection
class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
public:
AuthenticatorSelectionCriteria();
AuthenticatorSelectionCriteria(
AuthenticatorAttachment authenticator_attachment,
bool require_resident_key,
ResidentKeyRequirement resident_key,
UserVerificationRequirement user_verification_requirement);
AuthenticatorSelectionCriteria(const AuthenticatorSelectionCriteria& other);
AuthenticatorSelectionCriteria(AuthenticatorSelectionCriteria&& other);
......@@ -33,7 +35,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
return authenticator_attachment_;
}
bool require_resident_key() const { return require_resident_key_; }
ResidentKeyRequirement resident_key() const { return resident_key_; }
UserVerificationRequirement user_verification_requirement() const {
return user_verification_requirement_;
......@@ -43,8 +45,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
AuthenticatorAttachment attachment) {
authenticator_attachment_ = attachment;
}
void SetRequireResidentKeyForTesting(bool require) {
require_resident_key_ = require;
void SetResidentKeyForTesting(ResidentKeyRequirement resident_key) {
resident_key_ = resident_key;
}
void SetUserVerificationRequirementForTesting(
UserVerificationRequirement uv) {
......@@ -54,7 +56,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
private:
AuthenticatorAttachment authenticator_attachment_ =
AuthenticatorAttachment::kAny;
bool require_resident_key_ = false;
ResidentKeyRequirement resident_key_ = ResidentKeyRequirement::kDiscouraged;
UserVerificationRequirement user_verification_requirement_ =
UserVerificationRequirement::kPreferred;
};
......
......@@ -29,6 +29,16 @@ enum class AuthenticatorAttachment {
kCrossPlatform,
};
// A constraint on whether a client-side discoverable (resident) credential
// should be created during registration.
//
// https://w3c.github.io/webauthn/#enum-residentKeyRequirement
enum class ResidentKeyRequirement {
kDiscouraged,
kPreferred,
kRequired,
};
// User verification constraint passed on from the relying party as a parameter
// for AuthenticatorSelectionCriteria and for CtapGetAssertion request.
// https://w3c.github.io/webauthn/#enumdef-userverificationrequirement
......
......@@ -110,7 +110,8 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
}
const auto& auth_options = authenticator->Options();
const base::Optional<AuthenticatorSupportedOptions>& auth_options =
authenticator->Options();
if (!auth_options) {
// This authenticator doesn't know its capabilities yet, so we need
// to assume it can handle the request. This is the case for Windows,
......@@ -118,7 +119,8 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
return MakeCredentialStatus::kSuccess;
}
if (options.require_resident_key && !auth_options->supports_resident_key) {
if (options.resident_key == ResidentKeyRequirement::kRequired &&
!auth_options->supports_resident_key) {
return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
}
......@@ -327,18 +329,9 @@ MakeCredentialRequestHandler::Options::Options(
const AuthenticatorSelectionCriteria& authenticator_selection_criteria)
: authenticator_attachment(
authenticator_selection_criteria.authenticator_attachment()),
require_resident_key(
authenticator_selection_criteria.require_resident_key()),
resident_key(authenticator_selection_criteria.resident_key()),
user_verification(
authenticator_selection_criteria.user_verification_requirement()) {}
MakeCredentialRequestHandler::Options::Options(
const AuthenticatorSelectionCriteria& authenticator_selection_criteria,
CredProtectRequest cred_protect_request_,
bool enforce_cred_protect_policy)
: Options(authenticator_selection_criteria) {
cred_protect_request.emplace(std::move(cred_protect_request_),
enforce_cred_protect_policy);
}
MakeCredentialRequestHandler::Options::Options(Options&&) = default;
MakeCredentialRequestHandler::Options&
MakeCredentialRequestHandler::Options::operator=(const Options&) = default;
......@@ -361,6 +354,8 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler(
options_(options) {
// These parts of the request should be filled in by
// |SpecializeRequestForAuthenticator|.
DCHECK_EQ(request_.authenticator_attachment, AuthenticatorAttachment::kAny);
DCHECK(!request_.resident_key_required);
DCHECK(!request_.cred_protect);
DCHECK(!request_.android_client_data_ext);
DCHECK(!request_.cred_protect_enforce);
......@@ -368,17 +363,6 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler(
transport_availability_info().request_type =
FidoRequestHandlerBase::RequestType::kMakeCredential;
// Set the rk, uv and attachment fields, which were only initialized to
// default values up to here.
if (options_.require_resident_key) {
request_.resident_key_required = true;
request_.user_verification = UserVerificationRequirement::kRequired;
} else {
request_.resident_key_required = false;
request_.user_verification = options_.user_verification;
}
request_.authenticator_attachment = options_.authenticator_attachment;
Start();
}
......@@ -573,6 +557,24 @@ void MakeCredentialRequestHandler::HandleResponse(
return;
}
if (options_.resident_key == ResidentKeyRequirement::kPreferred &&
request->resident_key_required &&
status == CtapDeviceResponseCode::kCtap2ErrKeyStoreFull) {
// TODO(crbug/1117630): This probably requires a second touch and we should
// add UI for that. PR #962 aims to change CTAP2.1 to return this error
// before UP, so we might need to gate this on the supported CTAP version.
FIDO_LOG(DEBUG) << "Downgrading rk=preferred to non-resident credential "
"because key storage is full";
request->resident_key_required = false;
CtapMakeCredentialRequest request_copy(*request);
authenticator->MakeCredential(
std::move(request_copy),
base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
weak_factory_.GetWeakPtr(), authenticator,
std::move(request), base::ElapsedTimer()));
return;
}
const base::Optional<MakeCredentialStatus> maybe_result =
ConvertDeviceResponseCode(status);
if (!maybe_result) {
......@@ -969,6 +971,37 @@ void MakeCredentialRequestHandler::DispatchRequestWithToken(
void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
CtapMakeCredentialRequest* request,
const FidoAuthenticator* authenticator) {
// Only Windows cares about |authenticator_attachment| on the request.
request->authenticator_attachment = options_.authenticator_attachment;
const base::Optional<AuthenticatorSupportedOptions>& auth_options =
authenticator->Options();
switch (options_.resident_key) {
case ResidentKeyRequirement::kRequired:
request->resident_key_required = true;
request->user_verification = UserVerificationRequirement::kRequired;
break;
case ResidentKeyRequirement::kPreferred: {
// Create a resident key if the authenticator supports it and the UI is
// capable of prompting for PIN/UV.
request->resident_key_required =
#if defined(OS_WIN)
// Windows does not yet support rk=preferred.
!authenticator->IsWinNativeApiAuthenticator() &&
#endif
observer()->SupportsPIN() && auth_options &&
auth_options->supports_resident_key;
break;
}
case ResidentKeyRequirement::kDiscouraged:
request->resident_key_required = false;
break;
}
request->user_verification = request->resident_key_required
? UserVerificationRequirement::kRequired
: options_.user_verification;
if (options_.cred_protect_request &&
authenticator->SupportsCredProtectExtension()) {
request->cred_protect = CredProtectForAuthenticator(
......@@ -976,8 +1009,8 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
request->cred_protect_enforce = options_.cred_protect_request->second;
}
if (options_.android_client_data_ext && authenticator->Options() &&
authenticator->Options()->supports_android_client_data_ext) {
if (options_.android_client_data_ext && auth_options &&
auth_options->supports_android_client_data_ext) {
request->android_client_data_ext = *options_.android_client_data_ext;
}
......
......@@ -78,10 +78,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
Options();
Options(
const AuthenticatorSelectionCriteria& authenticator_selection_criteria);
Options(
const AuthenticatorSelectionCriteria& authenticator_selection_criteria,
CredProtectRequest cred_protect_request,
bool enforce_cred_protect_policy);
~Options();
Options(const Options&);
Options(Options&&);
......@@ -93,9 +89,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
AuthenticatorAttachment authenticator_attachment =
AuthenticatorAttachment::kAny;
// require_resident_key indicates whether the request must result in the
// creation of a client-side discoverable credential (aka resident key).
bool require_resident_key = false;
// resident_key indicates whether the request should result in the creation
// of a client-side discoverable credential (aka resident key).
ResidentKeyRequirement resident_key = ResidentKeyRequirement::kDiscouraged;
// user_verification indicates whether the authenticator should (or must)
// perform user verficiation before creating the credential.
......
......@@ -296,6 +296,13 @@ enum AttestationConveyancePreference {
ENTERPRISE,
};
// https://w3c.github.io/webauthn/#enum-residentKeyRequirement
enum ResidentKeyRequirement {
DISCOURAGED,
PREFERRED,
REQUIRED,
};
// https://w3c.github.io/webauthn/#enumdef-authenticatorattachment.
enum AuthenticatorAttachment {
NO_PREFERENCE,
......@@ -316,10 +323,9 @@ struct AuthenticatorSelectionCriteria {
// Filter authenticators by attachment type.
AuthenticatorAttachment authenticator_attachment;
// Whether the authenticator should store the created key so that the key
// can later be selected given only an RP ID (e.g. when |allow_credentials|
// is empty).
bool require_resident_key;
// Indicates a requirement or preference for creating a client-side
// discoverable credential. If set, overrides |require_resident_key|.
ResidentKeyRequirement resident_key;
// Indicates the relying party's need for a user-verifying authenticator.
UserVerificationRequirement user_verification;
......
......@@ -3,14 +3,12 @@
// found in the LICENSE file.
// https://w3c.github.io/webauthn/#enumdef-authenticatorattachment
enum AuthenticatorAttachment {
"platform",
"cross-platform"
};
// https://w3c.github.io/webauthn/#enumdef-userverificationrequirement
enum UserVerificationRequirement {
"required",
"preferred",
......@@ -18,9 +16,10 @@ enum UserVerificationRequirement {
};
// https://w3c.github.io/webauthn/#dictdef-authenticatorselectioncriteria
dictionary AuthenticatorSelectionCriteria {
AuthenticatorAttachment authenticatorAttachment;
boolean requireResidentKey = false;
// A DOMString expressing a ResidentKeyRequirement.
[RuntimeEnabled=WebAuthenticationResidentKeyRequirement] DOMString residentKey;
UserVerificationRequirement userVerification;
};
......@@ -40,19 +40,20 @@ using blink::mojom::blink::CableRegistration;
using blink::mojom::blink::CableRegistrationPtr;
using blink::mojom::blink::CredentialInfo;
using blink::mojom::blink::CredentialInfoPtr;
using blink::mojom::blink::CredentialType;
using blink::mojom::blink::CredentialManagerError;
using blink::mojom::blink::CredentialType;
using blink::mojom::blink::PublicKeyCredentialCreationOptionsPtr;
using blink::mojom::blink::PublicKeyCredentialDescriptor;
using blink::mojom::blink::PublicKeyCredentialDescriptorPtr;
using blink::mojom::blink::PublicKeyCredentialRpEntity;
using blink::mojom::blink::PublicKeyCredentialRpEntityPtr;
using blink::mojom::blink::PublicKeyCredentialUserEntity;
using blink::mojom::blink::PublicKeyCredentialUserEntityPtr;
using blink::mojom::blink::PublicKeyCredentialParameters;
using blink::mojom::blink::PublicKeyCredentialParametersPtr;
using blink::mojom::blink::PublicKeyCredentialRequestOptionsPtr;
using blink::mojom::blink::PublicKeyCredentialRpEntity;
using blink::mojom::blink::PublicKeyCredentialRpEntityPtr;
using blink::mojom::blink::PublicKeyCredentialType;
using blink::mojom::blink::PublicKeyCredentialUserEntity;
using blink::mojom::blink::PublicKeyCredentialUserEntityPtr;
using blink::mojom::blink::ResidentKeyRequirement;
using blink::mojom::blink::UserVerificationRequirement;
namespace {
......@@ -145,8 +146,7 @@ TypeConverter<CredentialManagerError, AuthenticatorStatus>::Convert(
return CredentialManagerError::ANDROID_ALGORITHM_UNSUPPORTED;
case blink::mojom::blink::AuthenticatorStatus::EMPTY_ALLOW_CREDENTIALS:
return CredentialManagerError::ANDROID_EMPTY_ALLOW_CREDENTIALS;
case blink::mojom::blink::AuthenticatorStatus::
ANDROID_NOT_SUPPORTED_ERROR:
case blink::mojom::blink::AuthenticatorStatus::ANDROID_NOT_SUPPORTED_ERROR:
return CredentialManagerError::ANDROID_NOT_SUPPORTED_ERROR;
case blink::mojom::blink::AuthenticatorStatus::
USER_VERIFICATION_UNSUPPORTED:
......@@ -248,6 +248,23 @@ String TypeConverter<String, AuthenticatorTransport>::Convert(
return "usb";
}
// static
base::Optional<blink::mojom::blink::ResidentKeyRequirement>
TypeConverter<base::Optional<blink::mojom::blink::ResidentKeyRequirement>,
String>::Convert(const String& requirement) {
if (requirement == "discouraged")
return ResidentKeyRequirement::DISCOURAGED;
if (requirement == "preferred")
return ResidentKeyRequirement::PREFERRED;
if (requirement == "required")
return ResidentKeyRequirement::REQUIRED;
// AuthenticatorSelection.resident_key is defined as DOMString expressing a
// ResidentKeyRequirement and unknown values must be treated as if the
// property were unset.
return base::nullopt;
}
// static
UserVerificationRequirement
TypeConverter<UserVerificationRequirement, String>::Convert(
......@@ -304,7 +321,18 @@ TypeConverter<AuthenticatorSelectionCriteriaPtr,
attachment = criteria.authenticatorAttachment();
mojo_criteria->authenticator_attachment =
ConvertTo<AuthenticatorAttachment>(attachment);
mojo_criteria->require_resident_key = criteria.requireResidentKey();
base::Optional<ResidentKeyRequirement> resident_key;
if (criteria.hasResidentKey()) {
resident_key = ConvertTo<base::Optional<ResidentKeyRequirement>>(
criteria.residentKey());
}
if (resident_key) {
mojo_criteria->resident_key = *resident_key;
} else {
mojo_criteria->resident_key = criteria.requireResidentKey()
? ResidentKeyRequirement::REQUIRED
: ResidentKeyRequirement::DISCOURAGED;
}
mojo_criteria->user_verification = UserVerificationRequirement::PREFERRED;
if (criteria.hasUserVerification()) {
mojo_criteria->user_verification = ConvertTo<UserVerificationRequirement>(
......
......@@ -75,6 +75,14 @@ struct TypeConverter<String, blink::mojom::blink::AuthenticatorTransport> {
static String Convert(const blink::mojom::blink::AuthenticatorTransport&);
};
template <>
struct TypeConverter<
base::Optional<blink::mojom::blink::ResidentKeyRequirement>,
String> {
static base::Optional<blink::mojom::blink::ResidentKeyRequirement> Convert(
const String&);
};
template <>
struct TypeConverter<blink::mojom::blink::UserVerificationRequirement, String> {
static blink::mojom::blink::UserVerificationRequirement Convert(
......
......@@ -642,7 +642,8 @@ void CreatePublicKeyCredentialForPaymentCredential(
mojom::blink::AuthenticatorSelectionCriteria::New();
selection_criteria->authenticator_attachment =
mojom::blink::AuthenticatorAttachment::PLATFORM;
selection_criteria->require_resident_key = false;
selection_criteria->resident_key =
mojom::blink::ResidentKeyRequirement::DISCOURAGED;
selection_criteria->user_verification =
mojom::blink::UserVerificationRequirement::REQUIRED;
mojo_options->authenticator_selection = std::move(selection_criteria);
......@@ -804,8 +805,8 @@ ScriptPromise CredentialsContainer::get(
if (!options->publicKey()->hasUserVerification()) {
resolver->GetFrame()->Console().AddMessage(MakeGarbageCollected<
ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kWarning,
"publicKey.userVerification was not set to any value in Web "
"Authentication navigator.credentials.get() call. This defaults to "
"'preferred', which is probably not what you want. If in doubt, set "
......@@ -1061,8 +1062,8 @@ ScriptPromise CredentialsContainer::create(
->hasUserVerification()) {
resolver->GetFrame()->Console().AddMessage(MakeGarbageCollected<
ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning,
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kWarning,
"publicKey.authenticatorSelection.userVerification was not set to "
"any value in Web Authentication navigator.credentials.create() "
"call. This defaults to 'preferred', which is probably not what you "
......@@ -1070,7 +1071,17 @@ ScriptPromise CredentialsContainer::create(
"https://chromium.googlesource.com/chromium/src/+/master/content/"
"browser/webauth/uv_preferred.md for details"));
}
if (options->publicKey()->hasAuthenticatorSelection() &&
options->publicKey()->authenticatorSelection()->hasResidentKey() &&
!mojo::ConvertTo<base::Optional<mojom::blink::ResidentKeyRequirement>>(
options->publicKey()->authenticatorSelection()->residentKey())) {
resolver->GetFrame()->Console().AddMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kJavaScript,
mojom::blink::ConsoleMessageLevel::kWarning,
"Ignoring unknown publicKey.authenticatorSelection.resident_key "
"value"));
}
auto mojo_options =
MojoPublicKeyCredentialCreationOptions::From(*options->publicKey());
if (!mojo_options) {
......
......@@ -2012,6 +2012,10 @@
name: "WebAuthenticationGetAssertionFeaturePolicy",
status: "experimental",
},
{
name: "WebAuthenticationResidentKeyRequirement",
status: "experimental",
},
// WebBluetooth is enabled by default on Android, ChromeOS and Mac.
// It is also supported in Windows 10 which is handled in runtime_features.cc
{
......
......@@ -46,7 +46,26 @@ promise_test(async t => {
var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
customMakeCredOptions.authenticatorSelection.requireResidentKey = true;
return navigator.credentials.create({ publicKey : customMakeCredOptions });
}, "navigator.credentials.create() with resident keys is supported");
}, "navigator.credentials.create() with requireResidentKey is supported");
promise_test(async t => {
var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
customMakeCredOptions.authenticatorSelection.requireResidentKey = false;
customMakeCredOptions.authenticatorSelection.residentKey = "required";
return navigator.credentials.create({ publicKey : customMakeCredOptions });
}, "navigator.credentials.create() resident_key overrides resident_key_required");
promise_test(async t => {
var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
customMakeCredOptions.authenticatorSelection.residentKey = "required";
return navigator.credentials.create({ publicKey : customMakeCredOptions });
}, "navigator.credentials.create() with resident_key is supported");
promise_test(async t => {
var customMakeCredOptions = deepCopy(MAKE_CREDENTIAL_OPTIONS);
customMakeCredOptions.authenticatorSelection.resident_key = "foobarbaz";
return navigator.credentials.create({ publicKey : customMakeCredOptions });
}, "navigator.credentials.create() invalid resident_key value is ignored");
promise_test(async t => {
authAbortController = new AbortController();
......
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