Commit f72000b5 authored by Adam Langley's avatar Adam Langley Committed by Commit Bot

device/fido: very rough resident keys support.

This is barely functional, but provides a framework which can be fleshed
out. The important point to review is that functionality is unchanged
when the feature flag is not set.

Bug: 941120
Change-Id: I9c0d19458bb8d5e1872144722232041b9a398304
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1531530Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Commit-Queue: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644827}
parent 7b9e32ec
...@@ -9423,6 +9423,18 @@ Please help our engineers fix this problem. Tell us what happened right before y ...@@ -9423,6 +9423,18 @@ Please help our engineers fix this problem. Tell us what happened right before y
<message name="IDS_WEBAUTHN_CLIENT_PIN_AUTHENTICATOR_REMOVED_DESCRIPTION" desc="Description in the error dialog shown after their security key (an external physical device for user authentication) was removed while the application was waiting for the security key PIN to be entered."> <message name="IDS_WEBAUTHN_CLIENT_PIN_AUTHENTICATOR_REMOVED_DESCRIPTION" desc="Description in the error dialog shown after their security key (an external physical device for user authentication) was removed while the application was waiting for the security key PIN to be entered.">
Your security key has been removed. Your security key has been removed.
</message> </message>
<message name="IDS_WEBAUTHN_ACCOUNT_COLUMN" desc="The title of a column in a table under which will be a list of usernames or email addresses, e.g. joe@gmail.com.">
Account
</message>
<message name="IDS_WEBAUTHN_NAME_COLUMN" desc="The title of a column in a table under which will be a list of names on accounts, i.e. names of people. For example, Joe Smith.">
Name
</message>
<message name="IDS_WEBAUTHN_SELECT_ACCOUNT" desc="The title on a dialog where the user is expected to select an account from a list. For example, the list may include several identities, e.g. joe@gmail.com, mary@gmail.com, and the user has to select one to login as.">
Select Account
</message>
<message name="IDS_WEBAUTHN_SELECT_ACCOUNT_DESC" desc="The description on a dialog where the user is expected to select an account from a list. For example, the list may include several identities, e.g. joe@gmail.com, mary@gmail.com, and the user has to select one to login as.">
Select an account to log in with:
</message>
</if> </if>
<if expr="is_macosx"> <if expr="is_macosx">
<message name="IDS_WEBAUTHN_TOUCH_ID_TITLE" desc="Title of the dialog shown when the user tries to sign in with Touch ID." meaning="'Touch ID' is the fingerprint recognition feature in macOS. Try to refer Apple support documentation in the target language for the appropriate product name translation."> <message name="IDS_WEBAUTHN_TOUCH_ID_TITLE" desc="Title of the dialog shown when the user tries to sign in with Touch ID." meaning="'Touch ID' is the fingerprint recognition feature in macOS. Try to refer Apple support documentation in the target language for the appropriate product name translation.">
......
e66d937341bd1b83a329177cf62b137794f3ea90
\ No newline at end of file
e66d937341bd1b83a329177cf62b137794f3ea90
\ No newline at end of file
e66d937341bd1b83a329177cf62b137794f3ea90
\ No newline at end of file
e66d937341bd1b83a329177cf62b137794f3ea90
\ No newline at end of file
...@@ -2899,6 +2899,8 @@ jumbo_split_static_library("ui") { ...@@ -2899,6 +2899,8 @@ jumbo_split_static_library("ui") {
"views/webauthn/authenticator_request_dialog_view.h", "views/webauthn/authenticator_request_dialog_view.h",
"views/webauthn/authenticator_request_sheet_view.cc", "views/webauthn/authenticator_request_sheet_view.cc",
"views/webauthn/authenticator_request_sheet_view.h", "views/webauthn/authenticator_request_sheet_view.h",
"views/webauthn/authenticator_select_account_sheet_view.cc",
"views/webauthn/authenticator_select_account_sheet_view.h",
"views/webauthn/authenticator_transport_selector_sheet_view.cc", "views/webauthn/authenticator_transport_selector_sheet_view.cc",
"views/webauthn/authenticator_transport_selector_sheet_view.h", "views/webauthn/authenticator_transport_selector_sheet_view.h",
"views/webauthn/ble_device_selection_sheet_view.cc", "views/webauthn/ble_device_selection_sheet_view.cc",
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include "chrome/browser/ui/views/webauthn/authenticator_select_account_sheet_view.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/models/table_model.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/table/table_view.h"
AuthenticatorSelectAccountSheetView::AuthenticatorSelectAccountSheetView(
std::unique_ptr<AuthenticatorSelectAccountSheetModel> sheet_model)
: AuthenticatorRequestSheetView(std::move(sheet_model)) {}
AuthenticatorSelectAccountSheetView::~AuthenticatorSelectAccountSheetView() {
if (table_) {
table_->SetModel(nullptr);
table_->set_observer(nullptr);
}
}
std::unique_ptr<views::View>
AuthenticatorSelectAccountSheetView::BuildStepSpecificContent() {
std::vector<ui::TableColumn> columns;
columns.emplace_back(IDS_WEBAUTHN_ACCOUNT_COLUMN, ui::TableColumn::LEFT, -1,
0);
columns.emplace_back(IDS_WEBAUTHN_NAME_COLUMN, ui::TableColumn::LEFT, -1, 0);
auto* sheet_model =
static_cast<AuthenticatorSelectAccountSheetModel*>(model());
auto table = std::make_unique<views::TableView>(
sheet_model, columns, views::TEXT_ONLY, true /* single_selection */);
table_ = table.get();
table_->set_observer(this);
auto scroll_view =
views::TableView::CreateScrollViewWithTable(std::move(table));
// The table is packed into a BoxLayout, which will allocate the minimum
// vertical size to each element. However, the default minimum size of the
// table's ScrollView is zero, so a vertical size must be set here to prevent
// the table disappearing.
scroll_view->SetPreferredSize(gfx::Size(
0, std::min(static_cast<size_t>(200u),
50 * (1 + sheet_model->dialog_model()->responses().size()))));
return std::move(scroll_view);
}
void AuthenticatorSelectAccountSheetView::OnSelectionChanged() {
auto* sheet_model =
static_cast<AuthenticatorSelectAccountSheetModel*>(model());
sheet_model->SetCurrentSelection(table_->FirstSelectedRow());
}
void AuthenticatorSelectAccountSheetView::OnDoubleClick() {
auto* sheet_model =
static_cast<AuthenticatorSelectAccountSheetModel*>(model());
sheet_model->SetCurrentSelection(table_->FirstSelectedRow());
sheet_model->OnAccept();
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_SELECT_ACCOUNT_SHEET_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_SELECT_ACCOUNT_SHEET_VIEW_H_
#include <memory>
#include "base/macros.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
#include "chrome/browser/ui/webauthn/sheet_models.h"
#include "ui/views/controls/table/table_view_observer.h"
namespace views {
class TableView;
}
// Web Authentication request dialog sheet view for selecting between one or
// more accounts.
class AuthenticatorSelectAccountSheetView
: public AuthenticatorRequestSheetView,
public views::TableViewObserver {
public:
explicit AuthenticatorSelectAccountSheetView(
std::unique_ptr<AuthenticatorSelectAccountSheetModel> model);
~AuthenticatorSelectAccountSheetView() override;
private:
// AuthenticatorRequestSheetView:
std::unique_ptr<views::View> BuildStepSpecificContent() override;
// views::TableViewObserver
void OnSelectionChanged() override;
void OnDoubleClick() override;
views::TableView* table_;
DISALLOW_COPY_AND_ASSIGN(AuthenticatorSelectAccountSheetView);
};
#endif // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_AUTHENTICATOR_SELECT_ACCOUNT_SHEET_VIEW_H_
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "chrome/browser/ui/views/webauthn/authenticator_ble_pin_entry_sheet_view.h" #include "chrome/browser/ui/views/webauthn/authenticator_ble_pin_entry_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h" #include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h" #include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_select_account_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/authenticator_transport_selector_sheet_view.h" #include "chrome/browser/ui/views/webauthn/authenticator_transport_selector_sheet_view.h"
#include "chrome/browser/ui/views/webauthn/ble_device_selection_sheet_view.h" #include "chrome/browser/ui/views/webauthn/ble_device_selection_sheet_view.h"
#include "chrome/browser/ui/webauthn/sheet_models.h" #include "chrome/browser/ui/webauthn/sheet_models.h"
...@@ -151,6 +152,10 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf( ...@@ -151,6 +152,10 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf(
AuthenticatorGenericErrorSheetModel:: AuthenticatorGenericErrorSheetModel::
ForClientPinErrorAuthenticatorRemoved(dialog_model)); ForClientPinErrorAuthenticatorRemoved(dialog_model));
break; break;
case Step::kSelectAccount:
sheet_view = std::make_unique<AuthenticatorSelectAccountSheetView>(
std::make_unique<AuthenticatorSelectAccountSheetModel>(dialog_model));
break;
case Step::kNotStarted: case Step::kNotStarted:
case Step::kClosed: case Step::kClosed:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>( sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
......
...@@ -876,3 +876,68 @@ base::string16 AuthenticatorGenericErrorSheetModel::GetStepTitle() const { ...@@ -876,3 +876,68 @@ base::string16 AuthenticatorGenericErrorSheetModel::GetStepTitle() const {
base::string16 AuthenticatorGenericErrorSheetModel::GetStepDescription() const { base::string16 AuthenticatorGenericErrorSheetModel::GetStepDescription() const {
return description_; return description_;
} }
// AuthenticatorSelectAccountSheetModel ---------------------------------------
AuthenticatorSelectAccountSheetModel::AuthenticatorSelectAccountSheetModel(
AuthenticatorRequestDialogModel* dialog_model)
: AuthenticatorSheetModelBase(dialog_model) {}
AuthenticatorSelectAccountSheetModel::~AuthenticatorSelectAccountSheetModel() =
default;
void AuthenticatorSelectAccountSheetModel::SetCurrentSelection(int selected) {
DCHECK_LE(0, selected);
DCHECK_LT(static_cast<size_t>(selected), dialog_model()->responses().size());
selected_ = selected;
}
void AuthenticatorSelectAccountSheetModel::OnAccept() {
dialog_model()->OnAccountSelected(selected_);
}
gfx::ImageSkia* AuthenticatorSelectAccountSheetModel::GetStepIllustration()
const {
// TODO: this is likely the wrong image.
return GetImage(IDR_WEBAUTHN_ILLUSTRATION_WELCOME);
}
base::string16 AuthenticatorSelectAccountSheetModel::GetStepTitle() const {
return l10n_util::GetStringUTF16(IDS_WEBAUTHN_SELECT_ACCOUNT);
}
base::string16 AuthenticatorSelectAccountSheetModel::GetStepDescription()
const {
return l10n_util::GetStringUTF16(IDS_WEBAUTHN_SELECT_ACCOUNT_DESC);
}
bool AuthenticatorSelectAccountSheetModel::IsAcceptButtonVisible() const {
return true;
}
bool AuthenticatorSelectAccountSheetModel::IsAcceptButtonEnabled() const {
return true;
}
base::string16 AuthenticatorSelectAccountSheetModel::GetAcceptButtonLabel()
const {
return l10n_util::GetStringUTF16(IDS_WEBAUTHN_WELCOME_SCREEN_NEXT);
}
int AuthenticatorSelectAccountSheetModel::RowCount() {
return dialog_model()->responses().size();
}
base::string16 AuthenticatorSelectAccountSheetModel::GetText(int row,
int column_id) {
const auto user = dialog_model()->responses()[row].user_entity();
if (column_id == IDS_WEBAUTHN_ACCOUNT_COLUMN) {
return base::UTF8ToUTF16(user->user_name().value_or(""));
} else {
return base::UTF8ToUTF16(user->user_display_name().value_or(""));
}
}
void AuthenticatorSelectAccountSheetModel::SetObserver(
ui::TableModelObserver* observer) {}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h" #include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h" #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "ui/base/models/table_model.h"
namespace ui { namespace ui {
class MenuModel; class MenuModel;
...@@ -428,4 +429,35 @@ class AuthenticatorGenericErrorSheetModel : public AuthenticatorSheetModelBase { ...@@ -428,4 +429,35 @@ class AuthenticatorGenericErrorSheetModel : public AuthenticatorSheetModelBase {
base::string16 description_; base::string16 description_;
}; };
// The sheet shown when the user needs to select an account.
class AuthenticatorSelectAccountSheetModel : public AuthenticatorSheetModelBase,
public ui::TableModel {
public:
explicit AuthenticatorSelectAccountSheetModel(
AuthenticatorRequestDialogModel* dialog_model);
~AuthenticatorSelectAccountSheetModel() override;
// Set the index of the currently selected row.
void SetCurrentSelection(int selected);
// AuthenticatorSheetModelBase:
void OnAccept() override;
private:
// AuthenticatorSheetModelBase:
gfx::ImageSkia* GetStepIllustration() const override;
base::string16 GetStepTitle() const override;
base::string16 GetStepDescription() const override;
bool IsAcceptButtonVisible() const override;
bool IsAcceptButtonEnabled() const override;
base::string16 GetAcceptButtonLabel() const override;
// ui::TableModel:
int RowCount() override;
base::string16 GetText(int row, int column_id) override;
void SetObserver(ui::TableModelObserver* observer) override;
size_t selected_ = 0;
};
#endif // CHROME_BROWSER_UI_WEBAUTHN_SHEET_MODELS_H_ #endif // CHROME_BROWSER_UI_WEBAUTHN_SHEET_MODELS_H_
...@@ -508,6 +508,28 @@ void AuthenticatorRequestDialogModel::UpdateAuthenticatorReferencePairingMode( ...@@ -508,6 +508,28 @@ void AuthenticatorRequestDialogModel::UpdateAuthenticatorReferencePairingMode(
is_in_pairing_mode); is_in_pairing_mode);
} }
// SelectAccount is called to trigger an account selection dialog.
void AuthenticatorRequestDialogModel::SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) {
responses_ = std::move(responses);
selection_callback_ = std::move(callback);
SetCurrentStep(Step::kSelectAccount);
}
void AuthenticatorRequestDialogModel::OnAccountSelected(size_t index) {
if (!selection_callback_) {
// It's possible that the user could activate the dialog more than once
// before the Webauthn request is completed and its torn down.
return;
}
auto selected = std::move(responses_[index]);
responses_.clear();
std::move(selection_callback_).Run(std::move(selected));
}
void AuthenticatorRequestDialogModel::SetSelectedAuthenticatorForTesting( void AuthenticatorRequestDialogModel::SetSelectedAuthenticatorForTesting(
AuthenticatorReference test_authenticator) { AuthenticatorReference test_authenticator) {
selected_authenticator_id_ = test_authenticator.authenticator_id(); selected_authenticator_id_ = test_authenticator.authenticator_id();
......
...@@ -87,6 +87,9 @@ class AuthenticatorRequestDialogModel { ...@@ -87,6 +87,9 @@ class AuthenticatorRequestDialogModel {
kClientPinErrorSoftBlock, kClientPinErrorSoftBlock,
kClientPinErrorHardBlock, kClientPinErrorHardBlock,
kClientPinErrorAuthenticatorRemoved, kClientPinErrorAuthenticatorRemoved,
// Account selection,
kSelectAccount,
}; };
// Implemented by the dialog to observe this model and show the UI panels // Implemented by the dialog to observe this model and show the UI panels
...@@ -306,6 +309,17 @@ class AuthenticatorRequestDialogModel { ...@@ -306,6 +309,17 @@ class AuthenticatorRequestDialogModel {
base::StringPiece authenticator_id, base::StringPiece authenticator_id,
bool is_in_pairing_mode); bool is_in_pairing_mode);
// SelectAccount is called to trigger an account selection dialog.
void SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback);
// OnAccountSelected is called when one of the accounts from |SelectAccount|
// has been picked. |index| is the index of the selected account in
// |responses()|.
void OnAccountSelected(size_t index);
void SetSelectedAuthenticatorForTesting(AuthenticatorReference authenticator); void SetSelectedAuthenticatorForTesting(AuthenticatorReference authenticator);
ObservableAuthenticatorList& saved_authenticators() { ObservableAuthenticatorList& saved_authenticators() {
...@@ -321,6 +335,10 @@ class AuthenticatorRequestDialogModel { ...@@ -321,6 +335,10 @@ class AuthenticatorRequestDialogModel {
bool has_attempted_pin_entry() const { return has_attempted_pin_entry_; } bool has_attempted_pin_entry() const { return has_attempted_pin_entry_; }
base::Optional<int> pin_attempts() const { return pin_attempts_; } base::Optional<int> pin_attempts() const { return pin_attempts_; }
const std::vector<device::AuthenticatorGetAssertionResponse>& responses() {
return responses_;
}
private: private:
void DispatchRequestAsync(AuthenticatorReference* authenticator, void DispatchRequestAsync(AuthenticatorReference* authenticator,
base::TimeDelta delay); base::TimeDelta delay);
...@@ -365,6 +383,11 @@ class AuthenticatorRequestDialogModel { ...@@ -365,6 +383,11 @@ class AuthenticatorRequestDialogModel {
bool has_attempted_pin_entry_ = false; bool has_attempted_pin_entry_ = false;
base::Optional<int> pin_attempts_; base::Optional<int> pin_attempts_;
// responses_ contains possible accounts to select between.
std::vector<device::AuthenticatorGetAssertionResponse> responses_;
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
selection_callback_;
base::WeakPtrFactory<AuthenticatorRequestDialogModel> weak_factory_; base::WeakPtrFactory<AuthenticatorRequestDialogModel> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AuthenticatorRequestDialogModel); DISALLOW_COPY_AND_ASSIGN(AuthenticatorRequestDialogModel);
......
...@@ -223,6 +223,22 @@ void ChromeAuthenticatorRequestDelegate::ShouldReturnAttestation( ...@@ -223,6 +223,22 @@ void ChromeAuthenticatorRequestDelegate::ShouldReturnAttestation(
#endif #endif
} }
bool ChromeAuthenticatorRequestDelegate::SupportsResidentKeys() {
return true;
}
void ChromeAuthenticatorRequestDelegate::SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) {
if (!IsWebAuthnUIEnabled() || !weak_dialog_model_) {
std::move(cancel_callback_).Run();
return;
}
weak_dialog_model_->SelectAccount(std::move(responses), std::move(callback));
}
bool ChromeAuthenticatorRequestDelegate::IsFocused() { bool ChromeAuthenticatorRequestDelegate::IsFocused() {
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
// Android is expected to use platform APIs for webauthn. // Android is expected to use platform APIs for webauthn.
......
...@@ -81,6 +81,11 @@ class ChromeAuthenticatorRequestDelegate ...@@ -81,6 +81,11 @@ class ChromeAuthenticatorRequestDelegate
void ShouldReturnAttestation( void ShouldReturnAttestation(
const std::string& relying_party_id, const std::string& relying_party_id,
base::OnceCallback<void(bool)> callback) override; base::OnceCallback<void(bool)> callback) override;
bool SupportsResidentKeys() override;
void SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) override;
bool IsFocused() override; bool IsFocused() override;
void UpdateLastTransportUsed( void UpdateLastTransportUsed(
device::FidoTransportProtocol transport) override; device::FidoTransportProtocol transport) override;
......
...@@ -639,7 +639,9 @@ void AuthenticatorCommon::MakeCredential( ...@@ -639,7 +639,9 @@ void AuthenticatorCommon::MakeCredential(
} }
if (options->authenticator_selection && if (options->authenticator_selection &&
options->authenticator_selection->require_resident_key) { options->authenticator_selection->require_resident_key &&
(!base::FeatureList::IsEnabled(device::kWebAuthResidentKeys) ||
!request_delegate_->SupportsResidentKeys())) {
// Disallow the creation of resident credentials. // Disallow the creation of resident credentials.
InvokeCallbackAndCleanup( InvokeCallbackAndCleanup(
std::move(callback), std::move(callback),
...@@ -806,11 +808,14 @@ void AuthenticatorCommon::GetAssertion( ...@@ -806,11 +808,14 @@ void AuthenticatorCommon::GetAssertion(
} }
if (options->allow_credentials.empty()) { if (options->allow_credentials.empty()) {
// Chrome currently does not support any resident keys. if (!base::FeatureList::IsEnabled(device::kWebAuthResidentKeys) ||
InvokeCallbackAndCleanup( !request_delegate_->SupportsResidentKeys()) {
std::move(callback), InvokeCallbackAndCleanup(
blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED); std::move(callback),
return; blink::mojom::AuthenticatorStatus::RESIDENT_CREDENTIALS_UNSUPPORTED);
return;
}
need_account_selection_ = true;
} }
if (options->appid) { if (options->appid) {
...@@ -1129,29 +1134,40 @@ void AuthenticatorCommon::OnSignResponse( ...@@ -1129,29 +1134,40 @@ void AuthenticatorCommon::OnSignResponse(
return; return;
case device::FidoReturnCode::kSuccess: case device::FidoReturnCode::kSuccess:
DCHECK(response_data.has_value()); DCHECK(response_data.has_value());
// Resident keys are not yet supported so there must be a single response.
DCHECK_EQ(1u, response_data->size());
auto& response = response_data.value()[0];
if (transport_used) { if (transport_used) {
request_delegate_->UpdateLastTransportUsed(*transport_used); request_delegate_->UpdateLastTransportUsed(*transport_used);
} }
base::Optional<bool> echo_appid_extension; if (need_account_selection_) {
if (app_id_) { request_delegate_->SelectAccount(
echo_appid_extension = std::move(*response_data),
(response.GetRpIdHash() == CreateApplicationParameter(*app_id_)); base::BindOnce(&AuthenticatorCommon::OnAccountSelected,
weak_factory_.GetWeakPtr()));
return;
} }
InvokeCallbackAndCleanup(std::move(get_assertion_response_callback_),
blink::mojom::AuthenticatorStatus::SUCCESS, OnAccountSelected(std::move(response_data.value()[0]));
CreateGetAssertionResponse(
std::move(client_data_json_),
std::move(response), echo_appid_extension));
return; return;
} }
NOTREACHED(); NOTREACHED();
} }
void AuthenticatorCommon::OnAccountSelected(
device::AuthenticatorGetAssertionResponse response) {
base::Optional<bool> echo_appid_extension;
if (app_id_) {
echo_appid_extension =
(response.GetRpIdHash() == CreateApplicationParameter(*app_id_));
}
InvokeCallbackAndCleanup(
std::move(get_assertion_response_callback_),
blink::mojom::AuthenticatorStatus::SUCCESS,
CreateGetAssertionResponse(std::move(client_data_json_),
std::move(response), echo_appid_extension));
return;
}
void AuthenticatorCommon::SignalFailureToRequestDelegate( void AuthenticatorCommon::SignalFailureToRequestDelegate(
AuthenticatorRequestClientDelegate::InterestingFailureReason reason) { AuthenticatorRequestClientDelegate::InterestingFailureReason reason) {
blink::mojom::AuthenticatorStatus status = blink::mojom::AuthenticatorStatus status =
...@@ -1268,6 +1284,7 @@ void AuthenticatorCommon::Cleanup() { ...@@ -1268,6 +1284,7 @@ void AuthenticatorCommon::Cleanup() {
client_data_json_.clear(); client_data_json_.clear();
app_id_.reset(); app_id_.reset();
attestation_requested_ = false; attestation_requested_ = false;
need_account_selection_ = false;
error_awaiting_user_acknowledgement_ = error_awaiting_user_acknowledgement_ =
blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR; blink::mojom::AuthenticatorStatus::NOT_ALLOWED_ERROR;
} }
......
...@@ -154,6 +154,11 @@ class CONTENT_EXPORT AuthenticatorCommon { ...@@ -154,6 +154,11 @@ class CONTENT_EXPORT AuthenticatorCommon {
// Runs when the user cancels WebAuthN request via UI dialog. // Runs when the user cancels WebAuthN request via UI dialog.
void Cancel(); void Cancel();
// Called when a GetAssertion has completed, either because an allow_list was
// used and so an answer is returned directly, or because the user selected an
// account from the options.
void OnAccountSelected(device::AuthenticatorGetAssertionResponse response);
// Decides whether or not UI is present that needs to block on user // Decides whether or not UI is present that needs to block on user
// acknowledgement before returning the error, and handles the error // acknowledgement before returning the error, and handles the error
// appropriately. // appropriately.
...@@ -193,6 +198,10 @@ class CONTENT_EXPORT AuthenticatorCommon { ...@@ -193,6 +198,10 @@ class CONTENT_EXPORT AuthenticatorCommon {
std::string relying_party_id_; std::string relying_party_id_;
std::unique_ptr<base::OneShotTimer> timer_; std::unique_ptr<base::OneShotTimer> timer_;
base::Optional<std::string> app_id_; base::Optional<std::string> app_id_;
// need_account_selection_ indicates if an empty allow-list was used, thus
// implying that an account selection dialog needs to be displayed to the user
// before returning any assertions.
bool need_account_selection_ = false;
// 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.
bool awaiting_attestation_response_ = false; bool awaiting_attestation_response_ = false;
......
...@@ -38,6 +38,18 @@ void AuthenticatorRequestClientDelegate::ShouldReturnAttestation( ...@@ -38,6 +38,18 @@ void AuthenticatorRequestClientDelegate::ShouldReturnAttestation(
std::move(callback).Run(true); std::move(callback).Run(true);
} }
bool AuthenticatorRequestClientDelegate::SupportsResidentKeys() {
return false;
}
void AuthenticatorRequestClientDelegate::SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) {
// SupportsResidentKeys returned false so this should never be called.
NOTREACHED();
}
bool AuthenticatorRequestClientDelegate::IsFocused() { bool AuthenticatorRequestClientDelegate::IsFocused() {
return true; return true;
} }
......
...@@ -83,6 +83,25 @@ class CONTENT_EXPORT AuthenticatorRequestClientDelegate ...@@ -83,6 +83,25 @@ class CONTENT_EXPORT AuthenticatorRequestClientDelegate
virtual void ShouldReturnAttestation(const std::string& relying_party_id, virtual void ShouldReturnAttestation(const std::string& relying_party_id,
base::OnceCallback<void(bool)> callback); base::OnceCallback<void(bool)> callback);
// SupportsResidentKeys returns true if this implementation of
// |AuthenticatorRequestClientDelegate| supports resident keys. If false then
// requests to create or get assertions will be immediately rejected and
// |SelectAccount| will never be called.
virtual bool SupportsResidentKeys();
// SelectAccount is called to allow the embedder to select between one or more
// accounts. This is triggered when the web page requests an unspecified
// credential (by passing an empty allow-list). In this case, any accounts
// will come from the authenticator's storage and the user should confirm the
// use of any specific account before it is returned. The callback takes the
// selected account, or else |cancel_callback| can be called.
//
// This is only called if |SupportsResidentKeys| returns true.
virtual void SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback);
// Returns whether the WebContents corresponding to |render_frame_host| is the // Returns whether the WebContents corresponding to |render_frame_host| is the
// active tab in the focused window. We do not want to allow // active tab in the focused window. We do not want to allow
// authenticatorMakeCredential operations to be triggered by background tabs. // authenticatorMakeCredential operations to be triggered by background tabs.
......
...@@ -41,7 +41,7 @@ std::vector<uint8_t> CtapGetAssertionRequest::EncodeAsCBOR() const { ...@@ -41,7 +41,7 @@ std::vector<uint8_t> CtapGetAssertionRequest::EncodeAsCBOR() const {
cbor_map[cbor::Value(1)] = cbor::Value(rp_id_); cbor_map[cbor::Value(1)] = cbor::Value(rp_id_);
cbor_map[cbor::Value(2)] = cbor::Value(client_data_hash_); cbor_map[cbor::Value(2)] = cbor::Value(client_data_hash_);
if (allow_list_) { if (allow_list_ && !allow_list_->empty()) {
cbor::Value::ArrayValue allow_list_array; cbor::Value::ArrayValue allow_list_array;
for (const auto& descriptor : *allow_list_) { for (const auto& descriptor : *allow_list_) {
allow_list_array.push_back(descriptor.ConvertToCBOR()); allow_list_array.push_back(descriptor.ConvertToCBOR());
...@@ -136,4 +136,9 @@ bool CtapGetAssertionRequest::CheckResponseRpIdHash( ...@@ -136,4 +136,9 @@ bool CtapGetAssertionRequest::CheckResponseRpIdHash(
response_rp_id_hash == *alternative_application_parameter()); response_rp_id_hash == *alternative_application_parameter());
} }
std::vector<uint8_t> CtapGetNextAssertionRequest::EncodeAsCBOR() const {
return {
static_cast<uint8_t>(CtapRequestCommand::kAuthenticatorGetNextAssertion)};
}
} // namespace device } // namespace device
...@@ -112,6 +112,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { ...@@ -112,6 +112,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
bool is_incognito_mode_ = false; bool is_incognito_mode_ = false;
}; };
class CtapGetNextAssertionRequest {
public:
std::vector<uint8_t> EncodeAsCBOR() const;
};
} // namespace device } // namespace device
#endif // DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_ #endif // DEVICE_FIDO_CTAP_GET_ASSERTION_REQUEST_H_
...@@ -22,4 +22,7 @@ extern const base::Feature kWebAuthProxyCryptotoken{ ...@@ -22,4 +22,7 @@ extern const base::Feature kWebAuthProxyCryptotoken{
extern const base::Feature kWebAuthPINSupport{ extern const base::Feature kWebAuthPINSupport{
"WebAuthenticationPINSupport", base::FEATURE_DISABLED_BY_DEFAULT}; "WebAuthenticationPINSupport", base::FEATURE_DISABLED_BY_DEFAULT};
extern const base::Feature kWebAuthResidentKeys{
"WebAuthenticationResidentKeys", base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace device } // namespace device
...@@ -24,6 +24,10 @@ extern const base::Feature kWebAuthProxyCryptotoken; ...@@ -24,6 +24,10 @@ extern const base::Feature kWebAuthProxyCryptotoken;
COMPONENT_EXPORT(DEVICE_FIDO) COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthPINSupport; extern const base::Feature kWebAuthPINSupport;
// Enable support for resident keys.
COMPONENT_EXPORT(DEVICE_FIDO)
extern const base::Feature kWebAuthResidentKeys;
} // namespace device } // namespace device
#endif // DEVICE_FIDO_FEATURES_H_ #endif // DEVICE_FIDO_FEATURES_H_
...@@ -13,6 +13,11 @@ ...@@ -13,6 +13,11 @@
namespace device { namespace device {
void FidoAuthenticator::GetNextAssertion(
FidoAuthenticator::GetAssertionCallback callback) {
NOTREACHED();
}
void FidoAuthenticator::GetTouch(base::OnceCallback<void()> callback) {} void FidoAuthenticator::GetTouch(base::OnceCallback<void()> callback) {}
void FidoAuthenticator::GetRetries( void FidoAuthenticator::GetRetries(
......
...@@ -69,6 +69,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { ...@@ -69,6 +69,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
MakeCredentialCallback callback) = 0; MakeCredentialCallback callback) = 0;
virtual void GetAssertion(CtapGetAssertionRequest request, virtual void GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) = 0; GetAssertionCallback callback) = 0;
// GetNextAssertion fetches the next assertion from a device that indicated in
// the response to |GetAssertion| that multiple results were available.
virtual void GetNextAssertion(GetAssertionCallback callback);
// GetTouch causes an (external) authenticator to flash and wait for a touch. // GetTouch causes an (external) authenticator to flash and wait for a touch.
virtual void GetTouch(base::OnceCallback<void()> callback); virtual void GetTouch(base::OnceCallback<void()> callback);
// GetRetries gets the number of PIN attempts remaining before an // GetRetries gets the number of PIN attempts remaining before an
......
...@@ -71,6 +71,18 @@ void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request, ...@@ -71,6 +71,18 @@ void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request,
std::move(callback)); std::move(callback));
} }
void FidoDeviceAuthenticator::GetNextAssertion(GetAssertionCallback callback) {
DCHECK(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
operation_ =
std::make_unique<Ctap2DeviceOperation<CtapGetNextAssertionRequest,
AuthenticatorGetAssertionResponse>>(
device_.get(), CtapGetNextAssertionRequest(), std::move(callback),
base::BindOnce(&ReadCTAPGetAssertionResponse));
operation_->Start();
}
void FidoDeviceAuthenticator::GetTouch(base::OnceCallback<void()> callback) { void FidoDeviceAuthenticator::GetTouch(base::OnceCallback<void()> callback) {
MakeCredential( MakeCredential(
MakeCredentialTask::GetTouchRequest(device()), MakeCredentialTask::GetTouchRequest(device()),
......
...@@ -45,6 +45,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator ...@@ -45,6 +45,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
MakeCredentialCallback callback) override; MakeCredentialCallback callback) override;
void GetAssertion(CtapGetAssertionRequest request, void GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) override; GetAssertionCallback callback) override;
void GetNextAssertion(GetAssertionCallback callback) override;
void GetTouch(base::OnceCallback<void()> callback) override; void GetTouch(base::OnceCallback<void()> callback) override;
void GetRetries(GetRetriesCallback callback) override; void GetRetries(GetRetriesCallback callback) override;
void GetEphemeralKey(GetEphemeralKeyCallback callback) override; void GetEphemeralKey(GetEphemeralKeyCallback callback) override;
......
...@@ -311,9 +311,68 @@ void GetAssertionRequestHandler::HandleResponse( ...@@ -311,9 +311,68 @@ void GetAssertionRequestHandler::HandleResponse(
} }
SetCredentialIdForResponseWithEmptyCredential(request_, *response); SetCredentialIdForResponseWithEmptyCredential(request_, *response);
std::vector<AuthenticatorGetAssertionResponse> responses; const size_t num_responses = response->num_credentials().value_or(1);
responses.emplace_back(std::move(*response)); if (num_responses == 0 || (num_responses > 1 && request_.allow_list() &&
OnAuthenticatorResponse(authenticator, response_code, std::move(responses)); !request_.allow_list()->empty())) {
OnAuthenticatorResponse(
authenticator, CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
DCHECK(responses_.empty());
responses_.emplace_back(std::move(*response));
if (num_responses > 1) {
// Multiple responses. Need to read them all.
state_ = State::kReadingMultipleResponses;
remaining_responses_ = num_responses - 1;
authenticator->GetNextAssertion(
base::BindOnce(&GetAssertionRequestHandler::HandleNextResponse,
weak_factory_.GetWeakPtr(), authenticator));
return;
}
OnAuthenticatorResponse(authenticator, response_code, std::move(responses_));
}
void GetAssertionRequestHandler::HandleNextResponse(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode status,
base::Optional<AuthenticatorGetAssertionResponse> response) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
DCHECK_EQ(State::kReadingMultipleResponses, state_);
DCHECK_LT(0u, remaining_responses_);
state_ = State::kFinished;
if (status == CtapDeviceResponseCode::kSuccess && !response) {
status = CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
}
if (status != CtapDeviceResponseCode::kSuccess) {
OnAuthenticatorResponse(authenticator, status, base::nullopt);
return;
}
if (!request_.CheckResponseRpIdHash(response->GetRpIdHash()) ||
!CheckResponseCredentialIdMatchesRequestAllowList(*authenticator,
request_, *response) ||
!CheckRequirementsOnResponseUserEntity(request_, *response)) {
OnAuthenticatorResponse(
authenticator, CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt);
return;
}
DCHECK(!responses_.empty());
responses_.emplace_back(std::move(*response));
remaining_responses_--;
if (remaining_responses_ > 0) {
state_ = State::kReadingMultipleResponses;
authenticator->GetNextAssertion(
base::BindOnce(&GetAssertionRequestHandler::HandleNextResponse,
weak_factory_.GetWeakPtr(), authenticator));
return;
}
OnAuthenticatorResponse(authenticator, status, std::move(responses_));
} }
void GetAssertionRequestHandler::HandleTouch(FidoAuthenticator* authenticator) { void GetAssertionRequestHandler::HandleTouch(FidoAuthenticator* authenticator) {
......
...@@ -46,6 +46,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler ...@@ -46,6 +46,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
kWaitingForPIN, kWaitingForPIN,
kGetEphemeralKey, kGetEphemeralKey,
kRequestWithPIN, kRequestWithPIN,
kReadingMultipleResponses,
kFinished, kFinished,
}; };
...@@ -59,6 +60,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler ...@@ -59,6 +60,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
CtapDeviceResponseCode response_code, CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorGetAssertionResponse> response); base::Optional<AuthenticatorGetAssertionResponse> response);
void HandleTouch(FidoAuthenticator* authenticator); void HandleTouch(FidoAuthenticator* authenticator);
void HandleNextResponse(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorGetAssertionResponse> response);
void OnRetriesResponse(CtapDeviceResponseCode status, void OnRetriesResponse(CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response); base::Optional<pin::RetriesResponse> response);
void OnHavePIN(std::string pin); void OnHavePIN(std::string pin);
...@@ -76,6 +81,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler ...@@ -76,6 +81,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
// requesting PIN etc. The object is owned by the underlying discovery object // requesting PIN etc. The object is owned by the underlying discovery object
// and this pointer is cleared if it's removed during processing. // and this pointer is cleared if it's removed during processing.
FidoAuthenticator* authenticator_ = nullptr; FidoAuthenticator* authenticator_ = nullptr;
// responses_ holds the set of responses while they are incrementally read
// from the device. Only used when more than one response is returned.
std::vector<AuthenticatorGetAssertionResponse> responses_;
// remaining_responses_ contains the number of responses that remain to be
// read when multiple responses are returned.
size_t remaining_responses_ = 0;
SEQUENCE_CHECKER(my_sequence_checker_); SEQUENCE_CHECKER(my_sequence_checker_);
base::WeakPtrFactory<GetAssertionRequestHandler> weak_factory_; base::WeakPtrFactory<GetAssertionRequestHandler> weak_factory_;
......
...@@ -120,10 +120,14 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler( ...@@ -120,10 +120,14 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler(
// default values up to here. TODO(martinkr): Initialize these fields earlier // default values up to here. TODO(martinkr): Initialize these fields earlier
// (in AuthenticatorImpl) and get rid of the separate // (in AuthenticatorImpl) and get rid of the separate
// AuthenticatorSelectionCriteriaParameter. // AuthenticatorSelectionCriteriaParameter.
request_.SetResidentKeyRequired( if (authenticator_selection_criteria_.require_resident_key()) {
authenticator_selection_criteria_.require_resident_key()); request_.SetResidentKeyRequired(true);
request_.SetUserVerification( request_.SetUserVerification(UserVerificationRequirement::kRequired);
authenticator_selection_criteria_.user_verification_requirement()); } else {
request_.SetResidentKeyRequired(false);
request_.SetUserVerification(
authenticator_selection_criteria_.user_verification_requirement());
}
request_.SetAuthenticatorAttachment( request_.SetAuthenticatorAttachment(
authenticator_selection_criteria_.authenticator_attachment()); authenticator_selection_criteria_.authenticator_attachment());
......
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