blob: 3e3b7f4e3c18cfd860507d78276ead221b36632c [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 <memory>
#include <optional>
#include <string>
#include <vector>
#include "libhwsec/backend/tpm2/recovery_crypto.h"
#include <brillo/secure_blob.h>
#include <crypto/scoped_openssl_types.h>
#include <libhwsec-foundation/crypto/big_num_util.h>
#include <libhwsec-foundation/status/status_chain_macros.h>
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <trunks/hmac_session.h>
#include <trunks/openssl_utility.h>
#include <trunks/policy_session.h>
#include <trunks/tpm_utility.h>
#include "libhwsec/error/tpm2_error.h"
#include "libhwsec/status.h"
#include "libhwsec/structures/operation_policy.h"
using hwsec_foundation::BigNumToSecureBlob;
using hwsec_foundation::CreateBigNum;
using hwsec_foundation::CreateBigNumContext;
using hwsec_foundation::EllipticCurve;
using hwsec_foundation::ScopedBN_CTX;
using hwsec_foundation::status::MakeStatus;
namespace hwsec {
namespace {
StatusOr<std::vector<std::string>> GetCurrentUserPolicyDigests(
ConfigTpm2& config, const std::string& current_user) {
ASSIGN_OR_RETURN(
const std::string& digest,
config.GetPolicyDigest(OperationPolicySetting{
.device_config_settings =
DeviceConfigSettings{
.current_user =
DeviceConfigSettings::CurrentUserSetting{
.username = std::nullopt,
},
}}),
_.WithStatus<TPMError>(
"Failed to convert prior login setting to policy digest"));
ASSIGN_OR_RETURN(
const std::string& extend_digest,
config.GetPolicyDigest(OperationPolicySetting{
.device_config_settings =
DeviceConfigSettings{
.current_user =
DeviceConfigSettings::CurrentUserSetting{
.username = current_user,
},
}}),
_.WithStatus<TPMError>(
"Failed to convert current user setting to policy digest"));
return std::vector<std::string>{digest, extend_digest};
}
} // namespace
StatusOr<std::optional<brillo::SecureBlob>>
RecoveryCryptoTpm2::GenerateKeyAuthValue() {
// This operation is intentionally not implemented on TPM 2.0.
return std::nullopt;
}
StatusOr<EncryptEccPrivateKeyResponse> RecoveryCryptoTpm2::EncryptEccPrivateKey(
const EncryptEccPrivateKeyRequest& request) {
ScopedBN_CTX bn_context = CreateBigNumContext();
if (!bn_context.get()) {
return MakeStatus<TPMError>("Failed to allocate BN_CTX structure",
TPMRetryAction::kNoRetry);
}
if (!request.own_key_pair.get()) {
return MakeStatus<TPMError>("Key pair cannot be null",
TPMRetryAction::kNoRetry);
}
const BIGNUM* own_priv_key_bn =
EC_KEY_get0_private_key(request.own_key_pair.get());
if (!own_priv_key_bn) {
return MakeStatus<TPMError>("Failed to get own_priv_key_bn",
TPMRetryAction::kNoRetry);
}
if (!request.ec.IsScalarValid(*own_priv_key_bn)) {
return MakeStatus<TPMError>("Scalar is not valid",
TPMRetryAction::kNoRetry);
}
// Convert own private key to blob.
brillo::SecureBlob own_priv_key;
if (!BigNumToSecureBlob(*own_priv_key_bn, request.ec.ScalarSizeInBytes(),
&own_priv_key)) {
return MakeStatus<TPMError>("Failed to convert BIGNUM to SecureBlob",
TPMRetryAction::kNoRetry);
}
const EC_POINT* pub_point =
EC_KEY_get0_public_key(request.own_key_pair.get());
if (!pub_point) {
return MakeStatus<TPMError>("Failed to get pub_point",
TPMRetryAction::kNoRetry);
}
crypto::ScopedBIGNUM pub_point_x_bn = CreateBigNum(),
pub_point_y_bn = CreateBigNum();
if (!pub_point_x_bn || !pub_point_y_bn) {
return MakeStatus<TPMError>("Failed to allocate BIGNUM",
TPMRetryAction::kNoRetry);
}
if (!request.ec.GetAffineCoordinates(*pub_point, bn_context.get(),
pub_point_x_bn.get(),
pub_point_y_bn.get())) {
return MakeStatus<TPMError>("Failed to get destination share x coordinate",
TPMRetryAction::kNoRetry);
}
brillo::SecureBlob pub_point_x;
if (!BigNumToSecureBlob(*pub_point_x_bn, MAX_ECC_KEY_BYTES, &pub_point_x)) {
return MakeStatus<TPMError>("Failed to convert BIGNUM to SecureBlob",
TPMRetryAction::kNoRetry);
}
brillo::SecureBlob pub_point_y;
if (!BigNumToSecureBlob(*pub_point_y_bn, MAX_ECC_KEY_BYTES, &pub_point_y)) {
return MakeStatus<TPMError>("Failed to convert BIGNUM to SecureBlob",
TPMRetryAction::kNoRetry);
}
// Translate EllipticCurve::CurveType to trunks curveID
trunks::TPM_ECC_CURVE tpm_curve_id = trunks::TPM_ECC_NONE;
switch (request.ec.GetCurveType()) {
case EllipticCurve::CurveType::kPrime256:
tpm_curve_id = trunks::TPM_ECC_NIST_P256;
break;
case EllipticCurve::CurveType::kPrime384:
tpm_curve_id = trunks::TPM_ECC_NIST_P384;
break;
case EllipticCurve::CurveType::kPrime521:
tpm_curve_id = trunks::TPM_ECC_NIST_P521;
break;
}
if (tpm_curve_id == trunks::TPM_ECC_NONE) {
return MakeStatus<TPMError>("Invalid tpm2 curve id",
TPMRetryAction::kNoRetry);
}
if (request.auth_value.has_value()) {
return MakeStatus<TPMError>(
"TPM2.0 don't support auth value for EncryptEccPrivateKey",
TPMRetryAction::kNoRetry);
}
ASSIGN_OR_RETURN(std::vector<std::string> policy_digests,
GetCurrentUserPolicyDigests(config_, request.current_user));
// Start a trial policy session for sealing the secret value.
std::unique_ptr<trunks::PolicySession> trial_session =
context_.GetTrunksFactory().GetTrialSession();
RETURN_IF_ERROR(MakeStatus<TPM2Error>(trial_session->StartUnboundSession(
/*salted=*/false, /*enable_encryption=*/false)))
.WithStatus<TPMError>("Failed to start trial session");
// Apply PolicyOR for restricting to the disjunction of the specified sets of
// PCR restrictions.
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(trial_session->PolicyOR(policy_digests)))
.WithStatus<TPMError>(
"Failed to restrict policy to logical disjunction of PCRs");
// Obtain the resulting policy digest.
std::string result_policy_digest;
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(trial_session->GetDigest(&result_policy_digest)))
.WithStatus<TPMError>("Failed to get policy digest");
// Create the TPM session.
ASSIGN_OR_RETURN(trunks::HmacSession & hmac_session,
session_management_.GetOrCreateHmacSession(
SessionSecuritySetting::kSaltAndEncrypted),
_.WithStatus<TPMError>("Failed to start hmac session"));
// Encrypt its own private key via the TPM2_Import command.
std::string encrypted_own_priv_key_string;
RETURN_IF_ERROR(
MakeStatus<TPM2Error>(
context_.GetTpmUtility().ImportECCKeyWithPolicyDigest(
trunks::TpmUtility::AsymmetricKeyUsage::kDecryptKey, tpm_curve_id,
pub_point_x.to_string(), pub_point_y.to_string(),
own_priv_key.to_string(), result_policy_digest,
hmac_session.GetDelegate(), &encrypted_own_priv_key_string)))
.WithStatus<TPMError>("Failed to import its own private key into TPM");
return EncryptEccPrivateKeyResponse{
.encrypted_own_priv_key =
brillo::BlobFromString(encrypted_own_priv_key_string),
};
}
StatusOr<crypto::ScopedEC_POINT>
RecoveryCryptoTpm2::GenerateDiffieHellmanSharedSecret(
const GenerateDhSharedSecretRequest& request) {
ScopedBN_CTX bn_context = CreateBigNumContext();
if (!bn_context.get()) {
return MakeStatus<TPMError>("Failed to allocate BN_CTX structure",
TPMRetryAction::kNoRetry);
}
if (!request.others_pub_point.get()) {
return MakeStatus<TPMError>("Public point cannot be null",
TPMRetryAction::kNoRetry);
}
// Obtain coordinates of the publisher public point.
crypto::ScopedBIGNUM others_pub_point_x_bn = CreateBigNum(),
others_pub_point_y_bn = CreateBigNum();
if (!others_pub_point_x_bn || !others_pub_point_y_bn) {
return MakeStatus<TPMError>("Failed to allocate BIGNUM",
TPMRetryAction::kNoRetry);
}
if (!request.ec.GetAffineCoordinates(
*request.others_pub_point, bn_context.get(),
others_pub_point_x_bn.get(), others_pub_point_y_bn.get())) {
return MakeStatus<TPMError>(
"Failed to get the other party's public point x coordinate",
TPMRetryAction::kNoRetry);
}
brillo::SecureBlob others_pub_point_x;
if (!BigNumToSecureBlob(*others_pub_point_x_bn, MAX_ECC_KEY_BYTES,
&others_pub_point_x)) {
return MakeStatus<TPMError>("Failed to convert BIGNUM to SecureBlob",
TPMRetryAction::kNoRetry);
}
brillo::SecureBlob others_pub_point_y;
if (!BigNumToSecureBlob(*others_pub_point_y_bn, MAX_ECC_KEY_BYTES,
&others_pub_point_y)) {
return MakeStatus<TPMError>("Failed to convert BIGNUM to SecureBlob",
TPMRetryAction::kNoRetry);
}
if (request.auth_value.has_value()) {
return MakeStatus<TPMError>(
"TPM2.0 don't support auth value for GenerateDiffieHellmanSharedSecret",
TPMRetryAction::kNoRetry);
}
brillo::Blob key_blob = request.encrypted_own_priv_key;
ASSIGN_OR_RETURN(
ScopedKey key,
key_management_.LoadKey(OperationPolicy{}, key_blob,
KeyManagement::LoadKeyOptions{}),
_.WithStatus<TPMError>("Failed to load encrypted ECC private key"));
ASSIGN_OR_RETURN(const KeyTpm2& key_data,
key_management_.GetKeyData(key.GetKey()));
OperationPolicy policy{
.device_configs = DeviceConfigs{DeviceConfig::kCurrentUser},
.permission = Permission{.auth_value = request.auth_value},
};
ASSIGN_OR_RETURN(std::vector<std::string> policy_digests,
GetCurrentUserPolicyDigests(config_, request.current_user));
// TODO(b/196192089): set enable_encryption to true
ASSIGN_OR_RETURN(
std::unique_ptr<trunks::PolicySession> session,
config_.GetTrunksPolicySession(policy, policy_digests, /*salted=*/true,
/*enable_encryption=*/false),
_.WithStatus<TPMError>("Failed to get session for policy"));
trunks::TPMS_ECC_POINT tpm_others_pub_point = {
trunks::Make_TPM2B_ECC_PARAMETER(others_pub_point_x.to_string()),
trunks::Make_TPM2B_ECC_PARAMETER(others_pub_point_y.to_string())};
// Perform the multiplication of the destination share and the other party's
// public point via the TPM2_ECDH_ZGen command.
trunks::TPM2B_ECC_POINT tpm_point_dh;
RETURN_IF_ERROR(MakeStatus<TPM2Error>(context_.GetTpmUtility().ECDHZGen(
key_data.key_handle,
trunks::Make_TPM2B_ECC_POINT(tpm_others_pub_point),
session->GetDelegate(), &tpm_point_dh)))
.WithStatus<TPMError>("Failed to call ECDH_ZGen");
// Return the point after converting it from the TPM representation.
crypto::ScopedEC_POINT point_dh = request.ec.CreatePoint();
if (!point_dh) {
return MakeStatus<TPMError>("Failed to allocate EC_POINT",
TPMRetryAction::kNoRetry);
}
if (!trunks::TpmToOpensslEccPoint(tpm_point_dh.point, *request.ec.GetGroup(),
point_dh.get())) {
return MakeStatus<TPMError>("Failed to convert TPM ECC point",
TPMRetryAction::kNoRetry);
}
return point_dh;
}
StatusOr<std::optional<RecoveryCryptoRsaKeyPair>>
RecoveryCryptoTpm2::GenerateRsaKeyPair() {
// This operation is intentionally not implemented on TPM 2.0.
return std::nullopt;
}
StatusOr<std::optional<brillo::Blob>> RecoveryCryptoTpm2::SignRequestPayload(
const brillo::Blob& encrypted_rsa_private_key,
const brillo::Blob& request_payload) {
// This operation is intentionally not implemented on TPM 2.0.
return std::nullopt;
}
} // namespace hwsec