Commit 6361e1e8 authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

webauthn: support non-standard default credProtect levels.

In the latest CTAP 2.1 draft, an authenticator can have a default
credProtect level other than one. Previously, Chromium couldn't
distinguish level one from not-specified thus, first, this change adds
that distinction.

Then there are additional complexities because Chromium sets a default
credProtect level of two for discoverable credentials, but we don't want
to override an authenticator default of three. Therefore the credProtect
level for a request becomes a property that can only be resolved in the
context of the specific authenticator that will receive the request.

We already have a property like this: the Android ClientDataJSON
extension. Thus pull these meta-level request properties into a
different structure so that MakeCredentialRequestHandler can craft
per-authenticator requests from that when needed.

This isn't perfectly clean because the Windows API acts as an
authenticator, but is actually a front for many authenticators. So we
have to stuff |cred_protect_enforce| in the request object, despite it
being a meta-level property.

BUG=1057126

Change-Id: Id1c02d4689492d597f5f29674166b97a8e720d2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2130869Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#756380}
parent 22aaaa25
...@@ -562,12 +562,14 @@ void AuthenticatorCommon::StartMakeCredentialRequest( ...@@ -562,12 +562,14 @@ void AuthenticatorCommon::StartMakeCredentialRequest(
} }
} }
make_credential_options_->allow_skipping_pin_touch = allow_skipping_pin_touch;
request_ = std::make_unique<device::MakeCredentialRequestHandler>( request_ = std::make_unique<device::MakeCredentialRequestHandler>(
discovery_factory_, discovery_factory_,
GetAvailableTransports(render_frame_host_, request_delegate_.get(), GetAvailableTransports(render_frame_host_, request_delegate_.get(),
caller_origin_), caller_origin_),
*ctap_make_credential_request_, *authenticator_selection_criteria_, *ctap_make_credential_request_, *authenticator_selection_criteria_,
allow_skipping_pin_touch, *make_credential_options_,
base::BindOnce(&AuthenticatorCommon::OnRegisterResponse, base::BindOnce(&AuthenticatorCommon::OnRegisterResponse,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
...@@ -797,12 +799,31 @@ void AuthenticatorCommon::MakeCredential( ...@@ -797,12 +799,31 @@ void AuthenticatorCommon::MakeCredential(
return; return;
} }
if (options->protection_policy == base::Optional<device::CredProtectRequest> cred_protect_request;
blink::mojom::ProtectionPolicy::UNSPECIFIED && switch (options->protection_policy) {
resident_key) { case blink::mojom::ProtectionPolicy::UNSPECIFIED:
// If not specified, UV_OR_CRED_ID_REQUIRED is made the default. if (resident_key) {
options->protection_policy = // If not specified, kUVOrCredIDRequired is made the default unless the
blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED; // authenticator defaults to something better.
cred_protect_request =
device::CredProtectRequest::kUVOrCredIDRequiredOrBetter;
}
break;
case blink::mojom::ProtectionPolicy::NONE:
cred_protect_request = device::CredProtectRequest::kUVOptional;
break;
case blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED:
cred_protect_request = device::CredProtectRequest::kUVOrCredIDRequired;
break;
case blink::mojom::ProtectionPolicy::UV_REQUIRED:
cred_protect_request = device::CredProtectRequest::kUVRequired;
break;
}
make_credential_options_.emplace();
if (cred_protect_request) {
make_credential_options_->cred_protect_request.emplace(
*cred_protect_request, options->enforce_protection_policy);
} }
DCHECK(make_credential_response_callback_.is_null()); DCHECK(make_credential_response_callback_.is_null());
...@@ -855,7 +876,7 @@ void AuthenticatorCommon::MakeCredential( ...@@ -855,7 +876,7 @@ void AuthenticatorCommon::MakeCredential(
// NOTE: Because Android has no way of building a clientDataJSON for // NOTE: Because Android has no way of building a clientDataJSON for
// cross-origin requests, we don't create the extension for those. This // cross-origin requests, we don't create the extension for those. This
// problem will go away once we add clientDataHash inputs to Android. // problem will go away once we add clientDataHash inputs to Android.
ctap_make_credential_request_->android_client_data_ext.emplace( make_credential_options_->android_client_data_ext.emplace(
client_data::kCreateType, caller_origin_, options->challenge); client_data::kCreateType, caller_origin_, options->challenge);
} }
...@@ -871,21 +892,6 @@ void AuthenticatorCommon::MakeCredential( ...@@ -871,21 +892,6 @@ void AuthenticatorCommon::MakeCredential(
attestation_requested_ = attestation_requested_ =
attestation != ::device::AttestationConveyancePreference::kNone; attestation != ::device::AttestationConveyancePreference::kNone;
switch (options->protection_policy) {
case blink::mojom::ProtectionPolicy::UNSPECIFIED:
case blink::mojom::ProtectionPolicy::NONE:
break;
case blink::mojom::ProtectionPolicy::UV_OR_CRED_ID_REQUIRED:
ctap_make_credential_request_->cred_protect =
std::make_pair(device::CredProtect::kUVOrCredIDRequired,
options->enforce_protection_policy);
break;
case blink::mojom::ProtectionPolicy::UV_REQUIRED:
ctap_make_credential_request_->cred_protect = std::make_pair(
device::CredProtect::kUVRequired, options->enforce_protection_policy);
break;
}
StartMakeCredentialRequest(/*allow_skipping_pin_touch=*/true); StartMakeCredentialRequest(/*allow_skipping_pin_touch=*/true);
} }
...@@ -1491,6 +1497,8 @@ void AuthenticatorCommon::Cleanup() { ...@@ -1491,6 +1497,8 @@ void AuthenticatorCommon::Cleanup() {
discovery_factory_->ResetRequestState(); discovery_factory_->ResetRequestState();
discovery_factory_ = nullptr; discovery_factory_ = nullptr;
} }
ctap_make_credential_request_.reset();
make_credential_options_.reset();
request_delegate_.reset(); request_delegate_.reset();
make_credential_response_callback_.Reset(); make_credential_response_callback_.Reset();
get_assertion_response_callback_.Reset(); get_assertion_response_callback_.Reset();
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "device/fido/ctap_make_credential_request.h" #include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h" #include "device/fido/fido_constants.h"
#include "device/fido/fido_transport_protocol.h" #include "device/fido/fido_transport_protocol.h"
#include "device/fido/make_credential_request_handler.h"
#include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h" #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
#include "url/origin.h" #include "url/origin.h"
...@@ -189,6 +190,8 @@ class CONTENT_EXPORT AuthenticatorCommon { ...@@ -189,6 +190,8 @@ class CONTENT_EXPORT AuthenticatorCommon {
base::Optional<std::string> app_id_; base::Optional<std::string> app_id_;
base::Optional<device::CtapMakeCredentialRequest> base::Optional<device::CtapMakeCredentialRequest>
ctap_make_credential_request_; ctap_make_credential_request_;
base::Optional<device::MakeCredentialRequestHandler::Options>
make_credential_options_;
base::Optional<device::CtapGetAssertionRequest> ctap_get_assertion_request_; base::Optional<device::CtapGetAssertionRequest> ctap_get_assertion_request_;
// awaiting_attestation_response_ is true if the embedder has been queried // awaiting_attestation_response_ is true if the embedder has been queried
// about an attestsation decision and the response is still pending. // about an attestsation decision and the response is still pending.
......
...@@ -4665,6 +4665,17 @@ static const char* ProtectionPolicyDescription( ...@@ -4665,6 +4665,17 @@ static const char* ProtectionPolicyDescription(
} }
} }
static const char* CredProtectDescription(device::CredProtect cred_protect) {
switch (cred_protect) {
case device::CredProtect::kUVOptional:
return "UV optional";
case device::CredProtect::kUVOrCredIDRequired:
return "UV or cred ID required";
case device::CredProtect::kUVRequired:
return "UV required";
}
}
TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) { TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) {
mojo::Remote<blink::mojom::Authenticator> authenticator = mojo::Remote<blink::mojom::Authenticator> authenticator =
ConnectToAuthenticator(); ConnectToAuthenticator();
...@@ -4767,7 +4778,7 @@ TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) { ...@@ -4767,7 +4778,7 @@ TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) {
EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status()); EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
ASSERT_EQ( ASSERT_EQ(
1u, virtual_device_factory_->mutable_state()->registrations.size()); 1u, virtual_device_factory_->mutable_state()->registrations.size());
const base::Optional<device::CredProtect> result = const device::CredProtect result =
virtual_device_factory_->mutable_state() virtual_device_factory_->mutable_state()
->registrations.begin() ->registrations.begin()
->second.protection; ->second.protection;
...@@ -4777,15 +4788,13 @@ TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) { ...@@ -4777,15 +4788,13 @@ TEST_F(ResidentKeyAuthenticatorImplTest, CredProtectRegistration) {
NOTREACHED(); NOTREACHED();
break; break;
case NONE: case NONE:
EXPECT_FALSE(result); EXPECT_EQ(device::CredProtect::kUVOptional, result);
break; break;
case UV_OR_CRED: case UV_OR_CRED:
ASSERT_TRUE(result); EXPECT_EQ(device::CredProtect::kUVOrCredIDRequired, result);
EXPECT_EQ(device::CredProtect::kUVOrCredIDRequired, *result);
break; break;
case UV_REQ: case UV_REQ:
ASSERT_TRUE(result); EXPECT_EQ(device::CredProtect::kUVRequired, result);
EXPECT_EQ(device::CredProtect::kUVRequired, *result);
break; break;
} }
break; break;
...@@ -4817,7 +4826,7 @@ TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorSetsCredProtect) { ...@@ -4817,7 +4826,7 @@ TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorSetsCredProtect) {
blink::mojom::ProtectionPolicy::UV_REQUIRED, blink::mojom::ProtectionPolicy::UV_REQUIRED,
}; };
constexpr device::CredProtect kDeviceLevels[] = { constexpr device::CredProtect kDeviceLevels[] = {
device::CredProtect::kUVOrCredIDRequired, // dummy entry. device::CredProtect::kUVOptional,
device::CredProtect::kUVOrCredIDRequired, device::CredProtect::kUVOrCredIDRequired,
device::CredProtect::kUVRequired, device::CredProtect::kUVRequired,
}; };
...@@ -4863,6 +4872,95 @@ TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorSetsCredProtect) { ...@@ -4863,6 +4872,95 @@ TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorSetsCredProtect) {
} }
} }
TEST_F(ResidentKeyAuthenticatorImplTest, AuthenticatorDefaultCredProtect) {
// Some authenticators may have a default credProtect level that isn't
// kUVOptional. This has complex interactions that are tested here.
mojo::Remote<blink::mojom::Authenticator> authenticator =
ConnectToAuthenticator();
constexpr struct {
blink::mojom::ProtectionPolicy requested_level;
device::CredProtect authenticator_default;
device::CredProtect result;
} kExpectations[] = {
// Standard case: normal authenticator and nothing specified. Chrome sets
// a default of kUVOrCredIDRequired for discoverable credentials.
{
blink::mojom::ProtectionPolicy::UNSPECIFIED,
device::CredProtect::kUVOptional,
device::CredProtect::kUVOrCredIDRequired,
},
// Chrome's default of |kUVOrCredIDRequired| should not prevent a site
// from requesting |kUVRequired| from a normal authenticator.
{
blink::mojom::ProtectionPolicy::UV_REQUIRED,
device::CredProtect::kUVOptional,
device::CredProtect::kUVRequired,
},
// Authenticator has a non-standard default, which should work fine.
{
blink::mojom::ProtectionPolicy::UNSPECIFIED,
device::CredProtect::kUVOrCredIDRequired,
device::CredProtect::kUVOrCredIDRequired,
},
// Authenticators can have a default of kUVRequired, but Chrome has a
// default of kUVOrCredIDRequired for discoverable credentials. We should
// not get a lesser protection level because of that.
{
blink::mojom::ProtectionPolicy::UNSPECIFIED,
device::CredProtect::kUVRequired,
device::CredProtect::kUVRequired,
},
// Site should be able to explicitly set credProtect kUVOptional despite
// an authenticator default.
{
blink::mojom::ProtectionPolicy::NONE,
device::CredProtect::kUVOrCredIDRequired,
device::CredProtect::kUVOptional,
},
};
device::VirtualCtap2Device::Config config;
config.pin_support = true;
config.resident_key_support = true;
config.cred_protect_support = true;
for (const auto& test : kExpectations) {
config.default_cred_protect = test.authenticator_default;
virtual_device_factory_->SetCtap2Config(config);
virtual_device_factory_->mutable_state()->registrations.clear();
SCOPED_TRACE(::testing::Message()
<< "result=" << CredProtectDescription(test.result));
SCOPED_TRACE(::testing::Message()
<< "default="
<< CredProtectDescription(test.authenticator_default));
SCOPED_TRACE(::testing::Message()
<< "request="
<< ProtectionPolicyDescription(test.requested_level));
PublicKeyCredentialCreationOptionsPtr options = make_credential_options();
options->authenticator_selection->SetRequireResidentKeyForTesting(true);
options->protection_policy = test.requested_level;
options->authenticator_selection->SetUserVerificationRequirementForTesting(
device::UserVerificationRequirement::kRequired);
TestMakeCredentialCallback callback_receiver;
authenticator->MakeCredential(std::move(options),
callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
ASSERT_EQ(1u,
virtual_device_factory_->mutable_state()->registrations.size());
const device::CredProtect result = virtual_device_factory_->mutable_state()
->registrations.begin()
->second.protection;
EXPECT_EQ(result, test.result) << CredProtectDescription(result);
}
}
TEST_F(ResidentKeyAuthenticatorImplTest, ProtectedNonResidentCreds) { TEST_F(ResidentKeyAuthenticatorImplTest, ProtectedNonResidentCreds) {
// Until we have UVToken, there's a danger that we'll preflight UV-required // Until we have UVToken, there's a danger that we'll preflight UV-required
// credential IDs such that the authenticator denies knowledge of all of them // credential IDs such that the authenticator denies knowledge of all of them
......
...@@ -87,6 +87,11 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) { ...@@ -87,6 +87,11 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) {
option_map.emplace(kUvTokenMapKey, true); option_map.emplace(kUvTokenMapKey, true);
} }
if (options.default_cred_protect != CredProtect::kUVOptional) {
option_map.emplace(kDefaultCredProtectKey,
static_cast<int64_t>(options.default_cred_protect));
}
return cbor::Value(std::move(option_map)); return cbor::Value(std::move(option_map));
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h" #include "base/optional.h"
#include "components/cbor/values.h" #include "components/cbor/values.h"
#include "device/fido/fido_constants.h"
namespace device { namespace device {
...@@ -77,6 +78,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions { ...@@ -77,6 +78,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions {
// supports_cred_protect is true if the authenticator supports the // supports_cred_protect is true if the authenticator supports the
// `credProtect` extension. See CTAP2 draft for details. // `credProtect` extension. See CTAP2 draft for details.
bool supports_cred_protect = false; bool supports_cred_protect = false;
// default_cred_protect specifies the default credProtect level applied by
// this authenticator.
CredProtect default_cred_protect = CredProtect::kUVOptional;
// Represents whether client pin is set and stored in authenticator. Set as // Represents whether client pin is set and stored in authenticator. Set as
// null optional if client pin capability is not supported by the // null optional if client pin capability is not supported by the
// authenticator. // authenticator.
......
...@@ -130,15 +130,13 @@ base::Optional<CtapMakeCredentialRequest> CtapMakeCredentialRequest::Parse( ...@@ -130,15 +130,13 @@ base::Optional<CtapMakeCredentialRequest> CtapMakeCredentialRequest::Parse(
} }
switch (cred_protect_it->second.GetUnsigned()) { switch (cred_protect_it->second.GetUnsigned()) {
case 1: case 1:
// Default behaviour. request.cred_protect = device::CredProtect::kUVOptional;
break; break;
case 2: case 2:
request.cred_protect = request.cred_protect = device::CredProtect::kUVOrCredIDRequired;
std::make_pair(device::CredProtect::kUVOrCredIDRequired, false);
break; break;
case 3: case 3:
request.cred_protect = request.cred_protect = device::CredProtect::kUVRequired;
std::make_pair(device::CredProtect::kUVRequired, false);
break; break;
default: default:
return base::nullopt; return base::nullopt;
...@@ -253,7 +251,7 @@ AsCTAPRequestValuePair(const CtapMakeCredentialRequest& request) { ...@@ -253,7 +251,7 @@ AsCTAPRequestValuePair(const CtapMakeCredentialRequest& request) {
if (request.cred_protect) { if (request.cred_protect) {
extensions.emplace(kExtensionCredProtect, extensions.emplace(kExtensionCredProtect,
static_cast<uint8_t>(request.cred_protect->first)); static_cast<int64_t>(*request.cred_protect));
} }
if (request.android_client_data_ext) { if (request.android_client_data_ext) {
......
...@@ -80,10 +80,13 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest { ...@@ -80,10 +80,13 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest {
// cred_protect indicates the level of protection afforded to a credential. // cred_protect indicates the level of protection afforded to a credential.
// This depends on a CTAP2 extension that not all authenticators will support. // This depends on a CTAP2 extension that not all authenticators will support.
// The second element is true if the indicated protection level must be // This is filled out by |MakeCredentialRequestHandler|.
// provided by the target authenticator for the MakeCredential request to be base::Optional<CredProtect> cred_protect;
// sent. // If |cred_protect| is not |nullopt|, this is true if the credProtect level
base::Optional<std::pair<CredProtect, bool>> cred_protect; // must be provided by the target authenticator for the MakeCredential request
// to be sent. This only makes sense when there is a collection of
// authenticators to consider, i.e. for the Windows API.
bool cred_protect_enforce = false;
base::Optional<AndroidClientDataExtensionInput> android_client_data_ext; base::Optional<AndroidClientDataExtensionInput> android_client_data_ext;
}; };
......
...@@ -366,6 +366,19 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( ...@@ -366,6 +366,19 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
options.supports_uv_token = option_map_it->second.GetBool(); options.supports_uv_token = option_map_it->second.GetBool();
} }
option_map_it = option_map.find(CBOR(kDefaultCredProtectKey));
if (option_map_it != option_map.end()) {
if (!option_map_it->second.is_unsigned()) {
return base::nullopt;
}
const int64_t value = option_map_it->second.GetInteger();
if (value != static_cast<uint8_t>(CredProtect::kUVOrCredIDRequired) &&
value != static_cast<uint8_t>(CredProtect::kUVRequired)) {
return base::nullopt;
}
options.default_cred_protect = static_cast<CredProtect>(value);
}
response.options = std::move(options); response.options = std::move(options);
} }
......
...@@ -130,4 +130,8 @@ ProtocolVersion FidoAuthenticator::SupportedProtocol() const { ...@@ -130,4 +130,8 @@ ProtocolVersion FidoAuthenticator::SupportedProtocol() const {
return ProtocolVersion::kUnknown; return ProtocolVersion::kUnknown;
} }
bool FidoAuthenticator::SupportsCredProtectExtension() const {
return Options() && Options()->supports_cred_protect;
}
} // namespace device } // namespace device
...@@ -194,6 +194,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { ...@@ -194,6 +194,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
virtual std::string GetId() const = 0; virtual std::string GetId() const = 0;
virtual base::string16 GetDisplayName() const = 0; virtual base::string16 GetDisplayName() const = 0;
virtual ProtocolVersion SupportedProtocol() const; virtual ProtocolVersion SupportedProtocol() const;
virtual bool SupportsCredProtectExtension() const;
virtual const base::Optional<AuthenticatorSupportedOptions>& Options() virtual const base::Optional<AuthenticatorSupportedOptions>& Options()
const = 0; const = 0;
virtual base::Optional<FidoTransportProtocol> AuthenticatorTransport() virtual base::Optional<FidoTransportProtocol> AuthenticatorTransport()
......
...@@ -32,6 +32,7 @@ const char kCredentialManagementPreviewMapKey[] = "credentialMgmtPreview"; ...@@ -32,6 +32,7 @@ const char kCredentialManagementPreviewMapKey[] = "credentialMgmtPreview";
const char kBioEnrollmentMapKey[] = "bioEnroll"; const char kBioEnrollmentMapKey[] = "bioEnroll";
const char kBioEnrollmentPreviewMapKey[] = "userVerificationMgmtPreview"; const char kBioEnrollmentPreviewMapKey[] = "userVerificationMgmtPreview";
const char kUvTokenMapKey[] = "uvToken"; const char kUvTokenMapKey[] = "uvToken";
const char kDefaultCredProtectKey[] = "defaultCredProtect";
const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(20); const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(20);
const base::TimeDelta kU2fRetryDelay = base::TimeDelta::FromMilliseconds(200); const base::TimeDelta kU2fRetryDelay = base::TimeDelta::FromMilliseconds(200);
......
...@@ -283,6 +283,7 @@ extern const char kCredentialManagementPreviewMapKey[]; ...@@ -283,6 +283,7 @@ extern const char kCredentialManagementPreviewMapKey[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentMapKey[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentMapKey[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentPreviewMapKey[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentPreviewMapKey[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kUvTokenMapKey[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kUvTokenMapKey[];
extern const char kDefaultCredProtectKey[];
// HID transport specific constants. // HID transport specific constants.
constexpr uint32_t kHidBroadcastChannel = 0xffffffff; constexpr uint32_t kHidBroadcastChannel = 0xffffffff;
...@@ -365,10 +366,21 @@ extern const base::TimeDelta kBleDevicePairingModeWaitingInterval; ...@@ -365,10 +366,21 @@ extern const base::TimeDelta kBleDevicePairingModeWaitingInterval;
// CredProtect enumerates the levels of credential protection specified by the // CredProtect enumerates the levels of credential protection specified by the
// `credProtect` CTAP2 extension. // `credProtect` CTAP2 extension.
enum class CredProtect : uint8_t { enum class CredProtect : uint8_t {
kUVOptional = 1,
kUVOrCredIDRequired = 2, kUVOrCredIDRequired = 2,
kUVRequired = 3, kUVRequired = 3,
}; };
// CredProtectRequest extends |CredProtect| with an additional value that
// represents a request for |kUVOrCredIDRequired|, unless the default is
// higher.
enum class CredProtectRequest : uint8_t {
kUVOptional = 1,
kUVOrCredIDRequired = 2,
kUVRequired = 3,
kUVOrCredIDRequiredOrBetter = 255,
};
// The map key for inserting the googleAndroidClientDataExtension output into a // The map key for inserting the googleAndroidClientDataExtension output into a
// CTAP2 makeCredential or getAssertion response. // CTAP2 makeCredential or getAssertion response.
constexpr int kAndroidClientDataExtOutputKey = 0xf0; constexpr int kAndroidClientDataExtOutputKey = 0xf0;
......
...@@ -83,11 +83,13 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test { ...@@ -83,11 +83,13 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test {
test_data::kClientDataJson, std::move(rp), std::move(user), test_data::kClientDataJson, std::move(rp), std::move(user),
std::move(credential_params)); std::move(credential_params));
MakeCredentialRequestHandler::Options options;
options.allow_skipping_pin_touch = true;
auto handler = std::make_unique<MakeCredentialRequestHandler>( auto handler = std::make_unique<MakeCredentialRequestHandler>(
fake_discovery_factory_.get(), supported_transports_, fake_discovery_factory_.get(), supported_transports_,
std::move(request_parameter), std::move(request_parameter),
std::move(authenticator_selection_criteria), std::move(authenticator_selection_criteria), options, cb_.callback());
/*allow_skipping_pin_touch=*/true, cb_.callback());
if (pending_mock_platform_device_) { if (pending_mock_platform_device_) {
platform_discovery_->AddDevice(std::move(pending_mock_platform_device_)); platform_discovery_->AddDevice(std::move(pending_mock_platform_device_));
platform_discovery_->WaitForCallToStartAndSimulateSuccess(); platform_discovery_->WaitForCallToStartAndSimulateSuccess();
......
...@@ -60,12 +60,31 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler ...@@ -60,12 +60,31 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
base::Optional<AuthenticatorMakeCredentialResponse>, base::Optional<AuthenticatorMakeCredentialResponse>,
const FidoAuthenticator*)>; const FidoAuthenticator*)>;
// Options contains higher-level request parameters that aren't part of the
// makeCredential request itself, or that need to be combined with knowledge
// of the specific authenticator, thus don't live in
// |CtapMakeCredentialRequest|.
struct COMPONENT_EXPORT(DEVICE_FIDO) Options {
Options();
~Options();
Options(const Options&);
bool allow_skipping_pin_touch = false;
base::Optional<AndroidClientDataExtensionInput> android_client_data_ext;
// cred_protect_request extends |CredProtect| to include information that
// applies at request-routing time. The second element is true if the
// indicated protection level must be provided by the target authenticator
// for the MakeCredential request to be sent.
base::Optional<std::pair<CredProtectRequest, bool>> cred_protect_request;
};
MakeCredentialRequestHandler( MakeCredentialRequestHandler(
FidoDiscoveryFactory* fido_discovery_factory, FidoDiscoveryFactory* fido_discovery_factory,
const base::flat_set<FidoTransportProtocol>& supported_transports, const base::flat_set<FidoTransportProtocol>& supported_transports,
CtapMakeCredentialRequest request_parameter, CtapMakeCredentialRequest request_parameter,
AuthenticatorSelectionCriteria authenticator_criteria, AuthenticatorSelectionCriteria authenticator_criteria,
bool allow_skipping_pin_touch, const Options& options,
CompletionCallback completion_callback); CompletionCallback completion_callback);
~MakeCredentialRequestHandler() override; ~MakeCredentialRequestHandler() override;
...@@ -110,15 +129,16 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler ...@@ -110,15 +129,16 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
base::Optional<pin::TokenResponse> response); base::Optional<pin::TokenResponse> response);
void DispatchRequestWithToken(pin::TokenResponse token); void DispatchRequestWithToken(pin::TokenResponse token);
void SpecializeRequestForAuthenticator(
CtapMakeCredentialRequest* request,
const FidoAuthenticator* authenticator);
CompletionCallback completion_callback_; CompletionCallback completion_callback_;
State state_ = State::kWaitingForTouch; State state_ = State::kWaitingForTouch;
CtapMakeCredentialRequest request_; CtapMakeCredentialRequest request_;
AuthenticatorSelectionCriteria authenticator_selection_criteria_; AuthenticatorSelectionCriteria authenticator_selection_criteria_;
base::Optional<AndroidClientDataExtensionInput> android_client_data_ext_; const Options options_;
// If true, the request handler may skip the first touch to select a device
// that will require a PIN.
bool allow_skipping_pin_touch_;
// authenticator_ points to the authenticator that will be used for this // authenticator_ points to the authenticator that will be used for this
// operation. It's only set after the user touches an authenticator to select // operation. It's only set after the user touches an authenticator to select
// it, after which point that authenticator will be used exclusively through // it, after which point that authenticator will be used exclusively through
......
...@@ -465,6 +465,11 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, ...@@ -465,6 +465,11 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
options.is_platform_device = true; options.is_platform_device = true;
} }
if (config.cred_protect_support) {
options_updated = true;
options.default_cred_protect = config.default_cred_protect;
}
if (options_updated) { if (options_updated) {
device_info_->options = std::move(options); device_info_->options = std::move(options);
} }
...@@ -763,18 +768,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( ...@@ -763,18 +768,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
cbor::Value(true)); cbor::Value(true));
} }
base::Optional<CredProtect> cred_protect; CredProtect cred_protect = config_.default_cred_protect;
if (request.cred_protect) { if (request.cred_protect) {
cred_protect = request.cred_protect->first; cred_protect = *request.cred_protect;
} }
if (config_.force_cred_protect) { if (config_.force_cred_protect) {
cred_protect = config_.force_cred_protect; cred_protect = *config_.force_cred_protect;
} }
if (cred_protect) { if (request.cred_protect ||
extensions_map.emplace( cred_protect != device::CredProtect::kUVOptional) {
cbor::Value(kExtensionCredProtect), extensions_map.emplace(cbor::Value(kExtensionCredProtect),
cbor::Value(cred_protect == CredProtect::kUVRequired ? 3 : 2)); cbor::Value(static_cast<int64_t>(cred_protect)));
} }
if (config_.add_extra_extension) { if (config_.add_extra_extension) {
...@@ -934,10 +939,9 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( ...@@ -934,10 +939,9 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
[user_verified, &request]( [user_verified, &request](
const std::pair<base::span<const uint8_t>, RegistrationData*>& const std::pair<base::span<const uint8_t>, RegistrationData*>&
candidate) -> bool { candidate) -> bool {
if (!candidate.second->protection) { switch (candidate.second->protection) {
return false; case CredProtect::kUVOptional:
} return false;
switch (*candidate.second->protection) {
case CredProtect::kUVOrCredIDRequired: case CredProtect::kUVOrCredIDRequired:
return request.allow_list.empty() && !user_verified; return request.allow_list.empty() && !user_verified;
case CredProtect::kUVRequired: case CredProtect::kUVRequired:
......
...@@ -58,6 +58,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device ...@@ -58,6 +58,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
// overrides any level requested in the makeCredential. // overrides any level requested in the makeCredential.
base::Optional<device::CredProtect> force_cred_protect; base::Optional<device::CredProtect> force_cred_protect;
// default_cred_protect, if |cred_protect_support| is true, is the
// credProtect level that will be set for makeCredential requests that do
// not specify one.
device::CredProtect default_cred_protect = device::CredProtect::kUVOptional;
// max_credential_count_in_list, if non-zero, is the value to return for // max_credential_count_in_list, if non-zero, is the value to return for
// maxCredentialCountInList in the authenticatorGetInfo reponse. // maxCredentialCountInList in the authenticatorGetInfo reponse.
// CTAP2_ERR_LIMIT_EXCEEDED will be returned for requests with an allow or // CTAP2_ERR_LIMIT_EXCEEDED will be returned for requests with an allow or
......
...@@ -59,7 +59,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { ...@@ -59,7 +59,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
bool is_resident = false; bool is_resident = false;
// is_u2f is true if the credential was created via a U2F interface. // is_u2f is true if the credential was created via a U2F interface.
bool is_u2f = false; bool is_u2f = false;
base::Optional<device::CredProtect> protection; device::CredProtect protection;
// user is only valid if |is_resident| is true. // user is only valid if |is_resident| is true.
base::Optional<device::PublicKeyCredentialUserEntity> user; base::Optional<device::PublicKeyCredentialUserEntity> user;
......
...@@ -183,6 +183,10 @@ bool WinWebAuthnApiAuthenticator::IsWinNativeApiAuthenticator() const { ...@@ -183,6 +183,10 @@ bool WinWebAuthnApiAuthenticator::IsWinNativeApiAuthenticator() const {
return true; return true;
} }
bool WinWebAuthnApiAuthenticator::SupportsCredProtectExtension() const {
return win_api_->Version() >= WEBAUTHN_API_VERSION_2;
}
const base::Optional<AuthenticatorSupportedOptions>& const base::Optional<AuthenticatorSupportedOptions>&
WinWebAuthnApiAuthenticator::Options() const { WinWebAuthnApiAuthenticator::Options() const {
// The request can potentially be fulfilled by any device that Windows // The request can potentially be fulfilled by any device that Windows
...@@ -197,10 +201,6 @@ base::WeakPtr<FidoAuthenticator> WinWebAuthnApiAuthenticator::GetWeakPtr() { ...@@ -197,10 +201,6 @@ base::WeakPtr<FidoAuthenticator> WinWebAuthnApiAuthenticator::GetWeakPtr() {
return weak_factory_.GetWeakPtr(); return weak_factory_.GetWeakPtr();
} }
bool WinWebAuthnApiAuthenticator::SupportsCredProtectExtension() const {
return win_api_->Version() >= WEBAUTHN_API_VERSION_2;
}
bool WinWebAuthnApiAuthenticator::ShowsPrivacyNotice() const { bool WinWebAuthnApiAuthenticator::ShowsPrivacyNotice() const {
return win_api_->Version() >= WEBAUTHN_API_VERSION_2; return win_api_->Version() >= WEBAUTHN_API_VERSION_2;
} }
......
...@@ -41,10 +41,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator ...@@ -41,10 +41,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator
WinWebAuthnApiAuthenticator(HWND current_window, WinWebAuthnApi* win_api_); WinWebAuthnApiAuthenticator(HWND current_window, WinWebAuthnApi* win_api_);
~WinWebAuthnApiAuthenticator() override; ~WinWebAuthnApiAuthenticator() override;
// SupportsCredProtectExtension returns whether the native API supports the
// credProtect CTAP extension.
bool SupportsCredProtectExtension() const;
// ShowsPrivacyNotice returns true if the Windows native UI will show a // ShowsPrivacyNotice returns true if the Windows native UI will show a
// privacy notice dialog before a MakeCredential request that might create // privacy notice dialog before a MakeCredential request that might create
// a resident key or that requests attestation. // a resident key or that requests attestation.
...@@ -64,6 +60,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator ...@@ -64,6 +60,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator
bool IsInPairingMode() const override; bool IsInPairingMode() const override;
bool IsPaired() const override; bool IsPaired() const override;
bool RequiresBlePairingPin() const override; bool RequiresBlePairingPin() const override;
// SupportsCredProtectExtension returns whether the native API supports the
// credProtect CTAP extension.
bool SupportsCredProtectExtension() const override;
const base::Optional<AuthenticatorSupportedOptions>& Options() const override; const base::Optional<AuthenticatorSupportedOptions>& Options() const override;
base::Optional<FidoTransportProtocol> AuthenticatorTransport() const override; base::Optional<FidoTransportProtocol> AuthenticatorTransport() const override;
bool IsWinNativeApiAuthenticator() const override; bool IsWinNativeApiAuthenticator() const override;
......
...@@ -235,14 +235,18 @@ AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api, ...@@ -235,14 +235,18 @@ AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api,
if (request.cred_protect) { if (request.cred_protect) {
// MakeCredentialRequestHandler rejects a request with credProtect // MakeCredentialRequestHandler rejects a request with credProtect
// enforced=true if webauthn.dll does not support credProtect. // enforced=true if webauthn.dll does not support credProtect.
if (request.cred_protect->second && if (request.cred_protect_enforce &&
webauthn_api->Version() < WEBAUTHN_API_VERSION_2) { webauthn_api->Version() < WEBAUTHN_API_VERSION_2) {
NOTREACHED(); NOTREACHED();
return {CtapDeviceResponseCode::kCtap2ErrNotAllowed, base::nullopt}; return {CtapDeviceResponseCode::kCtap2ErrNotAllowed, base::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{ maybe_cred_protect_extension = WEBAUTHN_CRED_PROTECT_EXTENSION_IN{
/*dwCredProtect=*/static_cast<uint8_t>(request.cred_protect->first), /*dwCredProtect=*/static_cast<uint8_t>(*request.cred_protect),
/*bRequireCredProtect=*/request.cred_protect->second, /*bRequireCredProtect=*/request.cred_protect_enforce,
}; };
extensions.emplace_back(WEBAUTHN_EXTENSION{ extensions.emplace_back(WEBAUTHN_EXTENSION{
/*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT, /*pwszExtensionIdentifier=*/WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT,
......
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