blob: b32ff3b358d7441eabc424ad8f4a969d6c1ed7ed [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "u2fd/u2f_command_processor_vendor.h"
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/containers/span.h>
#include <base/time/time.h>
#include <brillo/dbus/dbus_method_response.h>
#include <brillo/secure_blob.h>
#include <chromeos/cbor/values.h>
#include <chromeos/cbor/writer.h>
#include <libhwsec-foundation/status/status_chain_macros.h>
#include <openssl/sha.h>
#include <u2f/proto_bindings/u2f_interface.pb.h>
#include "u2fd/client/user_state.h"
#include "u2fd/client/util.h"
#include "u2fd/u2f_command_processor.h"
#include "u2fd/webauthn_handler.h"
using hwsec::TPMError;
using hwsec::TPMRetryAction;
using GenerateResult = hwsec::u2f::GenerateResult;
using Signature = hwsec::u2f::Signature;
using ConsumeMode = hwsec::u2f::ConsumeMode;
using UserPresenceMode = hwsec::u2f::UserPresenceMode;
namespace u2f {
namespace {
// COSE key parameters.
// https://tools.ietf.org/html/rfc8152#section-7.1
const int kCoseKeyKtyLabel = 1;
const int kCoseKeyKtyEC2 = 2;
const int kCoseKeyAlgLabel = 3;
const int kCoseKeyAlgES256 = -7;
// Double coordinate curve parameters.
// https://tools.ietf.org/html/rfc8152#section-13.1.1
const int kCoseECKeyCrvLabel = -1;
const int kCoseECKeyXLabel = -2;
const int kCoseECKeyYLabel = -3;
constexpr base::TimeDelta kVerificationTimeout = base::Seconds(10);
} // namespace
U2fCommandProcessorVendor::U2fCommandProcessorVendor(
std::unique_ptr<hwsec::U2fVendorFrontend> u2f_frontend,
std::function<void()> request_presence)
: u2f_frontend_(std::move(u2f_frontend)),
request_presence_(request_presence) {
CHECK(u2f_frontend_);
}
MakeCredentialResponse::MakeCredentialStatus
U2fCommandProcessorVendor::U2fGenerate(
const std::vector<uint8_t>& rp_id_hash,
const brillo::SecureBlob& credential_secret,
PresenceRequirement presence_requirement,
bool uv_compatible,
const brillo::Blob* auth_time_secret_hash,
std::vector<uint8_t>* credential_id,
CredentialPublicKey* credential_public_key,
std::vector<uint8_t>* /*unused*/) {
if (uv_compatible) {
if (!auth_time_secret_hash) {
LOG(ERROR) << "No auth-time secret hash to use for u2f_generate.";
return MakeCredentialResponse::INTERNAL_ERROR;
}
if (presence_requirement != PresenceRequirement::kPowerButton) {
ASSIGN_OR_RETURN(
const GenerateResult& result,
u2f_frontend_->Generate(
rp_id_hash, credential_secret, ConsumeMode::kNoConsume,
UserPresenceMode::kNotRequired, *auth_time_secret_hash),
_.WithStatus<TPMError>("Failed to generate U2F credential")
.LogError()
.As(MakeCredentialResponse::INTERNAL_ERROR));
if (!result.public_key) {
LOG(ERROR) << "No public key in generate result.";
return MakeCredentialResponse::INTERNAL_ERROR;
}
credential_public_key->raw = result.public_key->raw();
credential_public_key->cbor = EncodeCredentialPublicKeyInCBOR(
result.public_key->x(), result.public_key->y());
*credential_id = std::move(result.key_handle);
return MakeCredentialResponse::SUCCESS;
} else {
return SendU2fGenerateWaitForPresence(
/*is_up_only=*/false, rp_id_hash, credential_secret,
*auth_time_secret_hash, credential_id, credential_public_key);
}
} else {
// Non-versioned KH must be signed with power button press.
if (presence_requirement != PresenceRequirement::kPowerButton)
return MakeCredentialResponse::INTERNAL_ERROR;
return SendU2fGenerateWaitForPresence(
/*is_up_only=*/true, rp_id_hash, credential_secret, std::nullopt,
credential_id, credential_public_key);
}
}
GetAssertionResponse::GetAssertionStatus U2fCommandProcessorVendor::U2fSign(
const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& hash_to_sign,
const std::vector<uint8_t>& credential_id,
const brillo::SecureBlob& credential_secret,
const std::vector<uint8_t>* /*unused*/,
PresenceRequirement presence_requirement,
std::vector<uint8_t>* signature) {
ASSIGN_OR_RETURN(const hwsec::u2f::Config& config, GetConfig(),
_.WithStatus<TPMError>("Failed to get U2F config")
.LogError()
.As(GetAssertionResponse::INTERNAL_ERROR));
if (credential_id.size() == config.kh_size) {
if (presence_requirement != PresenceRequirement::kPowerButton) {
ASSIGN_OR_RETURN(
const Signature& sig,
u2f_frontend_->Sign(rp_id_hash, credential_secret,
/*auth_time_secret=*/std::nullopt, hash_to_sign,
ConsumeMode::kNoConsume,
UserPresenceMode::kNotRequired, credential_id),
_.WithStatus<TPMError>("Failed to sign using U2F credential")
.LogError()
.As(GetAssertionResponse::INTERNAL_ERROR));
std::optional<std::vector<uint8_t>> opt_signature =
util::SignatureToDerBytes(sig.r, sig.s);
if (!opt_signature.has_value()) {
return GetAssertionResponse::INTERNAL_ERROR;
}
*signature = *opt_signature;
return GetAssertionResponse::SUCCESS;
}
return SendU2fSignWaitForPresence(
/*is_up_only=*/false, rp_id_hash, credential_secret,
/*auth_time_secret=*/std::nullopt, hash_to_sign, credential_id,
signature);
} else if (credential_id.size() == config.up_only_kh_size) {
// Non-versioned KH must be signed with power button press.
if (presence_requirement != PresenceRequirement::kPowerButton)
return GetAssertionResponse::INTERNAL_ERROR;
return SendU2fSignWaitForPresence(
/*is_up_only=*/true, rp_id_hash, credential_secret,
/*auth_time_secret=*/std::nullopt, hash_to_sign, credential_id,
signature);
} else {
return GetAssertionResponse::UNKNOWN_CREDENTIAL_ID;
}
}
HasCredentialsResponse::HasCredentialsStatus
U2fCommandProcessorVendor::U2fSignCheckOnly(
const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& credential_id,
const brillo::SecureBlob& credential_secret,
const std::vector<uint8_t>* /*unused*/) {
ASSIGN_OR_RETURN(const hwsec::u2f::Config& config, GetConfig(),
_.WithStatus<TPMError>("Failed to get U2F config")
.LogError()
.As(HasCredentialsResponse::INTERNAL_ERROR));
hwsec::Status sign_status;
if (credential_id.size() == config.kh_size) {
sign_status =
u2f_frontend_->Check(rp_id_hash, credential_secret, credential_id);
} else if (credential_id.size() == config.up_only_kh_size) {
sign_status = u2f_frontend_->CheckUserPresenceOnly(
rp_id_hash, credential_secret, credential_id);
} else {
return HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID;
}
return sign_status.ok() ? HasCredentialsResponse::SUCCESS
: HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID;
}
MakeCredentialResponse::MakeCredentialStatus
U2fCommandProcessorVendor::G2fAttest(
const std::vector<uint8_t>& rp_id_hash,
const brillo::SecureBlob& credential_secret,
const std::vector<uint8_t>& challenge,
const std::vector<uint8_t>& credential_public_key,
const std::vector<uint8_t>& credential_id,
std::vector<uint8_t>* cert_out,
std::vector<uint8_t>* signature_out) {
std::optional<std::vector<uint8_t>> cert = GetG2fCert();
if (!cert.has_value()) {
return MakeCredentialResponse::INTERNAL_ERROR;
}
ASSIGN_OR_RETURN(
const Signature& signature,
u2f_frontend_->G2fAttest(rp_id_hash, credential_secret, challenge,
credential_id, credential_public_key),
_.WithStatus<TPMError>("Failed to attest U2F credential")
.LogError()
.As(MakeCredentialResponse::INTERNAL_ERROR));
std::optional<std::vector<uint8_t>> sig_opt =
util::SignatureToDerBytes(signature.r, signature.s);
if (!sig_opt.has_value()) {
LOG(ERROR) << "DER encoding of U2F_ATTEST signature failed.";
return MakeCredentialResponse::INTERNAL_ERROR;
}
*cert_out = std::move(*cert);
*signature_out = std::move(*sig_opt);
return MakeCredentialResponse::SUCCESS;
}
bool U2fCommandProcessorVendor::G2fSoftwareAttest(
const std::vector<uint8_t>& rp_id_hash,
const std::vector<uint8_t>& challenge,
const std::vector<uint8_t>& credential_public_key,
const std::vector<uint8_t>& credential_id,
std::vector<uint8_t>* cert_out,
std::vector<uint8_t>* signature_out) {
ASSIGN_OR_RETURN(
const brillo::Blob& data,
u2f_frontend_->GetG2fAttestData(rp_id_hash, challenge, credential_id,
credential_public_key),
_.WithStatus<TPMError>("Failed to get G2F attest data")
.LogError()
.As(false));
return util::DoSoftwareAttest(data, cert_out, signature_out);
}
CoseAlgorithmIdentifier U2fCommandProcessorVendor::GetAlgorithm() {
return CoseAlgorithmIdentifier::kEs256;
}
MakeCredentialResponse::MakeCredentialStatus
U2fCommandProcessorVendor::SendU2fGenerateWaitForPresence(
bool is_up_only,
const std::vector<uint8_t>& rp_id_hash,
const brillo::SecureBlob& credential_secret,
const std::optional<std::vector<uint8_t>>& auth_time_secret_hash,
std::vector<uint8_t>* credential_id,
CredentialPublicKey* credential_public_key) {
if (!is_up_only) {
DCHECK(auth_time_secret_hash.has_value());
}
auto generate_result = CallAndWaitForPresence<GenerateResult>(
[this, is_up_only, rp_id_hash, credential_secret,
auth_time_secret_hash]() {
if (is_up_only) {
return u2f_frontend_->GenerateUserPresenceOnly(
rp_id_hash, credential_secret, ConsumeMode::kConsume,
UserPresenceMode::kRequired);
}
return u2f_frontend_->Generate(
rp_id_hash, credential_secret, ConsumeMode::kConsume,
UserPresenceMode::kRequired, *auth_time_secret_hash);
});
if (!generate_result.ok()) {
LOG(ERROR) << "U2F_GENERATE failed:" << generate_result.status() << ".";
// TODO(b/235286980): Return suitable error depending on the generate_result
// here.
return MakeCredentialResponse::VERIFICATION_FAILED;
}
if (!generate_result->public_key) {
LOG(ERROR) << "No public key in generate result.";
return MakeCredentialResponse::INTERNAL_ERROR;
}
credential_public_key->raw = generate_result->public_key->raw();
credential_public_key->cbor = EncodeCredentialPublicKeyInCBOR(
generate_result->public_key->x(), generate_result->public_key->y());
*credential_id = std::move(generate_result->key_handle);
return MakeCredentialResponse::SUCCESS;
}
GetAssertionResponse::GetAssertionStatus
U2fCommandProcessorVendor::SendU2fSignWaitForPresence(
bool is_up_only,
const std::vector<uint8_t>& rp_id_hash,
const brillo::SecureBlob& credential_secret,
const std::optional<brillo::SecureBlob>& auth_time_secret,
const std::vector<uint8_t>& hash_to_sign,
const std::vector<uint8_t>& key_handle,
std::vector<uint8_t>* signature) {
auto sign_result = CallAndWaitForPresence<Signature>(
[this, is_up_only, rp_id_hash, credential_secret, auth_time_secret,
hash_to_sign, key_handle]() {
if (is_up_only) {
return u2f_frontend_->SignUserPresenceOnly(
rp_id_hash, credential_secret, hash_to_sign,
ConsumeMode::kConsume, UserPresenceMode::kRequired, key_handle);
}
return u2f_frontend_->Sign(
rp_id_hash, credential_secret, auth_time_secret, hash_to_sign,
ConsumeMode::kConsume, UserPresenceMode::kRequired, key_handle);
});
if (!sign_result.ok()) {
LOG(ERROR) << "U2F_SIGN failed:" << sign_result.status() << ".";
// TODO(b/235286980): Return suitable error depending on the sign_result
// here.
return GetAssertionResponse::VERIFICATION_FAILED;
}
std::optional<std::vector<uint8_t>> opt_signature =
util::SignatureToDerBytes(sign_result->r, sign_result->s);
if (!opt_signature.has_value()) {
LOG(ERROR) << "Failed to parse U2f signature.";
return GetAssertionResponse::INTERNAL_ERROR;
}
*signature = *opt_signature;
return GetAssertionResponse::SUCCESS;
}
template <typename T, typename F>
hwsec::StatusOr<T> U2fCommandProcessorVendor::CallAndWaitForPresence(F fn) {
hwsec::StatusOr<T> status = fn();
base::TimeTicks verification_start = base::TimeTicks::Now();
while (!status.ok() &&
status.err_status()->ToTPMRetryAction() ==
TPMRetryAction::kUserPresence &&
base::TimeTicks::Now() - verification_start < kVerificationTimeout) {
// We need user presence. Show a notification requesting it, and try again.
// We have a delay in request_presence_, so we didn't need to sleep again.
request_presence_();
status = fn();
}
return status;
}
std::vector<uint8_t> U2fCommandProcessorVendor::EncodeCredentialPublicKeyInCBOR(
base::span<const uint8_t> x, base::span<const uint8_t> y) {
cbor::Value::MapValue cbor_map;
cbor_map[cbor::Value(kCoseKeyKtyLabel)] = cbor::Value(kCoseKeyKtyEC2);
cbor_map[cbor::Value(kCoseKeyAlgLabel)] = cbor::Value(kCoseKeyAlgES256);
cbor_map[cbor::Value(kCoseECKeyCrvLabel)] = cbor::Value(1);
cbor_map[cbor::Value(kCoseECKeyXLabel)] = cbor::Value(x);
cbor_map[cbor::Value(kCoseECKeyYLabel)] = cbor::Value(y);
return *cbor::Writer::Write(cbor::Value(std::move(cbor_map)));
}
std::optional<std::vector<uint8_t>> U2fCommandProcessorVendor::GetG2fCert() {
ASSIGN_OR_RETURN(brillo::Blob cert, u2f_frontend_->GetG2fCert(),
_.WithStatus<TPMError>("Failed to get G2F cert")
.LogError()
.As(std::nullopt));
if (!util::RemoveCertificatePadding(&cert)) {
LOG(ERROR) << "Failed to remove padding from G2F certificate ";
return std::nullopt;
}
return cert;
}
hwsec::StatusOr<hwsec::u2f::Config> U2fCommandProcessorVendor::GetConfig() {
if (config_.has_value()) {
return *config_;
}
ASSIGN_OR_RETURN(config_, u2f_frontend_->GetConfig());
return *config_;
}
hwsec::StatusOr<int> U2fCommandProcessorVendor::CallAndWaitForPresenceForTest(
std::function<hwsec::StatusOr<int>()> fn) {
return CallAndWaitForPresence<int>(fn);
}
} // namespace u2f