| // 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 |