blob: 14180463a4991c2ed1f21c14f6c905559492964d [file] [log] [blame]
// 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 "chrome/browser/ui/webui/settings/settings_security_key_handler.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/contains.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
#include "chrome/browser/ui/webui/settings/settings_security_key_handler.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "device/fido/credential_management.h"
#include "device/fido/credential_management_handler.h"
#include "device/fido/fido_constants.h"
#include "device/fido/pin.h"
#include "device/fido/reset_request_handler.h"
#include "device/fido/set_pin_request_handler.h"
#include "ui/base/l10n/l10n_util.h"
using content::BrowserThread;
namespace {
base::flat_set<device::FidoTransportProtocol> supported_transports() {
// If we ever support BLE devices then additional thought will be required
// in the UI; therefore don't enable them here. NFC is not supported on
// desktop thus only USB devices remain to be enabled.
return {device::FidoTransportProtocol::kUsbHumanInterfaceDevice};
}
void HandleClose(base::RepeatingClosure close_callback,
const base::ListValue* args) {
DCHECK_EQ(0u, args->GetSize());
close_callback.Run();
}
base::DictionaryValue EncodeEnrollment(const std::vector<uint8_t>& id,
const std::string& name) {
base::DictionaryValue value;
value.SetStringKey("name", name);
value.SetStringKey("id", base::HexEncode(id.data(), id.size()));
return value;
}
} // namespace
namespace settings {
SecurityKeysHandlerBase::SecurityKeysHandlerBase() = default;
SecurityKeysHandlerBase::SecurityKeysHandlerBase(
std::unique_ptr<device::FidoDiscoveryFactory> discovery_factory)
: discovery_factory_(std::move(discovery_factory)) {}
SecurityKeysHandlerBase::~SecurityKeysHandlerBase() = default;
void SecurityKeysHandlerBase::OnJavascriptAllowed() {}
void SecurityKeysHandlerBase::OnJavascriptDisallowed() {
// If Javascript is disallowed, |Close| will invalidate all current WeakPtrs
// and thus drop all pending callbacks. This means that
// |IsJavascriptAllowed| doesn't need to be tested before each callback
// because, if the callback into this object happened, then Javascript is
// allowed.
Close();
}
SecurityKeysPINHandler::SecurityKeysPINHandler() = default;
SecurityKeysPINHandler::~SecurityKeysPINHandler() = default;
void SecurityKeysPINHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"securityKeyStartSetPIN",
base::BindRepeating(&SecurityKeysPINHandler::HandleStartSetPIN,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeySetPIN",
base::BindRepeating(&SecurityKeysPINHandler::HandleSetPIN,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyPINClose",
base::BindRepeating(&HandleClose,
base::BindRepeating(&SecurityKeysPINHandler::Close,
base::Unretained(this))));
}
void SecurityKeysPINHandler::Close() {
// Invalidate all existing WeakPtrs so that no stale callbacks occur.
weak_factory_.InvalidateWeakPtrs();
state_ = State::kNone;
set_pin_.reset();
callback_id_.clear();
}
void SecurityKeysPINHandler::HandleStartSetPIN(const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(State::kNone, state_);
DCHECK_EQ(1u, args->GetSize());
AllowJavascript();
DCHECK(callback_id_.empty());
callback_id_ = args->GetList()[0].GetString();
state_ = State::kStartSetPIN;
set_pin_ = std::make_unique<device::SetPINRequestHandler>(
supported_transports(),
base::BindOnce(&SecurityKeysPINHandler::OnGatherPIN,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&SecurityKeysPINHandler::OnSetPINComplete,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysPINHandler::OnGatherPIN(uint32_t current_min_pin_length,
uint32_t new_min_pin_length,
base::Optional<int64_t> num_retries) {
DCHECK_EQ(State::kStartSetPIN, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::Value::DictStorage response;
response.emplace("done", false);
response.emplace("error", base::Value::Type::NONE);
response.emplace("currentMinPinLength",
static_cast<int>(current_min_pin_length));
response.emplace("newMinPinLength", static_cast<int>(new_min_pin_length));
if (num_retries) {
state_ = State::kGatherChangePIN;
response.emplace("retries", static_cast<int>(*num_retries));
} else {
state_ = State::kGatherNewPIN;
response.emplace("retries", base::Value::Type::NONE);
}
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(response)));
}
void SecurityKeysPINHandler::OnSetPINComplete(
device::CtapDeviceResponseCode code) {
DCHECK(state_ == State::kStartSetPIN || state_ == State::kSettingPIN);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (code == device::CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
// In the event that the old PIN was incorrect, the UI may prompt again.
state_ = State::kGatherChangePIN;
} else {
state_ = State::kNone;
set_pin_.reset();
}
base::Value::DictStorage response;
response.emplace("done", true);
response.emplace("error", static_cast<int>(code));
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(response)));
}
void SecurityKeysPINHandler::HandleSetPIN(const base::ListValue* args) {
DCHECK(state_ == State::kGatherNewPIN || state_ == State::kGatherChangePIN);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(3u, args->GetSize());
DCHECK(callback_id_.empty());
callback_id_ = args->GetList()[0].GetString();
const std::string old_pin = args->GetList()[1].GetString();
const std::string new_pin = args->GetList()[2].GetString();
DCHECK((state_ == State::kGatherNewPIN) == old_pin.empty());
CHECK_EQ(device::pin::ValidatePIN(new_pin),
device::pin::PINEntryError::kNoError);
state_ = State::kSettingPIN;
set_pin_->ProvidePIN(old_pin, new_pin);
}
SecurityKeysResetHandler::SecurityKeysResetHandler() = default;
SecurityKeysResetHandler::~SecurityKeysResetHandler() = default;
void SecurityKeysResetHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"securityKeyReset",
base::BindRepeating(&SecurityKeysResetHandler::HandleReset,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyCompleteReset",
base::BindRepeating(&SecurityKeysResetHandler::HandleCompleteReset,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyResetClose",
base::BindRepeating(&HandleClose,
base::BindRepeating(&SecurityKeysResetHandler::Close,
base::Unretained(this))));
}
void SecurityKeysResetHandler::Close() {
// Invalidate all existing WeakPtrs so that no stale callbacks occur.
weak_factory_.InvalidateWeakPtrs();
state_ = State::kNone;
reset_.reset();
callback_id_.clear();
}
void SecurityKeysResetHandler::HandleReset(const base::ListValue* args) {
DCHECK_EQ(State::kNone, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
AllowJavascript();
DCHECK(callback_id_.empty());
callback_id_ = args->GetList()[0].GetString();
state_ = State::kStartReset;
reset_ = std::make_unique<device::ResetRequestHandler>(
supported_transports(),
base::BindOnce(&SecurityKeysResetHandler::OnResetSent,
weak_factory_.GetWeakPtr()),
base::BindOnce(&SecurityKeysResetHandler::OnResetFinished,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysResetHandler::OnResetSent() {
DCHECK_EQ(State::kStartReset, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// A reset message has been sent to a security key and it may complete
// before Javascript asks for the result. Therefore |HandleCompleteReset|
// and |OnResetFinished| may be called in either order.
state_ = State::kWaitingForResetNoCallbackYet;
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(0 /* success */));
}
void SecurityKeysResetHandler::HandleCompleteReset(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
DCHECK(callback_id_.empty());
callback_id_ = args->GetList()[0].GetString();
switch (state_) {
case State::kWaitingForResetNoCallbackYet:
// The reset operation hasn't completed. |callback_id_| will be used in
// |OnResetFinished| when it does.
state_ = State::kWaitingForResetHaveCallback;
break;
case State::kWaitingForCompleteReset:
// The reset operation has completed and we were waiting for this
// call from Javascript in order to provide the result.
state_ = State::kNone;
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(static_cast<int>(*reset_result_)));
reset_.reset();
break;
default:
NOTREACHED();
}
}
void SecurityKeysResetHandler::OnResetFinished(
device::CtapDeviceResponseCode result) {
switch (state_) {
case State::kWaitingForResetNoCallbackYet:
// The reset operation has completed, but Javascript hasn't called
// |CompleteReset| so we cannot make the callback yet.
state_ = State::kWaitingForCompleteReset;
reset_result_ = result;
break;
case State::kStartReset:
// The reset operation failed immediately, probably because the user
// selected a U2F device. |callback_id_| has been set by |Reset|.
[[fallthrough]];
case State::kWaitingForResetHaveCallback:
// The |CompleteReset| call has already provided |callback_id_| so the
// reset can be completed immediately.
state_ = State::kNone;
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(static_cast<int>(result)));
reset_.reset();
break;
default:
NOTREACHED();
}
}
SecurityKeysCredentialHandler::SecurityKeysCredentialHandler() = default;
SecurityKeysCredentialHandler::SecurityKeysCredentialHandler(
std::unique_ptr<device::FidoDiscoveryFactory> discovery_factory)
: SecurityKeysHandlerBase(std::move(discovery_factory)) {}
SecurityKeysCredentialHandler::~SecurityKeysCredentialHandler() = default;
void SecurityKeysCredentialHandler::HandleStart(const base::ListValue* args) {
DCHECK_EQ(State::kNone, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
DCHECK(!credential_management_);
AllowJavascript();
DCHECK(callback_id_.empty());
callback_id_ = args->GetList()[0].GetString();
state_ = State::kStart;
credential_management_ =
std::make_unique<device::CredentialManagementHandler>(
discovery_factory(), supported_transports(),
base::BindOnce(
&SecurityKeysCredentialHandler::OnCredentialManagementReady,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&SecurityKeysCredentialHandler::OnGatherPIN,
weak_factory_.GetWeakPtr()),
base::BindOnce(&SecurityKeysCredentialHandler::OnFinished,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysCredentialHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"securityKeyCredentialManagementStart",
base::BindRepeating(&SecurityKeysCredentialHandler::HandleStart,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyCredentialManagementPIN",
base::BindRepeating(&SecurityKeysCredentialHandler::HandlePIN,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyCredentialManagementEnumerate",
base::BindRepeating(&SecurityKeysCredentialHandler::HandleEnumerate,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyCredentialManagementDelete",
base::BindRepeating(&SecurityKeysCredentialHandler::HandleDelete,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyCredentialManagementClose",
base::BindRepeating(
&HandleClose,
base::BindRepeating(&SecurityKeysCredentialHandler::Close,
base::Unretained(this))));
}
void SecurityKeysCredentialHandler::Close() {
// Invalidate all existing WeakPtrs so that no stale callbacks occur.
weak_factory_.InvalidateWeakPtrs();
state_ = State::kNone;
credential_management_.reset();
callback_id_.clear();
credential_management_provide_pin_cb_.Reset();
DCHECK(!credential_management_provide_pin_cb_);
}
void SecurityKeysCredentialHandler::HandlePIN(const base::ListValue* args) {
DCHECK_EQ(State::kPIN, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(2u, args->GetSize());
DCHECK(credential_management_);
DCHECK(credential_management_provide_pin_cb_);
DCHECK(callback_id_.empty());
callback_id_ = args->GetList()[0].GetString();
std::string pin = args->GetList()[1].GetString();
std::move(credential_management_provide_pin_cb_).Run(pin);
}
void SecurityKeysCredentialHandler::HandleEnumerate(
const base::ListValue* args) {
DCHECK_EQ(state_, State::kReady);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
DCHECK(credential_management_);
DCHECK(callback_id_.empty());
state_ = State::kGettingCredentials;
callback_id_ = args->GetList()[0].GetString();
credential_management_->GetCredentials(
base::BindOnce(&SecurityKeysCredentialHandler::OnHaveCredentials,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysCredentialHandler::HandleDelete(const base::ListValue* args) {
DCHECK_EQ(State::kReady, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(2u, args->GetSize());
DCHECK(credential_management_);
DCHECK(callback_id_.empty());
state_ = State::kDeletingCredentials;
callback_id_ = args->GetList()[0].GetString();
std::vector<std::vector<uint8_t>> credential_ids;
for (const base::Value& el : args->GetList()[1].GetList()) {
std::vector<uint8_t> credential_id;
if (!base::HexStringToBytes(el.GetString(), &credential_id)) {
NOTREACHED();
continue;
}
credential_ids.emplace_back(std::move(credential_id));
}
credential_management_->DeleteCredentials(
std::move(credential_ids),
base::BindOnce(&SecurityKeysCredentialHandler::OnCredentialsDeleted,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysCredentialHandler::OnCredentialManagementReady() {
DCHECK(state_ == State::kStart || state_ == State::kPIN);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(credential_management_);
DCHECK(!callback_id_.empty());
state_ = State::kReady;
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value());
}
void SecurityKeysCredentialHandler::OnHaveCredentials(
device::CtapDeviceResponseCode status,
base::Optional<std::vector<device::AggregatedEnumerateCredentialsResponse>>
responses,
base::Optional<size_t> remaining_credentials) {
DCHECK_EQ(State::kGettingCredentials, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(credential_management_);
DCHECK(!callback_id_.empty());
if (status != device::CtapDeviceResponseCode::kSuccess) {
OnFinished(
device::CredentialManagementStatus::kAuthenticatorResponseInvalid);
return;
}
DCHECK(responses);
DCHECK(remaining_credentials);
state_ = State::kReady;
base::Value::ListStorage credentials;
for (const auto& response : *responses) {
for (const auto& credential : response.credentials) {
base::DictionaryValue credential_value;
std::string credential_id =
base::HexEncode(credential.credential_id_cbor_bytes.data(),
credential.credential_id_cbor_bytes.size());
if (credential_id.empty()) {
NOTREACHED();
continue;
}
credential_value.SetString("id", std::move(credential_id));
credential_value.SetString("relyingPartyId", response.rp.id);
credential_value.SetString("userName", credential.user.name.value_or(""));
credential_value.SetString("userDisplayName",
credential.user.display_name.value_or(""));
credentials.emplace_back(std::move(credential_value));
}
}
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::ListValue(std::move(credentials)));
}
void SecurityKeysCredentialHandler::OnGatherPIN(
uint32_t min_pin_length,
int64_t num_retries,
base::OnceCallback<void(std::string)> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!callback_id_.empty());
DCHECK(!credential_management_provide_pin_cb_);
credential_management_provide_pin_cb_ = std::move(callback);
base::Value::ListStorage response;
response.emplace_back(static_cast<int>(min_pin_length));
if (state_ == State::kStart) {
// Resolve the promise to startCredentialManagement().
state_ = State::kPIN;
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(response)));
return;
}
// Resolve the promise to credentialManagementProvidePIN().
DCHECK_EQ(state_, State::kPIN);
response.emplace_back(static_cast<int>(num_retries));
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(response)));
}
void SecurityKeysCredentialHandler::OnCredentialsDeleted(
device::CtapDeviceResponseCode status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(State::kDeletingCredentials, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(credential_management_);
DCHECK(!callback_id_.empty());
state_ = State::kReady;
ResolveJavascriptCallback(
base::Value(std::move(callback_id_)),
base::Value(l10n_util::GetStringUTF8(
status == device::CtapDeviceResponseCode::kSuccess
? IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_SUCCESS
: IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_FAILED)));
}
void SecurityKeysCredentialHandler::OnFinished(
device::CredentialManagementStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
int error;
bool requires_pin_change = false;
switch (status) {
case device::CredentialManagementStatus::kSoftPINBlock:
error = IDS_SETTINGS_SECURITY_KEYS_PIN_SOFT_LOCK;
break;
case device::CredentialManagementStatus::kHardPINBlock:
error = IDS_SETTINGS_SECURITY_KEYS_PIN_HARD_LOCK;
break;
case device::CredentialManagementStatus::
kAuthenticatorMissingCredentialManagement:
error = IDS_SETTINGS_SECURITY_KEYS_NO_CREDENTIAL_MANAGEMENT;
break;
case device::CredentialManagementStatus::kNoPINSet:
requires_pin_change = true;
error = IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_NO_PIN;
break;
case device::CredentialManagementStatus::kAuthenticatorResponseInvalid:
error = IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_ERROR;
break;
case device::CredentialManagementStatus::kForcePINChange:
requires_pin_change = true;
error = IDS_SETTINGS_SECURITY_KEYS_FORCE_PIN_CHANGE;
break;
case device::CredentialManagementStatus::kSuccess:
error = IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_REMOVED;
break;
}
FireWebUIListener("security-keys-credential-management-finished",
base::Value(l10n_util::GetStringUTF8(error)),
base::Value(requires_pin_change));
}
SecurityKeysBioEnrollmentHandler::SecurityKeysBioEnrollmentHandler() = default;
SecurityKeysBioEnrollmentHandler::SecurityKeysBioEnrollmentHandler(
std::unique_ptr<device::FidoDiscoveryFactory> discovery_factory)
: SecurityKeysHandlerBase(std::move(discovery_factory)) {}
SecurityKeysBioEnrollmentHandler::~SecurityKeysBioEnrollmentHandler() = default;
void SecurityKeysBioEnrollmentHandler::HandleStart(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, State::kNone);
DCHECK_EQ(1u, args->GetSize());
DCHECK(callback_id_.empty());
AllowJavascript();
state_ = State::kStart;
callback_id_ = args->GetList()[0].GetString();
bio_ = std::make_unique<device::BioEnrollmentHandler>(
supported_transports(),
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnReady,
weak_factory_.GetWeakPtr()),
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnError,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::OnGatherPIN,
weak_factory_.GetWeakPtr()),
discovery_factory());
}
void SecurityKeysBioEnrollmentHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollStart",
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::HandleStart,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollProvidePIN",
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::HandleProvidePIN,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollGetSensorInfo",
base::BindRepeating(
&SecurityKeysBioEnrollmentHandler::HandleGetSensorInfo,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollEnumerate",
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::HandleEnumerate,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollStartEnrolling",
base::BindRepeating(
&SecurityKeysBioEnrollmentHandler::HandleStartEnrolling,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollDelete",
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::HandleDelete,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollRename",
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::HandleRename,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollCancel",
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::HandleCancel,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"securityKeyBioEnrollClose",
base::BindRepeating(
&HandleClose,
base::BindRepeating(&SecurityKeysBioEnrollmentHandler::Close,
base::Unretained(this))));
}
void SecurityKeysBioEnrollmentHandler::Close() {
weak_factory_.InvalidateWeakPtrs();
state_ = State::kNone;
bio_.reset();
callback_id_.clear();
provide_pin_cb_.Reset();
}
void SecurityKeysBioEnrollmentHandler::OnReady(
device::BioEnrollmentHandler::SensorInfo sensor_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(bio_);
DCHECK_EQ(state_, State::kGatherPIN);
DCHECK(!callback_id_.empty());
state_ = State::kReady;
sensor_info_ = std::move(sensor_info);
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value());
}
void SecurityKeysBioEnrollmentHandler::OnError(
device::BioEnrollmentHandler::Error error) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
state_ = State::kNone;
int error_message;
bool requires_pin_change = false;
using Error = device::BioEnrollmentHandler::Error;
switch (error) {
case Error::kAuthenticatorRemoved:
error_message = IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_REMOVED;
break;
case Error::kSoftPINBlock:
error_message = IDS_SETTINGS_SECURITY_KEYS_PIN_SOFT_LOCK;
break;
case Error::kHardPINBlock:
error_message = IDS_SETTINGS_SECURITY_KEYS_PIN_HARD_LOCK;
break;
case Error::kAuthenticatorMissingBioEnrollment:
error_message = IDS_SETTINGS_SECURITY_KEYS_NO_BIOMETRIC_ENROLLMENT;
break;
case Error::kNoPINSet:
requires_pin_change = true;
error_message = IDS_SETTINGS_SECURITY_KEYS_BIO_NO_PIN;
break;
case Error::kAuthenticatorResponseInvalid:
error_message = IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_MANAGEMENT_ERROR;
break;
case Error::kForcePINChange:
requires_pin_change = true;
error_message = IDS_SETTINGS_SECURITY_KEYS_FORCE_PIN_CHANGE;
break;
}
FireWebUIListener("security-keys-bio-enroll-error",
base::Value(l10n_util::GetStringUTF8(error_message)),
base::Value(requires_pin_change));
// If |callback_id_| is not empty, there is an ongoing operation,
// which means there is an unresolved Promise. Reject it so that
// it isn't leaked.
if (!callback_id_.empty()) {
RejectJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value());
}
}
void SecurityKeysBioEnrollmentHandler::OnGatherPIN(
uint32_t min_pin_length,
int64_t retries,
base::OnceCallback<void(std::string)> cb) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!callback_id_.empty());
DCHECK(state_ == State::kStart || state_ == State::kGatherPIN);
state_ = State::kGatherPIN;
provide_pin_cb_ = std::move(cb);
base::Value::ListStorage response;
response.emplace_back(static_cast<int>(min_pin_length));
response.emplace_back(static_cast<int>(retries));
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(response)));
}
void SecurityKeysBioEnrollmentHandler::HandleProvidePIN(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(2u, args->GetSize());
DCHECK_EQ(state_, State::kGatherPIN);
state_ = State::kGatherPIN;
callback_id_ = args->GetList()[0].GetString();
std::move(provide_pin_cb_).Run(args->GetList()[1].GetString());
}
void SecurityKeysBioEnrollmentHandler::HandleGetSensorInfo(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
DCHECK_EQ(state_, State::kReady);
base::DictionaryValue response;
response.SetIntKey("maxTemplateFriendlyName",
sensor_info_.max_template_friendly_name);
if (sensor_info_.max_samples_for_enroll) {
response.SetIntKey("maxSamplesForEnroll",
*sensor_info_.max_samples_for_enroll);
}
ResolveJavascriptCallback(
base::Value(std::move(args->GetList()[0].GetString())),
std::move(response));
}
void SecurityKeysBioEnrollmentHandler::HandleEnumerate(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
DCHECK_EQ(state_, State::kReady);
state_ = State::kEnumerating;
callback_id_ = args->GetList()[0].GetString();
bio_->EnumerateTemplates(
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnHaveEnumeration,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysBioEnrollmentHandler::OnHaveEnumeration(
device::CtapDeviceResponseCode code,
base::Optional<std::map<std::vector<uint8_t>, std::string>> enrollments) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!callback_id_.empty());
DCHECK_EQ(state_, State::kEnumerating);
base::Value::ListStorage list;
if (enrollments) {
for (const auto& enrollment : *enrollments) {
base::DictionaryValue elem;
elem.SetStringKey("name", std::move(enrollment.second));
elem.SetStringKey("id", base::HexEncode(enrollment.first.data(),
enrollment.first.size()));
list.emplace_back(EncodeEnrollment(enrollment.first, enrollment.second));
}
}
state_ = State::kReady;
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::ListValue(std::move(list)));
}
void SecurityKeysBioEnrollmentHandler::HandleStartEnrolling(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(1u, args->GetSize());
DCHECK_EQ(state_, State::kReady);
state_ = State::kEnrolling;
callback_id_ = args->GetList()[0].GetString();
bio_->EnrollTemplate(
base::BindRepeating(
&SecurityKeysBioEnrollmentHandler::OnEnrollingResponse,
weak_factory_.GetWeakPtr()),
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnEnrollmentFinished,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysBioEnrollmentHandler::OnEnrollingResponse(
device::BioEnrollmentSampleStatus status,
uint8_t remaining_samples) {
DCHECK_EQ(state_, State::kEnrolling);
base::DictionaryValue d;
d.SetIntKey("status", static_cast<int>(status));
d.SetIntKey("remaining", static_cast<int>(remaining_samples));
FireWebUIListener("security-keys-bio-enroll-status", std::move(d));
}
void SecurityKeysBioEnrollmentHandler::OnEnrollmentFinished(
device::CtapDeviceResponseCode code,
std::vector<uint8_t> template_id) {
DCHECK_EQ(state_, State::kEnrolling);
DCHECK(!callback_id_.empty());
if (code == device::CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel ||
code == device::CtapDeviceResponseCode::kCtap2ErrFpDatabaseFull) {
state_ = State::kReady;
base::DictionaryValue d;
d.SetIntKey("code", static_cast<int>(code));
d.SetIntKey("remaining", 0);
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
std::move(d));
return;
}
if (code != device::CtapDeviceResponseCode::kSuccess) {
OnError(device::BioEnrollmentHandler::Error::kAuthenticatorResponseInvalid);
return;
}
bio_->EnumerateTemplates(base::BindOnce(
&SecurityKeysBioEnrollmentHandler::OnHavePostEnrollmentEnumeration,
weak_factory_.GetWeakPtr(), std::move(template_id)));
}
void SecurityKeysBioEnrollmentHandler::OnHavePostEnrollmentEnumeration(
std::vector<uint8_t> enrolled_template_id,
device::CtapDeviceResponseCode code,
base::Optional<std::map<std::vector<uint8_t>, std::string>> enrollments) {
DCHECK_EQ(state_, State::kEnrolling);
DCHECK(!callback_id_.empty());
state_ = State::kReady;
if (code != device::CtapDeviceResponseCode::kSuccess || !enrollments ||
!base::Contains(*enrollments, enrolled_template_id)) {
OnError(device::BioEnrollmentHandler::Error::kAuthenticatorResponseInvalid);
return;
}
base::DictionaryValue d;
d.SetIntKey("code", static_cast<int>(code));
d.SetIntKey("remaining", 0);
d.SetKey("enrollment",
EncodeEnrollment(enrolled_template_id,
(*enrollments)[enrolled_template_id]));
ResolveJavascriptCallback(base::Value(std::move(callback_id_)), std::move(d));
}
void SecurityKeysBioEnrollmentHandler::HandleDelete(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(2u, args->GetSize());
state_ = State::kDeleting;
callback_id_ = args->GetList()[0].GetString();
std::vector<uint8_t> template_id;
if (!base::HexStringToBytes(args->GetList()[1].GetString(), &template_id)) {
NOTREACHED();
return;
}
bio_->DeleteTemplate(
std::move(template_id),
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnDelete,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysBioEnrollmentHandler::OnDelete(
device::CtapDeviceResponseCode code) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, State::kDeleting);
DCHECK(!callback_id_.empty());
state_ = State::kEnumerating;
bio_->EnumerateTemplates(
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnHaveEnumeration,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysBioEnrollmentHandler::HandleRename(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(args->GetSize(), 3u);
state_ = State::kRenaming;
callback_id_ = args->GetList()[0].GetString();
std::vector<uint8_t> template_id;
if (!base::HexStringToBytes(args->GetList()[1].GetString(), &template_id)) {
NOTREACHED();
return;
}
bio_->RenameTemplate(
std::move(template_id), args->GetList()[2].GetString(),
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnRename,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysBioEnrollmentHandler::OnRename(
device::CtapDeviceResponseCode code) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, State::kRenaming);
DCHECK(!callback_id_.empty());
state_ = State::kEnumerating;
bio_->EnumerateTemplates(
base::BindOnce(&SecurityKeysBioEnrollmentHandler::OnHaveEnumeration,
weak_factory_.GetWeakPtr()));
}
void SecurityKeysBioEnrollmentHandler::HandleCancel(
const base::ListValue* args) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(state_, State::kEnrolling);
DCHECK_EQ(0u, args->GetSize());
DCHECK(!callback_id_.empty());
// OnEnrollmentFinished() will be invoked once the cancellation is complete.
bio_->CancelEnrollment();
}
} // namespace settings