| // Copyright (c) 2012 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 "chaps/opencryptoki_importer.h" |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| |
| #include "chaps/chaps_factory.h" |
| #include "chaps/chaps_utility.h" |
| #include "chaps/object.h" |
| #include "chaps/object_pool.h" |
| #include "chaps/tpm_utility.h" |
| #include "pkcs11/cryptoki.h" |
| |
| using base::FilePath; |
| using brillo::SecureBlob; |
| using std::map; |
| using std::string; |
| using std::vector; |
| |
| namespace { |
| |
| // Extracts a 32-bit integer by reinterpreting bytes. |
| uint32_t ExtractUint32(const void* data) { |
| uint32_t value; |
| memcpy(&value, data, sizeof(value)); |
| return value; |
| } |
| |
| } // namespace |
| |
| namespace chaps { |
| |
| OpencryptokiImporter::OpencryptokiImporter(int slot, |
| const FilePath& path, |
| TPMUtility* tpm, |
| ChapsFactory* factory) |
| : slot_(slot), |
| path_(path), |
| tpm_(tpm), |
| factory_(factory), |
| private_root_key_(0), |
| private_leaf_key_(0), |
| public_root_key_(0), |
| public_leaf_key_(0) {} |
| |
| OpencryptokiImporter::~OpencryptokiImporter() {} |
| |
| bool OpencryptokiImporter::ImportObjects(ObjectPool* object_pool) { |
| const char kOpencryptokiDir[] = ".tpm"; |
| const char kOpencryptokiObjectDir[] = "TOK_OBJ"; |
| const char kOpencryptokiMasterKey[] = "MK_PRIVATE"; |
| const char kOpencryptokiObjectIndex[] = "OBJ.IDX"; |
| |
| LOG(INFO) << "Importing opencryptoki objects."; |
| FilePath base_path = path_.DirName().Append(kOpencryptokiDir); |
| FilePath object_path = base_path.Append(kOpencryptokiObjectDir); |
| FilePath index_path = object_path.Append(kOpencryptokiObjectIndex); |
| master_key_path_ = base_path.Append(kOpencryptokiMasterKey); |
| if (!base::PathExists(index_path)) { |
| LOG(WARNING) << "Did not find any opencryptoki objects to import."; |
| return true; |
| } |
| string index; |
| if (!base::ReadFileToString(index_path, &index)) { |
| LOG(ERROR) << "Failed to read object index."; |
| return false; |
| } |
| vector<string> object_files = |
| base::SplitString(index, base::kWhitespaceASCII, base::KEEP_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| vector<AttributeMap> ready_for_import; |
| LOG(INFO) << "Found " << object_files.size() << " object files."; |
| // Try to read and process each file listed in the index file. If a problem |
| // occurs just move on the next one. |
| for (size_t i = 0; i < object_files.size(); ++i) { |
| string object_file_content; |
| if (!base::ReadFileToString(object_path.Append(object_files[i]), |
| &object_file_content)) { |
| LOG(WARNING) << "Failed to read object file: " << object_files[i]; |
| continue; |
| } |
| bool is_encrypted = false; |
| string flat_object; |
| if (!ExtractObjectData(object_file_content, &is_encrypted, &flat_object)) { |
| LOG(WARNING) << "Failed to parse object file: " << object_files[i]; |
| continue; |
| } |
| if (is_encrypted) { |
| // We can't process encrypted files until we have the master key. |
| encrypted_objects_[object_files[i]] = flat_object; |
| continue; |
| } |
| AttributeMap attributes; |
| if (!UnflattenObject(flat_object, object_files[i], false, &attributes)) { |
| LOG(WARNING) << "Failed to parse object attributes: " << object_files[i]; |
| continue; |
| } |
| if (!ProcessInternalObject(attributes, object_pool)) { |
| // This is an ordinary object. |
| ready_for_import.push_back(attributes); |
| } |
| } |
| if (encrypted_objects_.size() == 0 && ready_for_import.size() == 0) { |
| // Nothing to import, our job is done. |
| LOG(INFO) << "Did not find any opencryptoki objects to import."; |
| return true; |
| } |
| LOG(INFO) << "Found objects: " << encrypted_objects_.size() << " private, " |
| << ready_for_import.size() << " public."; |
| // Objects that have opencryptoki internal attributes such as tpm-protected |
| // blobs need to be moved to the chaps format. |
| int num_imported = 0; |
| for (size_t i = 0; i < ready_for_import.size(); ++i) { |
| if (IsPrivateKey(ready_for_import[i])) { |
| // Private keys need authorization data decrypted which requires the TPM. |
| // Queue up the object for later processing. |
| unflattened_objects_.push_back(ready_for_import[i]); |
| continue; |
| } |
| Object* object = NULL; |
| if (!CreateObjectInstance(ready_for_import[i], &object)) { |
| LOG(WARNING) << "Failed to create an object instance."; |
| continue; |
| } |
| if (object_pool->Import(object) == ObjectPool::Result::Success) |
| ++num_imported; |
| } |
| LOG(INFO) << "Imported: " << num_imported << "; Pending: " |
| << encrypted_objects_.size() + unflattened_objects_.size(); |
| return true; |
| } |
| |
| bool OpencryptokiImporter::FinishImportAsync(ObjectPool* object_pool) { |
| // If there are any encrypted objects, now is the time to decrypt them. |
| if (!DecryptPendingObjects()) { |
| LOG(WARNING) << "Failed to decrypt encrypted objects. Only public objects" |
| << " can be imported."; |
| } |
| // Objects that have opencryptoki internal attributes such as tpm-protected |
| // blobs need to be moved to the chaps format. |
| int num_imported = 0; |
| for (size_t i = 0; i < unflattened_objects_.size(); ++i) { |
| if (!ConvertToChapsFormat(&unflattened_objects_[i])) { |
| LOG(WARNING) << "Failed to convert an object to Chaps format."; |
| continue; |
| } |
| Object* object = NULL; |
| if (!CreateObjectInstance(unflattened_objects_[i], &object)) { |
| LOG(WARNING) << "Failed to create an object instance."; |
| continue; |
| } |
| if (object_pool->Import(object) == ObjectPool::Result::Success) |
| ++num_imported; |
| } |
| LOG(INFO) << "Finished importing " << num_imported << " pending objects."; |
| return true; |
| } |
| |
| bool OpencryptokiImporter::ExtractObjectData(const string& object_file_content, |
| bool* is_encrypted, |
| string* object_data) { |
| // An object file has a header formatted as follows: |
| // * Total Length - 4 bytes |
| // * Private Indicator - 1 byte |
| // * Object Data - All remaining bytes. |
| if (object_file_content.length() < 5) |
| return false; |
| size_t total_length = ExtractUint32(object_file_content.data()); |
| if (total_length != object_file_content.length()) |
| return false; |
| *is_encrypted = (0 != object_file_content[4]); |
| *object_data = object_file_content.substr(5); |
| return true; |
| } |
| |
| bool OpencryptokiImporter::UnflattenObject(const string& object_data, |
| const string& object_name, |
| bool is_encrypted, |
| AttributeMap* attributes) { |
| // A flattened object is laid out as follows: |
| // * Object Class - 4 bytes (ignored). |
| // * Number of Attributes - 4 bytes. |
| // * Object Name - 8 bytes. |
| // * Attribute List - No padding between items, each item as follows: |
| // ** CK_ATTRIBUTE with 32-bit fields - 12 bytes. |
| // ** CK_ATTRIBUTE::ulValueLen bytes of data. |
| if (object_data.length() < 16) |
| return false; |
| // If this exact number of attributes cannot be extracted the object will be |
| // considered corrupted. |
| int num_attrs = ExtractUint32(&object_data[4]); |
| // This is not needed but we'll consider the object corrupted if it doesn't |
| // match the file name from where this object data was extracted. |
| string stored_object_name(object_data.substr(8, 8)); |
| if (stored_object_name != object_name) { |
| LOG(ERROR) << "Object name mismatch: " << object_name << ", " |
| << stored_object_name; |
| return false; |
| } |
| size_t pos = 16; |
| for (int i = 0; i < num_attrs; ++i) { |
| if (object_data.size() < pos + 12) |
| return false; |
| CK_ATTRIBUTE_TYPE type = ExtractUint32(&object_data[pos]); |
| size_t length = ExtractUint32(&object_data[pos + 8]); |
| pos += 12; |
| if (object_data.size() < pos + length) |
| return false; |
| string data = object_data.substr(pos, length); |
| pos += length; |
| if (type == CKA_PRIVATE) { |
| if (data.size() < 1) |
| return false; |
| bool is_private = (data[0] != CK_FALSE); |
| if (is_encrypted != is_private) { |
| LOG(ERROR) << "Object privacy mismatch."; |
| return false; |
| } |
| } |
| (*attributes)[type] = data; |
| } |
| return true; |
| } |
| |
| bool OpencryptokiImporter::ProcessInternalObject(const AttributeMap& attributes, |
| ObjectPool* object_pool) { |
| const CK_ATTRIBUTE_TYPE kOpencryptokiHidden = CKA_VENDOR_DEFINED + 0x01000000; |
| const CK_ATTRIBUTE_TYPE kOpencryptokiOpaque = CKA_VENDOR_DEFINED + 1; |
| const char kPrivateRootKeyID[] = "PRIVATE ROOT KEY"; |
| const char kPrivateLeafKeyID[] = "PRIVATE LEAF KEY"; |
| const char kPublicRootKeyID[] = "PUBLIC ROOT KEY"; |
| const char kPublicLeafKeyID[] = "PUBLIC LEAF KEY"; |
| |
| // The primary indicator we use to determine if this object is an internal |
| // object is the opencryptoki hidden attribute (aka CKA_HIDDEN). |
| AttributeMap::const_iterator it = attributes.find(kOpencryptokiHidden); |
| if (it == attributes.end() || !it->second[0]) |
| return false; |
| |
| // From here on we will return 'true' even if we fail to import the object |
| // because we don't want an internal object to get treated as an ordinary |
| // object. The public key objects are not useful so we'll discard them. From |
| // the private key objects we'll extract the TPM-wrapped blobs. |
| it = attributes.find(CKA_CLASS); |
| if (it == attributes.end() || it->second.size() != 4) |
| return true; |
| CK_OBJECT_CLASS object_class = ExtractUint32(it->second.data()); |
| if (object_class != CKO_PRIVATE_KEY) |
| return true; |
| // Extract the TPM-wrapped blob. |
| it = attributes.find(kOpencryptokiOpaque); |
| if (it == attributes.end()) |
| return true; |
| string blob = it->second; |
| // Extract the ID so we can determine which object this is. If we don't |
| // recognize the ID then the object will be discarded. |
| it = attributes.find(CKA_ID); |
| if (it == attributes.end()) |
| return true; |
| string id = it->second; |
| if (id == kPrivateRootKeyID) { |
| if (!object_pool->SetInternalBlob(kLegacyPrivateRootKey, blob)) { |
| LOG(ERROR) << "Failed to write private root key blob."; |
| return true; |
| } |
| private_root_blob_ = blob; |
| } else if (id == kPrivateLeafKeyID) { |
| private_leaf_blob_ = blob; |
| } else if (id == kPublicRootKeyID) { |
| if (!object_pool->SetInternalBlob(kLegacyPublicRootKey, blob)) { |
| LOG(ERROR) << "Failed to write public root key blob."; |
| return true; |
| } |
| public_root_blob_ = blob; |
| } else if (id == kPublicLeafKeyID) { |
| public_leaf_blob_ = blob; |
| } |
| return true; |
| } |
| |
| bool OpencryptokiImporter::LoadKeyHierarchy(bool load_private) { |
| const char kDefaultAuthData[] = "111111"; |
| string& root_blob = load_private ? private_root_blob_ : public_root_blob_; |
| string& leaf_blob = load_private ? private_leaf_blob_ : public_leaf_blob_; |
| int& root_key = load_private ? private_root_key_ : public_root_key_; |
| int& leaf_key = load_private ? private_leaf_key_ : public_leaf_key_; |
| // Check if the requested hierarchy is already loaded. |
| if (root_key && leaf_key) |
| return true; |
| // We need both the root and leaf blobs in order to proceed. |
| if (root_blob.empty() || leaf_blob.empty()) |
| return false; |
| // Load the root key. |
| if (!tpm_->LoadKey(slot_, root_blob, SecureBlob(), &root_key)) |
| return false; |
| // Load the leaf key. |
| SecureBlob leaf_auth_data = Sha1( |
| SecureBlob(std::begin(kDefaultAuthData), std::end(kDefaultAuthData))); |
| if (!tpm_->LoadKeyWithParent(slot_, leaf_blob, leaf_auth_data, root_key, |
| &leaf_key)) |
| return false; |
| return true; |
| } |
| |
| bool OpencryptokiImporter::DecryptMasterKey(const string& encrypted_master_key, |
| SecureBlob* master_key) { |
| if (!LoadKeyHierarchy(true)) { |
| LOG(ERROR) << "Failed to load private key hierarchy."; |
| return false; |
| } |
| // Trousers defines the handle value 0 as NULL_HKEY so this check works. |
| CHECK(private_leaf_key_); |
| // The master key is encrypted with a simple bind to the private leaf key. |
| string master_key_str; |
| if (!tpm_->Unbind(private_leaf_key_, encrypted_master_key, &master_key_str)) { |
| LOG(ERROR) << "Failed to decrypt master key."; |
| return false; |
| } |
| *master_key = SecureBlob(master_key_str.begin(), master_key_str.end()); |
| ClearString(&master_key_str); |
| return true; |
| } |
| |
| bool OpencryptokiImporter::DecryptObject(const SecureBlob& key, |
| const string& encrypted_object_data, |
| string* object_data) { |
| // Objects are encrypted with AES-256-CBC and a hard-coded IV. |
| const char kOpencryptokiIV[] = ")#%&!*)^!()$&!&N"; |
| const size_t kSha1OutputBytes = 20; |
| string decrypted; |
| if (!RunCipher(false, key, kOpencryptokiIV, encrypted_object_data, |
| &decrypted)) |
| return false; |
| // The data is formatted as follows: |
| // * Length of object data - 4 bytes. |
| // * Object data - 'length' bytes. |
| // * SHA-1 of object data - 20 bytes. |
| if (decrypted.length() < 24) |
| return false; |
| size_t length = ExtractUint32(decrypted.data()); |
| if (decrypted.size() != 4 + length + kSha1OutputBytes) |
| return false; |
| *object_data = decrypted.substr(4, length); |
| if (Sha1(*object_data) != decrypted.substr(4 + length)) |
| return false; |
| return true; |
| } |
| |
| bool OpencryptokiImporter::ConvertToChapsFormat(AttributeMap* attributes) { |
| // There are two special attributes of private keys that need to be converted: |
| // 1. The tpm-wrapped blob (aka CKA_IBM_OPAQUE). |
| // 2. The encrypted authorization data (aka CKA_ENC_AUTHDATA). |
| const CK_ATTRIBUTE_TYPE kOpencryptokiOpaque = CKA_VENDOR_DEFINED + 1; |
| const CK_ATTRIBUTE_TYPE kOpencryptokiAuthData = |
| CKA_VENDOR_DEFINED + 0x01000001; |
| |
| // Sanity check for mandatory attributes that we need. |
| AttributeMap::iterator class_it = attributes->find(CKA_CLASS); |
| AttributeMap::iterator private_it = attributes->find(CKA_PRIVATE); |
| if (class_it == attributes->end() || class_it->second.size() < 4 || |
| private_it == attributes->end() || private_it->second.size() < 1) |
| return false; |
| |
| // If the object is not a private key, we can leave it as is. |
| CK_OBJECT_CLASS object_class = ExtractUint32(class_it->second.data()); |
| if (object_class != CKO_PRIVATE_KEY) |
| return true; |
| |
| // It is possible that these two attributes are missing and in that case we |
| // can leave the object untouched. If the blob attribute exists but the |
| // authorization data attribute doesn't, then it is considered a failure. |
| AttributeMap::iterator blob_it = attributes->find(kOpencryptokiOpaque); |
| if (blob_it == attributes->end()) |
| return true; |
| string tpm_wrapped_blob = blob_it->second; |
| AttributeMap::iterator auth_it = attributes->find(kOpencryptokiAuthData); |
| if (auth_it == attributes->end()) |
| return false; |
| string encrypted_auth_data = auth_it->second; |
| |
| // The value of CKA_PRIVATE tells us which hierarchy we're working with. |
| CK_BBOOL is_private = private_it->second[0]; |
| int leaf_key_handle = is_private ? private_leaf_key_ : public_leaf_key_; |
| if (!LoadKeyHierarchy(is_private)) { |
| LOG(ERROR) << "Failed to load key hierarchy: private=" << is_private; |
| return false; |
| } |
| // Trousers defines the handle value 0 as NULL_HKEY so this check works. |
| CHECK(leaf_key_handle); |
| |
| // Decrypt the authorization data. |
| string auth_data; |
| if (!tpm_->Unbind(leaf_key_handle, encrypted_auth_data, &auth_data)) { |
| LOG(ERROR) << "Failed to unbind authorization data."; |
| return false; |
| } |
| |
| // Remove the opencryptoki-specific attributes from the object and insert the |
| // expected chaps-specific attributes. |
| attributes->erase(kOpencryptokiOpaque); |
| attributes->erase(kOpencryptokiAuthData); |
| (*attributes)[kKeyBlobAttribute] = tpm_wrapped_blob; |
| (*attributes)[kAuthDataAttribute] = auth_data; |
| (*attributes)[kKeyInSoftware] = string(1, CK_FALSE); |
| (*attributes)[kLegacyAttribute] = string(1, CK_TRUE); |
| return true; |
| } |
| |
| bool OpencryptokiImporter::CreateObjectInstance(const AttributeMap& attributes, |
| Object** object) { |
| *object = factory_->CreateObject(); |
| for (AttributeMap::const_iterator it = attributes.begin(); |
| it != attributes.end(); ++it) { |
| (*object)->SetAttributeString(it->first, it->second); |
| } |
| CK_RV result = (*object)->FinalizeNewObject(); |
| if (result != CKR_OK) { |
| LOG(ERROR) << "Failed to validate new object: " << CK_RVToString(result); |
| return false; |
| } |
| return true; |
| } |
| |
| bool OpencryptokiImporter::IsPrivateKey(const AttributeMap& attributes) { |
| AttributeMap::const_iterator class_it = attributes.find(CKA_CLASS); |
| if (class_it == attributes.end() || class_it->second.size() < 4) |
| return false; |
| CK_OBJECT_CLASS object_class = ExtractUint32(class_it->second.data()); |
| return (object_class == CKO_PRIVATE_KEY); |
| } |
| |
| bool OpencryptokiImporter::DecryptPendingObjects() { |
| if (encrypted_objects_.size() > 0) { |
| string encrypted_master_key; |
| if (!base::PathExists(master_key_path_) || |
| !base::ReadFileToString(master_key_path_, &encrypted_master_key)) { |
| LOG(ERROR) << "Failed to read encrypted master key."; |
| return false; |
| } |
| SecureBlob master_key; |
| if (!DecryptMasterKey(encrypted_master_key, &master_key)) { |
| LOG(ERROR) << "Failed to decrypt the master key."; |
| return false; |
| } |
| for (map<string, string>::iterator iter = encrypted_objects_.begin(); |
| iter != encrypted_objects_.end(); ++iter) { |
| string flat_object; |
| if (!DecryptObject(master_key, iter->second, &flat_object)) { |
| LOG(WARNING) << "Failed to decrypt an encrypted object: " |
| << iter->first; |
| continue; |
| } |
| AttributeMap attributes; |
| if (!UnflattenObject(flat_object, iter->first, true, &attributes)) { |
| LOG(WARNING) << "Failed to parse object attributes: " << iter->first; |
| continue; |
| } |
| unflattened_objects_.push_back(attributes); |
| } |
| } |
| return true; |
| } |
| |
| } // namespace chaps |