| // Copyright 2019 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 <cstdint> |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/hash/sha1.h> |
| #include <base/run_loop.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <crypto/scoped_openssl_types.h> |
| #include <crypto/sha2.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/sha.h> |
| #include <tpm_manager/client/tpm_ownership_dbus_proxy.h> |
| #include <trunks/error_codes.h> |
| #include <trunks/trunks_factory_impl.h> |
| |
| #include "sealed_storage/sealed_storage.h" |
| |
| namespace { |
| |
| // Version tag at the start of serialized sealed blob. |
| enum SerializedVer : char { |
| kSerializedVer1 = 0x01, |
| kSerializedVer2 = 0x02, |
| }; |
| |
| // Magic value, sha256 of which is extended to PCRs when requested. |
| constexpr char kExtendMagic[] = "SealedStorage"; |
| |
| constexpr size_t kPolicySize = crypto::kSHA256Length; |
| constexpr size_t kPCRSize = crypto::kSHA256Length; |
| |
| // RAII version of SHA256_CTX, with auto-initialization on instantiation |
| // and auto-cleanup on leaving scope. |
| class SecureSHA256_CTX { |
| public: |
| SecureSHA256_CTX() { SHA256_Init(&ctx_); } |
| ~SecureSHA256_CTX() { OPENSSL_cleanse(&ctx_, sizeof(ctx_)); } |
| SHA256_CTX* get() { return &ctx_; } |
| |
| private: |
| SHA256_CTX ctx_; |
| }; |
| |
| // Get an 'empty policy' digest for the case when no PCR bindings are specified. |
| std::string GetEmptyPolicy() { |
| return std::string(kPolicySize, 0); |
| } |
| |
| // Get value to extend to a requested PCR. |
| std::string GetExtendValue() { |
| return crypto::SHA256HashString(kExtendMagic); |
| } |
| |
| // Get the expected initial PCR value before anything is extended to it. |
| std::string GetInitialPCRValue() { |
| return std::string(kPCRSize, 0); |
| } |
| |
| // Generate AES-256 key from Z point: key = SHA256(z.x) |
| brillo::SecureBlob GetKeyFromZ(const trunks::TPM2B_ECC_POINT& z) { |
| SecureSHA256_CTX ctx; |
| const trunks::TPM2B_ECC_PARAMETER& x = z.point.x; |
| SHA256_Update(ctx.get(), x.buffer, x.size); |
| brillo::SecureBlob key(crypto::kSHA256Length); |
| SHA256_Final(key.data(), ctx.get()); |
| return key; |
| } |
| |
| // Get a PCR0 value corresponding to one of the knwon modes. |
| std::string GetPCR0ValueForMode(const sealed_storage::BootMode& mode) { |
| std::string mode_str(std::cbegin(mode), std::cend(mode)); |
| std::string mode_digest = base::SHA1HashString(mode_str); |
| mode_digest.resize(kPCRSize); |
| return crypto::SHA256HashString(GetInitialPCRValue() + mode_digest); |
| } |
| |
| // Universal hex dump of any container with .data() and .size() |
| template <typename T> |
| std::string HexDump(const T& obj) { |
| return base::HexEncode(obj.data(), obj.size()); |
| } |
| |
| // Checks error code returned from the TPM. On error, prints to the log and |
| // returns false. On success, returns true. |
| bool CheckTpmResult(trunks::TPM_RC result, std::string op) { |
| if (result == trunks::TPM_RC_SUCCESS) { |
| return true; |
| } |
| |
| LOG(ERROR) << "Failed to " << op << ": " << trunks::GetErrorString(result); |
| return false; |
| } |
| |
| // Sends a request to the tpm_managerd, waits for a response (or an error) |
| // and fills it into the provides reply protobuf (only the error fields in |
| // case of error). |
| template <typename MethodType, typename ReplyProtoType> |
| void SendRequestAndWait(const MethodType& method, ReplyProtoType* reply_proto) { |
| auto handler = [](ReplyProtoType* target, base::RunLoop* loop, |
| const ReplyProtoType& reply) { |
| *target = reply; |
| loop->Quit(); |
| }; |
| |
| base::RunLoop loop; |
| auto callback = base::Bind(handler, reply_proto, &loop); |
| method.Run(callback); |
| loop.Run(); |
| } |
| |
| // Returns the string representing the openssl error. |
| std::string GetOpenSSLError() { |
| BIO* bio = BIO_new(BIO_s_mem()); |
| ERR_print_errors(bio); |
| char* data = NULL; |
| int data_len = BIO_get_mem_data(bio, &data); |
| std::string error_string(data, data_len); |
| BIO_free(bio); |
| return error_string; |
| } |
| |
| void ReportOpenSSLError(const std::string& op_name) { |
| LOG(ERROR) << "Failed to " << op_name; |
| VLOG(1) << "Error details: " << GetOpenSSLError(); |
| } |
| |
| // Gets policy data from serialized blob. |
| // Returns base::nullopt in case of error. |
| base::Optional<std::string> DeserializePolicyDigest( |
| std::string* serialized_data) { |
| DCHECK(serialized_data); |
| |
| uint16_t size; |
| if (trunks::Parse_uint16_t(serialized_data, &size, |
| nullptr /* value_bytes */) != |
| trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to parse policy digest size"; |
| return base::nullopt; |
| } |
| if (serialized_data->size() < size) { |
| LOG(ERROR) << "Policy digest longer than the remaining sealed data: " |
| << serialized_data->size() << " < " << size; |
| return base::nullopt; |
| } |
| |
| std::string policy_digest = serialized_data->substr(0, size); |
| serialized_data->erase(0, size); |
| if (policy_digest.size() != kPolicySize) { |
| LOG(ERROR) << "Unexpected policy digest size: " << policy_digest.size(); |
| return base::nullopt; |
| } |
| return policy_digest; |
| } |
| |
| } // namespace |
| |
| namespace sealed_storage { |
| |
| // Structure to hold the key and IV for software-based AES encryption and |
| // decryption. |
| struct Key { |
| public: |
| static const EVP_CIPHER* GetCipher() { return EVP_aes_256_cbc(); } |
| static uint16_t GetIVSize() { return EVP_CIPHER_iv_length(GetCipher()); } |
| static uint16_t GetKeySize() { return EVP_CIPHER_key_length(GetCipher()); } |
| static uint16_t GetBlockSize() { return EVP_CIPHER_block_size(GetCipher()); } |
| |
| // Initializes the structure from private and public seeds resulting from |
| // TPM-based ECDHE operation. |
| bool Init(const PrivSeeds& priv_seeds, const PubSeeds& pub_seeds); |
| |
| // Encrypts the plain data using the initialized key and IV. In case of error, |
| // returns nullopt. |
| base::Optional<Data> Encrypt(const SecretData& plain_data) const; |
| |
| // Decrypts the data using the initialized key and IV. Verifies that the |
| // resulting plaintext size matches the |expected size_|. In case of error, |
| // returns nullopt. |
| base::Optional<SecretData> Decrypt(const Data& encrypted_data) const; |
| |
| private: |
| brillo::SecureBlob key_; |
| brillo::Blob iv_; |
| uint16_t expected_size_; |
| }; |
| |
| Policy::PcrMap::value_type Policy::BootModePCR(const BootMode& mode) { |
| return {0, GetPCR0ValueForMode(mode)}; |
| } |
| |
| Policy::PcrMap::value_type Policy::UnchangedPCR(uint32_t pcr_num) { |
| return {pcr_num, GetInitialPCRValue()}; |
| } |
| |
| SealedStorage::SealedStorage(const Policy& policy, |
| trunks::TrunksFactory* trunks_factory, |
| tpm_manager::TpmOwnershipInterface* tpm_ownership) |
| : policy_(policy), |
| trunks_factory_(trunks_factory), |
| tpm_ownership_(tpm_ownership) { |
| DCHECK(trunks_factory_); |
| DCHECK(tpm_ownership_); |
| } |
| |
| SealedStorage::SealedStorage(const Policy& policy) |
| : policy_(policy), |
| dft_trunks_factory_(CreateTrunksFactory()), |
| dft_tpm_ownership_(CreateTpmOwnershipInterface()) { |
| trunks_factory_ = dft_trunks_factory_.get(); |
| tpm_ownership_ = dft_tpm_ownership_.get(); |
| DCHECK(trunks_factory_); |
| DCHECK(tpm_ownership_); |
| } |
| |
| ScopedTrunksFactory SealedStorage::CreateTrunksFactory() { |
| auto factory = std::make_unique<trunks::TrunksFactoryImpl>(); |
| if (!factory->Initialize()) { |
| LOG(ERROR) << "Failed to initialize TrunksFactory"; |
| factory.reset(nullptr); |
| } |
| return factory; |
| } |
| |
| ScopedTpmOwnership SealedStorage::CreateTpmOwnershipInterface() { |
| auto proxy = std::make_unique<tpm_manager::TpmOwnershipDBusProxy>(); |
| if (!proxy->Initialize()) { |
| LOG(ERROR) << "Failed to initialize TpmOwnershipDBusProxy"; |
| proxy.reset(nullptr); |
| } |
| return proxy; |
| } |
| |
| base::Optional<Data> SealedStorage::Seal(const SecretData& plain_data) const { |
| if (!CheckInitialized()) { |
| return base::nullopt; |
| } |
| |
| PrivSeeds priv_seeds; |
| PubSeeds pub_seeds; |
| if (!CreateEncryptionSeeds(&priv_seeds, &pub_seeds)) { |
| return base::nullopt; |
| } |
| pub_seeds.plain_size = plain_data.size(); |
| VLOG(2) << "Created encryption seeds"; |
| |
| Key key; |
| if (!key.Init(priv_seeds, pub_seeds)) { |
| return base::nullopt; |
| } |
| VLOG(2) << "Created encryption key"; |
| |
| auto encrypted_data = key.Encrypt(plain_data); |
| if (!encrypted_data) { |
| return base::nullopt; |
| } |
| VLOG(2) << "Encrypted data"; |
| |
| return SerializeSealedBlob(pub_seeds, encrypted_data.value()); |
| } |
| |
| base::Optional<SecretData> SealedStorage::Unseal( |
| const Data& sealed_data) const { |
| if (!CheckInitialized()) { |
| return base::nullopt; |
| } |
| |
| PubSeeds pub_seeds; |
| Data encrypted_data; |
| if (!DeserializeSealedBlob(sealed_data, &pub_seeds, &encrypted_data)) { |
| return base::nullopt; |
| } |
| VLOG(2) << "Deserialized sealed blob"; |
| |
| PrivSeeds priv_seeds; |
| if (!RestoreEncryptionSeeds(pub_seeds, &priv_seeds)) { |
| return base::nullopt; |
| } |
| VLOG(2) << "Restored encryption seeds"; |
| |
| Key key; |
| if (!key.Init(priv_seeds, pub_seeds)) { |
| return base::nullopt; |
| } |
| VLOG(2) << "Created encryption key"; |
| |
| return key.Decrypt(encrypted_data); |
| } |
| |
| bool SealedStorage::ExtendPCR(uint32_t pcr_num) const { |
| if (!CheckInitialized()) { |
| return false; |
| } |
| auto tpm_utility = trunks_factory_->GetTpmUtility(); |
| |
| return CheckTpmResult( |
| tpm_utility->ExtendPCR(pcr_num, GetExtendValue(), nullptr), "extend PCR"); |
| } |
| |
| base::Optional<bool> SealedStorage::CheckState() const { |
| if (!CheckInitialized()) { |
| return base::nullopt; |
| } |
| auto tpm_utility = trunks_factory_->GetTpmUtility(); |
| |
| for (const auto& pcr_val : policy_.pcr_map) { |
| if (pcr_val.second.empty()) { |
| continue; |
| } |
| std::string value; |
| auto result = tpm_utility->ReadPCR(pcr_val.first, &value); |
| if (!CheckTpmResult(result, "read PCR")) { |
| return base::nullopt; |
| } |
| if (value != pcr_val.second) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool SealedStorage::PrepareSealingKeyObject( |
| const base::Optional<std::string>& expected_digest, |
| trunks::TPM_HANDLE* key_handle, |
| std::string* key_name, |
| std::string* resulting_digest) const { |
| CHECK(key_handle); |
| CHECK(key_name); |
| |
| std::string endorsement_password; |
| if (!GetEndorsementPassword(&endorsement_password)) { |
| return false; |
| } |
| VLOG(2) << "Obtained endorsement password"; |
| |
| std::string policy_digest; |
| if (!policy_.pcr_map.empty()) { |
| auto tpm_utility = trunks_factory_->GetTpmUtility(); |
| auto result = tpm_utility->GetPolicyDigestForPcrValues( |
| policy_.pcr_map, false /* use_auth_value */, &policy_digest); |
| if (!CheckTpmResult(result, "calculate policy")) { |
| return false; |
| } |
| } else { |
| policy_digest = GetEmptyPolicy(); |
| } |
| VLOG(2) << "Created policy digest: " << HexDump(policy_digest); |
| if (expected_digest.has_value() && expected_digest.value() != policy_digest) { |
| VLOG(2) << "Expected policy digest: " << HexDump(expected_digest.value()); |
| LOG(ERROR) << "Policy mismatch"; |
| return false; |
| } |
| if (resulting_digest) { |
| *resulting_digest = policy_digest; |
| } |
| |
| trunks::TPMS_SENSITIVE_CREATE sensitive = {}; |
| memset(&sensitive, 0, sizeof(sensitive)); |
| sensitive.user_auth = trunks::Make_TPM2B_DIGEST(""); |
| sensitive.data = trunks::Make_TPM2B_SENSITIVE_DATA(""); |
| |
| trunks::TPMT_PUBLIC public_area = {}; |
| memset(&public_area, 0, sizeof(public_area)); |
| public_area.type = trunks::TPM_ALG_ECC; |
| public_area.name_alg = trunks::TPM_ALG_SHA256; |
| public_area.auth_policy = trunks::Make_TPM2B_DIGEST(policy_digest); |
| public_area.object_attributes = |
| trunks::kFixedTPM | trunks::kFixedParent | trunks::kSensitiveDataOrigin | |
| trunks::kAdminWithPolicy | trunks::kDecrypt | trunks::kNoDA; |
| public_area.parameters.ecc_detail.symmetric.algorithm = trunks::TPM_ALG_NULL; |
| public_area.parameters.ecc_detail.scheme.scheme = trunks::TPM_ALG_NULL; |
| public_area.parameters.ecc_detail.curve_id = trunks::TPM_ECC_NIST_P256; |
| public_area.parameters.ecc_detail.kdf.scheme = trunks::TPM_ALG_NULL; |
| public_area.unique.ecc.x = trunks::Make_TPM2B_ECC_PARAMETER(""); |
| public_area.unique.ecc.y = trunks::Make_TPM2B_ECC_PARAMETER(""); |
| |
| auto endorsement_auth = |
| trunks_factory_->GetPasswordAuthorization(endorsement_password); |
| std::string rh_endorsement_name; |
| trunks::Serialize_TPM_HANDLE(trunks::TPM_RH_ENDORSEMENT, |
| &rh_endorsement_name); |
| |
| return CreatePrimaryKeyObject( |
| "sealing key object", trunks::TPM_RH_ENDORSEMENT, rh_endorsement_name, |
| sensitive, public_area, endorsement_auth.get(), key_handle, key_name); |
| } |
| |
| bool SealedStorage::GetEndorsementPassword(std::string* password) const { |
| CHECK(password); |
| |
| if (!tpm_ownership_) { |
| LOG(ERROR) << "TpmOwnershipInterface is not initialized"; |
| return false; |
| } |
| |
| tpm_manager::GetTpmStatusRequest request; |
| auto method = base::Bind(&tpm_manager::TpmOwnershipInterface::GetTpmStatus, |
| base::Unretained(tpm_ownership_), request); |
| tpm_manager::GetTpmStatusReply tpm_status; |
| SendRequestAndWait(method, &tpm_status); |
| if (tpm_status.status() != tpm_manager::STATUS_SUCCESS) { |
| LOG(ERROR) << "Failed to get TpmStatus: " << tpm_status.status(); |
| return false; |
| } |
| *password = tpm_status.local_data().endorsement_password(); |
| return true; |
| } |
| |
| bool SealedStorage::CreatePrimaryKeyObject( |
| const std::string& object_descr, |
| trunks::TPM_HANDLE parent_handle, |
| const std::string& parent_name, |
| const trunks::TPMS_SENSITIVE_CREATE& sensitive, |
| const trunks::TPMT_PUBLIC& public_area, |
| trunks::AuthorizationDelegate* auth_delegate, |
| trunks::TPM_HANDLE* object_handle, |
| std::string* object_name) const { |
| DCHECK(object_handle); |
| DCHECK(object_name); |
| |
| trunks::TPML_PCR_SELECTION creation_pcrs = {}; |
| trunks::TPM2B_PUBLIC out_public = {}; |
| trunks::TPM2B_CREATION_DATA out_creation_data = {}; |
| trunks::TPM2B_DIGEST out_creation_hash = {}; |
| trunks::TPMT_TK_CREATION out_creation_ticket = {}; |
| |
| trunks::TPM2B_NAME out_name = {}; |
| auto result = trunks_factory_->GetTpm()->CreatePrimarySync( |
| parent_handle, parent_name, |
| trunks::Make_TPM2B_SENSITIVE_CREATE(sensitive), |
| trunks::Make_TPM2B_PUBLIC(public_area), |
| trunks::Make_TPM2B_DATA("") /* outside_info */, creation_pcrs, |
| object_handle, &out_public, &out_creation_data, &out_creation_hash, |
| &out_creation_ticket, &out_name, auth_delegate); |
| if (!CheckTpmResult(result, std::string("create ") + object_descr)) { |
| return false; |
| } |
| *object_name = trunks::StringFrom_TPM2B_NAME(out_name); |
| VLOG(2) << "Created " << object_descr << ": " << *object_handle; |
| |
| return true; |
| } |
| |
| bool SealedStorage::CheckInitialized() const { |
| if (!trunks_factory_ || !trunks_factory_->GetTpm()) { |
| LOG(ERROR) << "TrunksFactory is not initialized"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool SealedStorage::CreateEncryptionSeeds(PrivSeeds* priv_seeds, |
| PubSeeds* pub_seeds) const { |
| DCHECK(priv_seeds); |
| DCHECK(pub_seeds); |
| |
| trunks::TPM_HANDLE key_handle; |
| std::string key_name; |
| std::string resulting_digest; |
| if (!PrepareSealingKeyObject(base::nullopt, &key_handle, &key_name, |
| &resulting_digest)) { |
| return false; |
| } |
| pub_seeds->policy_digest = std::move(resulting_digest); |
| |
| auto result = trunks_factory_->GetTpm()->ECDH_KeyGenSync( |
| key_handle, key_name, &priv_seeds->z_point, &pub_seeds->pub_point, |
| nullptr /* authorization_delegate */); |
| if (!CheckTpmResult(result, "generate ECDH keypair")) { |
| return false; |
| } |
| VLOG(2) << "Generated ECDH keypair"; |
| |
| result = trunks_factory_->GetTpm()->GetRandomSync( |
| Key::GetIVSize(), &pub_seeds->iv, nullptr /* authorization_delegate */); |
| if (!CheckTpmResult(result, "generate IV")) { |
| return false; |
| } |
| VLOG(2) << "Generated IV"; |
| |
| return true; |
| } |
| |
| bool SealedStorage::RestoreEncryptionSeeds(const PubSeeds& pub_seeds, |
| PrivSeeds* priv_seeds) const { |
| DCHECK(priv_seeds); |
| |
| trunks::TPM_HANDLE key_handle; |
| std::string key_name; |
| if (!PrepareSealingKeyObject(pub_seeds.policy_digest, &key_handle, &key_name, |
| nullptr)) { |
| return false; |
| } |
| |
| auto policy_session = trunks_factory_->GetPolicySession(); |
| auto result = policy_session->StartUnboundSession(true, false); |
| if (!CheckTpmResult(result, "start policy session")) { |
| return false; |
| } |
| |
| if (!policy_.pcr_map.empty()) { |
| result = policy_session->PolicyPCR(policy_.pcr_map); |
| if (!CheckTpmResult(result, "restrict policy to PCRs")) { |
| return false; |
| } |
| } |
| VLOG(2) << "Created policy session"; |
| |
| result = trunks_factory_->GetTpm()->ECDH_ZGenSync( |
| key_handle, key_name, pub_seeds.pub_point, &priv_seeds->z_point, |
| policy_session->GetDelegate()); |
| if (!CheckTpmResult(result, "restore ECDH Z point")) { |
| return false; |
| } |
| VLOG(2) << "Restored ECDH Z point"; |
| |
| return true; |
| } |
| |
| base::Optional<Data> SealedStorage::SerializeSealedBlob( |
| const PubSeeds& pub_seeds, const Data& encrypted_data) const { |
| std::string serialized_data(1, kSerializedVer2); |
| |
| trunks::Serialize_uint16_t(pub_seeds.plain_size, &serialized_data); |
| |
| if (!pub_seeds.policy_digest.has_value()) { |
| LOG(ERROR) << "Missing policy digest during serialization"; |
| return base::nullopt; |
| } |
| if (pub_seeds.policy_digest->size() != kPolicySize) { |
| LOG(ERROR) << "Unexpected policy digest size during serialization: " |
| << pub_seeds.policy_digest->size(); |
| return base::nullopt; |
| } |
| trunks::Serialize_uint16_t(pub_seeds.policy_digest->size(), &serialized_data); |
| serialized_data.append(pub_seeds.policy_digest->begin(), |
| pub_seeds.policy_digest->end()); |
| |
| if (trunks::Serialize_TPM2B_ECC_POINT( |
| pub_seeds.pub_point, &serialized_data) != trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to serialize public point"; |
| return base::nullopt; |
| } |
| if (trunks::Serialize_TPM2B_DIGEST(pub_seeds.iv, &serialized_data) != |
| trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to serialize IV"; |
| return base::nullopt; |
| } |
| |
| if (encrypted_data.size() > UINT16_MAX) { |
| LOG(ERROR) << "Too long encrypted data: " << encrypted_data.size(); |
| return base::nullopt; |
| } |
| uint16_t size = encrypted_data.size(); |
| trunks::Serialize_uint16_t(size, &serialized_data); |
| serialized_data.append(encrypted_data.begin(), encrypted_data.end()); |
| return Data(serialized_data.begin(), serialized_data.end()); |
| } |
| |
| bool SealedStorage::DeserializeSealedBlob(const Data& sealed_data, |
| PubSeeds* pub_seeds, |
| Data* encrypted_data) const { |
| DCHECK(pub_seeds); |
| DCHECK(encrypted_data); |
| |
| std::string serialized_data(sealed_data.begin(), sealed_data.end()); |
| if (serialized_data.empty()) { |
| LOG(ERROR) << "Empty sealed data"; |
| return false; |
| } |
| |
| uint8_t version = serialized_data[0]; |
| serialized_data.erase(0, 1); |
| switch (version) { |
| case kSerializedVer1: |
| pub_seeds->plain_size = plain_size_for_v1_; |
| pub_seeds->policy_digest.reset(); |
| break; |
| case kSerializedVer2: |
| if (trunks::Parse_uint16_t(&serialized_data, &pub_seeds->plain_size, |
| nullptr /* value_bytes */) != |
| trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to parse plain data size"; |
| return false; |
| } |
| pub_seeds->policy_digest = DeserializePolicyDigest(&serialized_data); |
| if (!pub_seeds->policy_digest.has_value()) { |
| LOG(ERROR) << "Failed to parse policy digest"; |
| return false; |
| } |
| break; |
| default: |
| LOG(ERROR) << "Unexpected serialized version: " << version; |
| return false; |
| } |
| |
| if (trunks::Parse_TPM2B_ECC_POINT(&serialized_data, &pub_seeds->pub_point, |
| nullptr /* value_bytes */) != |
| trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to parse public point"; |
| return false; |
| } |
| if (trunks::Parse_TPM2B_DIGEST(&serialized_data, &pub_seeds->iv, |
| nullptr /* value_bytes */) != |
| trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to parse IV"; |
| return false; |
| } |
| |
| uint16_t size; |
| if (trunks::Parse_uint16_t(&serialized_data, &size, |
| nullptr /* value_bytes */) != |
| trunks::TPM_RC_SUCCESS) { |
| LOG(ERROR) << "Failed to parse encrypted data size"; |
| return false; |
| } |
| if (serialized_data.size() != size) { |
| LOG(ERROR) << "Unexpected encrypted data size: " << serialized_data.size() |
| << " != " << size; |
| return false; |
| } |
| encrypted_data->assign(serialized_data.begin(), serialized_data.end()); |
| return true; |
| } |
| |
| bool Key::Init(const PrivSeeds& priv_seeds, const PubSeeds& pub_seeds) { |
| if (pub_seeds.iv.size != GetIVSize()) { |
| LOG(ERROR) << "Unexpected input IV size: " << pub_seeds.iv.size; |
| return false; |
| } |
| iv_.assign(pub_seeds.iv.buffer, pub_seeds.iv.buffer + pub_seeds.iv.size); |
| if (iv_.size() != GetIVSize()) { |
| LOG(ERROR) << "Unexpected IV size: " << iv_.size(); |
| return false; |
| } |
| key_ = GetKeyFromZ(priv_seeds.z_point); |
| if (key_.size() != GetKeySize()) { |
| LOG(ERROR) << "Unexpected key size: " << key_.size(); |
| return false; |
| } |
| expected_size_ = pub_seeds.plain_size; |
| return true; |
| } |
| |
| base::Optional<Data> Key::Encrypt(const SecretData& plain_data) const { |
| if (plain_data.size() != expected_size_) { |
| LOG(ERROR) << "Unexpected plain data size: " << plain_data.size() |
| << " != " << expected_size_; |
| return base::nullopt; |
| } |
| |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (!ctx) { |
| ReportOpenSSLError("allocate encryption context"); |
| return base::nullopt; |
| } |
| if (!EVP_EncryptInit_ex(ctx.get(), GetCipher(), nullptr, key_.data(), |
| iv_.data())) { |
| ReportOpenSSLError("initialize encryption context"); |
| return base::nullopt; |
| } |
| |
| const size_t max_encrypted_size = plain_data.size() + GetBlockSize(); |
| Data encrypted_data(max_encrypted_size); |
| int encrypted_size; |
| if (!EVP_EncryptUpdate( |
| ctx.get(), static_cast<unsigned char*>(encrypted_data.data()), |
| &encrypted_size, plain_data.data(), plain_data.size())) { |
| ReportOpenSSLError("encrypt"); |
| return base::nullopt; |
| } |
| if (encrypted_size < 0 || encrypted_size > max_encrypted_size) { |
| LOG(ERROR) << "Unexpected encrypted data size: " << encrypted_size << " > " |
| << max_encrypted_size; |
| return base::nullopt; |
| } |
| |
| unsigned char* final_buf = nullptr; |
| if (encrypted_size < max_encrypted_size) { |
| final_buf = |
| static_cast<unsigned char*>(encrypted_data.data() + encrypted_size); |
| } |
| int encrypted_size_final = 0; |
| if (!EVP_EncryptFinal_ex(ctx.get(), final_buf, &encrypted_size_final)) { |
| ReportOpenSSLError("finalize encryption"); |
| return base::nullopt; |
| } |
| if (encrypted_size_final < 0) { |
| LOG(ERROR) << "Unexpected size for final encryption block: " |
| << encrypted_size_final; |
| return base::nullopt; |
| } |
| encrypted_size += encrypted_size_final; |
| if (encrypted_size > max_encrypted_size) { |
| LOG(ERROR) << "Unexpected encrypted data size after finalization: " |
| << encrypted_size << " > " << max_encrypted_size; |
| return base::nullopt; |
| } |
| encrypted_data.resize(encrypted_size); |
| |
| return encrypted_data; |
| } |
| |
| base::Optional<SecretData> Key::Decrypt(const Data& encrypted_data) const { |
| crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new()); |
| if (!ctx) { |
| ReportOpenSSLError("allocate decryption context"); |
| return base::nullopt; |
| } |
| |
| if (!EVP_DecryptInit_ex(ctx.get(), GetCipher(), nullptr, key_.data(), |
| iv_.data())) { |
| ReportOpenSSLError("initialize decryption context"); |
| return base::nullopt; |
| } |
| |
| const size_t max_decrypted_size = encrypted_data.size() + GetBlockSize(); |
| if (max_decrypted_size < expected_size_) { |
| LOG(ERROR) << "Not enough data for expected size: " << encrypted_data.size() |
| << " leads to max " << max_decrypted_size << " < " |
| << expected_size_; |
| return base::nullopt; |
| } |
| SecretData decrypted_data(max_decrypted_size); |
| int decrypted_size; |
| if (!EVP_DecryptUpdate( |
| ctx.get(), static_cast<unsigned char*>(decrypted_data.data()), |
| &decrypted_size, encrypted_data.data(), encrypted_data.size())) { |
| ReportOpenSSLError("decrypt"); |
| return base::nullopt; |
| } |
| if (decrypted_size < 0 || decrypted_size > max_decrypted_size) { |
| LOG(ERROR) << "Unexpected decrypted data size: " << decrypted_size << " > " |
| << max_decrypted_size; |
| return base::nullopt; |
| } |
| |
| unsigned char* final_buf = nullptr; |
| if (decrypted_size < max_decrypted_size) { |
| final_buf = |
| static_cast<unsigned char*>(decrypted_data.data() + decrypted_size); |
| } |
| int decrypted_size_final = 0; |
| if (!EVP_DecryptFinal_ex(ctx.get(), final_buf, &decrypted_size_final)) { |
| ReportOpenSSLError("finalize decryption"); |
| return base::nullopt; |
| } |
| if (decrypted_size_final < 0) { |
| LOG(ERROR) << "Unexpected size for final decryption block: " |
| << decrypted_size_final; |
| return base::nullopt; |
| } |
| decrypted_size += decrypted_size_final; |
| if (decrypted_size != expected_size_) { |
| LOG(ERROR) << "Unexpected decrypted data size: " << decrypted_size |
| << " != " << expected_size_; |
| return base::nullopt; |
| } |
| decrypted_data.resize(decrypted_size); |
| |
| return decrypted_data; |
| } |
| |
| } // namespace sealed_storage |