blob: 64347de3f75fd6d9b4d094519d11e6ebc2ebbef5 [file] [log] [blame]
// Copyright 2020 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 <sysexits.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/macros.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <brillo/dbus/dbus_method_invoker.h>
#include <brillo/dbus/dbus_object.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include <dbus/u2f/dbus-constants.h>
#include <u2f/proto_bindings/u2f_interface.pb.h>
constexpr int kRpIdHashBytes = 32;
constexpr int kFlagsBytes = 1;
constexpr int kCounterBytes = 4;
constexpr int kAaguidBytes = 16;
constexpr int kCredentialIdLengthBytes = 2;
template <typename Req, typename Resp>
Resp SendRequest(dbus::ObjectProxy* proxy,
const std::string& method_name,
const Req& req) {
brillo::ErrorPtr error;
std::unique_ptr<dbus::Response> dbus_response =
brillo::dbus_utils::CallMethodAndBlock(proxy, u2f::kU2FInterface,
method_name, &error, req);
if (!dbus_response) {
LOG(FATAL) << "Call to " << method_name << " failed";
}
Resp resp;
dbus::MessageReader reader(dbus_response.get());
if (!reader.PopArrayOfBytesAsProto(&resp)) {
LOG(FATAL) << "Failed to parse reply for call to " << method_name;
}
return resp;
}
std::string ResponseStatusToString(
u2f::MakeCredentialResponse::MakeCredentialStatus status) {
switch (status) {
case 1:
return "SUCCESS";
case 2:
return "VERIFICATION_FAILED";
case 3:
return "VERIFICATION_TIMEOUT";
case 4:
return "INVALID_REQUEST";
case 5:
return "INTERNAL_ERROR";
case 6:
return "EXCLUDED_CREDENTIAL_ID";
case 7:
return "REQUEST_PENDING";
case 8:
return "CANCELED";
default:
return "UNKNOWN";
}
}
std::string ResponseStatusToString(
u2f::GetAssertionResponse::GetAssertionStatus status) {
switch (status) {
case 1:
return "SUCCESS";
case 2:
return "VERIFICATION_FAILED";
case 3:
return "VERIFICATION_TIMEOUT";
case 4:
return "INVALID_REQUEST";
case 5:
return "INTERNAL_ERROR";
case 6:
return "UNKNOWN_CREDENTIAL_ID";
case 7:
return "REQUEST_PENDING";
case 8:
return "CANCELED";
default:
return "UNKNOWN";
}
}
std::string ResponseStatusToString(
u2f::HasCredentialsResponse::HasCredentialsStatus status) {
switch (status) {
case 1:
return "SUCCESS";
case 2:
return "INVALID_REQUEST";
case 3:
return "INTERNAL_ERROR";
case 4:
return "UNKNOWN_CREDENTIAL_ID";
default:
return "UNKNOWN";
}
}
std::string HexEncodeStr(const std::string& str) {
return base::HexEncode(str.data(), str.size());
}
void AppendToString(const std::vector<uint8_t>& vect, std::string* str) {
str->append(reinterpret_cast<const char*>(vect.data()), vect.size());
}
const std::vector<std::string> ParseCommaDelimitedString(
const std::string& str) {
return base::SplitString(str, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
}
std::string ExtractCredentialId(const std::string& authenticator_data) {
size_t credential_id_length_offset =
kRpIdHashBytes + kFlagsBytes + kCounterBytes + kAaguidBytes;
if (authenticator_data.size() <
credential_id_length_offset + kCredentialIdLengthBytes)
return std::string();
std::string length_str = authenticator_data.substr(
credential_id_length_offset, kCredentialIdLengthBytes);
uint16_t length = ((static_cast<uint16_t>(length_str.at(0))) << 8) +
static_cast<uint16_t>(length_str.at(1));
size_t credential_id_offset =
credential_id_length_offset + kCredentialIdLengthBytes;
if (authenticator_data.size() < credential_id_offset + length)
return std::string();
return authenticator_data.substr(credential_id_offset, length);
}
void MakeCredential(dbus::ObjectProxy* proxy,
int verification_type,
const std::string& rp_id,
int request_id,
const std::vector<std::string>& excluded_credential_ids) {
u2f::MakeCredentialRequest req;
req.set_verification_type(
static_cast<u2f::VerificationType>(verification_type));
req.set_rp_id(rp_id);
req.set_request_id(request_id);
for (const std::string& excluded_credential_id : excluded_credential_ids) {
if (!excluded_credential_id.empty()) {
std::vector<uint8_t> excluded_credential_id_bytes;
if (!base::HexStringToBytes(excluded_credential_id,
&excluded_credential_id_bytes)) {
LOG(FATAL) << "Could not parse excluded_credential_id bytes";
}
AppendToString(excluded_credential_id_bytes,
req.add_excluded_credential_id());
}
}
if (verification_type == u2f::VERIFICATION_USER_VERIFICATION) {
LOG(INFO) << "Please touch the fingerprint sensor.";
} else if (verification_type == u2f::VERIFICATION_USER_PRESENCE) {
LOG(INFO) << "Please press the power button.";
}
u2f::MakeCredentialResponse resp =
SendRequest<u2f::MakeCredentialRequest, u2f::MakeCredentialResponse>(
proxy, u2f::kU2FMakeCredential, req);
LOG(INFO) << "status: " << ResponseStatusToString(resp.status());
LOG(INFO) << "authenticator_data: "
<< HexEncodeStr(resp.authenticator_data());
LOG(INFO) << "credential_id: "
<< HexEncodeStr(ExtractCredentialId(resp.authenticator_data()));
LOG(INFO) << "attestation_format: " << resp.attestation_format();
LOG(INFO) << "attestation_statement: "
<< HexEncodeStr(resp.attestation_statement());
}
void GetAssertion(dbus::ObjectProxy* proxy,
int verification_type,
const std::string& rp_id,
int request_id,
const std::string& client_data_hash,
const std::vector<std::string>& allowed_credential_ids) {
u2f::GetAssertionRequest req;
req.set_verification_type(
static_cast<u2f::VerificationType>(verification_type));
req.set_rp_id(rp_id);
req.set_request_id(request_id);
req.set_client_data_hash(client_data_hash);
for (const std::string& allowed_credential_id : allowed_credential_ids) {
std::vector<uint8_t> credential_id_bytes;
if (!base::HexStringToBytes(allowed_credential_id, &credential_id_bytes)) {
LOG(FATAL) << "Could not parse credential_id bytes";
}
AppendToString(credential_id_bytes, req.add_allowed_credential_id());
}
u2f::GetAssertionResponse resp =
SendRequest<u2f::GetAssertionRequest, u2f::GetAssertionResponse>(
proxy, u2f::kU2FGetAssertion, req);
LOG(INFO) << "status: " << ResponseStatusToString(resp.status());
for (const auto& assertion : resp.assertion()) {
LOG(INFO) << "credential_id: " << HexEncodeStr(assertion.credential_id());
LOG(INFO) << "authenticator_data: "
<< HexEncodeStr(assertion.authenticator_data());
LOG(INFO) << "signature: " << HexEncodeStr(assertion.signature());
}
}
void HasCredentials(dbus::ObjectProxy* proxy,
const std::string& rp_id,
const std::vector<std::string>& credential_ids) {
u2f::HasCredentialsRequest req;
req.set_rp_id(rp_id);
for (const std::string& credential_id : credential_ids) {
std::vector<uint8_t> credential_id_bytes;
if (!base::HexStringToBytes(credential_id, &credential_id_bytes)) {
LOG(FATAL) << "Could not parse credential_id bytes";
}
AppendToString(credential_id_bytes, req.add_credential_id());
}
u2f::HasCredentialsResponse resp =
SendRequest<u2f::HasCredentialsRequest, u2f::HasCredentialsResponse>(
proxy, u2f::kU2FHasCredentials, req);
LOG(INFO) << "status: " << ResponseStatusToString(resp.status());
LOG(INFO) << "number matched: " << resp.credential_id().size();
for (const auto& cred : resp.credential_id()) {
LOG(INFO) << "credential_id: " << HexEncodeStr(cred);
}
}
void Cancel(dbus::ObjectProxy* proxy, int request_id) {
u2f::CancelWebAuthnFlowRequest req;
req.set_request_id(request_id);
u2f::CancelWebAuthnFlowResponse resp =
SendRequest<u2f::CancelWebAuthnFlowRequest,
u2f::CancelWebAuthnFlowResponse>(
proxy, u2f::kU2FCancelWebAuthnFlow, req);
LOG(INFO) << (resp.canceled() ? "Canceled" : "Not canceled");
}
void IsUvpaa(dbus::ObjectProxy* proxy) {
u2f::IsUvpaaResponse resp =
SendRequest<u2f::IsUvpaaRequest, u2f::IsUvpaaResponse>(
proxy, u2f::kU2FIsUvpaa, u2f::IsUvpaaRequest());
if (resp.available()) {
LOG(INFO) << "User verifying platform authenticator is available.";
} else {
LOG(INFO) << "User verifying platform authenticator is NOT available.";
}
}
int main(int argc, char* argv[]) {
DEFINE_bool(make_credential, false, "make a credential");
DEFINE_bool(get_assertion, false, "get an assertion");
DEFINE_bool(has_credentials, false,
"check validity/existence of credentials");
DEFINE_bool(cancel, false, "cancel ongoing WebAuthn operations");
DEFINE_bool(is_uvpaa, false, "check whether user-verification is available");
DEFINE_int32(verification_type, 1,
"type of verification to request: presence=1, verification=2");
DEFINE_string(rp_id, "", "relaying party ID (domain name)");
DEFINE_int32(request_id, 1,
"identifier of a request, can be used for cancellation");
DEFINE_string(client_data_hash, "", "client data hash, as a hex string");
DEFINE_string(credential_id, "",
"comma-separated list of credential IDs, as hex strings");
DEFINE_string(excluded_credential_id, "",
"comma-separated list of credential IDs to be exluded in "
"MakeCredential, as hex strings");
brillo::FlagHelper::Init(argc, argv,
"webauthntool - WebAuthn DBus API testing tool");
brillo::InitLog(brillo::kLogToStderrIfTty);
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus = new dbus::Bus(options);
if (!bus->Connect()) {
LOG(ERROR) << "Cannot connect to D-Bus.";
return EX_IOERR;
}
dbus::ObjectProxy* u2f_proxy = bus->GetObjectProxy(
u2f::kU2FServiceName, dbus::ObjectPath(u2f::kU2FServicePath));
CHECK(u2f_proxy) << "Couldn't get u2f proxy";
if (FLAGS_make_credential) {
MakeCredential(u2f_proxy, FLAGS_verification_type, FLAGS_rp_id,
FLAGS_request_id,
ParseCommaDelimitedString(FLAGS_excluded_credential_id));
return EX_OK;
}
if (FLAGS_get_assertion) {
GetAssertion(u2f_proxy, FLAGS_verification_type, FLAGS_rp_id,
FLAGS_request_id, FLAGS_client_data_hash,
ParseCommaDelimitedString(FLAGS_credential_id));
return EX_OK;
}
if (FLAGS_has_credentials) {
HasCredentials(u2f_proxy, FLAGS_rp_id,
ParseCommaDelimitedString(FLAGS_credential_id));
return EX_OK;
}
if (FLAGS_cancel) {
Cancel(u2f_proxy, FLAGS_request_id);
return EX_OK;
}
if (FLAGS_is_uvpaa) {
IsUvpaa(u2f_proxy);
return EX_OK;
}
LOG(INFO) << "Please specify a command.";
return EX_USAGE;
}