| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "device_management/install_attributes/lockbox.h" | |
| #include <arpa/inet.h> | |
| #include <limits.h> | |
| #include <openssl/sha.h> | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/check.h> | |
| #include <base/check_op.h> | |
| #include <base/files/file_path.h> | |
| #include <base/logging.h> | |
| #include <base/notreached.h> | |
| #include <base/strings/string_split.h> | |
| #include <base/sys_byteorder.h> | |
| #include <base/threading/platform_thread.h> | |
| #include <base/time/time.h> | |
| #include <brillo/secure_blob.h> | |
| #include <libhwsec/status.h> | |
| #include <libhwsec-foundation/crypto/secure_blob_util.h> | |
| #include <libhwsec-foundation/crypto/sha.h> | |
| #include <libstorage/platform/platform.h> | |
| using base::FilePath; | |
| using brillo::SecureBlob; | |
| using hwsec::TPMErrorBase; | |
| using hwsec_foundation::CreateSecureRandomBlob; | |
| using hwsec_foundation::SecureBlobToHex; | |
| using hwsec_foundation::Sha256; | |
| namespace device_management { | |
| std::ostream& operator<<(std::ostream& out, LockboxError error) { | |
| return out << static_cast<int>(error); | |
| } | |
| Lockbox::Lockbox(const hwsec::CryptohomeFrontend* hwsec, hwsec::Space space) | |
| : hwsec_(hwsec), space_(space) { | |
| CHECK(hwsec_); | |
| } | |
| Lockbox::~Lockbox() {} | |
| bool Lockbox::Reset(LockboxError* error) { | |
| uint32_t nvram_bytes = LockboxContents::kNvramSize; | |
| if (hwsec::Status status = hwsec_->PrepareSpace(space_, nvram_bytes); | |
| !status.ok()) { | |
| LOG(ERROR) << "Failed to prepare lockbox space: " << status; | |
| *error = LockboxError::kTpmError; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool Lockbox::Store(const brillo::Blob& blob, LockboxError* error) { | |
| // Construct a LockboxContents instance. | |
| std::unique_ptr<LockboxContents> contents = LockboxContents::New(); | |
| // Create the random key material. | |
| brillo::SecureBlob key_material = | |
| CreateSecureRandomBlob(contents->key_material_size()); | |
| brillo::SecureBlob nvram_blob; | |
| if (!contents->SetKeyMaterial(key_material) || !contents->Protect(blob) || | |
| !contents->Encode(&nvram_blob)) { | |
| LOG(ERROR) << "Failed to set up lockbox contents."; | |
| *error = LockboxError::kNvramInvalid; | |
| return false; | |
| } | |
| // Write the hash to nvram | |
| if (hwsec::Status status = hwsec_->StoreSpace( | |
| space_, brillo::Blob(nvram_blob.begin(), nvram_blob.end())); | |
| !status.ok()) { | |
| LOG(ERROR) << "Failed to write lockbox space: " << status; | |
| *error = LockboxError::kTpmError; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // static | |
| std::unique_ptr<LockboxContents> LockboxContents::New() { | |
| std::unique_ptr<LockboxContents> result(new LockboxContents()); | |
| result->key_material_.resize(kKeyMaterialSize); | |
| return result; | |
| } | |
| bool LockboxContents::Decode(const brillo::SecureBlob& nvram_data) { | |
| // Reject data of incorrect size. | |
| if (nvram_data.size() != kNvramSize) { | |
| return false; | |
| } | |
| brillo::SecureBlob::const_iterator cursor = nvram_data.begin(); | |
| // Extract the expected data size from the NVRAM. For historic reasons, this | |
| // is encoded in reverse host byte order (!). | |
| uint32_t reversed_size; | |
| uint8_t* reversed_size_ptr = reinterpret_cast<uint8_t*>(&reversed_size); | |
| std::copy(cursor, cursor + sizeof(reversed_size), reversed_size_ptr); | |
| cursor += sizeof(reversed_size); | |
| size_ = base::ByteSwap(reversed_size); | |
| // Grab the flags. | |
| flags_ = *cursor++; | |
| // Grab the key material. | |
| key_material_.assign(cursor, cursor + key_material_size()); | |
| cursor += key_material_size(); | |
| // Grab the hash. | |
| std::copy(cursor, cursor + sizeof(hash_), hash_); | |
| cursor += sizeof(hash_); | |
| // Per the checks at function entry we should have exactly reached the end. | |
| CHECK(cursor == nvram_data.end()); | |
| return true; | |
| } | |
| bool LockboxContents::Encode(brillo::SecureBlob* blob) { | |
| // Encode the data size. For historic reasons, this is encoded in reverse host | |
| // byte order (!). | |
| uint32_t reversed_size = base::ByteSwap(size_); | |
| uint8_t* reversed_size_ptr = reinterpret_cast<uint8_t*>(&reversed_size); | |
| blob->insert(blob->end(), reversed_size_ptr, | |
| reversed_size_ptr + sizeof(reversed_size)); | |
| // Append the flags byte. | |
| blob->push_back(flags_); | |
| // Append the key material. | |
| blob->insert(blob->end(), std::begin(key_material_), std::end(key_material_)); | |
| // Append the hash. | |
| blob->insert(blob->end(), std::begin(hash_), std::end(hash_)); | |
| return true; | |
| } | |
| bool LockboxContents::SetKeyMaterial(const brillo::SecureBlob& key_material) { | |
| if (key_material.size() != key_material_size()) { | |
| return false; | |
| } | |
| key_material_ = key_material; | |
| return true; | |
| } | |
| bool LockboxContents::Protect(const brillo::Blob& blob) { | |
| brillo::SecureBlob salty_blob(blob); | |
| salty_blob.insert(salty_blob.end(), key_material_.begin(), | |
| key_material_.end()); | |
| SecureBlob salty_blob_hash = Sha256(salty_blob); | |
| CHECK_EQ(sizeof(hash_), salty_blob_hash.size()); | |
| std::copy(salty_blob_hash.begin(), salty_blob_hash.end(), std::begin(hash_)); | |
| size_ = blob.size(); | |
| return true; | |
| } | |
| LockboxContents::VerificationResult LockboxContents::Verify( | |
| const brillo::Blob& blob) { | |
| // Make sure that the file size matches what was stored in nvram. | |
| if (blob.size() != size_) { | |
| LOG(ERROR) << "Verify() expected " << size_ << " , but received " | |
| << blob.size() << " bytes."; | |
| return VerificationResult::kSizeMismatch; | |
| } | |
| // Compute the hash of the blob to verify. | |
| brillo::SecureBlob salty_blob(blob); | |
| salty_blob.insert(salty_blob.end(), key_material_.begin(), | |
| key_material_.end()); | |
| SecureBlob salty_blob_hash = Sha256(salty_blob); | |
| // Validate the blob hash versus the stored hash. | |
| if (sizeof(hash_) != salty_blob_hash.size() || | |
| brillo::SecureMemcmp(hash_, salty_blob_hash.data(), sizeof(hash_))) { | |
| LOG(ERROR) << "Verify() hash mismatch!"; | |
| return VerificationResult::kHashMismatch; | |
| } | |
| return VerificationResult::kValid; | |
| } | |
| } // namespace device_management |