| // Copyright 2021 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_command_processor_gsc.h" |
| |
| #include <functional> |
| #include <vector> |
| |
| #include <base/optional.h> |
| #include <base/time/time.h> |
| #include <brillo/dbus/dbus_method_response.h> |
| #include <openssl/sha.h> |
| #include <trunks/cr50_headers/u2f.h> |
| #include <u2f/proto_bindings/u2f_interface.pb.h> |
| |
| #include "u2fd/tpm_vendor_cmd.h" |
| #include "u2fd/user_state.h" |
| #include "u2fd/util.h" |
| #include "u2fd/webauthn_handler.h" |
| |
| namespace u2f { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kVerificationTimeout = |
| base::TimeDelta::FromSeconds(10); |
| |
| // Cr50 Response codes. |
| // TODO(louiscollard): Don't duplicate these. |
| constexpr uint32_t kCr50StatusNotAllowed = 0x507; |
| |
| } // namespace |
| |
| U2fCommandProcessorGsc::U2fCommandProcessorGsc( |
| TpmVendorCommandProxy* tpm_proxy, std::function<void()> request_presence) |
| : tpm_proxy_(tpm_proxy), request_presence_(request_presence) {} |
| |
| MakeCredentialResponse::MakeCredentialStatus |
| U2fCommandProcessorGsc::U2fGenerate( |
| const std::vector<uint8_t>& rp_id_hash, |
| const std::vector<uint8_t>& credential_secret, |
| PresenceRequirement presence_requirement, |
| bool uv_compatible, |
| const brillo::Blob* auth_time_secret_hash, |
| std::vector<uint8_t>* credential_id, |
| std::vector<uint8_t>* credential_public_key) { |
| DCHECK(rp_id_hash.size() == SHA256_DIGEST_LENGTH); |
| |
| struct u2f_generate_req generate_req = {}; |
| if (!util::VectorToObject(rp_id_hash, generate_req.appId, |
| sizeof(generate_req.appId))) { |
| return MakeCredentialResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_secret, generate_req.userSecret, |
| sizeof(generate_req.userSecret))) { |
| return MakeCredentialResponse::INVALID_REQUEST; |
| } |
| |
| if (uv_compatible) { |
| if (!auth_time_secret_hash) { |
| LOG(ERROR) << "No auth-time secret hash to use for u2f_generate."; |
| return MakeCredentialResponse::INTERNAL_ERROR; |
| } |
| generate_req.flags |= U2F_UV_ENABLED_KH; |
| memcpy(generate_req.authTimeSecretHash, auth_time_secret_hash->data(), |
| auth_time_secret_hash->size()); |
| struct u2f_generate_versioned_resp generate_resp = {}; |
| |
| MakeCredentialResponse::MakeCredentialStatus status; |
| if (presence_requirement != PresenceRequirement::kPowerButton) { |
| uint32_t generate_status = |
| tpm_proxy_->SendU2fGenerate(generate_req, &generate_resp); |
| if (generate_status != 0) |
| return MakeCredentialResponse::INTERNAL_ERROR; |
| |
| util::AppendToVector(generate_resp.pubKey, credential_public_key); |
| util::AppendToVector(generate_resp.keyHandle, credential_id); |
| status = MakeCredentialResponse::SUCCESS; |
| } else { |
| // Require user presence, consume. |
| generate_req.flags |= U2F_AUTH_ENFORCE; |
| status = SendU2fGenerateWaitForPresence( |
| &generate_req, &generate_resp, credential_id, credential_public_key); |
| } |
| if (status == MakeCredentialResponse::SUCCESS) { |
| InsertAuthTimeSecretHashToCredentialId(auth_time_secret_hash, |
| credential_id); |
| } |
| return status; |
| } else { |
| // Non-versioned KH must be signed with power button press. |
| if (presence_requirement != PresenceRequirement::kPowerButton) |
| return MakeCredentialResponse::INTERNAL_ERROR; |
| // Require user presence, consume. |
| generate_req.flags |= U2F_AUTH_ENFORCE; |
| struct u2f_generate_resp generate_resp = {}; |
| return SendU2fGenerateWaitForPresence(&generate_req, &generate_resp, |
| credential_id, credential_public_key); |
| } |
| } |
| |
| GetAssertionResponse::GetAssertionStatus U2fCommandProcessorGsc::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 std::vector<uint8_t>& credential_secret, |
| PresenceRequirement presence_requirement, |
| std::vector<uint8_t>* signature) { |
| DCHECK(rp_id_hash.size() == SHA256_DIGEST_LENGTH); |
| |
| if (credential_id.size() == U2F_V1_KH_SIZE + SHA256_DIGEST_LENGTH) { |
| // Allow waiving presence if sign_req.authTimeSecret is correct. |
| struct u2f_sign_versioned_req sign_req = {}; |
| if (!util::VectorToObject(rp_id_hash, sign_req.appId, |
| sizeof(sign_req.appId))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_secret, sign_req.userSecret, |
| sizeof(sign_req.userSecret))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| std::vector<uint8_t> key_handle(credential_id); |
| RemoveAuthTimeSecretHashFromCredentialId(&key_handle); |
| if (!util::VectorToObject(key_handle, &sign_req.keyHandle, |
| sizeof(sign_req.keyHandle))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(hash_to_sign, sign_req.hash, |
| sizeof(sign_req.hash))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| struct u2f_sign_resp sign_resp = {}; |
| |
| if (presence_requirement != PresenceRequirement::kPowerButton) { |
| uint32_t sign_status = tpm_proxy_->SendU2fSign(sign_req, &sign_resp); |
| if (sign_status != 0) |
| return GetAssertionResponse::INTERNAL_ERROR; |
| |
| base::Optional<std::vector<uint8_t>> opt_signature = |
| util::SignatureToDerBytes(sign_resp.sig_r, sign_resp.sig_s); |
| if (!opt_signature.has_value()) { |
| return GetAssertionResponse::INTERNAL_ERROR; |
| } |
| *signature = *opt_signature; |
| return GetAssertionResponse::SUCCESS; |
| } |
| |
| // Require user presence, consume. |
| sign_req.flags |= U2F_AUTH_ENFORCE; |
| return SendU2fSignWaitForPresence(&sign_req, &sign_resp, signature); |
| } else if (credential_id.size() == U2F_V0_KH_SIZE) { |
| // Non-versioned KH must be signed with power button press. |
| if (presence_requirement != PresenceRequirement::kPowerButton) |
| return GetAssertionResponse::INTERNAL_ERROR; |
| |
| struct u2f_sign_req sign_req = { |
| .flags = U2F_AUTH_ENFORCE // Require user presence, consume. |
| }; |
| if (!util::VectorToObject(rp_id_hash, sign_req.appId, |
| sizeof(sign_req.appId))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_secret, sign_req.userSecret, |
| sizeof(sign_req.userSecret))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_id, &sign_req.keyHandle, |
| sizeof(sign_req.keyHandle))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(hash_to_sign, sign_req.hash, |
| sizeof(sign_req.hash))) { |
| return GetAssertionResponse::INVALID_REQUEST; |
| } |
| |
| struct u2f_sign_resp sign_resp = {}; |
| return SendU2fSignWaitForPresence(&sign_req, &sign_resp, signature); |
| } else { |
| return GetAssertionResponse::UNKNOWN_CREDENTIAL_ID; |
| } |
| } |
| |
| HasCredentialsResponse::HasCredentialsStatus |
| U2fCommandProcessorGsc::U2fSignCheckOnly( |
| const std::vector<uint8_t>& rp_id_hash, |
| const std::vector<uint8_t>& credential_id, |
| const std::vector<uint8_t>& credential_secret) { |
| uint32_t sign_status; |
| |
| if (credential_id.size() == U2F_V1_KH_SIZE + SHA256_DIGEST_LENGTH) { |
| struct u2f_sign_versioned_req sign_req = {.flags = U2F_AUTH_CHECK_ONLY}; |
| if (!util::VectorToObject(rp_id_hash, sign_req.appId, |
| sizeof(sign_req.appId))) { |
| return HasCredentialsResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_secret, sign_req.userSecret, |
| sizeof(sign_req.userSecret))) { |
| return HasCredentialsResponse::INVALID_REQUEST; |
| } |
| std::vector<uint8_t> key_handle(credential_id); |
| RemoveAuthTimeSecretHashFromCredentialId(&key_handle); |
| if (!util::VectorToObject(key_handle, &sign_req.keyHandle, |
| sizeof(sign_req.keyHandle))) { |
| return HasCredentialsResponse::INVALID_REQUEST; |
| } |
| |
| struct u2f_sign_resp sign_resp; |
| |
| sign_status = tpm_proxy_->SendU2fSign(sign_req, &sign_resp); |
| brillo::SecureClearContainer(sign_req.userSecret); |
| } else if (credential_id.size() == U2F_V0_KH_SIZE) { |
| struct u2f_sign_req sign_req = {.flags = U2F_AUTH_CHECK_ONLY}; |
| if (!util::VectorToObject(rp_id_hash, sign_req.appId, |
| sizeof(sign_req.appId))) { |
| return HasCredentialsResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_secret, sign_req.userSecret, |
| sizeof(sign_req.userSecret))) { |
| return HasCredentialsResponse::INVALID_REQUEST; |
| } |
| if (!util::VectorToObject(credential_id, &sign_req.keyHandle, |
| sizeof(sign_req.keyHandle))) { |
| return HasCredentialsResponse::INVALID_REQUEST; |
| } |
| |
| struct u2f_sign_resp sign_resp; |
| |
| sign_status = tpm_proxy_->SendU2fSign(sign_req, &sign_resp); |
| brillo::SecureClearContainer(sign_req.userSecret); |
| } else { |
| return HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID; |
| } |
| |
| // Return status of 0 indicates the credential is valid. |
| return (sign_status == 0) ? HasCredentialsResponse::SUCCESS |
| : HasCredentialsResponse::UNKNOWN_CREDENTIAL_ID; |
| } |
| |
| MakeCredentialResponse::MakeCredentialStatus U2fCommandProcessorGsc::G2fAttest( |
| const std::vector<uint8_t>& data, |
| const brillo::SecureBlob& secret, |
| uint8_t format, |
| std::vector<uint8_t>* signature_out) { |
| struct u2f_attest_req attest_req = { |
| .format = format, .dataLen = static_cast<uint8_t>(data.size())}; |
| if (!util::VectorToObject(secret, attest_req.userSecret, |
| sizeof(attest_req.userSecret))) { |
| return MakeCredentialResponse::INTERNAL_ERROR; |
| } |
| if (!util::VectorToObject(data, attest_req.data, sizeof(attest_req.data))) { |
| return MakeCredentialResponse::INTERNAL_ERROR; |
| } |
| |
| struct u2f_attest_resp attest_resp = {}; |
| uint32_t attest_status = tpm_proxy_->SendU2fAttest(attest_req, &attest_resp); |
| |
| brillo::SecureClearBytes(&attest_req.userSecret, |
| sizeof(attest_req.userSecret)); |
| |
| if (attest_status != 0) { |
| // 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 MakeCredentialResponse::INTERNAL_ERROR; |
| } |
| |
| 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 MakeCredentialResponse::INTERNAL_ERROR; |
| } |
| |
| *signature_out = *signature; |
| |
| return MakeCredentialResponse::SUCCESS; |
| } |
| |
| base::Optional<std::vector<uint8_t>> U2fCommandProcessorGsc::GetG2fCert() { |
| return util::GetG2fCert(tpm_proxy_); |
| } |
| |
| // This is needed for backward compatibility. Credential id's that were already |
| // generated have inserted hash, so we continue to insert/remove them. |
| void U2fCommandProcessorGsc::InsertAuthTimeSecretHashToCredentialId( |
| const brillo::Blob* auth_time_secret_hash, std::vector<uint8_t>* input) { |
| CHECK(input->size() == U2F_V1_KH_SIZE); |
| // The auth time secret hash should be inserted right after the header and |
| // the authorization salt, before the authorization hmac. |
| input->insert( |
| input->cbegin() + offsetof(u2f_versioned_key_handle, authorization_hmac), |
| auth_time_secret_hash->cbegin(), auth_time_secret_hash->cend()); |
| } |
| |
| // This is needed for backward compatibility. Credential id's that were already |
| // generated have inserted hash, so we continue to insert/remove them. |
| void U2fCommandProcessorGsc::RemoveAuthTimeSecretHashFromCredentialId( |
| std::vector<uint8_t>* input) { |
| CHECK_EQ(input->size(), U2F_V1_KH_SIZE + SHA256_DIGEST_LENGTH); |
| // The auth time secret hash is after the header and the authorization salt, |
| // before the authorization hmac. Remove it so that cr50 recognizes the KH. |
| const std::vector<uint8_t>::const_iterator remove_begin = |
| input->cbegin() + offsetof(u2f_versioned_key_handle, authorization_hmac); |
| input->erase(remove_begin, remove_begin + SHA256_DIGEST_LENGTH); |
| } |
| |
| template <typename Response> |
| MakeCredentialResponse::MakeCredentialStatus |
| U2fCommandProcessorGsc::SendU2fGenerateWaitForPresence( |
| struct u2f_generate_req* generate_req, |
| Response* generate_resp, |
| std::vector<uint8_t>* credential_id, |
| std::vector<uint8_t>* credential_public_key) { |
| uint32_t generate_status = -1; |
| |
| CallAndWaitForPresence( |
| [this, generate_req, generate_resp]() { |
| return tpm_proxy_->SendU2fGenerate(*generate_req, generate_resp); |
| }, |
| &generate_status); |
| brillo::SecureClearContainer(generate_req->userSecret); |
| |
| if (generate_status == 0) { |
| util::AppendToVector(generate_resp->pubKey, credential_public_key); |
| util::AppendToVector(generate_resp->keyHandle, credential_id); |
| return MakeCredentialResponse::SUCCESS; |
| } |
| |
| return MakeCredentialResponse::VERIFICATION_FAILED; |
| } |
| |
| template <typename Request> |
| GetAssertionResponse::GetAssertionStatus |
| U2fCommandProcessorGsc::SendU2fSignWaitForPresence( |
| Request* sign_req, |
| struct u2f_sign_resp* sign_resp, |
| std::vector<uint8_t>* signature) { |
| uint32_t sign_status = -1; |
| |
| CallAndWaitForPresence( |
| [this, sign_req, sign_resp]() { |
| return tpm_proxy_->SendU2fSign(*sign_req, sign_resp); |
| }, |
| &sign_status); |
| brillo::SecureClearContainer(sign_req->userSecret); |
| |
| if (sign_status == 0) { |
| base::Optional<std::vector<uint8_t>> opt_signature = |
| util::SignatureToDerBytes(sign_resp->sig_r, sign_resp->sig_s); |
| if (!opt_signature.has_value()) { |
| return GetAssertionResponse::INTERNAL_ERROR; |
| } |
| *signature = *opt_signature; |
| return GetAssertionResponse::SUCCESS; |
| } |
| |
| return GetAssertionResponse::VERIFICATION_FAILED; |
| } |
| |
| void U2fCommandProcessorGsc::CallAndWaitForPresence( |
| std::function<uint32_t()> fn, uint32_t* status) { |
| *status = fn(); |
| base::TimeTicks verification_start = base::TimeTicks::Now(); |
| while (*status == kCr50StatusNotAllowed && |
| 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(); |
| } |
| } |
| |
| } // namespace u2f |