blob: b6cefe8646aa262a3aee62cfb64f8eb23feb811f [file] [log] [blame]
// Copyright 2016 The Chromium OS 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 <sstream>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/memory/weak_ptr.h>
#include <base/message_loop/message_loop.h>
#include <base/run_loop.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <brillo/flag_helper.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <dbus/object_manager.h>
#include "biod/biometrics_manager.h"
#include "biod/proto_bindings/constants.pb.h"
#include "biod/proto_bindings/messages.pb.h"
static const char kHelpText[] =
"biod_client_tool, used to pretend to be a biometrics client, like a lock "
"screen or fingerprint enrollment app\n\n"
"commands:\n"
" enroll <biometrics manager> <user id> <label> - Starts an enroll "
"session for the biometrics manager that will result in the enrollment of "
"a record with the given user ID and label.\n"
" authenticate <biometrics manager> - Performs authentication with the "
"given biometrics manager until the program is interrupted.\n"
" list [<user_id>] - Lists available biometrics managers and optionally "
"user's records.\n"
" unenroll <record> - Removes the given record.\n"
" set_label <record> <label> - Sets the label for the given record to "
"<label>.\n"
" destroy_all [<biometrics manager>] - Destroys all records for the given "
"biometrics manager, or all biometrics managers if no object path is "
"given.\n\n"
"The <biometrics manager> parameter is the D-Bus object path of the "
"biometrics manager, and can be abbreviated as the path's basename (the "
"part after the last forward slash)\n\n"
"The <record> parameter is also a D-Bus object path.";
static const int kDbusTimeoutMs = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT;
using BiometricsManagerType = biod::BiometricType;
using ScanResult = biod::ScanResult;
using EnrollScanDoneCallback = biod::BiometricsManager::EnrollScanDoneCallback;
using AuthScanDoneCallback = biod::BiometricsManager::AuthScanDoneCallback;
using SessionFailedCallback = biod::BiometricsManager::SessionFailedCallback;
const char* BiometricsManagerTypeToString(BiometricsManagerType type) {
switch (type) {
case BiometricsManagerType::BIOMETRIC_TYPE_UNKNOWN:
return "Unknown";
case BiometricsManagerType::BIOMETRIC_TYPE_FINGERPRINT:
return "Fingerprint";
default:
return "Unknown";
}
}
const char* ScanResultToString(ScanResult result) {
switch (result) {
case ScanResult::SCAN_RESULT_SUCCESS:
return "Success";
case ScanResult::SCAN_RESULT_PARTIAL:
return "Partial";
case ScanResult::SCAN_RESULT_INSUFFICIENT:
return "Insufficient";
case ScanResult::SCAN_RESULT_SENSOR_DIRTY:
return "Sensor Dirty";
case ScanResult::SCAN_RESULT_TOO_SLOW:
return "Too Slow";
case ScanResult::SCAN_RESULT_TOO_FAST:
return "Too Fast";
case ScanResult::SCAN_RESULT_IMMOBILE:
return "Immobile";
default:
return "Unknown Result";
}
}
class RecordProxy {
public:
RecordProxy(const scoped_refptr<dbus::Bus>& bus, const dbus::ObjectPath& path)
: bus_(bus), path_(path) {
proxy_ = bus_->GetObjectProxy(biod::kBiodServiceName, path_);
GetLabel();
}
const dbus::ObjectPath& path() const { return path_; }
const std::string& label() const { return label_; }
bool SetLabel(const std::string& label) {
dbus::MethodCall method_call(biod::kRecordInterface,
biod::kRecordSetLabelMethod);
dbus::MessageWriter method_writer(&method_call);
method_writer.AppendString(label);
return !!proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
}
bool Remove() {
dbus::MethodCall method_call(biod::kRecordInterface,
biod::kRecordRemoveMethod);
return !!proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
}
private:
void GetLabel() {
dbus::MethodCall method_call(dbus::kPropertiesInterface,
dbus::kPropertiesGet);
dbus::MessageWriter method_writer(&method_call);
method_writer.AppendString(biod::kRecordInterface);
method_writer.AppendString(biod::kRecordLabelProperty);
std::unique_ptr<dbus::Response> response =
proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
CHECK(response);
dbus::MessageReader response_reader(response.get());
CHECK(response_reader.PopVariantOfString(&label_));
}
scoped_refptr<dbus::Bus> bus_;
dbus::ObjectPath path_;
dbus::ObjectProxy* proxy_;
std::string label_;
};
class BiometricsManagerProxy {
public:
using FinishCallback = base::Callback<void(bool success)>;
BiometricsManagerProxy(const scoped_refptr<dbus::Bus>& bus,
const dbus::ObjectPath& path,
dbus::MessageReader* pset_reader)
: bus_(bus), weak_factory_(this) {
proxy_ = bus_->GetObjectProxy(biod::kBiodServiceName, path);
while (pset_reader->HasMoreData()) {
dbus::MessageReader pset_entry_reader(nullptr);
std::string property_name;
CHECK(pset_reader->PopDictEntry(&pset_entry_reader));
CHECK(pset_entry_reader.PopString(&property_name));
if (property_name == biod::kBiometricsManagerBiometricTypeProperty) {
CHECK(pset_entry_reader.PopVariantOfUint32(
reinterpret_cast<uint32_t*>(&type_)));
}
}
proxy_->ConnectToSignal(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerEnrollScanDoneSignal,
base::Bind(&BiometricsManagerProxy::OnEnrollScanDone,
weak_factory_.GetWeakPtr()),
base::Bind(&BiometricsManagerProxy::OnSignalConnected,
weak_factory_.GetWeakPtr()));
proxy_->ConnectToSignal(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerAuthScanDoneSignal,
base::Bind(&BiometricsManagerProxy::OnAuthScanDone,
weak_factory_.GetWeakPtr()),
base::Bind(&BiometricsManagerProxy::OnSignalConnected,
weak_factory_.GetWeakPtr()));
proxy_->ConnectToSignal(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerSessionFailedSignal,
base::Bind(&BiometricsManagerProxy::OnSessionFailed,
weak_factory_.GetWeakPtr()),
base::Bind(&BiometricsManagerProxy::OnSignalConnected,
weak_factory_.GetWeakPtr()));
}
const dbus::ObjectPath& path() const { return proxy_->object_path(); }
BiometricsManagerType type() const { return type_; }
void SetFinishHandler(const FinishCallback& on_finish) {
on_finish_ = on_finish;
}
dbus::ObjectProxy* StartEnrollSession(const std::string& user_id,
const std::string& label) {
dbus::MethodCall method_call(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerStartEnrollSessionMethod);
dbus::MessageWriter method_writer(&method_call);
method_writer.AppendString(user_id);
method_writer.AppendString(label);
std::unique_ptr<dbus::Response> response =
proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
if (!response)
return nullptr;
dbus::MessageReader response_reader(response.get());
dbus::ObjectPath enroll_session_path;
CHECK(response_reader.PopObjectPath(&enroll_session_path));
dbus::ObjectProxy* enroll_session_proxy =
bus_->GetObjectProxy(biod::kBiodServiceName, enroll_session_path);
return enroll_session_proxy;
}
dbus::ObjectProxy* StartAuthSession() {
dbus::MethodCall method_call(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerStartAuthSessionMethod);
std::unique_ptr<dbus::Response> response =
proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
if (!response)
return nullptr;
dbus::MessageReader response_reader(response.get());
dbus::ObjectPath auth_path;
CHECK(response_reader.PopObjectPath(&auth_path));
dbus::ObjectProxy* auth_proxy =
bus_->GetObjectProxy(biod::kBiodServiceName, auth_path);
return auth_proxy;
}
bool DestroyAllRecords() {
dbus::MethodCall method_call(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerDestroyAllRecordsMethod);
return !!proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
}
std::vector<RecordProxy> GetRecordsForUser(const std::string& user_id) const {
dbus::MethodCall method_call(
biod::kBiometricsManagerInterface,
biod::kBiometricsManagerGetRecordsForUserMethod);
dbus::MessageWriter method_writer(&method_call);
method_writer.AppendString(user_id);
std::unique_ptr<dbus::Response> response =
proxy_->CallMethodAndBlock(&method_call, kDbusTimeoutMs);
CHECK(response);
std::vector<RecordProxy> records;
dbus::MessageReader response_reader(response.get());
dbus::MessageReader records_reader(nullptr);
CHECK(response_reader.PopArray(&records_reader));
while (records_reader.HasMoreData()) {
dbus::ObjectPath record_path;
CHECK(records_reader.PopObjectPath(&record_path));
records.emplace_back(bus_, record_path);
}
return records;
}
base::WeakPtr<BiometricsManagerProxy> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
void OnFinish(bool success) {
if (!on_finish_.is_null())
on_finish_.Run(success);
}
void OnEnrollScanDone(dbus::Signal* signal) {
dbus::MessageReader signal_reader(signal);
biod::EnrollScanDone proto;
if (!signal_reader.PopArrayOfBytesAsProto(&proto)) {
LOG(ERROR) << "Unable to decode protocol buffer from "
<< biod::kBiometricsManagerEnrollScanDoneSignal << " signal.";
return;
}
if (proto.has_percent_complete()) {
LOG(INFO) << "Biometric Scanned: "
<< ScanResultToString(proto.scan_result()) << " "
<< proto.percent_complete() << "% complete";
} else {
LOG(INFO) << "Biometric Scanned: "
<< ScanResultToString(proto.scan_result());
}
if (proto.done()) {
LOG(INFO) << "Biometric enrollment complete";
OnFinish(true);
}
}
void OnAuthScanDone(dbus::Signal* signal) {
dbus::MessageReader signal_reader(signal);
ScanResult scan_result;
dbus::MessageReader matches_reader(nullptr);
std::vector<std::string> user_ids;
CHECK(signal_reader.PopUint32(reinterpret_cast<uint32_t*>(&scan_result)));
LOG(INFO) << "Authentication: " << ScanResultToString(scan_result);
CHECK(signal_reader.PopArray(&matches_reader));
while (matches_reader.HasMoreData()) {
dbus::MessageReader entry_reader(nullptr);
CHECK(matches_reader.PopDictEntry(&entry_reader));
std::string user_id;
CHECK(entry_reader.PopString(&user_id));
dbus::MessageReader record_object_paths_reader(nullptr);
CHECK(entry_reader.PopArray(&record_object_paths_reader));
std::stringstream record_object_paths_joined;
while (record_object_paths_reader.HasMoreData()) {
dbus::ObjectPath record_object_path;
CHECK(record_object_paths_reader.PopObjectPath(&record_object_path));
record_object_paths_joined << " \"" << record_object_path.value()
<< "\"";
}
LOG(INFO) << "Recognized user ID \"" << user_id
<< "\" with record object paths"
<< record_object_paths_joined.str();
}
}
void OnSessionFailed(dbus::Signal* signal) {
LOG(ERROR) << "Biometric device failed";
OnFinish(false);
}
void OnSignalConnected(const std::string& interface,
const std::string& signal,
bool success) {
if (!success) {
LOG(ERROR) << "Failed to connect to signal " << signal << " on interface "
<< interface;
OnFinish(false);
}
}
scoped_refptr<dbus::Bus> bus_;
dbus::ObjectProxy* proxy_;
BiometricsManagerType type_;
std::vector<RecordProxy> records_;
FinishCallback on_finish_;
base::WeakPtrFactory<BiometricsManagerProxy> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(BiometricsManagerProxy);
};
class BiodProxy {
public:
explicit BiodProxy(const scoped_refptr<dbus::Bus>& bus) : bus_(bus) {
proxy_ = bus_->GetObjectProxy(biod::kBiodServiceName,
dbus::ObjectPath(biod::kBiodServicePath));
dbus::MethodCall get_objects_method(dbus::kObjectManagerInterface,
dbus::kObjectManagerGetManagedObjects);
std::unique_ptr<dbus::Response> objects_msg =
proxy_->CallMethodAndBlock(&get_objects_method, kDbusTimeoutMs);
CHECK(objects_msg) << "Failed to retrieve biometrics managers.";
dbus::MessageReader reader(objects_msg.get());
dbus::MessageReader array_reader(nullptr);
CHECK(reader.PopArray(&array_reader));
while (array_reader.HasMoreData()) {
dbus::MessageReader dict_entry_reader(nullptr);
dbus::ObjectPath object_path;
CHECK(array_reader.PopDictEntry(&dict_entry_reader));
CHECK(dict_entry_reader.PopObjectPath(&object_path));
dbus::MessageReader interface_reader(nullptr);
CHECK(dict_entry_reader.PopArray(&interface_reader));
while (interface_reader.HasMoreData()) {
dbus::MessageReader interface_entry_reader(nullptr);
std::string interface_name;
CHECK(interface_reader.PopDictEntry(&interface_entry_reader));
CHECK(interface_entry_reader.PopString(&interface_name));
dbus::MessageReader pset_reader(nullptr);
CHECK(interface_entry_reader.PopArray(&pset_reader));
if (interface_name == biod::kBiometricsManagerInterface) {
biometrics_managers_.emplace_back(
std::make_unique<BiometricsManagerProxy>(
bus_, object_path, &pset_reader));
}
}
}
}
base::WeakPtr<BiometricsManagerProxy> GetBiometricsManager(
base::StringPiece path) {
bool short_path =
!base::StartsWith(path, "/", base::CompareCase::SENSITIVE);
for (auto& biometrics_manager : biometrics_managers_) {
const std::string& biometrics_manager_path =
biometrics_manager->path().value();
if (short_path) {
if (base::EndsWith(
biometrics_manager_path, path, base::CompareCase::SENSITIVE)) {
return biometrics_manager->GetWeakPtr();
}
} else if (biometrics_manager_path == path) {
return biometrics_manager->GetWeakPtr();
}
}
return nullptr;
}
const std::vector<std::unique_ptr<BiometricsManagerProxy>>&
biometrics_managers() const {
return biometrics_managers_;
}
int DestroyAllRecords() {
int ret = 0;
for (auto& biometrics_manager : biometrics_managers_) {
if (!biometrics_manager->DestroyAllRecords()) {
LOG(ERROR) << "Failed to destroy record from BiometricsManager at "
<< biometrics_manager->path().value();
ret = 1;
}
}
if (ret)
LOG(WARNING) << "Not all records were destroyed";
return ret;
}
private:
scoped_refptr<dbus::Bus> bus_;
dbus::ObjectProxy* proxy_;
std::vector<std::unique_ptr<BiometricsManagerProxy>> biometrics_managers_;
};
void OnFinish(base::RunLoop* run_loop, int* ret_ptr, bool success) {
*ret_ptr = success ? 0 : 1;
run_loop->Quit();
}
int DoEnroll(base::WeakPtr<BiometricsManagerProxy> biometrics_manager,
const std::string& user_id,
const std::string& label) {
dbus::ObjectProxy* enroll_session_object =
biometrics_manager->StartEnrollSession(user_id, label);
if (enroll_session_object) {
LOG(INFO) << "Biometric enrollment started";
} else {
LOG(ERROR) << "Biometric enrollment failed to start";
return 1;
}
base::RunLoop run_loop;
int ret = 1;
biometrics_manager->SetFinishHandler(base::Bind(&OnFinish, &run_loop, &ret));
run_loop.Run();
if (ret) {
LOG(INFO) << "Ending biometric enrollment";
dbus::MethodCall cancel_call(biod::kEnrollSessionInterface,
biod::kEnrollSessionCancelMethod);
enroll_session_object->CallMethodAndBlock(&cancel_call, kDbusTimeoutMs);
}
return ret;
}
int DoAuthenticate(base::WeakPtr<BiometricsManagerProxy> biometrics_manager) {
dbus::ObjectProxy* auth_session_object =
biometrics_manager->StartAuthSession();
if (auth_session_object) {
LOG(INFO) << "Biometric authentication started";
} else {
LOG(ERROR) << "Biometric authentication failed to start";
return 1;
}
base::RunLoop run_loop;
int ret = 1;
biometrics_manager->SetFinishHandler(base::Bind(&OnFinish, &run_loop, &ret));
run_loop.Run();
if (ret) {
LOG(INFO) << "Ending biometric authentication";
dbus::MethodCall end_call(biod::kAuthSessionInterface,
biod::kAuthSessionEndMethod);
auth_session_object->CallMethodAndBlock(&end_call, kDbusTimeoutMs);
}
return ret;
}
int DoList(BiodProxy* biod, const std::string& user_id) {
LOG(INFO) << biod::kBiodServicePath << " : BioD Root Object Path";
for (const auto& biometrics_manager : biod->biometrics_managers()) {
base::StringPiece biometrics_manager_path =
biometrics_manager->path().value();
if (base::StartsWith(biometrics_manager_path,
biod::kBiodServicePath,
base::CompareCase::SENSITIVE)) {
biometrics_manager_path =
biometrics_manager_path.substr(sizeof(biod::kBiodServicePath));
}
LOG(INFO) << " " << biometrics_manager_path << " : "
<< BiometricsManagerTypeToString(biometrics_manager->type())
<< " Biometric";
biometrics_manager_path = biometrics_manager->path().value();
if (user_id.empty())
continue;
for (const RecordProxy& record :
biometrics_manager->GetRecordsForUser(user_id)) {
base::StringPiece record_path(record.path().value());
if (base::StartsWith(record_path,
biometrics_manager_path,
base::CompareCase::SENSITIVE)) {
record_path = record_path.substr(biometrics_manager_path.size() + 1);
}
LOG(INFO) << " " << record_path
<< " : Record Label=" << record.label();
}
}
return 0;
}
int main(int argc, char* argv[]) {
brillo::FlagHelper::Init(argc, argv, kHelpText);
base::CommandLine::StringVector args =
base::CommandLine::ForCurrentProcess()->GetArgs();
if (args.size() == 0) {
LOG(ERROR) << "Expected a command.";
LOG(INFO) << "Get help with with the --help flag.";
return 1;
}
base::MessageLoopForIO message_loop;
dbus::Bus::Options bus_options;
bus_options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(bus_options));
CHECK(bus->Connect()) << "Failed to connect to system D-Bus.";
BiodProxy biod(bus.get());
const auto& command = args[0];
if (command == "enroll") {
if (args.size() < 4) {
LOG(ERROR) << "Expected 3 parameters for enroll command.";
return 1;
}
base::WeakPtr<BiometricsManagerProxy> biometrics_manager =
biod.GetBiometricsManager(args[1]);
CHECK(biometrics_manager)
<< "Failed to find biometrics manager with given path";
return DoEnroll(biometrics_manager, args[2], args[3]);
}
if (command == "authenticate") {
if (args.size() < 2) {
LOG(ERROR) << "Expected 2 parameters for authenticate command.";
return 1;
}
base::WeakPtr<BiometricsManagerProxy> biometrics_manager =
biod.GetBiometricsManager(args[1]);
CHECK(biometrics_manager)
<< "Failed to find biometrics manager with given path";
return DoAuthenticate(biometrics_manager);
}
if (command == "list") {
return DoList(&biod, args.size() < 2 ? "" : args[1]);
}
if (command == "unenroll") {
if (args.size() < 2) {
LOG(ERROR) << "Expected 1 parameter for unenroll command.";
return 1;
}
RecordProxy record(bus, dbus::ObjectPath(args[1]));
return record.Remove() ? 0 : 1;
}
if (command == "set_label") {
if (args.size() < 3) {
LOG(ERROR) << "Expected 2 parameters for set_label command.";
return 1;
}
RecordProxy record(bus, dbus::ObjectPath(args[1]));
return record.SetLabel(args[2]);
}
if (command == "destroy_all") {
if (args.size() >= 2) {
base::WeakPtr<BiometricsManagerProxy> biometrics_manager =
biod.GetBiometricsManager(args[1]);
CHECK(biometrics_manager)
<< "Failed to find biometrics_manager with given path";
return biometrics_manager->DestroyAllRecords() ? 0 : 1;
} else {
return biod.DestroyAllRecords();
}
}
LOG(ERROR) << "Unrecognized command " << command;
LOG(INFO) << kHelpText;
return 1;
}