| // Copyright 2019 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_msg_handler.h" |
| |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <brillo/secure_blob.h> |
| #include <libhwsec-foundation/status/status_chain_macros.h> |
| #include <session_manager/dbus-proxies.h> |
| #include <trunks/cr50_headers/u2f.h> |
| |
| #include "u2fd/client/util.h" |
| #include "u2fd/u2f_corp_processor_interface.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 { |
| |
| // 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"; |
| |
| std::optional<std::vector<uint8_t>> GetG2fCert( |
| hwsec::U2fVendorFrontend* u2f_frontend) { |
| ASSIGN_OR_RETURN(std::vector<uint8_t> 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; |
| } |
| |
| } // namespace |
| |
| U2fMessageHandler::U2fMessageHandler( |
| std::unique_ptr<AllowlistingUtil> allowlisting_util, |
| std::function<void()> request_user_presence, |
| UserState* user_state, |
| hwsec::U2fVendorFrontend* u2f_frontend, |
| org::chromium::SessionManagerInterfaceProxy* sm_proxy, |
| MetricsLibraryInterface* metrics, |
| bool allow_g2f_attestation, |
| U2fCorpProcessorInterface* u2f_corp_processor) |
| : allowlisting_util_(std::move(allowlisting_util)), |
| request_user_presence_(request_user_presence), |
| user_state_(user_state), |
| u2f_frontend_(u2f_frontend), |
| metrics_(metrics), |
| allow_g2f_attestation_(allow_g2f_attestation), |
| u2f_corp_processor_(u2f_corp_processor) { |
| CHECK(u2f_frontend_); |
| } |
| |
| U2fResponseApdu U2fMessageHandler::ProcessMsg(const std::string& req) { |
| uint16_t u2f_status = 0; |
| |
| std::optional<U2fCommandApdu> apdu = |
| U2fCommandApdu::ParseFromString(req, &u2f_status); |
| |
| if (!apdu.has_value()) { |
| return BuildEmptyResponse(u2f_status ?: U2F_SW_WTF); |
| } |
| |
| U2fIns ins = apdu->Ins(); |
| |
| if (static_cast<int>(ins) <= static_cast<int>(U2fIns::kU2fVersion)) { |
| // 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: { |
| std::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: { |
| std::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: |
| if (u2f_corp_processor_) { |
| return u2f_corp_processor_->ProcessApdu(*apdu); |
| } |
| u2f_status = U2F_SW_INS_NOT_SUPPORTED; |
| break; |
| } |
| |
| return BuildEmptyResponse(u2f_status ?: U2F_SW_WTF); |
| } |
| |
| U2fResponseApdu U2fMessageHandler::ProcessU2fRegister( |
| const U2fRegisterRequestApdu& request) { |
| VLOG(1) << "U2F registration requested."; |
| |
| std::optional<brillo::SecureBlob> user_secret = user_state_->GetUserSecret(); |
| if (!user_secret.has_value()) { |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| |
| hwsec::StatusOr<GenerateResult> generate_result = |
| u2f_frontend_->GenerateUserPresenceOnly(request.GetAppId(), *user_secret, |
| ConsumeMode::kConsume, |
| UserPresenceMode::kRequired); |
| if (!generate_result.ok()) { |
| if (generate_result.err_status()->ToTPMRetryAction() == |
| TPMRetryAction::kUserPresence) { |
| LOG(WARNING) << "U2fGenerate requests user presence."; |
| request_user_presence_(); |
| return BuildEmptyResponse(U2F_SW_CONDITIONS_NOT_SATISFIED); |
| } |
| LOG(ERROR) << "U2fGenerate failed:" << generate_result.status() << "."; |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| if (!generate_result->public_key) { |
| LOG(ERROR) << "No public key in generate result."; |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| std::vector<uint8_t> public_key = generate_result->public_key->raw(); |
| std::vector<uint8_t> key_handle = std::move(generate_result->key_handle); |
| |
| std::vector<uint8_t> data_to_sign = util::BuildU2fRegisterResponseSignedData( |
| request.GetAppId(), request.GetChallenge(), public_key, key_handle); |
| |
| std::vector<uint8_t> attestation_cert; |
| std::vector<uint8_t> signature; |
| |
| if (allow_g2f_attestation_ && request.UseG2fAttestation()) { |
| std::optional<std::vector<uint8_t>> g2f_cert = GetG2fCert(u2f_frontend_); |
| |
| if (!g2f_cert.has_value()) { |
| LOG(ERROR) << "Failed to get g2f cert."; |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| attestation_cert = *g2f_cert; |
| |
| ASSIGN_OR_RETURN(const Signature& sig, |
| u2f_frontend_->G2fAttest(request.GetAppId(), *user_secret, |
| request.GetChallenge(), |
| key_handle, public_key), |
| _.WithStatus<TPMError>("Failed to attest U2F credential") |
| .LogError() |
| .As(BuildEmptyResponse(U2F_SW_WTF))); |
| |
| std::optional<std::vector<uint8_t>> sig_der = |
| util::SignatureToDerBytes(sig.r, sig.s); |
| if (!sig_der.has_value()) { |
| LOG(ERROR) << "DER encoding of U2F_ATTEST signature failed."; |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| signature = std::move(*sig_der); |
| |
| 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 { |
| ASSIGN_OR_RETURN( |
| const std::vector<uint8_t>& data, |
| u2f_frontend_->GetG2fAttestData( |
| request.GetAppId(), request.GetChallenge(), key_handle, public_key), |
| _.WithStatus<TPMError>("Failed to get G2F attest data") |
| .LogError() |
| .As(BuildEmptyResponse(U2F_SW_WTF))); |
| if (!util::DoSoftwareAttest(data, &attestation_cert, &signature)) { |
| LOG(ERROR) << "Failed to do software attest."; |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| } |
| |
| // Prepare response, as specified by "U2F Raw Message Formats". |
| U2fResponseApdu register_resp; |
| register_resp.AppendByte(kU2fVer2Prefix); |
| register_resp.AppendBytes(public_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); |
| |
| VLOG(1) << "Finished processing U2F registration request."; |
| 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) { |
| VLOG(1) << "U2F authentication requested."; |
| |
| std::optional<brillo::SecureBlob> user_secret = user_state_->GetUserSecret(); |
| if (!user_secret.has_value()) { |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| |
| if (request.IsAuthenticateCheckOnly()) { |
| // The authenticate only version of this command always returns an error (on |
| // success, returns an error requesting presence). |
| hwsec::Status status = u2f_frontend_->CheckUserPresenceOnly( |
| request.GetAppId(), *user_secret, request.GetKeyHandle()); |
| if (!status.ok()) { |
| LOG(ERROR) << "U2fSignCheckOnly failed: " << status << "."; |
| return BuildEmptyResponse(status.err_status()->ToTPMRetryAction() == |
| TPMRetryAction::kUserAuth |
| ? U2F_SW_WRONG_DATA |
| : U2F_SW_WTF); |
| } |
| |
| VLOG(1) << "Finished processing U2F authentication (check-only) request."; |
| return BuildEmptyResponse(U2F_SW_CONDITIONS_NOT_SATISFIED); |
| } |
| |
| std::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> hash_to_sign = |
| util::Sha256(BuildU2fAuthenticateResponseSignedData( |
| request.GetAppId(), request.GetChallenge(), *counter)); |
| |
| hwsec::StatusOr<Signature> sig = u2f_frontend_->SignUserPresenceOnly( |
| request.GetAppId(), *user_secret, hash_to_sign, ConsumeMode::kConsume, |
| UserPresenceMode::kRequired, request.GetKeyHandle()); |
| if (!sig.ok()) { |
| auto action = sig.err_status()->ToTPMRetryAction(); |
| if (action == TPMRetryAction::kUserPresence) { |
| LOG(WARNING) << "U2fSign requests user presence."; |
| request_user_presence_(); |
| return BuildEmptyResponse(U2F_SW_CONDITIONS_NOT_SATISFIED); |
| } |
| LOG(ERROR) << "U2fSign failed:" << sig.status() << "."; |
| return BuildEmptyResponse( |
| action == TPMRetryAction::kUserAuth ? U2F_SW_WRONG_DATA : U2F_SW_WTF); |
| } |
| std::optional<std::vector<uint8_t>> sig_der = |
| util::SignatureToDerBytes(sig->r, sig->s); |
| if (!sig_der.has_value()) { |
| return BuildEmptyResponse(U2F_SW_WTF); |
| } |
| |
| if (!user_state_->IncrementCounter()) { |
| LOG(ERROR) << "Failed to increment counter value."; |
| // 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(*sig_der); |
| auth_resp.SetStatus(U2F_SW_NO_ERROR); |
| |
| VLOG(1) << "Finished processing U2F authentication request."; |
| return auth_resp; |
| } |
| |
| U2fResponseApdu U2fMessageHandler::BuildEmptyResponse(uint16_t sw) { |
| U2fResponseApdu resp_apdu; |
| resp_apdu.SetStatus(sw); |
| return resp_apdu; |
| } |
| |
| } // namespace u2f |