blob: a70c7a57977cb49b60eecd05b78e9e01acb1f1f2 [file] [log] [blame]
// Copyright 2019 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 "u2fd/u2f_msg_handler.h"
#include <utility>
#include <base/logging.h>
#include <brillo/secure_blob.h>
#include <trunks/cr50_headers/u2f.h>
#include "u2fd/util.h"
namespace u2f {
namespace {
// Response to the APDU requesting the U2F protocol version
constexpr char kSupportedU2fVersion[] = "U2F_V2";
// U2F_REGISTER response prefix, indicating U2F_VER_2.
// See FIDO "U2F Raw Message Formats" spec.
constexpr uint8_t kU2fVer2Prefix = 5;
// UMA Metric names.
constexpr char kU2fCommand[] = "Platform.U2F.Command";
} // namespace
U2fMessageHandler::U2fMessageHandler(
std::unique_ptr<AllowlistingUtil> allowlisting_util,
std::function<void()> request_user_presence,
UserState* user_state,
TpmVendorCommandProxy* proxy,
MetricsLibraryInterface* metrics,
bool allow_legacy_kh_sign,
bool allow_g2f_attestation)
: allowlisting_util_(std::move(allowlisting_util)),
request_user_presence_(request_user_presence),
user_state_(user_state),
proxy_(proxy),
metrics_(metrics),
allow_legacy_kh_sign_(allow_legacy_kh_sign),
allow_g2f_attestation_(allow_g2f_attestation) {}
U2fResponseApdu U2fMessageHandler::ProcessMsg(const std::string& req) {
uint16_t u2f_status = 0;
base::Optional<U2fCommandApdu> apdu =
U2fCommandApdu::ParseFromString(req, &u2f_status);
if (!apdu.has_value()) {
return BuildEmptyResponse(u2f_status ?: U2F_SW_WTF);
}
U2fIns ins = apdu->Ins();
// TODO(crbug.com/1218246) Change UMA enum name kU2fCommand if new enums for
// U2fIns are added to avoid data discontinuity, then use <largest-enum>+1
// rather than <largest-enum>.
metrics_->SendEnumToUMA(kU2fCommand, static_cast<int>(ins),
static_cast<int>(U2fIns::kU2fVersion));
// TODO(louiscollard): Check expected response length is large enough.
switch (ins) {
case U2fIns::kU2fRegister: {
base::Optional<U2fRegisterRequestApdu> reg_apdu =
U2fRegisterRequestApdu::FromCommandApdu(*apdu, &u2f_status);
// Chrome may send a dummy register request, which is designed to
// cause a USB device to flash it's LED. We should simply ignore
// these.
if (reg_apdu.has_value()) {
if (reg_apdu->IsChromeDummyWinkRequest()) {
return BuildEmptyResponse(U2F_SW_CONDITIONS_NOT_SATISFIED);
} else {
return ProcessU2fRegister(*reg_apdu);
}
}
break; // Handle error.
}
case U2fIns::kU2fAuthenticate: {
base::Optional<U2fAuthenticateRequestApdu> auth_apdu =
U2fAuthenticateRequestApdu::FromCommandApdu(*apdu, &u2f_status);
if (auth_apdu.has_value()) {
return ProcessU2fAuthenticate(*auth_apdu);
}
break; // Handle error.
}
case U2fIns::kU2fVersion: {
if (!apdu->Body().empty()) {
u2f_status = U2F_SW_WRONG_LENGTH;
break;
}
U2fResponseApdu response;
response.AppendString(kSupportedU2fVersion);
response.SetStatus(U2F_SW_NO_ERROR);
return response;
}
default:
u2f_status = U2F_SW_INS_NOT_SUPPORTED;
break;
}
return BuildEmptyResponse(u2f_status ?: U2F_SW_WTF);
}
U2fResponseApdu U2fMessageHandler::ProcessU2fRegister(
const U2fRegisterRequestApdu& request) {
std::vector<uint8_t> pub_key;
std::vector<uint8_t> key_handle;
Cr50CmdStatus generate_status =
DoU2fGenerate(request.GetAppId(), &pub_key, &key_handle);
if (generate_status == Cr50CmdStatus::kNotAllowed) {
request_user_presence_();
}
if (generate_status != Cr50CmdStatus::kSuccess) {
return BuildErrorResponse(generate_status);
}
std::vector<uint8_t> data_to_sign = util::BuildU2fRegisterResponseSignedData(
request.GetAppId(), request.GetChallenge(), pub_key, key_handle);
std::vector<uint8_t> attestation_cert;
std::vector<uint8_t> signature;
std::vector<uint8_t> allowlisting_data;
if (allow_g2f_attestation_ && request.UseG2fAttestation()) {
base::Optional<std::vector<uint8_t>> g2f_cert = util::GetG2fCert(proxy_);
if (g2f_cert.has_value()) {
attestation_cert = *g2f_cert;
} else {
return BuildEmptyResponse(U2F_SW_WTF);
}
Cr50CmdStatus attest_status =
DoG2fAttest(data_to_sign, U2F_ATTEST_FORMAT_REG_RESP, &signature);
if (attest_status != Cr50CmdStatus::kSuccess) {
return BuildEmptyResponse(U2F_SW_WTF);
}
if (allowlisting_util_ != nullptr &&
!allowlisting_util_->AppendDataToCert(&attestation_cert)) {
LOG(ERROR) << "Failed to get allowlisting data for G2F Enroll Request";
return BuildEmptyResponse(U2F_SW_WTF);
}
} else if (!util::DoSoftwareAttest(data_to_sign, &attestation_cert,
&signature)) {
return BuildEmptyResponse(U2F_SW_WTF);
}
// Prepare response, as specified by "U2F Raw Message Formats".
U2fResponseApdu register_resp;
register_resp.AppendByte(kU2fVer2Prefix);
register_resp.AppendBytes(pub_key);
register_resp.AppendByte(key_handle.size());
register_resp.AppendBytes(key_handle);
register_resp.AppendBytes(attestation_cert);
register_resp.AppendBytes(signature);
register_resp.SetStatus(U2F_SW_NO_ERROR);
return register_resp;
}
namespace {
// A success response to a U2F_AUTHENTICATE request includes a signature over
// the following data, in this format.
std::vector<uint8_t> BuildU2fAuthenticateResponseSignedData(
const std::vector<uint8_t>& app_id,
const std::vector<uint8_t>& challenge,
const std::vector<uint8_t>& counter) {
std::vector<uint8_t> to_sign;
util::AppendToVector(app_id, &to_sign);
to_sign.push_back(U2F_AUTH_FLAG_TUP);
util::AppendToVector(counter, &to_sign);
util::AppendToVector(challenge, &to_sign);
return to_sign;
}
} // namespace
U2fResponseApdu U2fMessageHandler::ProcessU2fAuthenticate(
const U2fAuthenticateRequestApdu& request) {
if (request.IsAuthenticateCheckOnly()) {
// The authenticate only version of this command always returns an error (on
// success, returns an error requesting presence).
Cr50CmdStatus sign_status =
DoU2fSignCheckOnly(request.GetAppId(), request.GetKeyHandle());
if (sign_status == Cr50CmdStatus::kSuccess) {
return BuildEmptyResponse(U2F_SW_CONDITIONS_NOT_SATISFIED);
} else {
return BuildErrorResponse(sign_status);
}
}
base::Optional<std::vector<uint8_t>> counter = user_state_->GetCounter();
if (!counter.has_value()) {
LOG(ERROR) << "Failed to retrieve counter value";
return BuildEmptyResponse(U2F_SW_WTF);
}
std::vector<uint8_t> to_sign = BuildU2fAuthenticateResponseSignedData(
request.GetAppId(), request.GetChallenge(), *counter);
std::vector<uint8_t> signature;
Cr50CmdStatus sign_status =
DoU2fSign(request.GetAppId(), request.GetKeyHandle(),
util::Sha256(to_sign), &signature);
if (sign_status == Cr50CmdStatus::kNotAllowed) {
request_user_presence_();
}
if (sign_status != Cr50CmdStatus::kSuccess) {
return BuildErrorResponse(sign_status);
}
if (!user_state_->IncrementCounter()) {
// If we can't increment the counter we must not return the signed
// response, as the next authenticate response would end up having
// the same counter value.
return BuildEmptyResponse(U2F_SW_WTF);
}
// Everything succeeded; build response.
// Prepare response, as specified by "U2F Raw Message Formats".
U2fResponseApdu auth_resp;
auth_resp.AppendByte(U2F_AUTH_FLAG_TUP);
auth_resp.AppendBytes(*counter);
auth_resp.AppendBytes(signature);
auth_resp.SetStatus(U2F_SW_NO_ERROR);
return auth_resp;
}
U2fMessageHandler::Cr50CmdStatus U2fMessageHandler::DoU2fGenerate(
const std::vector<uint8_t>& app_id,
std::vector<uint8_t>* pub_key,
std::vector<uint8_t>* key_handle) {
base::AutoLock(proxy_->GetLock());
base::Optional<brillo::SecureBlob> user_secret = user_state_->GetUserSecret();
if (!user_secret.has_value()) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_generate_req generate_req = {
.flags = U2F_AUTH_ENFORCE // Require user presence, consume.
};
if (!util::VectorToObject(app_id, generate_req.appId,
sizeof(generate_req.appId))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(*user_secret, generate_req.userSecret,
sizeof(generate_req.userSecret))) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_generate_resp generate_resp = {};
Cr50CmdStatus generate_status = static_cast<Cr50CmdStatus>(
proxy_->SendU2fGenerate(generate_req, &generate_resp));
brillo::SecureClearBytes(&generate_req.userSecret,
sizeof(generate_req.userSecret));
if (generate_status != Cr50CmdStatus::kSuccess) {
return generate_status;
}
util::AppendToVector(generate_resp.pubKey, pub_key);
util::AppendToVector(generate_resp.keyHandle, key_handle);
return Cr50CmdStatus::kSuccess;
}
U2fMessageHandler::Cr50CmdStatus U2fMessageHandler::DoU2fSign(
const std::vector<uint8_t>& app_id,
const std::vector<uint8_t>& key_handle,
const std::vector<uint8_t>& hash,
std::vector<uint8_t>* signature_out) {
base::AutoLock(proxy_->GetLock());
base::Optional<brillo::SecureBlob> user_secret = user_state_->GetUserSecret();
if (!user_secret.has_value()) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_sign_req sign_req = {
.flags = U2F_AUTH_ENFORCE // Require user presence, consume.
};
if (allow_legacy_kh_sign_)
sign_req.flags |= SIGN_LEGACY_KH;
if (!util::VectorToObject(app_id, sign_req.appId, sizeof(sign_req.appId))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(*user_secret, sign_req.userSecret,
sizeof(sign_req.userSecret))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(key_handle, &sign_req.keyHandle,
sizeof(sign_req.keyHandle))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(hash, sign_req.hash, sizeof(sign_req.hash))) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_sign_resp sign_resp = {};
Cr50CmdStatus sign_status =
static_cast<Cr50CmdStatus>(proxy_->SendU2fSign(sign_req, &sign_resp));
brillo::SecureClearBytes(&sign_req.userSecret, sizeof(sign_req.userSecret));
if (sign_status != Cr50CmdStatus::kSuccess) {
return sign_status;
}
base::Optional<std::vector<uint8_t>> signature =
util::SignatureToDerBytes(sign_resp.sig_r, sign_resp.sig_s);
if (!signature.has_value()) {
return Cr50CmdStatus::kInvalidResponseData;
}
*signature_out = *signature;
return Cr50CmdStatus::kSuccess;
}
U2fMessageHandler::Cr50CmdStatus U2fMessageHandler::DoU2fSignCheckOnly(
const std::vector<uint8_t>& app_id,
const std::vector<uint8_t>& key_handle) {
base::AutoLock(proxy_->GetLock());
base::Optional<brillo::SecureBlob> user_secret = user_state_->GetUserSecret();
if (!user_secret.has_value()) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_sign_req sign_req = {
.flags = U2F_AUTH_CHECK_ONLY // No user presence required, no consume.
};
if (!util::VectorToObject(app_id, sign_req.appId, sizeof(sign_req.appId))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(*user_secret, sign_req.userSecret,
sizeof(sign_req.userSecret))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(key_handle, &sign_req.keyHandle,
sizeof(sign_req.keyHandle))) {
return Cr50CmdStatus::kInvalidState;
}
Cr50CmdStatus sign_status =
static_cast<Cr50CmdStatus>(proxy_->SendU2fSign(sign_req, nullptr));
brillo::SecureClearBytes(&sign_req.userSecret, sizeof(sign_req.userSecret));
return sign_status;
}
U2fMessageHandler::Cr50CmdStatus U2fMessageHandler::DoG2fAttest(
const std::vector<uint8_t>& data,
uint8_t format,
std::vector<uint8_t>* signature_out) {
base::AutoLock(proxy_->GetLock());
base::Optional<brillo::SecureBlob> user_secret = user_state_->GetUserSecret();
if (!user_secret.has_value()) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_attest_req attest_req = {
.format = format, .dataLen = static_cast<uint8_t>(data.size())};
if (!util::VectorToObject(*user_secret, attest_req.userSecret,
sizeof(attest_req.userSecret))) {
return Cr50CmdStatus::kInvalidState;
}
if (!util::VectorToObject(data, attest_req.data, sizeof(attest_req.data))) {
return Cr50CmdStatus::kInvalidState;
}
struct u2f_attest_resp attest_resp = {};
Cr50CmdStatus attest_status = static_cast<Cr50CmdStatus>(
proxy_->SendU2fAttest(attest_req, &attest_resp));
brillo::SecureClearBytes(&attest_req.userSecret,
sizeof(attest_req.userSecret));
if (attest_status != Cr50CmdStatus::kSuccess) {
// We are attesting to a key handle that we just created, so if
// attestation fails we have hit some internal error.
LOG(ERROR) << "U2F_ATTEST failed, status: " << std::hex
<< static_cast<uint32_t>(attest_status);
return attest_status;
}
base::Optional<std::vector<uint8_t>> signature =
util::SignatureToDerBytes(attest_resp.sig_r, attest_resp.sig_s);
if (!signature.has_value()) {
LOG(ERROR) << "DER encoding of U2F_ATTEST signature failed.";
return Cr50CmdStatus::kInvalidResponseData;
}
*signature_out = *signature;
return Cr50CmdStatus::kSuccess;
}
U2fResponseApdu U2fMessageHandler::BuildEmptyResponse(uint16_t sw) {
U2fResponseApdu resp_apdu;
resp_apdu.SetStatus(sw);
return resp_apdu;
}
U2fResponseApdu U2fMessageHandler::BuildErrorResponse(Cr50CmdStatus status) {
uint16_t sw;
switch (status) {
case Cr50CmdStatus::kNotAllowed:
sw = U2F_SW_CONDITIONS_NOT_SATISFIED;
break;
case Cr50CmdStatus::kPasswordRequired:
sw = U2F_SW_WRONG_DATA;
break;
case Cr50CmdStatus::kInvalidState:
sw = U2F_SW_WTF;
break;
default:
LOG(ERROR) << "Unexpected Cr50CmdStatus: " << std::hex
<< static_cast<uint32_t>(status);
sw = U2F_SW_WTF;
}
return BuildEmptyResponse(sw);
}
} // namespace u2f