blob: 834603aa37180dd9200ce1b346ceb484831659d1 [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 <utility>
#include "base/callback.h"
#include "base/optional.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 "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "content/public/common/service_manager_connection.h"
#include "device/fido/pin.h"
#include "device/fido/reset_request_handler.h"
#include "device/fido/set_pin_request_handler.h"
using content::BrowserThread;
namespace {
constexpr char kStartSetPIN[] = "securityKeyStartSetPIN";
constexpr char kSetPIN[] = "securityKeySetPIN";
constexpr char kClose[] = "securityKeyClose";
constexpr char kReset[] = "securityKeyReset";
constexpr char kCompleteReset[] = "securityKeyCompleteReset";
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};
}
} // namespace
namespace settings {
SecurityKeysHandler::SecurityKeysHandler()
: state_(State::kNone),
weak_factory_(new base::WeakPtrFactory<SecurityKeysHandler>(this)) {}
SecurityKeysHandler::~SecurityKeysHandler() = default;
void SecurityKeysHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
kStartSetPIN, base::BindRepeating(&SecurityKeysHandler::HandleStartSetPIN,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSetPIN, base::BindRepeating(&SecurityKeysHandler::HandleSetPIN,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kClose, base::BindRepeating(&SecurityKeysHandler::HandleClose,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReset, base::BindRepeating(&SecurityKeysHandler::HandleReset,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCompleteReset,
base::BindRepeating(&SecurityKeysHandler::HandleCompleteReset,
base::Unretained(this)));
}
void SecurityKeysHandler::OnJavascriptAllowed() {}
void SecurityKeysHandler::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();
}
void SecurityKeysHandler::Close() {
// Invalidate all existing WeakPtrs so that no stale callbacks occur.
weak_factory_ =
std::make_unique<base::WeakPtrFactory<SecurityKeysHandler>>(this);
state_ = State::kNone;
set_pin_.reset();
reset_.reset();
callback_id_.clear();
}
void SecurityKeysHandler::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>(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
supported_transports(),
base::BindOnce(&SecurityKeysHandler::OnGatherPIN,
weak_factory_->GetWeakPtr()),
base::BindRepeating(&SecurityKeysHandler::OnSetPINComplete,
weak_factory_->GetWeakPtr()));
}
void SecurityKeysHandler::OnGatherPIN(base::Optional<int64_t> num_retries) {
DCHECK_EQ(State::kStartSetPIN, state_);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::Value::ListStorage list;
list.emplace_back(0 /* process not complete */);
if (num_retries) {
state_ = State::kGatherChangePIN;
list.emplace_back(static_cast<int>(*num_retries));
} else {
state_ = State::kGatherNewPIN;
list.emplace_back(base::Value::Type::NONE);
}
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(list)));
}
void SecurityKeysHandler::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::ListStorage list;
list.emplace_back(1 /* process complete */);
list.emplace_back(static_cast<int>(code));
ResolveJavascriptCallback(base::Value(std::move(callback_id_)),
base::Value(std::move(list)));
}
void SecurityKeysHandler::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(device::pin::IsValid(new_pin));
state_ = State::kSettingPIN;
set_pin_->ProvidePIN(old_pin, new_pin);
}
void SecurityKeysHandler::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>(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
supported_transports(),
base::BindOnce(&SecurityKeysHandler::OnResetSent,
weak_factory_->GetWeakPtr()),
base::BindOnce(&SecurityKeysHandler::OnResetFinished,
weak_factory_->GetWeakPtr()));
}
void SecurityKeysHandler::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 SecurityKeysHandler::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 SecurityKeysHandler::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();
}
}
void SecurityKeysHandler::HandleClose(const base::ListValue* args) {
DCHECK_EQ(0u, args->GetSize());
Close();
}
} // namespace settings