blob: c304c604b8eff557bd42355a1d97a880e1439f42 [file] [log] [blame]
// Copyright 2015 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 "trunks/session_manager_impl.h"
#include <string>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/secure_blob.h>
#include <crypto/openssl_util.h>
#include <crypto/libcrypto-compat.h>
#include <crypto/scoped_openssl_types.h>
#include <libhwsec-foundation/utility/crypto.h>
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#if defined(OPENSSL_IS_BORINGSSL)
#include <openssl/mem.h>
#endif
#include <openssl/obj_mac.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include "trunks/error_codes.h"
#include "trunks/openssl_utility.h"
#include "trunks/tpm_generated.h"
#include "trunks/tpm_utility.h"
namespace {
constexpr size_t kWellKnownExponent = 0x10001;
// The label constant for RSAES-OAEP and ECDH session secret generation, defined
// in the TPM 2.0 specs, Part 1, Annex B.10.2 and C.6.2.
constexpr char kSessionKeyLabelValue[] = "SECRET\0";
constexpr size_t kSessionKeyLabelLength = sizeof(kSessionKeyLabelValue) - 1;
constexpr size_t kSessionSecretSize = SHA256_DIGEST_SIZE;
// Curve ID of the ECC salting key, used in OpenSSL and equivalent to
// TPM_ECC_NIST_P256 in TPM2.
constexpr int kEccCurveID = NID_X9_62_prime256v1;
// Retry limit of ECDH ECC point generation and Z point computation.
constexpr int kEcdhKeyGenRetryLimit = 3;
static_assert(kEcdhKeyGenRetryLimit > 0,
"ECDH keygen retry limit should be greater than 0.");
// Generates an ephemeral ECC key pair and stores the public part in
// |ephemeral_point|. Computes the Z point and stores it in |z_point|, using
// the public part of salting key, |salting_key_pub|, and the ephemeral ECC
// key. |ephemeral_point| and |z_point| can be used to create a secure ECDH
// channel. Returns if all operations succeeded.
//
// Check the TPM 2.0 specs Part 1, Annex C.6.1 for the definition of Z point.
bool GenerateEcdhKeys(const trunks::TPMS_ECC_POINT& salting_key_pub,
trunks::TPMS_ECC_POINT* ephemeral_point,
trunks::TPMS_ECC_POINT* z_point) {
crypto::ScopedEC_KEY ephemeral_key(EC_KEY_new_by_curve_name(kEccCurveID));
if (!ephemeral_key.get()) {
LOG(ERROR) << "Failed to create an ephemeral ECC key object: "
<< hwsec_foundation::utility::GetOpensslError();
return false;
}
if (!EC_KEY_generate_key(ephemeral_key.get())) {
LOG(ERROR) << "Failed to generate an ephemeral ECC key: "
<< hwsec_foundation::utility::GetOpensslError();
return false;
}
const EC_POINT* ephemeral_key_pub =
EC_KEY_get0_public_key(ephemeral_key.get());
const BIGNUM* ephemeral_key_pri =
EC_KEY_get0_private_key(ephemeral_key.get());
const crypto::ScopedEC_GROUP ec_group(
EC_GROUP_new_by_curve_name(kEccCurveID));
if (!ec_group.get()) {
LOG(ERROR) << "Failed to generate EC_GROUP for the "
<< "ephemeral key and z point: "
<< hwsec_foundation::utility::GetOpensslError();
return false;
}
crypto::ScopedEC_POINT z_ec_point(EC_POINT_new(ec_group.get()));
crypto::ScopedEC_POINT salting_key_ec_point(EC_POINT_new(ec_group.get()));
if (!TpmToOpensslEccPoint(salting_key_pub, *ec_group.get(),
salting_key_ec_point.get())) {
LOG(ERROR) << "Failed to get EC_POINT for the ECC salting key.";
return false;
}
if (!EC_POINT_mul(ec_group.get(), z_ec_point.get(),
nullptr /* unused multiplier */, salting_key_ec_point.get(),
ephemeral_key_pri, nullptr /* unused context */)) {
LOG(ERROR) << "Failed to compute the Z point.";
return false;
}
if (EC_POINT_is_at_infinity(ec_group.get(), z_ec_point.get())) {
// There is a small chance that the product Z is the infinity point. Returns
// false here and the caller may try again.
LOG(WARNING) << "The Z point is at infinity. Need to try again.";
return false;
}
if (!OpensslToTpmEccPoint(*ec_group.get(), *ephemeral_key_pub,
trunks::kEccKeySize, ephemeral_point)) {
LOG(ERROR) << "Failed to convert the ephemeral key.";
return false;
}
if (!OpensslToTpmEccPoint(*ec_group.get(), *z_ec_point.get(),
trunks::kEccKeySize, z_point)) {
LOG(ERROR) << "Failed to convert the Z point.";
return false;
}
return true;
}
// Generates a plaintext |salt| and encrypts the salt using TPM's RSA salting
// key |public_area| with PKCS1_OAEP padding. The encrypted salt is stored in
// |encrypted_salt|. The salt generation and encryption follows the TPM 2.0
// specs Part 1, Annex B.10.2. The pointers |salt| and |encrypted_salt| must
// be initialized first. Returns TPM_RC_SUCCESS on success or other values on
// an error.
//
// Currently only supports RSA-2048, and the generated |salt| will be 256-bit
// long.
trunks::TPM_RC GenerateRsaSessionSalt(const trunks::TPMT_PUBLIC& public_area,
brillo::SecureBlob* salt,
std::string* encrypted_salt) {
const uint16_t rsa_key_size = public_area.unique.rsa.size;
if (rsa_key_size != 256) {
LOG(ERROR) << "Invalid RSA salting key length: "
<< public_area.unique.rsa.size;
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
*salt = hwsec_foundation::utility::CreateSecureRandomBlob(kSessionSecretSize);
if (salt->size() != kSessionSecretSize) {
LOG(ERROR) << "Error generating a cryptographically random salt.";
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
crypto::ScopedRSA salting_key_rsa(RSA_new());
crypto::ScopedBIGNUM n(BN_new()), e(BN_new());
if (!salting_key_rsa || !n || !e) {
LOG(ERROR) << "Failed to allocate RSA or BIGNUM: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
if (!BN_set_word(e.get(), kWellKnownExponent) ||
!BN_bin2bn(public_area.unique.rsa.buffer, rsa_key_size, n.get())) {
LOG(ERROR) << "Error setting public area of rsa key: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
if (!RSA_set0_key(salting_key_rsa.get(), n.release(), e.release(), nullptr)) {
LOG(ERROR) << "Failed to set exponent or modulus.";
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
crypto::ScopedEVP_PKEY salting_key(EVP_PKEY_new());
if (!salting_key) {
LOG(ERROR) << "Failed to allocate EVP_PKEY: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
if (!EVP_PKEY_set1_RSA(salting_key.get(), salting_key_rsa.get())) {
LOG(ERROR) << "Error setting up EVP_PKEY: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
// EVP_PKEY_CTX_set0_rsa_oaep_label takes ownership so we need to malloc.
uint8_t* oaep_label =
static_cast<uint8_t*>(OPENSSL_malloc(kSessionKeyLabelLength));
memcpy(oaep_label, kSessionKeyLabelValue, kSessionKeyLabelLength);
crypto::ScopedEVP_PKEY_CTX salt_encrypt_context(
EVP_PKEY_CTX_new(salting_key.get(), nullptr));
if (!salt_encrypt_context ||
!EVP_PKEY_encrypt_init(salt_encrypt_context.get()) ||
!EVP_PKEY_CTX_set_rsa_padding(salt_encrypt_context.get(),
RSA_PKCS1_OAEP_PADDING) ||
!EVP_PKEY_CTX_set_rsa_oaep_md(salt_encrypt_context.get(), EVP_sha256()) ||
!EVP_PKEY_CTX_set_rsa_mgf1_md(salt_encrypt_context.get(), EVP_sha256()) ||
!EVP_PKEY_CTX_set0_rsa_oaep_label(salt_encrypt_context.get(), oaep_label,
kSessionKeyLabelLength)) {
LOG(ERROR) << "Error setting up salt encrypt context: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
size_t out_length = EVP_PKEY_size(salting_key.get());
encrypted_salt->resize(out_length);
if (!EVP_PKEY_encrypt(salt_encrypt_context.get(),
reinterpret_cast<uint8_t*>(base::data(*encrypted_salt)),
&out_length, salt->data(), salt->size())) {
LOG(ERROR) << "Error encrypting salt: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
encrypted_salt->resize(out_length);
return trunks::TPM_RC_SUCCESS;
}
// Generates an ephemeral ECC key, serializes its public part, and stores it
// in |serialized_ephemeral_point|. The serialized public part is treated as
// the "encryptedSalt" in the TPM command TPM2_StartAuthSession() (TPM 2.0
// specs Part 3, Section 11.1.1). Also, follows the specs Part 1, Section
// 11.4.9.3 and Annex C.6.1 and C.6.2 and use the salting key |public_area| to
// compute |seed|. The seed is used as a session secret, similar to "salt" in
// RSA-encrypted sessions. The pointers |serialized_ephemeral_point| and
// |seed| must be initialized first. Returns TPM_RC_SUCCESS on success or
// other values on an error.
//
// The TPM will be able to recover the session secret, seed, from the
// ephemeral public point and the private part of the salting key.
trunks::TPM_RC GenerateEccSessionSalt(const trunks::TPMT_PUBLIC& public_area,
brillo::SecureBlob* seed,
std::string* serialized_ephemeral_point) {
if (public_area.name_alg != trunks::TPM_ALG_SHA256 ||
public_area.unique.ecc.x.size != trunks::kEccKeySize ||
public_area.unique.ecc.y.size != trunks::kEccKeySize) {
LOG(ERROR) << "Invalid ECC salting key attributes.";
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
// Generates an ephemeral key pair, computes Z point from the private
// ephemeral key and TPM's public salting key, and gets the public ephemeral
// point and Z point.
const trunks::TPMS_ECC_POINT& salting_key_pub_point = public_area.unique.ecc;
trunks::TPMS_ECC_POINT ephemeral_point;
trunks::TPMS_ECC_POINT z_point;
for (int try_count = 0; try_count < kEcdhKeyGenRetryLimit; ++try_count) {
if (GenerateEcdhKeys(salting_key_pub_point, &ephemeral_point, &z_point)) {
break;
}
if (try_count == kEcdhKeyGenRetryLimit - 1) {
LOG(ERROR) << "Couldn't generate ECC points for ECDH session after "
<< kEcdhKeyGenRetryLimit << " attempts. Giving up.";
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
LOG(WARNING) << "Error generating ECC points for ECDH session. "
"Trying again...";
}
trunks::TPM_RC result =
Serialize_TPMS_ECC_POINT(ephemeral_point, serialized_ephemeral_point);
if (result != trunks::TPM_RC_SUCCESS) {
LOG(ERROR) << "Error serializing initiator's public point: "
<< trunks::GetErrorString(result);
return result;
}
// Follows TPM 2.0 specs Part 1, Annex C.6.1 and computes the session secret,
// seed, using a KDFe. Part 4, Section 10.2.13.8.3 shows an example of generic
// KDFe implementation. That implementation is ported and simplified below. We
// assume the hash algorithm to be SHA-256, as specified in the salting key's
// public_area.name_alg. Its digest length equals the seed length, so we only
// need to run the hashes for one iteration.
// Big-Endian 32-bit 1.
uint8_t marshaled_counter[4] = {0, 0, 0, 1};
const trunks::TPM2B_ECC_PARAMETER& z_value = z_point.x;
const trunks::TPM2B_ECC_PARAMETER& party_u_info = ephemeral_point.x;
const trunks::TPM2B_ECC_PARAMETER& party_v_info = salting_key_pub_point.x;
seed->resize(kSessionSecretSize);
crypto::ScopedEVP_MD_CTX ctx(EVP_MD_CTX_new());
const EVP_MD* digest_type = EVP_sha256();
unsigned int final_seed_size = 0;
if (!EVP_DigestInit(ctx.get(), digest_type) ||
!EVP_DigestUpdate(ctx.get(), marshaled_counter,
base::size(marshaled_counter)) ||
!EVP_DigestUpdate(ctx.get(), z_value.buffer, z_value.size) ||
!EVP_DigestUpdate(ctx.get(), kSessionKeyLabelValue,
kSessionKeyLabelLength) ||
!EVP_DigestUpdate(ctx.get(), party_u_info.buffer, party_u_info.size) ||
!EVP_DigestUpdate(ctx.get(), party_v_info.buffer, party_v_info.size) ||
!EVP_DigestFinal(ctx.get(),
reinterpret_cast<unsigned char*>(seed->data()),
&final_seed_size) ||
final_seed_size != seed->size()) {
LOG(ERROR) << "Error creating a SHA-256 digest: "
<< hwsec_foundation::utility::GetOpensslError();
return trunks::TRUNKS_RC_SESSION_SETUP_ERROR;
}
return trunks::TPM_RC_SUCCESS;
}
} // namespace
namespace trunks {
SessionManagerImpl::SessionManagerImpl(const TrunksFactory& factory)
: factory_(factory), session_handle_(kUninitializedHandle) {
crypto::EnsureOpenSSLInit();
}
SessionManagerImpl::~SessionManagerImpl() {
CloseSession();
}
void SessionManagerImpl::CloseSession() {
if (session_handle_ == kUninitializedHandle) {
return;
}
TPM_RC result = factory_.GetTpm()->FlushContextSync(session_handle_, nullptr);
if (result != TPM_RC_SUCCESS) {
LOG(WARNING) << "Error closing tpm session: " << GetErrorString(result);
}
session_handle_ = kUninitializedHandle;
}
TPM_RC SessionManagerImpl::StartSession(
TPM_SE session_type,
TPMI_DH_ENTITY bind_entity,
const std::string& bind_authorization_value,
bool salted,
bool enable_encryption,
HmacAuthorizationDelegate* delegate) {
CHECK(delegate);
// If we already have an active session, close it.
CloseSession();
brillo::SecureBlob salt;
std::string encrypted_salt;
TPMI_DH_OBJECT tpm_key = TPM_RH_NULL;
if (salted) {
tpm_key = kSaltingKey;
TPM_RC salt_result = GenerateSessionSalt(&salt, &encrypted_salt);
if (salt_result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error creating session secret: "
<< GetErrorString(salt_result);
return salt_result;
}
}
TPM2B_ENCRYPTED_SECRET encrypted_secret =
Make_TPM2B_ENCRYPTED_SECRET(encrypted_salt);
TPMI_ALG_HASH hash_algorithm = TPM_ALG_SHA256;
TPMT_SYM_DEF symmetric_algorithm;
if (enable_encryption) {
symmetric_algorithm.algorithm = TPM_ALG_AES;
symmetric_algorithm.key_bits.aes = 128;
symmetric_algorithm.mode.aes = TPM_ALG_CFB;
} else {
symmetric_algorithm.algorithm = TPM_ALG_NULL;
}
TPM2B_NONCE nonce_caller;
TPM2B_NONCE nonce_tpm;
// We use sha1_digest_size here because that is the minimum length
// needed for the nonce.
nonce_caller.size = SHA1_DIGEST_SIZE;
CHECK_EQ(RAND_bytes(nonce_caller.buffer, nonce_caller.size), 1)
<< "Error generating a cryptographically random nonce.";
Tpm* tpm = factory_.GetTpm();
// Then we use TPM2_StartAuthSession to start a session with the TPM.
// The TPM returns the tpm_nonce and the session_handle referencing the
// created session.
// The TPM2 command below needs no authorization. This is why we can use
// the empty string "", when referring to the handle names for the salting
// key and the bind entity.
TPM_RC tpm_result = tpm->StartAuthSessionSync(
tpm_key,
"", // salt_handle_name.
bind_entity,
"", // bind_entity_name.
nonce_caller, encrypted_secret, session_type, symmetric_algorithm,
hash_algorithm, &session_handle_, &nonce_tpm,
nullptr); // No Authorization.
if (tpm_result) {
LOG(ERROR) << "Error creating an authorization session: "
<< GetErrorString(tpm_result);
return tpm_result;
}
bool hmac_result = delegate->InitSession(
session_handle_, nonce_tpm, nonce_caller, salt.to_string(),
bind_authorization_value, enable_encryption);
if (!hmac_result) {
LOG(ERROR) << "Failed to initialize an authorization session delegate.";
return TPM_RC_FAILURE;
}
return TPM_RC_SUCCESS;
}
TPM_RC SessionManagerImpl::GenerateSessionSalt(brillo::SecureBlob* salt,
std::string* encrypted_salt) {
TPMT_PUBLIC public_area;
TPM_RC result = factory_.GetTpmCache()->GetSaltingKeyPublicArea(&public_area);
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error fetching salting key public info: "
<< GetErrorString(result);
return result;
}
const TPMI_ALG_PUBLIC& salting_key_type = public_area.type;
if (salting_key_type == TPM_ALG_RSA) {
result = GenerateRsaSessionSalt(public_area, salt, encrypted_salt);
} else if (salting_key_type == TPM_ALG_ECC) {
result = GenerateEccSessionSalt(public_area, salt, encrypted_salt);
} else {
LOG(ERROR) << "Unsupported salting key type: " << salting_key_type;
return TRUNKS_RC_SESSION_SETUP_ERROR;
}
if (result != TPM_RC_SUCCESS) {
LOG(ERROR) << "Error generating a session salt: " << GetErrorString(result);
return result;
}
return TPM_RC_SUCCESS;
}
} // namespace trunks