| // 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/check.h> |
| #include <base/command_line.h> |
| #include <base/files/file_descriptor_watcher_posix.h> |
| #include <base/logging.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/run_loop.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/task/single_thread_task_executor.h> |
| |
| #include <brillo/flag_helper.h> |
| |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "biod/biod_proxy/biometrics_manager_proxy_base.h" |
| #include "biod/biod_version.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"; |
| } |
| } |
| |
| 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 biod::BiometricsManagerProxyBase { |
| public: |
| using FinishCallback = base::RepeatingCallback<void(bool success)>; |
| |
| static std::unique_ptr<BiometricsManagerProxy> Create( |
| const scoped_refptr<dbus::Bus>& bus, |
| const dbus::ObjectPath& path, |
| dbus::MessageReader* pset_reader) { |
| auto biometrics_manager_proxy = |
| base::WrapUnique(new BiometricsManagerProxy()); |
| |
| if (!biometrics_manager_proxy->Initialize(bus, path, pset_reader)) |
| return nullptr; |
| return biometrics_manager_proxy; |
| } |
| |
| BiometricsManagerType type() const { return type_; } |
| |
| 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; |
| } |
| |
| 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: |
| BiometricsManagerProxy() : weak_factory_(this) {} |
| BiometricsManagerProxy(const BiometricsManagerProxy&) = delete; |
| BiometricsManagerProxy& operator=(const BiometricsManagerProxy&) = delete; |
| |
| bool Initialize(const scoped_refptr<dbus::Bus>& bus, |
| const dbus::ObjectPath& path, |
| dbus::MessageReader* pset_reader) { |
| if (!BiometricsManagerProxyBase::Initialize(bus, path)) { |
| LOG(ERROR) << "Cannot get dbus object proxy for biod"; |
| return false; |
| } |
| |
| 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::BindRepeating(&BiometricsManagerProxy::OnEnrollScanDone, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&BiometricsManagerProxy::OnSignalConnected, |
| weak_factory_.GetWeakPtr())); |
| proxy_->ConnectToSignal( |
| biod::kBiometricsManagerInterface, |
| biod::kBiometricsManagerAuthScanDoneSignal, |
| base::BindRepeating(&BiometricsManagerProxy::OnAuthScanDone, |
| weak_factory_.GetWeakPtr()), |
| base::BindOnce(&BiometricsManagerProxy::OnSignalConnected, |
| weak_factory_.GetWeakPtr())); |
| return true; |
| } |
| |
| 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(); |
| } |
| } |
| |
| BiometricsManagerType type_ = BiometricsManagerType::BIOMETRIC_TYPE_UNKNOWN; |
| std::vector<RecordProxy> records_; |
| base::WeakPtrFactory<BiometricsManagerProxy> weak_factory_; |
| }; |
| |
| 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) { |
| auto biometrics_manager = |
| BiometricsManagerProxy::Create(bus_, object_path, &pset_reader); |
| if (biometrics_manager) |
| biometrics_managers_.emplace_back(std::move(biometrics_manager)); |
| } |
| } |
| } |
| } |
| |
| base::WeakPtr<BiometricsManagerProxy> GetBiometricsManager( |
| base::StringPiece path) { |
| bool short_path = |
| !base::StartsWith(path, "/", base::CompareCase::SENSITIVE); |
| |
| for (auto& biometrics_manager : biometrics_managers_) { |
| 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::BindRepeating(&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) { |
| bool success = biometrics_manager->StartAuthSession(); |
| |
| if (!success) { |
| LOG(ERROR) << "Biometric authentication failed to start"; |
| return 1; |
| } |
| LOG(INFO) << "Biometric authentication started"; |
| |
| base::RunLoop run_loop; |
| |
| int ret = 1; |
| biometrics_manager->SetFinishHandler( |
| base::BindRepeating(&OnFinish, &run_loop, &ret)); |
| |
| run_loop.Run(); |
| |
| if (ret) { |
| biometrics_manager->EndAuthSession(); |
| } |
| |
| 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); |
| |
| biod::LogVersion(); |
| |
| 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::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO); |
| base::FileDescriptorWatcher watcher(task_executor.task_runner()); |
| dbus::Bus::Options bus_options; |
| bus_options.bus_type = dbus::Bus::SYSTEM; |
| auto bus = base::MakeRefCounted<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; |
| } |