device/fido: add PIN support to makeCredential and getAssertion.
This change adds additional PIN logic to these flows, although the UI
isn't fully hooked up yet. This is guarded by a feature flag and the
intent is that there's no behaviour change without the feature flag
being set.
BUG=870892
Change-Id: Idd718c5b1ed76e1acd3d4041339b63f910fff41a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1493433
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#637502}
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index 6abee61..504584ca 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -751,12 +751,15 @@
} else {
// Submit PIN to authenticator for verification.
DCHECK(mode_ == AuthenticatorClientPinEntrySheetModel::Mode::kPinEntry);
+ // TODO: use device::pin::IsValid instead.
if (pin_code_.size() < kMinPinLength) {
delegate_->ShowPinError(
l10n_util::GetStringUTF16(IDS_WEBAUTHN_PIN_ENTRY_ERROR_TOO_SHORT));
return;
}
}
- // TODO(martinkr): Actually set the PIN/request the PIN token and continue
- // the request.
+
+ if (dialog_model()) {
+ dialog_model()->OnHavePIN(base::UTF16ToUTF8(pin_code_));
+ }
}
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index eb30aa4..b87c9d5 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -378,6 +378,14 @@
SetCurrentStep(Step::kKeyAlreadyRegistered);
}
+void AuthenticatorRequestDialogModel::OnSoftPINBlock() {
+ // TODO
+}
+
+void AuthenticatorRequestDialogModel::OnHardPINBlock() {
+ // TODO
+}
+
void AuthenticatorRequestDialogModel::OnBluetoothPoweredStateChanged(
bool powered) {
transport_availability_.is_ble_powered = powered;
@@ -424,6 +432,18 @@
ble_device_paired_callback_ = std::move(ble_device_paired_callback);
}
+void AuthenticatorRequestDialogModel::SetPINCallback(
+ base::OnceCallback<void(std::string)> pin_callback) {
+ pin_callback_ = std::move(pin_callback);
+}
+
+void AuthenticatorRequestDialogModel::OnHavePIN(const std::string& pin) {
+ // TODO: disable the PIN submission action once activated. Otherwise
+ // |OnHavePIN| may be called twice because they'll be a delay between
+ // submitted the PIN and figuring out whether it's valid or not.
+ std::move(pin_callback_).Run(pin);
+}
+
void AuthenticatorRequestDialogModel::AddAuthenticator(
const device::FidoAuthenticator& authenticator) {
if (!authenticator.AuthenticatorTransport()) {
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index fbd7957..5112777 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -263,6 +263,14 @@
// one of excluded credentials (during a MakeCredential request).
void OnActivatedKeyAlreadyRegistered();
+ // To be called when the selected authenticator cannot currently handle PIN
+ // requests because it needs a power-cycle due to too many failures.
+ void OnSoftPINBlock();
+
+ // To be called when the selected authenticator must be reset before
+ // performing any PIN operations because of too many failures.
+ void OnHardPINBlock();
+
// To be called when the Bluetooth adapter powered state changes.
void OnBluetoothPoweredStateChanged(bool powered);
@@ -276,6 +284,11 @@
void SetBleDevicePairedCallback(
BleDevicePairedCallback ble_device_paired_callback);
+ void SetPINCallback(base::OnceCallback<void(std::string)> pin_callback);
+
+ // OnHavePIN is called when the user enters a PIN in the UI.
+ void OnHavePIN(const std::string& pin);
+
void UpdateAuthenticatorReferenceId(base::StringPiece old_authenticator_id,
std::string new_authenticator_id);
void AddAuthenticator(const device::FidoAuthenticator& authenticator);
@@ -334,6 +347,7 @@
BlePairingCallback ble_pairing_callback_;
base::RepeatingClosure bluetooth_adapter_power_on_callback_;
BleDevicePairedCallback ble_device_paired_callback_;
+ base::OnceCallback<void(std::string)> pin_callback_;
base::WeakPtrFactory<AuthenticatorRequestDialogModel> weak_factory_;
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
index a99083f..6a5bc018 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -134,6 +134,12 @@
case InterestingFailureReason::kKeyAlreadyRegistered:
weak_dialog_model_->OnActivatedKeyAlreadyRegistered();
break;
+ case InterestingFailureReason::kSoftPINBlock:
+ weak_dialog_model_->OnSoftPINBlock();
+ break;
+ case InterestingFailureReason::kHardPINBlock:
+ weak_dialog_model_->OnHardPINBlock();
+ break;
}
return true;
}
@@ -391,6 +397,29 @@
weak_dialog_model_->OnBluetoothPoweredStateChanged(is_powered_on);
}
+
+void ChromeAuthenticatorRequestDelegate::CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) {
+ if (!weak_dialog_model_)
+ return;
+
+ weak_dialog_model_->SetPINCallback(std::move(provide_pin_cb));
+ if (attempts) {
+ weak_dialog_model_->SetCurrentStep(
+ AuthenticatorRequestDialogModel::Step::kClientPinEntry);
+ } else {
+ weak_dialog_model_->SetCurrentStep(
+ AuthenticatorRequestDialogModel::Step::kClientPinSetup);
+ }
+}
+
+void ChromeAuthenticatorRequestDelegate::FinishCollectPIN() {
+ // TODO: add a distinct step for this.
+ weak_dialog_model_->SetCurrentStep(
+ AuthenticatorRequestDialogModel::Step::kUsbInsertAndActivate);
+}
+
void ChromeAuthenticatorRequestDelegate::OnModelDestroyed() {
DCHECK(weak_dialog_model_);
weak_dialog_model_ = nullptr;
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
index 600e269..387ad00 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
@@ -100,6 +100,10 @@
void FidoAuthenticatorPairingModeChanged(base::StringPiece authenticator_id,
bool is_in_pairing_mode) override;
void BluetoothAdapterPowerChanged(bool is_powered_on) override;
+ void CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) override;
+ void FinishCollectPIN() override;
// AuthenticatorRequestDialogModel::Observer:
void OnModelDestroyed() override;
diff --git a/content/browser/webauth/authenticator_impl.cc b/content/browser/webauth/authenticator_impl.cc
index b70dff4..0c22c16 100644
--- a/content/browser/webauth/authenticator_impl.cc
+++ b/content/browser/webauth/authenticator_impl.cc
@@ -719,7 +719,7 @@
request_->GetWeakPtr()) /* bluetooth_adapter_power_on_callback */,
base::BindRepeating(
&device::FidoRequestHandlerBase::InitiatePairingWithDevice,
- request_->GetWeakPtr()) /* ble_pairing_callback*/);
+ request_->GetWeakPtr()) /* ble_pairing_callback */);
request_->set_observer(request_delegate_.get());
request_->SetPlatformAuthenticatorOrMarkUnavailable(
@@ -927,6 +927,8 @@
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kKeyAlreadyRegistered);
return;
+ case device::FidoReturnCode::kAuthenticatorRemovedDuringPINEntry:
+ [[fallthrough]];
case device::FidoReturnCode::kAuthenticatorResponseInvalid:
// The response from the authenticator was corrupted.
InvokeCallbackAndCleanup(
@@ -944,6 +946,16 @@
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr,
Focus::kDoCheck);
return;
+ case device::FidoReturnCode::kSoftPINBlock:
+ SignalFailureToRequestDelegate(
+ AuthenticatorRequestClientDelegate::InterestingFailureReason::
+ kSoftPINBlock);
+ return;
+ case device::FidoReturnCode::kHardPINBlock:
+ SignalFailureToRequestDelegate(
+ AuthenticatorRequestClientDelegate::InterestingFailureReason::
+ kHardPINBlock);
+ return;
case device::FidoReturnCode::kSuccess:
DCHECK(response_data.has_value());
@@ -1079,6 +1091,8 @@
AuthenticatorRequestClientDelegate::InterestingFailureReason::
kKeyNotRegistered);
return;
+ case device::FidoReturnCode::kAuthenticatorRemovedDuringPINEntry:
+ [[fallthrough]];
case device::FidoReturnCode::kAuthenticatorResponseInvalid:
// The response from the authenticator was corrupted.
InvokeCallbackAndCleanup(
@@ -1094,6 +1108,16 @@
std::move(get_assertion_response_callback_),
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR, nullptr);
return;
+ case device::FidoReturnCode::kSoftPINBlock:
+ SignalFailureToRequestDelegate(
+ AuthenticatorRequestClientDelegate::InterestingFailureReason::
+ kSoftPINBlock);
+ return;
+ case device::FidoReturnCode::kHardPINBlock:
+ SignalFailureToRequestDelegate(
+ AuthenticatorRequestClientDelegate::InterestingFailureReason::
+ kHardPINBlock);
+ return;
case device::FidoReturnCode::kSuccess:
DCHECK(response_data.has_value());
if (transport_used) {
@@ -1133,6 +1157,13 @@
case AuthenticatorRequestClientDelegate::InterestingFailureReason::kTimeout:
status = blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
break;
+ case AuthenticatorRequestClientDelegate::InterestingFailureReason::
+ kSoftPINBlock:
+ status = blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
+ break;
+ case AuthenticatorRequestClientDelegate::InterestingFailureReason::
+ kHardPINBlock:
+ status = blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
}
error_awaiting_user_acknowledgement_ = status;
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 8924c79..65cf700 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -4,6 +4,7 @@
#include "content/browser/webauth/authenticator_impl.h"
+#include <list>
#include <memory>
#include <string>
#include <utility>
@@ -2448,4 +2449,256 @@
}
}
+// PINList is a list of expected |attempts| values and the PIN to answer with.
+using PINList = std::list<std::pair<base::Optional<int>, std::string>>;
+
+class PINTestAuthenticatorRequestDelegate
+ : public AuthenticatorRequestClientDelegate {
+ public:
+ explicit PINTestAuthenticatorRequestDelegate(
+ const PINList& pins,
+ base::Optional<InterestingFailureReason>* failure_reason)
+ : expected_(pins), failure_reason_(failure_reason) {}
+ ~PINTestAuthenticatorRequestDelegate() override { DCHECK(expected_.empty()); }
+
+ void CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) override {
+ DCHECK(!expected_.empty());
+ DCHECK(attempts == expected_.front().first)
+ << "got: " << attempts.value_or(-1)
+ << " expected: " << expected_.front().first.value_or(-1);
+ std::string pin = std::move(expected_.front().second);
+ expected_.pop_front();
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(provide_pin_cb), std::move(pin)));
+ }
+
+ bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override {
+ *failure_reason_ = reason;
+ return AuthenticatorRequestClientDelegate::DoesBlockRequestOnFailure(
+ reason);
+ }
+
+ private:
+ PINList expected_;
+ base::Optional<InterestingFailureReason>* const failure_reason_;
+ DISALLOW_COPY_AND_ASSIGN(PINTestAuthenticatorRequestDelegate);
+};
+
+class PINTestAuthenticatorContentBrowserClient : public ContentBrowserClient {
+ public:
+ std::unique_ptr<AuthenticatorRequestClientDelegate>
+ GetWebAuthenticationRequestDelegate(
+ RenderFrameHost* render_frame_host) override {
+ return std::make_unique<PINTestAuthenticatorRequestDelegate>(
+ expected, &failure_reason);
+ }
+
+ PINList expected;
+ base::Optional<InterestingFailureReason> failure_reason;
+};
+
+class PINAuthenticatorContentBrowserClientTest : public AuthenticatorImplTest {
+ public:
+ PINAuthenticatorContentBrowserClientTest() = default;
+
+ void SetUp() override {
+ scoped_feature_list_.InitAndEnableFeature(device::kWebAuthPINSupport);
+
+ AuthenticatorImplTest::SetUp();
+ old_client_ = SetBrowserClientForTesting(&test_client_);
+ virtual_device_.EnablePINSupport();
+ NavigateAndCommit(GURL(kTestOrigin1));
+ }
+
+ void TearDown() override {
+ SetBrowserClientForTesting(old_client_);
+ AuthenticatorImplTest::TearDown();
+ }
+
+ protected:
+ static PublicKeyCredentialCreationOptionsPtr make_credential_options() {
+ PublicKeyCredentialCreationOptionsPtr options =
+ GetTestPublicKeyCredentialCreationOptions();
+ options->authenticator_selection->user_verification =
+ blink::mojom::UserVerificationRequirement::REQUIRED;
+ return options;
+ }
+
+ static PublicKeyCredentialRequestOptionsPtr get_credential_options() {
+ PublicKeyCredentialRequestOptionsPtr options =
+ GetTestPublicKeyCredentialRequestOptions();
+ options->user_verification =
+ blink::mojom::UserVerificationRequirement::REQUIRED;
+ return options;
+ }
+
+ static bool HasUV(const TestMakeCredentialCallback& callback) {
+ DCHECK_EQ(AuthenticatorStatus::SUCCESS, callback.status());
+ base::Optional<Value> attestation_value =
+ Reader::Read(callback.value()->attestation_object);
+ DCHECK(attestation_value);
+ DCHECK(attestation_value->is_map());
+ const auto& attestation = attestation_value->GetMap();
+
+ const auto auth_data_it = attestation.find(Value("authData"));
+ DCHECK(auth_data_it != attestation.end() &&
+ auth_data_it->second.is_bytestring());
+ base::Optional<device::AuthenticatorData> auth_data =
+ device::AuthenticatorData::DecodeAuthenticatorData(
+ auth_data_it->second.GetBytestring());
+ return auth_data->obtained_user_verification();
+ }
+
+ static bool HasUV(const TestGetAssertionCallback& callback) {
+ DCHECK_EQ(AuthenticatorStatus::SUCCESS, callback.status());
+ base::Optional<device::AuthenticatorData> auth_data =
+ device::AuthenticatorData::DecodeAuthenticatorData(
+ callback.value()->authenticator_data);
+ return auth_data->obtained_user_verification();
+ }
+
+ PINTestAuthenticatorContentBrowserClient test_client_;
+ device::test::ScopedVirtualFidoDevice virtual_device_;
+
+ private:
+ ContentBrowserClient* old_client_ = nullptr;
+ base::test::ScopedFeatureList scoped_feature_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(PINAuthenticatorContentBrowserClientTest);
+};
+
+static constexpr char kTestPIN[] = "1234";
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, MakeCredentialSet) {
+ TestServiceManagerContext smc;
+ test_client_.expected = {{base::nullopt, kTestPIN}};
+
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestMakeCredentialCallback callback_receiver;
+ authenticator->MakeCredential(make_credential_options(),
+ callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
+ EXPECT_EQ(kTestPIN, virtual_device_.mutable_state()->pin);
+ EXPECT_TRUE(HasUV(callback_receiver));
+}
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, MakeCredentialUse) {
+ TestServiceManagerContext smc;
+ virtual_device_.mutable_state()->pin = kTestPIN;
+ virtual_device_.mutable_state()->retries = 8;
+
+ test_client_.expected = {{8, "wrong"}, {7, kTestPIN}};
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestMakeCredentialCallback callback_receiver;
+ authenticator->MakeCredential(make_credential_options(),
+ callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
+ EXPECT_EQ(8, virtual_device_.mutable_state()->retries);
+ EXPECT_TRUE(HasUV(callback_receiver));
+}
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, MakeCredentialSoftLock) {
+ TestServiceManagerContext smc;
+ virtual_device_.mutable_state()->pin = kTestPIN;
+ virtual_device_.mutable_state()->retries = 8;
+
+ test_client_.expected = {{8, "wrong"}, {7, "wrong"}, {6, "wrong"}};
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestMakeCredentialCallback callback_receiver;
+ authenticator->MakeCredential(make_credential_options(),
+ callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
+ EXPECT_EQ(5, virtual_device_.mutable_state()->retries);
+ EXPECT_TRUE(virtual_device_.mutable_state()->soft_locked);
+ ASSERT_TRUE(test_client_.failure_reason.has_value());
+ EXPECT_EQ(InterestingFailureReason::kSoftPINBlock,
+ *test_client_.failure_reason);
+}
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, MakeCredentialHardLock) {
+ TestServiceManagerContext smc;
+ virtual_device_.mutable_state()->pin = kTestPIN;
+ virtual_device_.mutable_state()->retries = 1;
+
+ test_client_.expected = {{1, "wrong"}};
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestMakeCredentialCallback callback_receiver;
+ authenticator->MakeCredential(make_credential_options(),
+ callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
+ EXPECT_EQ(0, virtual_device_.mutable_state()->retries);
+ ASSERT_TRUE(test_client_.failure_reason.has_value());
+ EXPECT_EQ(InterestingFailureReason::kHardPINBlock,
+ *test_client_.failure_reason);
+}
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, GetAssertion) {
+ TestServiceManagerContext smc;
+ virtual_device_.mutable_state()->pin = kTestPIN;
+ virtual_device_.mutable_state()->retries = 8;
+
+ PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
+ ASSERT_TRUE(virtual_device_.mutable_state()->InjectRegistration(
+ options->allow_credentials[0]->id, kTestRelyingPartyId));
+
+ test_client_.expected = {{8, "wrong"}, {7, kTestPIN}};
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestGetAssertionCallback callback_receiver;
+ authenticator->GetAssertion(std::move(options), callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
+ EXPECT_EQ(8, virtual_device_.mutable_state()->retries);
+ EXPECT_TRUE(HasUV(callback_receiver));
+}
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, GetAssertionSoftLock) {
+ TestServiceManagerContext smc;
+ virtual_device_.mutable_state()->pin = kTestPIN;
+ virtual_device_.mutable_state()->retries = 8;
+
+ PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
+ ASSERT_TRUE(virtual_device_.mutable_state()->InjectRegistration(
+ options->allow_credentials[0]->id, kTestRelyingPartyId));
+
+ test_client_.expected = {{8, "wrong"}, {7, "wrong"}, {6, "wrong"}};
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestGetAssertionCallback callback_receiver;
+ authenticator->GetAssertion(std::move(options), callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
+ EXPECT_EQ(5, virtual_device_.mutable_state()->retries);
+ EXPECT_TRUE(virtual_device_.mutable_state()->soft_locked);
+ ASSERT_TRUE(test_client_.failure_reason.has_value());
+ EXPECT_EQ(InterestingFailureReason::kSoftPINBlock,
+ *test_client_.failure_reason);
+}
+
+TEST_F(PINAuthenticatorContentBrowserClientTest, GetAssertionHardLock) {
+ TestServiceManagerContext smc;
+ virtual_device_.mutable_state()->pin = kTestPIN;
+ virtual_device_.mutable_state()->retries = 1;
+
+ PublicKeyCredentialRequestOptionsPtr options = get_credential_options();
+ ASSERT_TRUE(virtual_device_.mutable_state()->InjectRegistration(
+ options->allow_credentials[0]->id, kTestRelyingPartyId));
+
+ test_client_.expected = {{1, "wrong"}};
+ AuthenticatorPtr authenticator = ConnectToAuthenticator();
+ TestGetAssertionCallback callback_receiver;
+ authenticator->GetAssertion(std::move(options), callback_receiver.callback());
+ callback_receiver.WaitForCallback();
+ EXPECT_EQ(AuthenticatorStatus::NOT_ALLOWED_ERROR, callback_receiver.status());
+ EXPECT_EQ(0, virtual_device_.mutable_state()->retries);
+ ASSERT_TRUE(test_client_.failure_reason.has_value());
+ EXPECT_EQ(InterestingFailureReason::kHardPINBlock,
+ *test_client_.failure_reason);
+}
+
} // namespace content
diff --git a/content/public/browser/authenticator_request_client_delegate.cc b/content/public/browser/authenticator_request_client_delegate.cc
index dffc975..07d3039 100644
--- a/content/public/browser/authenticator_request_client_delegate.cc
+++ b/content/public/browser/authenticator_request_client_delegate.cc
@@ -83,4 +83,10 @@
base::StringPiece authenticator_id,
bool is_in_pairing_mode) {}
+void AuthenticatorRequestClientDelegate::CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) {}
+
+void AuthenticatorRequestClientDelegate::FinishCollectPIN() {}
+
} // namespace content
diff --git a/content/public/browser/authenticator_request_client_delegate.h b/content/public/browser/authenticator_request_client_delegate.h
index c73ae20..3c6a600 100644
--- a/content/public/browser/authenticator_request_client_delegate.h
+++ b/content/public/browser/authenticator_request_client_delegate.h
@@ -39,6 +39,8 @@
kTimeout,
kKeyNotRegistered,
kKeyAlreadyRegistered,
+ kSoftPINBlock,
+ kHardPINBlock,
};
AuthenticatorRequestClientDelegate();
@@ -131,6 +133,10 @@
std::string new_authenticator_id) override;
void FidoAuthenticatorPairingModeChanged(base::StringPiece authenticator_id,
bool is_in_pairing_mode) override;
+ void CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) override;
+ void FinishCollectPIN() override;
private:
DISALLOW_COPY_AND_ASSIGN(AuthenticatorRequestClientDelegate);
diff --git a/device/fido/ble_adapter_manager_unittest.cc b/device/fido/ble_adapter_manager_unittest.cc
index f91ba0d..1c8d773 100644
--- a/device/fido/ble_adapter_manager_unittest.cc
+++ b/device/fido/ble_adapter_manager_unittest.cc
@@ -48,6 +48,10 @@
std::string new_authenticator_id));
MOCK_METHOD2(FidoAuthenticatorPairingModeChanged,
void(base::StringPiece, bool));
+ MOCK_METHOD2(CollectPIN,
+ void(base::Optional<int>,
+ base::OnceCallback<void(std::string)>));
+ MOCK_METHOD0(FinishCollectPIN, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockObserver);
diff --git a/device/fido/ctap2_device_operation.h b/device/fido/ctap2_device_operation.h
index 1cba8f9..ad9edd54a 100644
--- a/device/fido/ctap2_device_operation.h
+++ b/device/fido/ctap2_device_operation.h
@@ -35,11 +35,11 @@
base::OnceCallback<base::Optional<Response>(base::span<const uint8_t>)>;
Ctap2DeviceOperation(FidoDevice* device,
- const Request& request,
+ Request request,
DeviceResponseCallback callback,
DeviceResponseParser device_response_parser)
: DeviceOperation<Request, Response>(device,
- request,
+ std::move(request),
std::move(callback)),
device_response_parser_(std::move(device_response_parser)),
weak_factory_(this) {}
diff --git a/device/fido/device_operation.h b/device/fido/device_operation.h
index 21db99f..70901ae 100644
--- a/device/fido/device_operation.h
+++ b/device/fido/device_operation.h
@@ -38,9 +38,11 @@
// Represents a per device logic that is owned by FidoTask. Thus,
// DeviceOperation does not outlive |request|.
DeviceOperation(FidoDevice* device,
- const Request& request,
+ Request request,
DeviceResponseCallback callback)
- : device_(device), request_(request), callback_(std::move(callback)) {}
+ : device_(device),
+ request_(std::move(request)),
+ callback_(std::move(callback)) {}
virtual ~DeviceOperation() = default;
@@ -65,7 +67,7 @@
private:
FidoDevice* const device_ = nullptr;
- const Request& request_;
+ Request request_;
DeviceResponseCallback callback_;
DISALLOW_COPY_AND_ASSIGN(DeviceOperation);
diff --git a/device/fido/fido_authenticator.cc b/device/fido/fido_authenticator.cc
index feb8705..093fe35 100644
--- a/device/fido/fido_authenticator.cc
+++ b/device/fido/fido_authenticator.cc
@@ -33,7 +33,7 @@
}
void FidoAuthenticator::SetPIN(const std::string& pin,
- pin::KeyAgreementResponse& peer_key,
+ const pin::KeyAgreementResponse& peer_key,
FidoAuthenticator::SetPINCallback callback) {
NOTREACHED();
}
diff --git a/device/fido/fido_authenticator.h b/device/fido/fido_authenticator.h
index 634f7c81..bfef7d1 100644
--- a/device/fido/fido_authenticator.h
+++ b/device/fido/fido_authenticator.h
@@ -90,7 +90,7 @@
// only valid to call this method if |Options| indicates that the
// authenticator supports PINs.
virtual void SetPIN(const std::string& pin,
- pin::KeyAgreementResponse& peer_key,
+ const pin::KeyAgreementResponse& peer_key,
SetPINCallback callback);
// ChangePIN alters the PIN on a device that already has a PIN set. The
// length of |pin| must respect |pin::kMinLength| and |pin::kMaxLength|. It is
diff --git a/device/fido/fido_constants.h b/device/fido/fido_constants.h
index 85cfa30..0c38e214 100644
--- a/device/fido/fido_constants.h
+++ b/device/fido/fido_constants.h
@@ -29,6 +29,9 @@
kUserConsentButCredentialNotRecognized,
// The user explicitly refused to provide consent.
kUserConsentDenied,
+ kAuthenticatorRemovedDuringPINEntry,
+ kSoftPINBlock,
+ kHardPINBlock,
};
enum class ProtocolVersion {
diff --git a/device/fido/fido_device_authenticator.cc b/device/fido/fido_device_authenticator.cc
index d2807351..fae86f8 100644
--- a/device/fido/fido_device_authenticator.cc
+++ b/device/fido/fido_device_authenticator.cc
@@ -7,11 +7,13 @@
#include <utility>
#include "base/bind.h"
+#include "base/feature_list.h"
#include "base/logging.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
+#include "device/fido/features.h"
#include "device/fido/fido_device.h"
#include "device/fido/get_assertion_task.h"
#include "device/fido/make_credential_task.h"
@@ -55,19 +57,22 @@
void FidoDeviceAuthenticator::MakeCredential(CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
- DCHECK(!task_);
DCHECK(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
DCHECK(Options());
- // Update the request to the "effective" user verification requirement.
- // https://w3c.github.io/webauthn/#effective-user-verification-requirement-for-credential-creation
- if (Options()->user_verification_availability ==
- AuthenticatorSupportedOptions::UserVerificationAvailability::
- kSupportedAndConfigured) {
- request.SetUserVerification(UserVerificationRequirement::kRequired);
- } else {
- request.SetUserVerification(UserVerificationRequirement::kDiscouraged);
+ // When PIN support is enabled, the mapping from Webauthn's ternary user-
+ // verification preference to CTAP2's binary option is done inside the request
+ // handler instead.
+ if (!base::FeatureList::IsEnabled(device::kWebAuthPINSupport) ||
+ request.user_verification() == UserVerificationRequirement::kPreferred) {
+ if (Options()->user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured) {
+ request.SetUserVerification(UserVerificationRequirement::kRequired);
+ } else {
+ request.SetUserVerification(UserVerificationRequirement::kDiscouraged);
+ }
}
// TODO(martinkr): Change FidoTasks to take all request parameters by const
@@ -78,19 +83,26 @@
void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) {
- DCHECK(!task_);
DCHECK(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
DCHECK(Options());
+ const bool pin_support =
+ base::FeatureList::IsEnabled(device::kWebAuthPINSupport);
// Update the request to the "effective" user verification requirement.
// https://w3c.github.io/webauthn/#effective-user-verification-requirement-for-assertion
- if (Options()->user_verification_availability ==
- AuthenticatorSupportedOptions::UserVerificationAvailability::
- kSupportedAndConfigured) {
- request.SetUserVerification(UserVerificationRequirement::kRequired);
- } else {
- request.SetUserVerification(UserVerificationRequirement::kDiscouraged);
+ if (!pin_support ||
+ request.user_verification() == UserVerificationRequirement::kPreferred) {
+ if (Options()->user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured ||
+ (pin_support && Options()->client_pin_availability ==
+ AuthenticatorSupportedOptions::
+ ClientPinAvailability::kSupportedAndPinSet)) {
+ request.SetUserVerification(UserVerificationRequirement::kRequired);
+ } else {
+ request.SetUserVerification(UserVerificationRequirement::kDiscouraged);
+ }
}
task_ = std::make_unique<GetAssertionTask>(device_.get(), std::move(request),
@@ -203,7 +215,7 @@
}
void FidoDeviceAuthenticator::SetPIN(const std::string& pin,
- pin::KeyAgreementResponse& peer_key,
+ const pin::KeyAgreementResponse& peer_key,
SetPINCallback callback) {
DCHECK(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
diff --git a/device/fido/fido_device_authenticator.h b/device/fido/fido_device_authenticator.h
index 31cf8aa..fff40969 100644
--- a/device/fido/fido_device_authenticator.h
+++ b/device/fido/fido_device_authenticator.h
@@ -52,7 +52,7 @@
const pin::KeyAgreementResponse& peer_key,
GetPINTokenCallback callback) override;
void SetPIN(const std::string& pin,
- pin::KeyAgreementResponse& peer_key,
+ const pin::KeyAgreementResponse& peer_key,
SetPINCallback callback) override;
void ChangePIN(const std::string& old_pin,
const std::string& new_pin,
diff --git a/device/fido/fido_request_handler.h b/device/fido/fido_request_handler.h
index f54b79b..646cde0b 100644
--- a/device/fido/fido_request_handler.h
+++ b/device/fido/fido_request_handler.h
@@ -78,6 +78,8 @@
authenticator->AuthenticatorTransport());
}
+ CompletionCallback completion_callback_;
+
private:
static base::Optional<FidoReturnCode>
ConvertDeviceResponseCodeToFidoReturnCode(
@@ -120,8 +122,6 @@
}
}
- CompletionCallback completion_callback_;
-
DISALLOW_COPY_AND_ASSIGN(FidoRequestHandler);
};
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 1de5d5e..93525cd 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -140,6 +140,17 @@
virtual void FidoAuthenticatorPairingModeChanged(
base::StringPiece authenticator_id,
bool is_in_pairing_mode) = 0;
+
+ // CollectPIN is called when a PIN is needed to complete a request. The
+ // |retries| parameter is either |nullopt| to indicate that the user needs
+ // to set a PIN, or contains the number of PIN attempts remaining before a
+ // hard lock.
+ virtual void CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) = 0;
+
+ // CollectClientPin is guaranteed to have been called previously.
+ virtual void FinishCollectPIN() = 0;
};
// TODO(https://crbug.com/769631): Remove the dependency on Connector once
diff --git a/device/fido/fido_request_handler_unittest.cc b/device/fido/fido_request_handler_unittest.cc
index 32c38b5..521d0e7 100644
--- a/device/fido/fido_request_handler_unittest.cc
+++ b/device/fido/fido_request_handler_unittest.cc
@@ -126,6 +126,14 @@
void FidoAuthenticatorPairingModeChanged(base::StringPiece authenticator_id,
bool is_in_pairing_mode) override {}
+ void CollectPIN(
+ base::Optional<int> attempts,
+ base::OnceCallback<void(std::string)> provide_pin_cb) override {
+ NOTREACHED();
+ }
+
+ void FinishCollectPIN() override { NOTREACHED(); }
+
private:
TransportAvailabilityNotificationReceiver
transport_availability_notification_receiver_;
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index a10bcf4..bc70bf2f 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -6,15 +6,19 @@
#include <algorithm>
#include <set>
+#include <string>
#include <utility>
#include "base/bind.h"
+#include "base/feature_list.h"
#include "base/stl_util.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/cable/fido_cable_discovery.h"
+#include "device/fido/features.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_discovery_factory.h"
#include "device/fido/get_assertion_task.h"
+#include "device/fido/pin.h"
namespace device {
@@ -102,7 +106,8 @@
// Checks UserVerificationRequirement enum passed from the relying party is
// compatible with the authenticator.
bool CheckUserVerificationCompatible(FidoAuthenticator* authenticator,
- const CtapGetAssertionRequest& request) {
+ const CtapGetAssertionRequest& request,
+ bool have_observer) {
const auto& opt_options = authenticator->Options();
if (!opt_options) {
// This authenticator doesn't know its capabilities yet, so we need
@@ -111,11 +116,16 @@
return true;
}
+ const bool pin_support =
+ base::FeatureList::IsEnabled(device::kWebAuthPINSupport) && have_observer;
return request.user_verification() !=
UserVerificationRequirement::kRequired ||
opt_options->user_verification_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
- kSupportedAndConfigured;
+ kSupportedAndConfigured ||
+ (pin_support && opt_options->client_pin_availability ==
+ AuthenticatorSupportedOptions::
+ ClientPinAvailability::kSupportedAndPinSet);
}
base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP(
@@ -187,20 +197,97 @@
GetAssertionRequestHandler::~GetAssertionRequestHandler() = default;
+static bool WillNeedPIN(const CtapGetAssertionRequest& request,
+ const FidoAuthenticator* authenticator) {
+ const auto& opt_options = authenticator->Options();
+ if (request.user_verification() ==
+ UserVerificationRequirement::kDiscouraged ||
+ !opt_options ||
+ opt_options->user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured) {
+ return false;
+ }
+
+ return opt_options->client_pin_availability ==
+ AuthenticatorSupportedOptions::ClientPinAvailability::
+ kSupportedAndPinSet;
+}
+
void GetAssertionRequestHandler::DispatchRequest(
FidoAuthenticator* authenticator) {
- if (!CheckUserVerificationCompatible(authenticator, request_))
- return;
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
- authenticator->GetAssertion(
- request_, base::BindOnce(&GetAssertionRequestHandler::HandleResponse,
- weak_factory_.GetWeakPtr(), authenticator));
+ if (state_ != State::kWaitingForTouch ||
+ !CheckUserVerificationCompatible(authenticator, request_, observer())) {
+ return;
+ }
+
+ if (base::FeatureList::IsEnabled(device::kWebAuthPINSupport) &&
+ WillNeedPIN(request_, authenticator)) {
+ CtapGetAssertionRequest request(request_);
+ // Set empty pinAuth to trigger just a touch.
+ request.SetPinAuth({});
+ authenticator->GetAssertion(
+ request, base::BindOnce(&GetAssertionRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator));
+ } else {
+ authenticator->GetAssertion(
+ request_, base::BindOnce(&GetAssertionRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator));
+ }
+}
+
+void GetAssertionRequestHandler::AuthenticatorRemoved(
+ FidoDiscoveryBase* discovery,
+ FidoAuthenticator* authenticator) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+
+ FidoRequestHandlerBase::AuthenticatorRemoved(discovery, authenticator);
+
+ if (authenticator == authenticator_) {
+ authenticator_ = nullptr;
+ if (state_ == State::kWaitingForPIN ||
+ state_ == State::kWaitingForSecondTouch) {
+ state_ = State::kFinished;
+ std::move(completion_callback_)
+ .Run(FidoReturnCode::kAuthenticatorRemovedDuringPINEntry,
+ base::nullopt, base::nullopt);
+ }
+ }
}
void GetAssertionRequestHandler::HandleResponse(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorGetAssertionResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+
+ if (state_ != State::kWaitingForTouch &&
+ state_ != State::kWaitingForSecondTouch) {
+ return;
+ }
+
+ if (state_ == State::kWaitingForTouch &&
+ base::FeatureList::IsEnabled(device::kWebAuthPINSupport) &&
+ WillNeedPIN(request_, authenticator)) {
+ if (response_code != CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid &&
+ response_code != CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
+ VLOG(1) << "Expected invalid pinAuth error but got "
+ << static_cast<int>(response_code);
+ return;
+ }
+ DCHECK(observer());
+ CancelActiveAuthenticators(authenticator->GetId());
+ state_ = State::kGettingRetries;
+ authenticator_ = authenticator;
+ authenticator_->GetRetries(
+ base::BindOnce(&GetAssertionRequestHandler::OnRetriesResponse,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ state_ = State::kFinished;
if (response_code != CtapDeviceResponseCode::kSuccess) {
OnAuthenticatorResponse(authenticator, response_code, base::nullopt);
return;
@@ -219,4 +306,120 @@
OnAuthenticatorResponse(authenticator, response_code, std::move(response));
}
+void GetAssertionRequestHandler::OnRetriesResponse(
+ CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK_EQ(state_, State::kGettingRetries);
+
+ if (status == CtapDeviceResponseCode::kSuccess && !response) {
+ status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+ }
+
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ state_ = State::kFinished;
+ FidoReturnCode ret = FidoReturnCode::kAuthenticatorResponseInvalid;
+ if (status == CtapDeviceResponseCode::kCtap2ErrPinBlocked) {
+ ret = FidoReturnCode::kHardPINBlock;
+ }
+ std::move(completion_callback_).Run(ret, base::nullopt, base::nullopt);
+ return;
+ }
+
+ state_ = State::kWaitingForPIN;
+ observer()->CollectPIN(response->retries,
+ base::BindOnce(&GetAssertionRequestHandler::OnHavePIN,
+ weak_factory_.GetWeakPtr()));
+}
+
+void GetAssertionRequestHandler::OnHavePIN(std::string pin) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK_EQ(State::kWaitingForPIN, state_);
+ DCHECK(pin::IsValid(pin));
+
+ if (authenticator_ == nullptr) {
+ // Authenticator was detached. The request will already have been canceled
+ // but this callback may have been waiting in a queue.
+ DCHECK(!completion_callback_);
+ return;
+ }
+
+ state_ = State::kGetEphemeralKey;
+ authenticator_->GetEphemeralKey(
+ base::BindOnce(&GetAssertionRequestHandler::OnHaveEphemeralKey,
+ weak_factory_.GetWeakPtr(), std::move(pin)));
+}
+
+void GetAssertionRequestHandler::OnHaveEphemeralKey(
+ std::string pin,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::KeyAgreementResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK_EQ(State::kGetEphemeralKey, state_);
+
+ if (status == CtapDeviceResponseCode::kSuccess && !response) {
+ status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+ }
+
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ state_ = State::kFinished;
+ std::move(completion_callback_)
+ .Run(FidoReturnCode::kAuthenticatorResponseInvalid, base::nullopt,
+ base::nullopt);
+ return;
+ }
+
+ state_ = State::kRequestWithPIN;
+ authenticator_->GetPINToken(
+ std::move(pin), *response,
+ base::BindOnce(&GetAssertionRequestHandler::OnHavePINToken,
+ weak_factory_.GetWeakPtr()));
+}
+
+void GetAssertionRequestHandler::OnHavePINToken(
+ CtapDeviceResponseCode status,
+ base::Optional<pin::TokenResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK_EQ(state_, State::kRequestWithPIN);
+
+ if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
+ state_ = State::kGettingRetries;
+ authenticator_->GetRetries(
+ base::BindOnce(&GetAssertionRequestHandler::OnRetriesResponse,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ if (status == CtapDeviceResponseCode::kSuccess && !response) {
+ status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+ }
+
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ state_ = State::kFinished;
+ FidoReturnCode ret;
+ switch (status) {
+ case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
+ ret = FidoReturnCode::kSoftPINBlock;
+ break;
+ case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
+ ret = FidoReturnCode::kHardPINBlock;
+ break;
+ default:
+ ret = FidoReturnCode::kAuthenticatorResponseInvalid;
+ break;
+ }
+ std::move(completion_callback_).Run(ret, base::nullopt, base::nullopt);
+ return;
+ }
+
+ observer()->FinishCollectPIN();
+ state_ = State::kWaitingForSecondTouch;
+ request_.SetPinAuth(response->PinAuth(request_.client_data_hash()));
+ request_.SetPinProtocol(pin::kProtocolVersion);
+
+ authenticator_->GetAssertion(
+ request_, base::BindOnce(&GetAssertionRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator_));
+}
+
} // namespace device
diff --git a/device/fido/get_assertion_request_handler.h b/device/fido/get_assertion_request_handler.h
index e561de8..c52efca 100644
--- a/device/fido/get_assertion_request_handler.h
+++ b/device/fido/get_assertion_request_handler.h
@@ -6,6 +6,7 @@
#define DEVICE_FIDO_GET_ASSERTION_REQUEST_HANDLER_H_
#include <memory>
+#include <string>
#include "base/callback.h"
#include "base/macros.h"
@@ -36,15 +37,43 @@
~GetAssertionRequestHandler() override;
private:
+ enum class State {
+ kWaitingForTouch,
+ kWaitingForSecondTouch,
+ kGettingRetries,
+ kWaitingForPIN,
+ kGetEphemeralKey,
+ kRequestWithPIN,
+ kFinished,
+ };
+
// FidoRequestHandlerBase:
void DispatchRequest(FidoAuthenticator* authenticator) override;
+ void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
+ FidoAuthenticator* authenticator) override;
void HandleResponse(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorGetAssertionResponse> response);
+ void OnRetriesResponse(CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response);
+ void OnHavePIN(std::string pin);
+ void OnHaveEphemeralKey(std::string pin,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::KeyAgreementResponse> response);
+ void OnHavePINToken(CtapDeviceResponseCode status,
+ base::Optional<pin::TokenResponse> response);
+ State state_ = State::kWaitingForTouch;
CtapGetAssertionRequest request_;
+ // authenticator_ points to the authenticator that will be used for this
+ // operation. It's only set after the user touches an authenticator to select
+ // it, after which point that authenticator will be used exclusively through
+ // requesting PIN etc. The object is owned by the underlying discovery object
+ // and this pointer is cleared if it's removed during processing.
+ FidoAuthenticator* authenticator_ = nullptr;
+ SEQUENCE_CHECKER(my_sequence_checker_);
base::WeakPtrFactory<GetAssertionRequestHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(GetAssertionRequestHandler);
diff --git a/device/fido/make_credential_request_handler.cc b/device/fido/make_credential_request_handler.cc
index 0522cd1..fa54a9f 100644
--- a/device/fido/make_credential_request_handler.cc
+++ b/device/fido/make_credential_request_handler.cc
@@ -5,26 +5,36 @@
#include "device/fido/make_credential_request_handler.h"
#include <set>
+#include <string>
#include <utility>
#include "base/bind.h"
+#include "base/feature_list.h"
#include "base/stl_util.h"
#include "device/fido/authenticator_make_credential_response.h"
+#include "device/fido/features.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/make_credential_task.h"
+#include "device/fido/pin.h"
#include "services/service_manager/public/cpp/connector.h"
namespace device {
+using ClientPinAvailability =
+ AuthenticatorSupportedOptions::ClientPinAvailability;
+
namespace {
bool CheckIfAuthenticatorSelectionCriteriaAreSatisfied(
FidoAuthenticator* authenticator,
- const AuthenticatorSelectionCriteria& authenticator_selection_criteria) {
+ const AuthenticatorSelectionCriteria& authenticator_selection_criteria,
+ bool have_observer) {
using UvAvailability =
AuthenticatorSupportedOptions::UserVerificationAvailability;
+ const bool pin_support =
+ base::FeatureList::IsEnabled(device::kWebAuthPINSupport) && have_observer;
const auto& opt_options = authenticator->Options();
if (!opt_options) {
@@ -39,17 +49,25 @@
!opt_options->is_platform_device) ||
(authenticator_selection_criteria.authenticator_attachment() ==
AuthenticatorAttachment::kCrossPlatform &&
- opt_options->is_platform_device))
+ opt_options->is_platform_device)) {
return false;
+ }
if (authenticator_selection_criteria.require_resident_key() &&
- !opt_options->supports_resident_key)
+ !opt_options->supports_resident_key) {
return false;
+ }
- return authenticator_selection_criteria.user_verification_requirement() !=
- UserVerificationRequirement::kRequired ||
- opt_options->user_verification_availability ==
- UvAvailability::kSupportedAndConfigured;
+ if (authenticator_selection_criteria.user_verification_requirement() ==
+ UserVerificationRequirement::kRequired &&
+ opt_options->user_verification_availability !=
+ UvAvailability::kSupportedAndConfigured &&
+ (!pin_support || opt_options->client_pin_availability ==
+ ClientPinAvailability::kNotSupported)) {
+ return false;
+ }
+
+ return true;
}
base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP(
@@ -102,11 +120,35 @@
MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default;
+// WillNeedPIN returns what type of PIN intervention will be needed to serve the
+// given request on the given authenticator.
+// |kNotSupported|: no PIN involved.
+// |kSupportedButPinNotSet|: will need to set a new PIN.
+// |kSupportedAndPinSet|: will need to prompt for an existing PIN.
+static ClientPinAvailability WillNeedPIN(
+ const CtapMakeCredentialRequest& request,
+ const FidoAuthenticator* authenticator) {
+ const auto& opt_options = authenticator->Options();
+ if (request.user_verification() != UserVerificationRequirement::kRequired ||
+ !opt_options ||
+ opt_options->user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured) {
+ return ClientPinAvailability::kNotSupported;
+ }
+
+ return opt_options->client_pin_availability;
+}
+
void MakeCredentialRequestHandler::DispatchRequest(
FidoAuthenticator* authenticator) {
- if (!CheckIfAuthenticatorSelectionCriteriaAreSatisfied(
- authenticator, authenticator_selection_criteria_))
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+
+ if (state_ != State::kWaitingForTouch ||
+ !CheckIfAuthenticatorSelectionCriteriaAreSatisfied(
+ authenticator, authenticator_selection_criteria_, observer())) {
return;
+ }
// Set the rk, uv and attachment fields, which were only initialized to
// default values up to here. TODO(martinkr): Initialize these fields earlier
@@ -119,16 +161,98 @@
request_parameter_.SetAuthenticatorAttachment(
authenticator_selection_criteria_.authenticator_attachment());
- authenticator->MakeCredential(
- request_parameter_,
- base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
- weak_factory_.GetWeakPtr(), authenticator));
+ if (base::FeatureList::IsEnabled(device::kWebAuthPINSupport) &&
+ WillNeedPIN(request_parameter_, authenticator) !=
+ ClientPinAvailability::kNotSupported) {
+ // Set an empty pinAuth parameter to wait for a touch and then report an
+ // error.
+ CtapMakeCredentialRequest request(request_parameter_);
+ request.SetPinAuth({});
+ authenticator->MakeCredential(
+ request, base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator));
+ } else {
+ authenticator->MakeCredential(
+ request_parameter_,
+ base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator));
+ }
+}
+
+void MakeCredentialRequestHandler::AuthenticatorRemoved(
+ FidoDiscoveryBase* discovery,
+ FidoAuthenticator* authenticator) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+
+ FidoRequestHandlerBase::AuthenticatorRemoved(discovery, authenticator);
+
+ if (authenticator == authenticator_) {
+ authenticator_ = nullptr;
+ if (state_ == State::kWaitingForPIN || state_ == State::kWaitingForNewPIN ||
+ state_ == State::kWaitingForSecondTouch) {
+ state_ = State::kFinished;
+ std::move(completion_callback_)
+ .Run(FidoReturnCode::kAuthenticatorRemovedDuringPINEntry,
+ base::nullopt, base::nullopt);
+ }
+ }
}
void MakeCredentialRequestHandler::HandleResponse(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorMakeCredentialResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+
+ if (state_ != State::kWaitingForTouch &&
+ state_ != State::kWaitingForSecondTouch) {
+ return;
+ }
+
+ if (base::FeatureList::IsEnabled(device::kWebAuthPINSupport) &&
+ state_ == State::kWaitingForTouch) {
+ switch (WillNeedPIN(request_parameter_, authenticator)) {
+ case ClientPinAvailability::kSupportedAndPinSet:
+ // Will need to get PIN to handle this request.
+ if (response_code != CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid &&
+ response_code != CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
+ VLOG(1) << "Expected invalid pinAuth error but got "
+ << static_cast<int>(response_code);
+ return;
+ }
+ DCHECK(observer());
+ CancelActiveAuthenticators(authenticator->GetId());
+ state_ = State::kGettingRetries;
+ authenticator_ = authenticator;
+ authenticator_->GetRetries(
+ base::BindOnce(&MakeCredentialRequestHandler::OnRetriesResponse,
+ weak_factory_.GetWeakPtr()));
+ return;
+
+ case ClientPinAvailability::kSupportedButPinNotSet:
+ // Will need to set a PIN to handle this request.
+ if (response_code != CtapDeviceResponseCode::kCtap2ErrPinNotSet) {
+ VLOG(1) << "Expected PIN-not-set error but got "
+ << static_cast<int>(response_code);
+ return;
+ }
+ DCHECK(observer());
+ CancelActiveAuthenticators(authenticator->GetId());
+ state_ = State::kWaitingForNewPIN;
+ authenticator_ = authenticator;
+ observer()->CollectPIN(
+ base::nullopt,
+ base::BindOnce(&MakeCredentialRequestHandler::OnHavePIN,
+ weak_factory_.GetWeakPtr()));
+ return;
+
+ case ClientPinAvailability::kNotSupported:
+ // No PIN needed for this request.
+ break;
+ }
+ }
+
+ state_ = State::kFinished;
if (response_code != CtapDeviceResponseCode::kSuccess) {
OnAuthenticatorResponse(authenticator, response_code, base::nullopt);
return;
@@ -146,8 +270,161 @@
OnAuthenticatorResponse(authenticator, response_code, std::move(response));
}
+void MakeCredentialRequestHandler::OnHavePIN(std::string pin) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK(state_ == State::kWaitingForPIN || state_ == State::kWaitingForNewPIN);
+ DCHECK(pin::IsValid(pin));
+
+ if (authenticator_ == nullptr) {
+ // Authenticator was detached. The request will already have been canceled
+ // but this callback may have been waiting in a queue.
+ DCHECK(!completion_callback_);
+ return;
+ }
+
+ if (state_ == State::kWaitingForPIN) {
+ state_ = State::kGetEphemeralKey;
+ } else {
+ DCHECK_EQ(state_, State::kWaitingForNewPIN);
+ state_ = State::kGetEphemeralKeyForNewPIN;
+ }
+
+ authenticator_->GetEphemeralKey(
+ base::BindOnce(&MakeCredentialRequestHandler::OnHaveEphemeralKey,
+ weak_factory_.GetWeakPtr(), std::move(pin)));
+}
+
+void MakeCredentialRequestHandler::OnRetriesResponse(
+ CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK_EQ(state_, State::kGettingRetries);
+
+ if (status == CtapDeviceResponseCode::kSuccess && !response) {
+ status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+ }
+
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ state_ = State::kFinished;
+ FidoReturnCode ret = FidoReturnCode::kAuthenticatorResponseInvalid;
+ if (status == CtapDeviceResponseCode::kCtap2ErrPinBlocked) {
+ ret = FidoReturnCode::kHardPINBlock;
+ }
+ std::move(completion_callback_).Run(ret, base::nullopt, base::nullopt);
+ return;
+ }
+
+ state_ = State::kWaitingForPIN;
+ observer()->CollectPIN(
+ response->retries,
+ base::BindOnce(&MakeCredentialRequestHandler::OnHavePIN,
+ weak_factory_.GetWeakPtr()));
+}
+
+void MakeCredentialRequestHandler::OnHaveEphemeralKey(
+ std::string pin,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::KeyAgreementResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK(state_ == State::kGetEphemeralKey ||
+ state_ == State::kGetEphemeralKeyForNewPIN);
+
+ if (status == CtapDeviceResponseCode::kSuccess && !response) {
+ status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+ }
+
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ state_ = State::kFinished;
+ std::move(completion_callback_)
+ .Run(FidoReturnCode::kAuthenticatorResponseInvalid, base::nullopt,
+ base::nullopt);
+ return;
+ }
+
+ if (state_ == State::kGetEphemeralKey) {
+ state_ = State::kRequestWithPIN;
+ authenticator_->GetPINToken(
+ std::move(pin), *response,
+ base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ DCHECK_EQ(state_, State::kGetEphemeralKeyForNewPIN);
+ state_ = State::kSettingPIN;
+ authenticator_->SetPIN(
+ pin, *response,
+ base::BindOnce(&MakeCredentialRequestHandler::OnHaveSetPIN,
+ weak_factory_.GetWeakPtr(), pin, *response));
+ }
+}
+
+void MakeCredentialRequestHandler::OnHaveSetPIN(
+ std::string pin,
+ pin::KeyAgreementResponse key_agreement,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::EmptyResponse> response) {
+ DCHECK_EQ(state_, State::kSettingPIN);
+
+ // Having just set the PIN, we need to immediately turn around and use it to
+ // get a PIN token.
+ state_ = State::kRequestWithPIN;
+ authenticator_->GetPINToken(
+ std::move(pin), key_agreement,
+ base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken,
+ weak_factory_.GetWeakPtr()));
+}
+
+void MakeCredentialRequestHandler::OnHavePINToken(
+ CtapDeviceResponseCode status,
+ base::Optional<pin::TokenResponse> response) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+ DCHECK_EQ(state_, State::kRequestWithPIN);
+
+ if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
+ state_ = State::kGettingRetries;
+ authenticator_->GetRetries(
+ base::BindOnce(&MakeCredentialRequestHandler::OnRetriesResponse,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+
+ if (status == CtapDeviceResponseCode::kSuccess && !response) {
+ status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
+ }
+
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ state_ = State::kFinished;
+ FidoReturnCode ret;
+ switch (status) {
+ case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
+ ret = FidoReturnCode::kSoftPINBlock;
+ break;
+ case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
+ ret = FidoReturnCode::kHardPINBlock;
+ break;
+ default:
+ ret = FidoReturnCode::kAuthenticatorResponseInvalid;
+ break;
+ }
+ std::move(completion_callback_).Run(ret, base::nullopt, base::nullopt);
+ return;
+ }
+
+ observer()->FinishCollectPIN();
+ state_ = State::kWaitingForSecondTouch;
+ request_parameter_.SetPinAuth(
+ response->PinAuth(request_parameter_.client_data_hash()));
+ request_parameter_.SetPinProtocol(pin::kProtocolVersion);
+
+ authenticator_->MakeCredential(
+ request_parameter_,
+ base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator_));
+}
+
void MakeCredentialRequestHandler::SetPlatformAuthenticatorOrMarkUnavailable(
base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
+
if (platform_authenticator_info) {
// TODO(crbug.com/873710): In the case of a request with
// AuthenticatorAttachment::kAny and when there is no embedder-provided
diff --git a/device/fido/make_credential_request_handler.h b/device/fido/make_credential_request_handler.h
index 20a3ae17..241a755e 100644
--- a/device/fido/make_credential_request_handler.h
+++ b/device/fido/make_credential_request_handler.h
@@ -6,12 +6,14 @@
#define DEVICE_FIDO_MAKE_CREDENTIAL_REQUEST_HANDLER_H_
#include <memory>
+#include <string>
#include "base/callback.h"
#include "base/component_export.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
+#include "base/sequence_checker.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
@@ -44,16 +46,51 @@
override;
private:
+ enum class State {
+ kWaitingForTouch,
+ kWaitingForSecondTouch,
+ kGettingRetries,
+ kWaitingForPIN,
+ kWaitingForNewPIN,
+ kGetEphemeralKey,
+ kGetEphemeralKeyForNewPIN,
+ kSettingPIN,
+ kRequestWithPIN,
+ kFinished,
+ };
+
// FidoRequestHandlerBase:
void DispatchRequest(FidoAuthenticator* authenticator) override;
+ void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
+ FidoAuthenticator* authenticator) override;
void HandleResponse(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorMakeCredentialResponse> response);
+ void OnHavePIN(std::string pin);
+ void OnRetriesResponse(CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response);
+ void OnHaveEphemeralKey(std::string pin,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::KeyAgreementResponse> response);
+ void OnHaveSetPIN(std::string pin,
+ pin::KeyAgreementResponse key_agreement,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::EmptyResponse> response);
+ void OnHavePINToken(CtapDeviceResponseCode status,
+ base::Optional<pin::TokenResponse> response);
+ State state_ = State::kWaitingForTouch;
CtapMakeCredentialRequest request_parameter_;
AuthenticatorSelectionCriteria authenticator_selection_criteria_;
+ // authenticator_ points to the authenticator that will be used for this
+ // operation. It's only set after the user touches an authenticator to select
+ // it, after which point that authenticator will be used exclusively through
+ // requesting PIN etc. The object is owned by the underlying discovery object
+ // and this pointer is cleared if it's removed during processing.
+ FidoAuthenticator* authenticator_ = nullptr;
+ SEQUENCE_CHECKER(my_sequence_checker_);
base::WeakPtrFactory<MakeCredentialRequestHandler> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MakeCredentialRequestHandler);
diff --git a/device/fido/make_credential_task.cc b/device/fido/make_credential_task.cc
index c24efb3..218049b9 100644
--- a/device/fido/make_credential_task.cc
+++ b/device/fido/make_credential_task.cc
@@ -72,7 +72,7 @@
void MakeCredentialTask::MakeCredential() {
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
- device(), request_parameter_, std::move(callback_),
+ device(), std::move(request_parameter_), std::move(callback_),
base::BindOnce(&ReadCTAPMakeCredentialResponse,
device()->DeviceTransport()));
register_operation_->Start();
@@ -87,7 +87,7 @@
DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
register_operation_ = std::make_unique<U2fRegisterOperation>(
- device(), request_parameter_,
+ device(), std::move(request_parameter_),
base::BindOnce(&MakeCredentialTask::MaybeRevertU2fFallback,
weak_factory_.GetWeakPtr()));
register_operation_->Start();
diff --git a/device/fido/pin.cc b/device/fido/pin.cc
index 60041407..2d12008 100644
--- a/device/fido/pin.cc
+++ b/device/fido/pin.cc
@@ -90,8 +90,13 @@
return base::nullopt;
}
+ const int64_t retries = it->second.GetUnsigned();
+ if (retries > INT_MAX) {
+ return base::nullopt;
+ }
+
RetriesResponse ret;
- ret.retries = it->second.GetUnsigned();
+ ret.retries = static_cast<int>(retries);
return ret;
}
@@ -399,6 +404,8 @@
TokenRequest::~TokenRequest() = default;
+TokenRequest::TokenRequest(TokenRequest&& other) = default;
+
const std::array<uint8_t, 32>& TokenRequest::shared_key() const {
return shared_key_;
}
diff --git a/device/fido/pin.h b/device/fido/pin.h
index 352639e8..0d13397 100644
--- a/device/fido/pin.h
+++ b/device/fido/pin.h
@@ -22,6 +22,10 @@
namespace device {
namespace pin {
+// kProtocolVersion is the version of the PIN protocol that this code
+// implements.
+constexpr int kProtocolVersion = 1;
+
// IsValid returns true if |pin|, which must be UTF-8, is a syntactically valid
// PIN.
bool IsValid(const std::string& pin);
@@ -48,7 +52,7 @@
// retries is the number of PIN attempts remaining before the authenticator
// locks.
- int64_t retries;
+ int retries;
private:
RetriesResponse();
@@ -129,6 +133,7 @@
public:
TokenRequest(const std::string& pin, const KeyAgreementResponse& peer_key);
~TokenRequest();
+ TokenRequest(TokenRequest&&);
TokenRequest(const TokenRequest&) = delete;
// shared_key returns the shared ECDH key that was used to encrypt the PIN.
@@ -139,7 +144,7 @@
private:
std::array<uint8_t, 32> shared_key_;
- const cbor::Value::MapValue cose_key_;
+ cbor::Value::MapValue cose_key_;
uint8_t pin_hash_[16];
};
diff --git a/device/fido/pin_internal.h b/device/fido/pin_internal.h
index 870c6bb..105b85d 100644
--- a/device/fido/pin_internal.h
+++ b/device/fido/pin_internal.h
@@ -19,10 +19,6 @@
namespace device {
namespace pin {
-// kProtocolVersion is the version of the PIN protocol that this code
-// implements.
-constexpr int kProtocolVersion = 1;
-
// Subcommand enumerates the subcommands to the main |authenticatorClientPIN|
// command. See
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#authenticatorClientPIN
diff --git a/device/fido/u2f_register_operation.h b/device/fido/u2f_register_operation.h
index e5dab24..cf423f9 100644
--- a/device/fido/u2f_register_operation.h
+++ b/device/fido/u2f_register_operation.h
@@ -13,12 +13,12 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
+#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/device_operation.h"
namespace device {
class FidoDevice;
-class CtapMakeCredentialRequest;
class AuthenticatorMakeCredentialResponse;
class PublicKeyCredentialDescriptor;
diff --git a/device/fido/u2f_sign_operation.h b/device/fido/u2f_sign_operation.h
index 70271a48..99e10b9b 100644
--- a/device/fido/u2f_sign_operation.h
+++ b/device/fido/u2f_sign_operation.h
@@ -15,13 +15,13 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
+#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/device_operation.h"
#include "device/fido/fido_constants.h"
namespace device {
class FidoDevice;
-class CtapGetAssertionRequest;
class AuthenticatorGetAssertionResponse;
// Represents per device authentication logic for U2F tokens. Handles iterating