blob: 1d90f6fd9c52731fb2cbd4d23dbc4479a4347d0b [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/deriving.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <base/functional/callback_helpers.h>
#include <brillo/secure_blob.h>
#include <libhwsec-foundation/crypto/big_num_util.h>
#include <libhwsec-foundation/crypto/elliptic_curve.h>
#include <libhwsec-foundation/crypto/sha.h>
#include <libhwsec-foundation/status/status_chain_macros.h>
#include <trunks/openssl_utility.h>
#include <trunks/tpm_utility.h>
#include "libhwsec/error/tpm2_error.h"
#include "libhwsec/status.h"
using brillo::Blob;
using brillo::SecureBlob;
using hwsec_foundation::CreateBigNumContext;
using hwsec_foundation::EllipticCurve;
using hwsec_foundation::ScopedBN_CTX;
using hwsec_foundation::SecureBlobToBigNum;
using hwsec_foundation::Sha256;
using hwsec_foundation::status::MakeStatus;
namespace hwsec {
namespace {
constexpr uint32_t kDefaultTpmRsaModulusSize = 2048;
constexpr int kMinDeriveBlobSize = 32;
constexpr EllipticCurve::CurveType kDefaultCurve =
EllipticCurve::CurveType::kPrime256;
hwsec::StatusOr<trunks::TPMS_ECC_POINT> DeriveTpmEccPointFromSeed(
const SecureBlob& seed) {
// Generate an ECC private key (scalar) based on the seed.
crypto::ScopedBIGNUM private_key = SecureBlobToBigNum(Sha256(seed));
ScopedBN_CTX context = CreateBigNumContext();
if (!context.get()) {
return MakeStatus<TPMError>("Failed to allocate BN_CTX structure",
TPMRetryAction::kNoRetry);
}
std::optional<EllipticCurve> ec =
EllipticCurve::Create(kDefaultCurve, context.get());
if (!ec) {
return MakeStatus<TPMError>("Failed to create EllipticCurve",
TPMRetryAction::kNoRetry);
}
if (!ec->IsScalarValid(*private_key)) {
// Generate another blob may resolve this issue.
return MakeStatus<TPMError>("ECC scalar out of range",
TPMRetryAction::kEllipticCurveScalarOutOfRange);
}
crypto::ScopedEC_POINT public_point =
ec->MultiplyWithGenerator(*private_key, context.get());
if (!public_point) {
return MakeStatus<TPMError>("Failed to multiply with generator",
TPMRetryAction::kNoRetry);
}
trunks::TPMS_ECC_POINT out_point;
if (!trunks::OpensslToTpmEccPoint(*ec->GetGroup(), *public_point,
ec->AffineCoordinateSizeInBytes(),
&out_point)) {
return MakeStatus<TPMError>("Error converting OpenSSL to TPM ECC point",
TPMRetryAction::kNoRetry);
}
return out_point;
}
} // namespace
StatusOr<Blob> DerivingTpm2::Derive(Key key, const Blob& blob) {
ASSIGN_OR_RETURN(
const SecureBlob& result,
SecureDerive(std::move(key), SecureBlob(blob.begin(), blob.end())),
_.WithStatus<TPMError>("Failed to derive secure blob"));
return Blob(result.begin(), result.end());
}
StatusOr<SecureBlob> DerivingTpm2::SecureDerive(Key key,
const SecureBlob& blob) {
ASSIGN_OR_RETURN(const KeyTpm2& key_data, key_management_.GetKeyData(key));
switch (key_data.cache.public_area.type) {
case trunks::TPM_ALG_RSA:
return DeriveRsaKey(key_data, blob);
case trunks::TPM_ALG_ECC:
return DeriveEccKey(key_data, blob);
default:
return MakeStatus<TPMError>("Unknown algorithm",
TPMRetryAction::kNoRetry);
}
}
StatusOr<SecureBlob> DerivingTpm2::DeriveRsaKey(const KeyTpm2& key_data,
const SecureBlob& blob) {
if (blob.size() != kDefaultTpmRsaModulusSize / 8) {
return MakeStatus<TPMError>(
"Unexpected blob size: " + std::to_string(blob.size()),
TPMRetryAction::kNoRetry);
}
// To guarantee that pass_blob is lower that public key modulus, just set the
// first byte to 0.
std::string value_to_decrypt = blob.to_string();
value_to_decrypt[0] = 0;
// Cleanup the data from secure blob.
base::ScopedClosureRunner cleanup_value_to_decrypt(base::BindOnce(
brillo::SecureClearContainer<std::string>, std::ref(value_to_decrypt)));
ASSIGN_OR_RETURN(
ConfigTpm2::TrunksSession session,
config_.GetTrunksSession(key_data.cache.policy,
SessionSecuritySetting::kNoEncrypted),
_.WithStatus<TPMError>("Failed to get session for policy"));
std::string decrypted_value;
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(context_.GetTpmUtility().AsymmetricDecrypt(
key_data.key_handle, trunks::TPM_ALG_NULL, trunks::TPM_ALG_NULL,
value_to_decrypt, session.delegate, &decrypted_value)))
.WithStatus<TPMError>("Failed to decrypt blob");
return Sha256(SecureBlob(decrypted_value));
}
StatusOr<SecureBlob> DerivingTpm2::DeriveEccKey(const KeyTpm2& key_data,
const SecureBlob& blob) {
if (blob.size() < kMinDeriveBlobSize) {
return MakeStatus<TPMError>(
"Unexpected blob size: " + std::to_string(blob.size()),
TPMRetryAction::kNoRetry);
}
ASSIGN_OR_RETURN(
const trunks::TPMS_ECC_POINT& ecc_point, DeriveTpmEccPointFromSeed(blob),
_.WithStatus<TPMError>("Failed to derive TPM ECC point from seed"));
trunks::TPM2B_ECC_POINT in_point = trunks::Make_TPM2B_ECC_POINT(ecc_point);
trunks::TPM2B_ECC_POINT z_point;
ASSIGN_OR_RETURN(
ConfigTpm2::TrunksSession session,
config_.GetTrunksSession(key_data.cache.policy,
SessionSecuritySetting::kNoEncrypted),
_.WithStatus<TPMError>("Failed to get session for policy"));
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(context_.GetTpmUtility().ECDHZGen(
key_data.key_handle, in_point, session.delegate, &z_point)))
.WithStatus<TPMError>("Failed to ECDH ZGen");
if (z_point.point.x.size > sizeof(z_point.point.x.buffer)) {
return MakeStatus<TPMError>("Z point overflow", TPMRetryAction::kNoRetry);
}
return Sha256(SecureBlob(StringFrom_TPM2B_ECC_PARAMETER(z_point.point.x)));
}
} // namespace hwsec