text: //
  // Copyright (C) 2015 The Android Open Source Project
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  //
  
  #include "trunks/session_manager_impl.h"
  
  #include <string>
  
  #include <base/logging.h>
  #include <base/stl_util.h>
  #include <crypto/openssl_util.h>
  #include <crypto/scoped_openssl_types.h>
  #include <openssl/err.h>
  #include <openssl/evp.h>
  #include <openssl/mem.h>
  #include <openssl/rand.h>
  #include <openssl/rsa.h>
  
  #include "trunks/error_codes.h"
  #include "trunks/tpm_generated.h"
  #include "trunks/tpm_utility.h"
  
  namespace {
  const size_t kWellKnownExponent = 0x10001;
  
  std::string GetOpenSSLError() {
    BIO* bio = BIO_new(BIO_s_mem());
    ERR_print_errors(bio);
    char* data = nullptr;
    int data_len = BIO_get_mem_data(bio, &data);
    std::string error_string(data, data_len);
    BIO_free(bio);
    return error_string;
  }
  }  // 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 enable_encryption,
      HmacAuthorizationDelegate* delegate) {
    CHECK(delegate);
    // If we already have an active session, close it.
    CloseSession();
  
    std::string salt(SHA256_DIGEST_SIZE, 0);
    unsigned char* salt_buffer =
        reinterpret_cast<unsigned char*>(string_as_array(&salt));
    CHECK_EQ(RAND_bytes(salt_buffer, salt.size()), 1)
        << "Error generating a cryptographically random salt.";
    // First we encrypt the cryptographically secure salt using PKCS1_OAEP
    // padded RSA public key encryption. This is specified in TPM2.0
    // Part1 Architecture, Appendix B.10.2.
    std::string encrypted_salt;
    TPM_RC salt_result = EncryptSalt(salt, &encrypted_salt);
    if (salt_result != TPM_RC_SUCCESS) {
      LOG(ERROR) << "Error encrypting salt: " << GetErrorString(salt_result);
      return salt_result;
    }
  
    TPM2B_ENCRYPTED_SECRET encrypted_secret =
        Make_TPM2B_ENCRYPTED_SECRET(encrypted_salt);
    // Then we use TPM2_StartAuthSession to start a HMAC session with the TPM.
    // The tpm returns the tpm_nonce and the session_handle referencing the
    // created session.
    TPMI_ALG_HASH hash_algorithm = TPM_ALG_SHA256;
    TPMT_SYM_DEF symmetric_algorithm;
    symmetric_algorithm.algorithm = TPM_ALG_AES;
    symmetric_algorithm.key_bits.aes = 128;
    symmetric_algorithm.mode.aes = TPM_ALG_CFB;
  
    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();
    // 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(kSaltingKey,
                                                  "",  // 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,
      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::EncryptSalt(const std::string& salt,
                                         std::string* encrypted_salt) {
    TPM2B_NAME out_name;
    TPM2B_NAME qualified_name;
    TPM2B_PUBLIC public_data;
    public_data.public_area.unique.rsa.size = 0;
    TPM_RC result = factory_.GetTpm()->ReadPublicSync(
        kSaltingKey, "" /*object_handle_name (not used)*/, &public_data,
        &out_name, &qualified_name, nullptr /*authorization_delegate*/);
    if (result != TPM_RC_SUCCESS) {
      LOG(ERROR) << "Error fetching salting key public info: "
                 << GetErrorString(result);
      return result;
    }
    crypto::ScopedRSA salting_key_rsa(RSA_new());
    salting_key_rsa->e = BN_new();
    if (!salting_key_rsa->e) {
      LOG(ERROR) << "Error creating exponent for RSA: " << GetOpenSSLError();
      return TRUNKS_RC_SESSION_SETUP_ERROR;
    }
    BN_set_word(salting_key_rsa->e, kWellKnownExponent);
    salting_key_rsa->n =
        BN_bin2bn(public_data.public_area.unique.rsa.buffer,
                  public_data.public_area.unique.rsa.size, nullptr);
    if (!salting_key_rsa->n) {
      LOG(ERROR) << "Error setting public area of rsa key: " << GetOpenSSLError();
      return TRUNKS_RC_SESSION_SETUP_ERROR;
    }
    crypto::ScopedEVP_PKEY salting_key(EVP_PKEY_new());
    if (!EVP_PKEY_set1_RSA(salting_key.get(), salting_key_rsa.get())) {
      LOG(ERROR) << "Error setting up EVP_PKEY: " << GetOpenSSLError();
      return TRUNKS_RC_SESSION_SETUP_ERROR;
    }
    // Label for RSAES-OAEP. Defined in TPM2.0 Part1 Architecture,
    // Appendix B.10.2.
    const size_t kOaepLabelSize = 7;
    const char kOaepLabelValue[] = "SECRET\0";
    // EVP_PKEY_CTX_set0_rsa_oaep_label takes ownership so we need to malloc.
    uint8_t* oaep_label = static_cast<uint8_t*>(OPENSSL_malloc(kOaepLabelSize));
    memcpy(oaep_label, kOaepLabelValue, kOaepLabelSize);
    crypto::ScopedEVP_PKEY_CTX salt_encrypt_context(
        EVP_PKEY_CTX_new(salting_key.get(), nullptr));
    if (!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,
                                          kOaepLabelSize)) {
      LOG(ERROR) << "Error setting up salt encrypt context: "
                 << GetOpenSSLError();
      return 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*>(string_as_array(encrypted_salt)),
            &out_length, reinterpret_cast<const uint8_t*>(salt.data()),
            salt.size())) {
      LOG(ERROR) << "Error encrypting salt: " << GetOpenSSLError();
      return TRUNKS_RC_SESSION_SETUP_ERROR;
    }
    encrypted_salt->resize(out_length);
    return TPM_RC_SUCCESS;
  }
  
  }  // namespace trunks
  