blob: 72e451e5f3c56e0b768478b1ea0b973714cbf9b6 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "libhwsec/backend/tpm2/u2f.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <brillo/secure_blob.h>
#include <libhwsec-foundation/status/status_chain_macros.h>
#include <openssl/sha.h>
#include <trunks/cr50_headers/u2f.h>
#include <trunks/tpm_utility.h>
#include "libhwsec/backend/vendor.h"
#include "libhwsec/error/tpm2_error.h"
#include "libhwsec/status.h"
using hwsec_foundation::status::MakeStatus;
namespace hwsec {
using u2f::ConsumeMode;
using u2f::FipsCertificationLevel;
using u2f::FipsCertificationStatus;
using u2f::FipsInfo;
using u2f::FipsStatus;
using u2f::GenerateResult;
using u2f::Signature;
using u2f::UserPresenceMode;
namespace {
constexpr u2f::Config kConfigTpm2{
.up_only_kh_size = U2F_V0_KH_SIZE,
.kh_size = U2F_V1_KH_SIZE + SHA256_DIGEST_LENGTH,
};
constexpr uint32_t kCr50StatusNotAllowed = 0x507;
constexpr uint32_t kCr50StatusPasswordRequired = 0x50a;
// Cr50's U2F implementation is L1+L3 physical when FIPS mode is activated.
constexpr FipsCertificationLevel kCr50FipsCertificationLevel{
.physical_certification_status = FipsCertificationStatus::kLevel3,
.logical_certification_status = FipsCertificationStatus::kLevel1,
};
// Ti50's U2F implementation is always in FIPS mode but hasn't been certified.
constexpr FipsCertificationLevel kTi50FipsCertificationLevel{
.physical_certification_status = FipsCertificationStatus::kNotCertified,
.logical_certification_status = FipsCertificationStatus::kNotCertified,
};
// This two functions are needed for backward compatibility. Key handles
// that were already generated have inserted hash, so we continue to
// insert/remove them.
bool InsertAuthTimeSecretHashToKeyHandle(
const brillo::Blob& auth_time_secret_hash, brillo::Blob& input) {
if (input.size() != kConfigTpm2.kh_size - SHA256_DIGEST_LENGTH) {
return false;
}
// 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());
return true;
}
bool RemoveAuthTimeSecretHashFromCredentialId(brillo::Blob& input) {
if (input.size() != kConfigTpm2.kh_size) {
return false;
}
// The auth time secret hash is after the header and the authorization salt,
// before the authorization hmac. Remove it so that the U2F authenticator
// recognizes the key handle.
const brillo::Blob::const_iterator remove_begin =
input.cbegin() + offsetof(u2f_versioned_key_handle, authorization_hmac);
input.erase(remove_begin, remove_begin + SHA256_DIGEST_LENGTH);
return true;
}
class PublicKeyTpm2 : public u2f::PublicKey {
public:
static std::optional<PublicKeyTpm2> from_raw(brillo::Blob raw) {
if (raw.size() != U2F_EC_POINT_SIZE) {
return std::nullopt;
}
PublicKeyTpm2 ret;
ret.data_ = std::move(raw);
return ret;
}
base::span<const uint8_t> x() const override {
return base::make_span(data_.data() + offsetof(u2f_ec_point, x),
static_cast<size_t>(U2F_EC_KEY_SIZE));
}
base::span<const uint8_t> y() const override {
return base::make_span(data_.data() + offsetof(u2f_ec_point, y),
static_cast<size_t>(U2F_EC_KEY_SIZE));
}
const brillo::Blob& raw() const override { return data_; }
private:
PublicKeyTpm2() = default;
brillo::Blob data_;
};
} // namespace
StatusOr<bool> U2fTpm2::IsEnabled() {
if (enabled_.has_value()) {
return *enabled_;
}
// TODO(b/257335815): Add Ti50 case here after its tpm_version is separated
// from Cr50.
enabled_ = context_.GetTpmUtility().IsGsc();
return *enabled_;
}
StatusOr<GenerateResult> U2fTpm2::GenerateUserPresenceOnly(
const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
ConsumeMode consume_mode,
UserPresenceMode up_mode) {
brillo::Blob public_key;
brillo::Blob key_handle;
if (auto status = MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fGenerate(
/*version=*/0, app_id, user_secret,
consume_mode == ConsumeMode::kConsume,
up_mode == UserPresenceMode::kRequired,
/*auth_time_secret_hash=*/std::nullopt, &public_key, &key_handle));
!status.ok() && status->ErrorCode() == kCr50StatusNotAllowed) {
return MakeStatus<TPMError>("Failed to generate U2F credential",
TPMRetryAction::kUserPresence)
.Wrap(std::move(status));
} else if (!status.ok()) {
return MakeStatus<TPMError>("Failed to generate U2F credential")
.Wrap(std::move(status));
}
auto pub = PublicKeyTpm2::from_raw(std::move(public_key));
if (!pub.has_value()) {
return MakeStatus<TPMError>("Invalid public key", TPMRetryAction::kNoRetry);
}
return GenerateResult{
.public_key = std::make_unique<PublicKeyTpm2>(*pub),
.key_handle = key_handle,
};
}
StatusOr<GenerateResult> U2fTpm2::Generate(
const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
ConsumeMode consume_mode,
UserPresenceMode up_mode,
const brillo::Blob& auth_time_secret_hash) {
brillo::Blob public_key;
brillo::Blob key_handle;
if (auto status = MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fGenerate(
/*version=*/1, app_id, user_secret,
consume_mode == ConsumeMode::kConsume,
up_mode == UserPresenceMode::kRequired, auth_time_secret_hash,
&public_key, &key_handle));
!status.ok() && status->ErrorCode() == kCr50StatusNotAllowed) {
return MakeStatus<TPMError>("Failed to generate U2F credential",
TPMRetryAction::kUserPresence)
.Wrap(std::move(status));
} else if (!status.ok()) {
return MakeStatus<TPMError>("Failed to generate U2F credential")
.Wrap(std::move(status));
}
if (!InsertAuthTimeSecretHashToKeyHandle(auth_time_secret_hash, key_handle)) {
return MakeStatus<TPMError>("Invalid U2F key handle is generated",
TPMRetryAction::kNoRetry);
}
auto pub = PublicKeyTpm2::from_raw(std::move(public_key));
if (!pub.has_value()) {
return MakeStatus<TPMError>("Invalid public key", TPMRetryAction::kNoRetry);
}
return GenerateResult{
.public_key = std::make_unique<PublicKeyTpm2>(*pub),
.key_handle = key_handle,
};
}
StatusOr<Signature> U2fTpm2::SignUserPresenceOnly(
const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
const brillo::Blob& hash_to_sign,
ConsumeMode consume_mode,
UserPresenceMode up_mode,
const brillo::Blob& key_handle) {
brillo::Blob sig_r;
brillo::Blob sig_s;
if (auto status = MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fSign(
/*version=*/0, app_id, user_secret, /*auth_time_secret=*/std::nullopt,
hash_to_sign, /*check_only=*/false,
consume_mode == ConsumeMode::kConsume,
up_mode == UserPresenceMode::kRequired, key_handle, &sig_r, &sig_s));
!status.ok() && status->ErrorCode() == kCr50StatusNotAllowed) {
return MakeStatus<TPMError>("Failed to sign using U2F credential",
TPMRetryAction::kUserPresence)
.Wrap(std::move(status));
} else if (!status.ok() &&
status->ErrorCode() == kCr50StatusPasswordRequired) {
return MakeStatus<TPMError>("Failed to sign using U2F credential",
TPMRetryAction::kUserAuth)
.Wrap(std::move(status));
} else if (!status.ok()) {
return MakeStatus<TPMError>("Failed to sign using U2F credential")
.Wrap(std::move(status));
}
return Signature{
.r = sig_r,
.s = sig_s,
};
}
StatusOr<Signature> U2fTpm2::Sign(
const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
const std::optional<brillo::SecureBlob>& auth_time_secret,
const brillo::Blob& hash_to_sign,
ConsumeMode consume_mode,
UserPresenceMode up_mode,
const brillo::Blob& key_handle) {
brillo::Blob sig_r;
brillo::Blob sig_s;
brillo::Blob kh(key_handle);
if (!RemoveAuthTimeSecretHashFromCredentialId(kh)) {
return MakeStatus<TPMError>("Invalid U2F key handle",
TPMRetryAction::kNoRetry);
}
if (auto status = MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fSign(
/*version=*/1, app_id, user_secret, auth_time_secret, hash_to_sign,
/*check_only=*/false, consume_mode == ConsumeMode::kConsume,
up_mode == UserPresenceMode::kRequired, kh, &sig_r, &sig_s));
!status.ok() && status->ErrorCode() == kCr50StatusNotAllowed) {
return MakeStatus<TPMError>("Failed to sign using U2F credential",
TPMRetryAction::kUserPresence)
.Wrap(std::move(status));
} else if (!status.ok() &&
status->ErrorCode() == kCr50StatusPasswordRequired) {
return MakeStatus<TPMError>("Failed to sign using U2F credential",
TPMRetryAction::kUserAuth)
.Wrap(std::move(status));
} else if (!status.ok()) {
return MakeStatus<TPMError>("Failed to sign using U2F credential")
.Wrap(std::move(status));
}
return Signature{
.r = sig_r,
.s = sig_s,
};
}
Status U2fTpm2::CheckUserPresenceOnly(const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
const brillo::Blob& key_handle) {
return MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fSign(
/*version=*/0, app_id, user_secret, std::nullopt, std::nullopt,
/*check_only=*/true, /*consume=*/false,
/*up_required=*/false, key_handle, /*sig_r=*/nullptr,
/*sig_s=*/nullptr));
}
Status U2fTpm2::Check(const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
const brillo::Blob& key_handle) {
brillo::Blob kh(key_handle);
if (!RemoveAuthTimeSecretHashFromCredentialId(kh)) {
return MakeStatus<TPMError>("Invalid U2F key handle",
TPMRetryAction::kNoRetry);
}
return MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fSign(
/*version=*/1, app_id, user_secret, std::nullopt, std::nullopt,
/*check_only=*/true, /*consume=*/false,
/*up_required=*/false, kh, /*sig_r=*/nullptr,
/*sig_s=*/nullptr));
}
StatusOr<Signature> U2fTpm2::G2fAttest(const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
const brillo::Blob& challenge,
const brillo::Blob& key_handle,
const brillo::Blob& public_key) {
ASSIGN_OR_RETURN(brillo::Blob data,
GetG2fAttestData(app_id, challenge, key_handle, public_key));
if (user_secret.size() != U2F_USER_SECRET_SIZE) {
return MakeStatus<TPMError>("Invalid parameters", TPMRetryAction::kNoRetry);
}
brillo::Blob sig_r;
brillo::Blob sig_s;
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fAttest(
user_secret, U2F_ATTEST_FORMAT_REG_RESP, data, &sig_r, &sig_s)))
.WithStatus<TPMError>("Failed to attest U2F credential");
return Signature{
.r = sig_r,
.s = sig_s,
};
}
StatusOr<brillo::Blob> U2fTpm2::GetG2fAttestData(
const brillo::Blob& app_id,
const brillo::Blob& challenge,
const brillo::Blob& key_handle,
const brillo::Blob& public_key) {
if (app_id.size() != U2F_APPID_SIZE || challenge.size() != U2F_CHAL_SIZE ||
key_handle.size() != U2F_V0_KH_SIZE ||
public_key.size() != U2F_EC_POINT_SIZE) {
return MakeStatus<TPMError>("Invalid parameters", TPMRetryAction::kNoRetry);
}
g2f_register_msg_v0 msg{};
msg.reserved = 0;
std::copy(app_id.begin(), app_id.end(), msg.app_id);
std::copy(challenge.begin(), challenge.end(), msg.challenge);
memcpy(&msg.key_handle, key_handle.data(), key_handle.size());
memcpy(&msg.public_key, public_key.data(), public_key.size());
brillo::Blob data(sizeof(msg));
memcpy(data.data(), &msg, sizeof(msg));
return data;
}
StatusOr<Signature> U2fTpm2::CorpAttest(const brillo::Blob& app_id,
const brillo::SecureBlob& user_secret,
const brillo::Blob& challenge,
const brillo::Blob& key_handle,
const brillo::Blob& public_key,
const brillo::Blob& salt) {
if (app_id.size() != U2F_APPID_SIZE ||
user_secret.size() != U2F_USER_SECRET_SIZE ||
challenge.size() != CORP_CHAL_SIZE ||
key_handle.size() != U2F_V0_KH_SIZE ||
public_key.size() != U2F_EC_POINT_SIZE || salt.size() != CORP_SALT_SIZE) {
return MakeStatus<TPMError>("Invalid parameters", TPMRetryAction::kNoRetry);
}
corp_register_msg_v0 msg{};
auto* data = reinterpret_cast<corp_attest_data*>(&msg.data);
std::copy(challenge.begin(), challenge.end(), data->challenge);
memcpy(&data->public_key, public_key.data(), public_key.size());
std::copy(salt.begin(), salt.end(), data->salt);
std::copy(app_id.begin(), app_id.end(), msg.app_id);
memcpy(&msg.key_handle, key_handle.data(), key_handle.size());
brillo::Blob msg_blob(sizeof(msg));
memcpy(msg_blob.data(), &msg, sizeof(msg));
brillo::Blob sig_r;
brillo::Blob sig_s;
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(context_.GetTpmUtility().U2fAttest(
user_secret, CORP_ATTEST_FORMAT_REG_RESP, msg_blob, &sig_r, &sig_s)))
.WithStatus<TPMError>("Failed to attest U2F credential");
return Signature{
.r = sig_r,
.s = sig_s,
};
}
StatusOr<u2f::Config> U2fTpm2::GetConfig() {
return kConfigTpm2;
}
StatusOr<FipsInfo> U2fTpm2::GetFipsInfo() {
ASSIGN_OR_RETURN(Vendor::GscType gsc_type, vendor_.GetGscType(),
_.WithStatus<TPMError>("Failed to get GSC type"));
switch (gsc_type) {
case Vendor::GscType::kNotGsc:
return MakeStatus<TPMError>("Invalid GSC type", TPMRetryAction::kNoRetry);
case Vendor::GscType::kTi50:
// Ti50's U2F implementation is always in FIPS mode.
return FipsInfo{
.activation_status = FipsStatus::kActive,
.certification_level = kTi50FipsCertificationLevel,
};
case Vendor::GscType::kCr50:
// Rest of this function assumes Cr50 GSC type.
break;
}
if (fips_info_.has_value()) {
return *fips_info_;
}
bool is_active;
RETURN_IF_ERROR(MakeStatus<TPM2Error>(
context_.GetTpmUtility().U2fGetFipsStatus(&is_active)))
.WithStatus<TPMError>("Failed to get FIPS status");
if (is_active) {
fips_info_ = FipsInfo{
.activation_status = FipsStatus::kActive,
.certification_level = kCr50FipsCertificationLevel,
};
} else {
fips_info_ = FipsInfo{.activation_status = FipsStatus::kNotActive};
}
return *fips_info_;
}
Status U2fTpm2::ActivateFips() {
ASSIGN_OR_RETURN(Vendor::GscType gsc_type, vendor_.GetGscType(),
_.WithStatus<TPMError>("Failed to get GSC type"));
switch (gsc_type) {
case Vendor::GscType::kNotGsc:
return MakeStatus<TPMError>("Invalid GSC type", TPMRetryAction::kNoRetry);
case Vendor::GscType::kTi50:
// Ti50's U2F implementation is always in FIPS mode.
return OkStatus();
case Vendor::GscType::kCr50:
// Rest of this function assumes Cr50 GSC type.
break;
}
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(context_.GetTpmUtility().ActivateFips()))
.WithStatus<TPMError>("Failed to activate FIPS");
fips_info_ = FipsInfo{
.activation_status = FipsStatus::kActive,
.certification_level = kCr50FipsCertificationLevel,
};
return OkStatus();
}
} // namespace hwsec