| // 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/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/crypto_utility.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::GetOpensslError(); |
| return false; |
| } |
| if (!EC_KEY_generate_key(ephemeral_key.get())) { |
| LOG(ERROR) << "Failed to generate an ephemeral ECC key: " |
| << hwsec::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::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::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::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::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::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::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::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::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::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 |