| // 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/session_impl.h" |
| |
| #include <map> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <chromeos/secure_blob.h> |
| #include <openssl/bio.h> |
| #include <openssl/des.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/hmac.h> |
| #include <openssl/rand.h> |
| #include <openssl/rsa.h> |
| |
| #include "chaps/chaps.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 chromeos::SecureBlob; |
| using std::hex; |
| using std::map; |
| using std::set; |
| using std::string; |
| using std::vector; |
| |
| namespace chaps { |
| |
| static const int kDefaultAuthDataBytes = 20; |
| static const int kMaxCipherBlockBytes = 16; |
| static const int kMaxRSAOutputBytes = 2048; |
| static const int kMaxDigestOutputBytes = EVP_MAX_MD_SIZE; |
| static const int kMinRSAKeyBits = 512; |
| static const int kMaxRSAKeyBitsHW = 2048; // Max supported by the TPM. |
| static const int kMaxRSAKeyBitsSW = kMaxRSAOutputBytes * 8; |
| |
| SessionImpl::SessionImpl(int slot_id, |
| ObjectPool* token_object_pool, |
| TPMUtility* tpm_utility, |
| ChapsFactory* factory, |
| HandleGenerator* handle_generator, |
| bool is_read_only) |
| : factory_(factory), |
| find_results_valid_(false), |
| is_read_only_(is_read_only), |
| slot_id_(slot_id), |
| token_object_pool_(token_object_pool), |
| tpm_utility_(tpm_utility), |
| is_legacy_loaded_(false), |
| private_root_key_(0), |
| public_root_key_(0) { |
| CHECK(token_object_pool_); |
| CHECK(tpm_utility_); |
| CHECK(factory_); |
| session_object_pool_.reset(factory_->CreateObjectPool(handle_generator, |
| NULL, |
| NULL)); |
| CHECK(session_object_pool_.get()); |
| } |
| |
| SessionImpl::~SessionImpl() { |
| } |
| |
| int SessionImpl::GetSlot() const { |
| return slot_id_; |
| } |
| |
| CK_STATE SessionImpl::GetState() const { |
| return is_read_only_ ? CKS_RO_USER_FUNCTIONS : CKS_RW_USER_FUNCTIONS; |
| } |
| |
| bool SessionImpl::IsReadOnly() const { |
| return is_read_only_; |
| } |
| |
| bool SessionImpl::IsOperationActive(OperationType type) const { |
| CHECK(type < kNumOperationTypes); |
| return operation_context_[type].is_valid_; |
| } |
| |
| CK_RV SessionImpl::CreateObject(const CK_ATTRIBUTE_PTR attributes, |
| int num_attributes, |
| int* new_object_handle) { |
| return CreateObjectInternal(attributes, |
| num_attributes, |
| NULL, |
| new_object_handle); |
| } |
| |
| CK_RV SessionImpl::CopyObject(const CK_ATTRIBUTE_PTR attributes, |
| int num_attributes, |
| int object_handle, |
| int* new_object_handle) { |
| const Object* orig_object = NULL; |
| if (!GetObject(object_handle, &orig_object)) |
| return CKR_OBJECT_HANDLE_INVALID; |
| CHECK(orig_object); |
| return CreateObjectInternal(attributes, |
| num_attributes, |
| orig_object, |
| new_object_handle); |
| } |
| |
| CK_RV SessionImpl::DestroyObject(int object_handle) { |
| const Object* object = NULL; |
| if (!GetObject(object_handle, &object)) |
| return CKR_OBJECT_HANDLE_INVALID; |
| CHECK(object); |
| ObjectPool* pool = object->IsTokenObject() ? token_object_pool_ |
| : session_object_pool_.get(); |
| if (!pool->Delete(object)) |
| return CKR_GENERAL_ERROR; |
| return CKR_OK; |
| } |
| |
| bool SessionImpl::GetObject(int object_handle, const Object** object) { |
| CHECK(object); |
| if (token_object_pool_->FindByHandle(object_handle, object)) |
| return true; |
| return session_object_pool_->FindByHandle(object_handle, object); |
| } |
| |
| bool SessionImpl::GetModifiableObject(int object_handle, Object** object) { |
| CHECK(object); |
| const Object* const_object; |
| if (!GetObject(object_handle, &const_object)) |
| return false; |
| ObjectPool* pool = const_object->IsTokenObject() ? token_object_pool_ |
| : session_object_pool_.get(); |
| *object = pool->GetModifiableObject(const_object); |
| return true; |
| } |
| |
| bool SessionImpl::FlushModifiableObject(Object* object) { |
| CHECK(object); |
| ObjectPool* pool = object->IsTokenObject() ? token_object_pool_ |
| : session_object_pool_.get(); |
| return pool->Flush(object); |
| } |
| |
| CK_RV SessionImpl::FindObjectsInit(const CK_ATTRIBUTE_PTR attributes, |
| int num_attributes) { |
| if (find_results_valid_) |
| return CKR_OPERATION_ACTIVE; |
| scoped_ptr<Object> search_template(factory_->CreateObject()); |
| CHECK(search_template.get()); |
| search_template->SetAttributes(attributes, num_attributes); |
| vector<const Object*> objects; |
| if (!search_template->IsAttributePresent(CKA_TOKEN) || |
| search_template->IsTokenObject()) { |
| if (!token_object_pool_->Find(search_template.get(), &objects)) |
| return CKR_GENERAL_ERROR; |
| } |
| if (!search_template->IsAttributePresent(CKA_TOKEN) || |
| !search_template->IsTokenObject()) { |
| if (!session_object_pool_->Find(search_template.get(), &objects)) |
| return CKR_GENERAL_ERROR; |
| } |
| find_results_.clear(); |
| find_results_offset_ = 0; |
| find_results_valid_ = true; |
| for (size_t i = 0; i < objects.size(); ++i) { |
| find_results_.push_back(objects[i]->handle()); |
| } |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::FindObjects(int max_object_count, |
| vector<int>* object_handles) { |
| CHECK(object_handles); |
| if (!find_results_valid_) |
| return CKR_OPERATION_NOT_INITIALIZED; |
| size_t end_offset = find_results_offset_ + |
| static_cast<size_t>(max_object_count); |
| if (end_offset > find_results_.size()) |
| end_offset = find_results_.size(); |
| for (size_t i = find_results_offset_; i < end_offset; ++i) { |
| object_handles->push_back(find_results_[i]); |
| } |
| find_results_offset_ += object_handles->size(); |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::FindObjectsFinal() { |
| if (!find_results_valid_) |
| return CKR_OPERATION_NOT_INITIALIZED; |
| find_results_valid_ = false; |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::OperationInit(OperationType operation, |
| CK_MECHANISM_TYPE mechanism, |
| const string& mechanism_parameter, |
| const Object* key) { |
| CHECK(operation < kNumOperationTypes); |
| OperationContext* context = &operation_context_[operation]; |
| if (context->is_valid_) { |
| LOG(ERROR) << "Operation is already active."; |
| return CKR_OPERATION_ACTIVE; |
| } |
| context->Clear(); |
| context->mechanism_ = mechanism; |
| context->parameter_ = mechanism_parameter; |
| if (!IsValidMechanism(operation, mechanism)) { |
| LOG(ERROR) << "Mechanism not supported: 0x" << hex << mechanism; |
| return CKR_MECHANISM_INVALID; |
| } |
| if (operation != kDigest) { |
| // Make sure the key is valid for the mechanism. |
| CHECK(key); |
| if (!IsValidKeyType(operation, |
| mechanism, |
| key->GetObjectClass(), |
| key->GetAttributeInt(CKA_KEY_TYPE, -1))) { |
| LOG(ERROR) << "Key type mismatch."; |
| return CKR_KEY_TYPE_INCONSISTENT; |
| } |
| if (!key->GetAttributeBool(GetRequiredKeyUsage(operation), false)) { |
| LOG(ERROR) << "Key function not permitted."; |
| return CKR_KEY_FUNCTION_NOT_PERMITTED; |
| } |
| if (IsRSA(mechanism)) { |
| int key_size = key->GetAttributeString(CKA_MODULUS).length() * 8; |
| if (key_size < kMinRSAKeyBits || key_size > kMaxRSAKeyBitsSW) { |
| LOG(ERROR) << "Key size not supported: " << key_size; |
| return CKR_KEY_SIZE_RANGE; |
| } |
| } |
| } |
| if (operation == kEncrypt || operation == kDecrypt) { |
| if (mechanism == CKM_RSA_PKCS) { |
| context->key_ = key; |
| context->is_valid_ = true; |
| } else { |
| return CipherInit((operation == kEncrypt), |
| mechanism, |
| mechanism_parameter, |
| key); |
| } |
| } else { |
| // It is valid for GetOpenSSLDigest to return NULL (e.g. CKM_RSA_PKCS). |
| const EVP_MD* digest = GetOpenSSLDigest(mechanism); |
| if (IsHMAC(mechanism)) { |
| string key_material = key->GetAttributeString(CKA_VALUE); |
| HMAC_CTX_init(&context->hmac_context_); |
| HMAC_Init_ex(&context->hmac_context_, |
| key_material.data(), |
| key_material.length(), |
| digest, |
| NULL); |
| context->is_hmac_ = true; |
| } else if (digest) { |
| EVP_DigestInit(&context->digest_context_, digest); |
| context->is_digest_ = true; |
| } |
| if (IsRSA(mechanism)) |
| context->key_ = key; |
| context->is_valid_ = true; |
| } |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::OperationUpdate(OperationType operation, |
| const string& data_in, |
| int* required_out_length, |
| string* data_out) { |
| CHECK(operation < kNumOperationTypes); |
| OperationContext* context = &operation_context_[operation]; |
| if (!context->is_valid_) { |
| LOG(ERROR) << "Operation is not initialized."; |
| return CKR_OPERATION_NOT_INITIALIZED; |
| } |
| if (context->is_finished_) { |
| LOG(ERROR) << "Operation is finished."; |
| return CKR_OPERATION_ACTIVE; |
| } |
| context->is_incremental_ = true; |
| return OperationUpdateInternal(operation, |
| data_in, |
| required_out_length, |
| data_out); |
| } |
| |
| CK_RV SessionImpl::OperationUpdateInternal(OperationType operation, |
| const string& data_in, |
| int* required_out_length, |
| string* data_out) { |
| CHECK(operation < kNumOperationTypes); |
| OperationContext* context = &operation_context_[operation]; |
| if (context->is_cipher_) { |
| return CipherUpdate(context, data_in, required_out_length, data_out); |
| } else if (context->is_digest_) { |
| EVP_DigestUpdate(&context->digest_context_, |
| data_in.data(), |
| data_in.length()); |
| } else if (context->is_hmac_) { |
| HMAC_Update(&context->hmac_context_, |
| ConvertStringToByteBuffer(data_in.c_str()), |
| data_in.length()); |
| } else { |
| // We don't need to process now; just queue the data. |
| context->data_ += data_in; |
| } |
| if (required_out_length) |
| *required_out_length = 0; |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::OperationFinal(OperationType operation, |
| int* required_out_length, |
| string* data_out) { |
| CHECK(required_out_length); |
| CHECK(data_out); |
| CHECK(operation < kNumOperationTypes); |
| OperationContext* context = &operation_context_[operation]; |
| if (!context->is_valid_) { |
| LOG(ERROR) << "Operation is not initialized."; |
| return CKR_OPERATION_NOT_INITIALIZED; |
| } |
| if (!context->is_incremental_ && context->is_finished_) { |
| LOG(ERROR) << "Operation is not incremental."; |
| return CKR_OPERATION_ACTIVE; |
| } |
| context->is_incremental_ = true; |
| return OperationFinalInternal(operation, required_out_length, data_out); |
| } |
| |
| CK_RV SessionImpl::OperationFinalInternal(OperationType operation, |
| int* required_out_length, |
| string* data_out) { |
| CHECK(operation < kNumOperationTypes); |
| OperationContext* context = &operation_context_[operation]; |
| context->is_valid_ = false; |
| // Complete the operation if it has not already been done. |
| if (!context->is_finished_) { |
| if (context->is_cipher_) { |
| CK_RV result = CipherFinal(context); |
| if (result != CKR_OK) |
| return result; |
| } else if (context->is_digest_) { |
| unsigned char buffer[kMaxDigestOutputBytes]; |
| unsigned int out_length = 0; |
| EVP_DigestFinal(&context->digest_context_, buffer, &out_length); |
| context->data_ = string(reinterpret_cast<char*>(buffer), out_length); |
| } else if (context->is_hmac_) { |
| unsigned char buffer[kMaxDigestOutputBytes]; |
| unsigned int out_length = 0; |
| HMAC_Final(&context->hmac_context_, buffer, &out_length); |
| HMAC_CTX_cleanup(&context->hmac_context_); |
| context->data_ = string(reinterpret_cast<char*>(buffer), out_length); |
| } |
| // Some RSA mechanisms use a digest so it's important to finish the digest |
| // before finishing the RSA computation. |
| if (IsRSA(context->mechanism_)) { |
| if (operation == kEncrypt) { |
| if (!RSAEncrypt(context)) |
| return CKR_FUNCTION_FAILED; |
| } else if (operation == kDecrypt) { |
| if (!RSADecrypt(context)) |
| return CKR_FUNCTION_FAILED; |
| } else if (operation == kSign) { |
| if (!RSASign(context)) |
| return CKR_FUNCTION_FAILED; |
| } |
| } |
| context->is_finished_ = true; |
| } |
| CK_RV result = GetOperationOutput(context, |
| required_out_length, |
| data_out); |
| if (result == CKR_BUFFER_TOO_SMALL) { |
| // We'll keep the context valid so a subsequent call can pick up the data. |
| context->is_valid_ = true; |
| } |
| return result; |
| } |
| |
| CK_RV SessionImpl::VerifyFinal(const string& signature) { |
| OperationContext* context = &operation_context_[kVerify]; |
| // Call the generic OperationFinal so any digest or HMAC computation gets |
| // finalized. |
| int max_out_length = INT_MAX; |
| string data_out; |
| CK_RV result = OperationFinal(kVerify, &max_out_length, &data_out); |
| if (result != CKR_OK) |
| return result; |
| // We only support two Verify mechanisms, HMAC and RSA. |
| if (context->is_hmac_) { |
| // The data_out contents will be the computed HMAC. To verify an HMAC, it is |
| // recomputed and literally compared. |
| if (signature.length() != data_out.length()) |
| return CKR_SIGNATURE_LEN_RANGE; |
| if (0 != chromeos::SecureMemcmp(signature.data(), |
| data_out.data(), |
| signature.length())) |
| return CKR_SIGNATURE_INVALID; |
| } else { |
| // The data_out contents will be the computed digest. |
| return RSAVerify(context, data_out, signature); |
| } |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::OperationSinglePart(OperationType operation, |
| const string& data_in, |
| int* required_out_length, |
| string* data_out) { |
| CHECK(operation < kNumOperationTypes); |
| OperationContext* context = &operation_context_[operation]; |
| if (!context->is_valid_) { |
| LOG(ERROR) << "Operation is not initialized."; |
| return CKR_OPERATION_NOT_INITIALIZED; |
| } |
| if (context->is_incremental_) { |
| LOG(ERROR) << "Operation is incremental."; |
| return CKR_OPERATION_ACTIVE; |
| } |
| CK_RV result = CKR_OK; |
| if (!context->is_finished_) { |
| string update, final; |
| int max = INT_MAX; |
| result = OperationUpdateInternal(operation, data_in, &max, &update); |
| if (result != CKR_OK) |
| return result; |
| max = INT_MAX; |
| result = OperationFinalInternal(operation, &max, &final); |
| if (result != CKR_OK) |
| return result; |
| context->data_ = update + final; |
| context->is_finished_ = true; |
| } |
| context->is_valid_ = false; |
| result = GetOperationOutput(context, |
| required_out_length, |
| data_out); |
| if (result == CKR_BUFFER_TOO_SMALL) { |
| // We'll keep the context valid so a subsequent call can pick up the data. |
| context->is_valid_ = true; |
| } |
| return result; |
| } |
| |
| CK_RV SessionImpl::GenerateKey(CK_MECHANISM_TYPE mechanism, |
| const string& mechanism_parameter, |
| const CK_ATTRIBUTE_PTR attributes, |
| int num_attributes, |
| int* new_key_handle) { |
| CHECK(new_key_handle); |
| scoped_ptr<Object> object(factory_->CreateObject()); |
| CHECK(object.get()); |
| CK_RV result = object->SetAttributes(attributes, num_attributes); |
| if (result != CKR_OK) |
| return result; |
| CK_KEY_TYPE key_type = 0; |
| string key_material; |
| switch (mechanism) { |
| case CKM_DES_KEY_GEN: { |
| key_type = CKK_DES; |
| if (!GenerateDESKey(&key_material)) |
| return CKR_FUNCTION_FAILED; |
| break; |
| } |
| case CKM_DES3_KEY_GEN: { |
| key_type = CKK_DES3; |
| string des[3]; |
| for (int i = 0; i < 3; ++i) { |
| if (!GenerateDESKey(&des[i])) |
| return CKR_FUNCTION_FAILED; |
| } |
| key_material = des[0] + des[1] + des[2]; |
| break; |
| } |
| case CKM_AES_KEY_GEN: { |
| key_type = CKK_AES; |
| if (!object->IsAttributePresent(CKA_VALUE_LEN)) |
| return CKR_TEMPLATE_INCOMPLETE; |
| int key_length = object->GetAttributeInt(CKA_VALUE_LEN, 0); |
| if (key_length != 16 && key_length != 24 && key_length != 32) |
| return CKR_KEY_SIZE_RANGE; |
| key_material = GenerateRandomSoftware(key_length); |
| break; |
| } |
| case CKM_GENERIC_SECRET_KEY_GEN: { |
| key_type = CKK_GENERIC_SECRET; |
| if (!object->IsAttributePresent(CKA_VALUE_LEN)) |
| return CKR_TEMPLATE_INCOMPLETE; |
| int key_length = object->GetAttributeInt(CKA_VALUE_LEN, 0); |
| if (key_length < 1) |
| return CKR_KEY_SIZE_RANGE; |
| key_material = GenerateRandomSoftware(key_length); |
| break; |
| } |
| default: { |
| LOG(ERROR) << "GenerateKey: Mechanism not supported: " << hex |
| << mechanism; |
| return CKR_MECHANISM_INVALID; |
| } |
| } |
| object->SetAttributeInt(CKA_CLASS, CKO_SECRET_KEY); |
| object->SetAttributeInt(CKA_KEY_TYPE, key_type); |
| object->SetAttributeString(CKA_VALUE, key_material); |
| object->SetAttributeBool(CKA_LOCAL, true); |
| object->SetAttributeInt(CKA_KEY_GEN_MECHANISM, mechanism); |
| result = object->FinalizeNewObject(); |
| if (result != CKR_OK) |
| return result; |
| ObjectPool* pool = object->IsTokenObject() ? token_object_pool_ |
| : session_object_pool_.get(); |
| if (!pool->Insert(object.get())) |
| return CKR_FUNCTION_FAILED; |
| *new_key_handle = object.release()->handle(); |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::GenerateKeyPair(CK_MECHANISM_TYPE mechanism, |
| const string& mechanism_parameter, |
| const CK_ATTRIBUTE_PTR public_attributes, |
| int num_public_attributes, |
| const CK_ATTRIBUTE_PTR private_attributes, |
| int num_private_attributes, |
| int* new_public_key_handle, |
| int* new_private_key_handle) { |
| CHECK(new_public_key_handle); |
| CHECK(new_private_key_handle); |
| if (mechanism != CKM_RSA_PKCS_KEY_PAIR_GEN) { |
| LOG(ERROR) << "GenerateKeyPair: Mechanism not supported: " << hex |
| << mechanism; |
| return CKR_MECHANISM_INVALID; |
| } |
| scoped_ptr<Object> public_object(factory_->CreateObject()); |
| CHECK(public_object.get()); |
| scoped_ptr<Object> private_object(factory_->CreateObject()); |
| CHECK(private_object.get()); |
| CK_RV result = public_object->SetAttributes(public_attributes, |
| num_public_attributes); |
| if (result != CKR_OK) |
| return result; |
| result = private_object->SetAttributes(private_attributes, |
| num_private_attributes); |
| if (result != CKR_OK) |
| return result; |
| // CKA_PUBLIC_EXPONENT is optional. The default is 65537 (0x10001). |
| string public_exponent("\x01\x00\x01", 3); |
| if (public_object->IsAttributePresent(CKA_PUBLIC_EXPONENT)) |
| public_exponent = public_object->GetAttributeString(CKA_PUBLIC_EXPONENT); |
| public_object->SetAttributeString(CKA_PUBLIC_EXPONENT, public_exponent); |
| private_object->SetAttributeString(CKA_PUBLIC_EXPONENT, public_exponent); |
| if (!public_object->IsAttributePresent(CKA_MODULUS_BITS)) |
| return CKR_TEMPLATE_INCOMPLETE; |
| int modulus_bits = public_object->GetAttributeInt(CKA_MODULUS_BITS, 0); |
| if (modulus_bits < kMinRSAKeyBits || modulus_bits > kMaxRSAKeyBitsSW) |
| return CKR_KEY_SIZE_RANGE; |
| ObjectPool* pool = token_object_pool_; |
| // Check if we are able to back this key with the TPM. |
| if (tpm_utility_->IsTPMAvailable() && |
| private_object->IsTokenObject() && |
| modulus_bits <= kMaxRSAKeyBitsHW) { |
| string auth_data = GenerateRandomSoftware(kDefaultAuthDataBytes); |
| string key_blob; |
| int tpm_key_handle; |
| if (!tpm_utility_->GenerateKey(slot_id_, |
| modulus_bits, |
| public_exponent, |
| SecureBlob(auth_data.data(), |
| auth_data.length()), |
| &key_blob, |
| &tpm_key_handle)) |
| return CKR_FUNCTION_FAILED; |
| string modulus; |
| if (!tpm_utility_->GetPublicKey(tpm_key_handle, &public_exponent, &modulus)) |
| return CKR_FUNCTION_FAILED; |
| public_object->SetAttributeString(CKA_MODULUS, modulus); |
| private_object->SetAttributeString(CKA_MODULUS, modulus); |
| private_object->SetAttributeString(kAuthDataAttribute, auth_data); |
| private_object->SetAttributeString(kKeyBlobAttribute, key_blob); |
| } else { |
| if (!private_object->IsTokenObject()) |
| pool = session_object_pool_.get(); |
| if (!GenerateKeyPairSoftware(modulus_bits, |
| public_exponent, |
| public_object.get(), |
| private_object.get())) |
| return CKR_FUNCTION_FAILED; |
| } |
| public_object->SetAttributeInt(CKA_CLASS, CKO_PUBLIC_KEY); |
| public_object->SetAttributeInt(CKA_KEY_TYPE, CKK_RSA); |
| private_object->SetAttributeInt(CKA_CLASS, CKO_PRIVATE_KEY); |
| private_object->SetAttributeInt(CKA_KEY_TYPE, CKK_RSA); |
| public_object->SetAttributeBool(CKA_LOCAL, true); |
| private_object->SetAttributeBool(CKA_LOCAL, true); |
| public_object->SetAttributeInt(CKA_KEY_GEN_MECHANISM, mechanism); |
| private_object->SetAttributeInt(CKA_KEY_GEN_MECHANISM, mechanism); |
| result = public_object->FinalizeNewObject(); |
| if (result != CKR_OK) |
| return result; |
| result = private_object->FinalizeNewObject(); |
| if (result != CKR_OK) |
| return result; |
| if (!pool->Insert(public_object.get())) |
| return CKR_FUNCTION_FAILED; |
| if (!pool->Insert(private_object.get())) { |
| pool->Delete(public_object.release()); |
| return CKR_FUNCTION_FAILED; |
| } |
| *new_public_key_handle = public_object.release()->handle(); |
| *new_private_key_handle = private_object.release()->handle(); |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::SeedRandom(const string& seed) { |
| RAND_seed(seed.data(), seed.length()); |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::GenerateRandom(int num_bytes, string* random_data) { |
| *random_data = GenerateRandomSoftware(num_bytes); |
| return CKR_OK; |
| } |
| |
| void SessionImpl::WaitForPrivateObjects() { |
| scoped_ptr<Object> all_private(factory_->CreateObject()); |
| CHECK(all_private.get()); |
| all_private->SetAttributeBool(CKA_PRIVATE, true); |
| vector<const Object*> found; |
| token_object_pool_->Find(all_private.get(), &found); |
| } |
| |
| bool SessionImpl::IsValidKeyType(OperationType operation, |
| CK_MECHANISM_TYPE mechanism, |
| CK_OBJECT_CLASS object_class, |
| CK_KEY_TYPE key_type) { |
| CK_KEY_TYPE expected_key_type = 0; |
| CK_OBJECT_CLASS expected_class = 0; |
| CK_OBJECT_CLASS asymmetric_class = CKO_PUBLIC_KEY; |
| if (operation == kSign || operation == kDecrypt) |
| asymmetric_class = CKO_PRIVATE_KEY; |
| switch (mechanism) { |
| case CKM_DES_ECB: |
| case CKM_DES_CBC: |
| case CKM_DES_CBC_PAD: |
| expected_key_type = CKK_DES; |
| expected_class = CKO_SECRET_KEY; |
| break; |
| case CKM_DES3_ECB: |
| case CKM_DES3_CBC: |
| case CKM_DES3_CBC_PAD: |
| expected_key_type = CKK_DES3; |
| expected_class = CKO_SECRET_KEY; |
| break; |
| case CKM_AES_ECB: |
| case CKM_AES_CBC: |
| case CKM_AES_CBC_PAD: |
| expected_key_type = CKK_AES; |
| expected_class = CKO_SECRET_KEY; |
| break; |
| case CKM_RSA_PKCS: |
| case CKM_MD5_RSA_PKCS: |
| case CKM_SHA1_RSA_PKCS: |
| case CKM_SHA256_RSA_PKCS: |
| case CKM_SHA384_RSA_PKCS: |
| case CKM_SHA512_RSA_PKCS: |
| expected_key_type = CKK_RSA; |
| expected_class = asymmetric_class; |
| break; |
| case CKM_MD5_HMAC: |
| case CKM_SHA_1_HMAC: |
| case CKM_SHA256_HMAC: |
| case CKM_SHA384_HMAC: |
| case CKM_SHA512_HMAC: |
| expected_key_type = CKK_GENERIC_SECRET; |
| expected_class = CKO_SECRET_KEY; |
| break; |
| default: |
| return false; |
| } |
| return (key_type == expected_key_type && |
| object_class == expected_class); |
| } |
| |
| bool SessionImpl::IsValidMechanism(OperationType operation, |
| CK_MECHANISM_TYPE mechanism) { |
| if (operation == kEncrypt || operation == kDecrypt) { |
| switch (mechanism) { |
| case CKM_DES_ECB: |
| case CKM_DES_CBC: |
| case CKM_DES_CBC_PAD: |
| case CKM_DES3_ECB: |
| case CKM_DES3_CBC: |
| case CKM_DES3_CBC_PAD: |
| case CKM_AES_ECB: |
| case CKM_AES_CBC: |
| case CKM_AES_CBC_PAD: |
| case CKM_RSA_PKCS: |
| return true; |
| } |
| } else if (operation == kSign || operation == kVerify) { |
| switch (mechanism) { |
| case CKM_RSA_PKCS: |
| case CKM_MD5_RSA_PKCS: |
| case CKM_SHA1_RSA_PKCS: |
| case CKM_SHA256_RSA_PKCS: |
| case CKM_SHA384_RSA_PKCS: |
| case CKM_SHA512_RSA_PKCS: |
| case CKM_MD5_HMAC: |
| case CKM_SHA_1_HMAC: |
| case CKM_SHA256_HMAC: |
| case CKM_SHA384_HMAC: |
| case CKM_SHA512_HMAC: |
| return true; |
| } |
| } else { |
| switch (mechanism) { |
| case CKM_MD5: |
| case CKM_SHA_1: |
| case CKM_SHA256: |
| case CKM_SHA384: |
| case CKM_SHA512: |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| CK_RV SessionImpl::CipherInit(bool is_encrypt, |
| CK_MECHANISM_TYPE mechanism, |
| const string& mechanism_parameter, |
| const Object* key) { |
| OperationType operation = is_encrypt ? kEncrypt : kDecrypt; |
| EVP_CIPHER_CTX* context = |
| &operation_context_[operation].cipher_context_; |
| string key_material = key->GetAttributeString(CKA_VALUE); |
| const EVP_CIPHER* cipher_type = GetOpenSSLCipher(mechanism, |
| key_material.size()); |
| if (!cipher_type) { |
| LOG(ERROR) << "Mechanism not supported: 0x" << hex << mechanism; |
| return CKR_MECHANISM_INVALID; |
| } |
| // The mechanism parameter is the IV for cipher modes which require an IV, |
| // otherwise it is expected to be empty. |
| if (static_cast<int>(mechanism_parameter.size()) != |
| EVP_CIPHER_iv_length(cipher_type)) { |
| LOG(ERROR) << "IV length is invalid: " << mechanism_parameter.size(); |
| return CKR_MECHANISM_PARAM_INVALID; |
| } |
| if (static_cast<int>(key_material.size()) != |
| EVP_CIPHER_key_length(cipher_type)) { |
| LOG(ERROR) << "Key size not supported: " << key_material.size(); |
| return CKR_KEY_SIZE_RANGE; |
| } |
| if (!EVP_CipherInit(context, |
| cipher_type, |
| ConvertStringToByteBuffer(key_material.c_str()), |
| ConvertStringToByteBuffer(mechanism_parameter.c_str()), |
| is_encrypt)) { |
| LOG(ERROR) << "EVP_CipherInit failed: " << GetOpenSSLError(); |
| return CKR_FUNCTION_FAILED; |
| } |
| EVP_CIPHER_CTX_set_padding(context, IsPaddingEnabled(mechanism)); |
| operation_context_[operation].is_valid_ = true; |
| operation_context_[operation].is_cipher_ = true; |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::CipherUpdate(OperationContext* context, |
| const string& data_in, |
| int* required_out_length, |
| string* data_out) { |
| CHECK(required_out_length); |
| CHECK(data_out); |
| // If we have output already waiting, we don't need to process input. |
| if (context->data_.empty()) { |
| int in_length = data_in.length(); |
| int out_length = in_length + kMaxCipherBlockBytes; |
| context->data_.resize(out_length); |
| if (!EVP_CipherUpdate( |
| &context->cipher_context_, |
| ConvertStringToByteBuffer(context->data_.c_str()), |
| &out_length, |
| ConvertStringToByteBuffer(data_in.c_str()), |
| in_length)) { |
| EVP_CIPHER_CTX_cleanup(&context->cipher_context_); |
| context->is_valid_ = false; |
| LOG(ERROR) << "EVP_CipherUpdate failed: " << GetOpenSSLError(); |
| return CKR_FUNCTION_FAILED; |
| } |
| context->data_.resize(out_length); |
| } |
| return GetOperationOutput(context, |
| required_out_length, |
| data_out); |
| } |
| |
| CK_RV SessionImpl::CipherFinal(OperationContext* context) { |
| if (context->data_.empty()) { |
| int out_length = kMaxCipherBlockBytes * 2; |
| context->data_.resize(out_length); |
| if (!EVP_CipherFinal( |
| &context->cipher_context_, |
| ConvertStringToByteBuffer(context->data_.c_str()), |
| &out_length)) { |
| LOG(ERROR) << "EVP_CipherFinal failed: " << GetOpenSSLError(); |
| EVP_CIPHER_CTX_cleanup(&context->cipher_context_); |
| return CKR_FUNCTION_FAILED; |
| } |
| EVP_CIPHER_CTX_cleanup(&context->cipher_context_); |
| context->data_.resize(out_length); |
| } |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::CreateObjectInternal(const CK_ATTRIBUTE_PTR attributes, |
| int num_attributes, |
| const Object* copy_from_object, |
| int* new_object_handle) { |
| CHECK(new_object_handle); |
| CHECK(attributes || num_attributes == 0); |
| scoped_ptr<Object> object(factory_->CreateObject()); |
| CHECK(object.get()); |
| CK_RV result = CKR_OK; |
| if (copy_from_object) { |
| result = object->Copy(copy_from_object); |
| if (result != CKR_OK) |
| return result; |
| } |
| result = object->SetAttributes(attributes, num_attributes); |
| if (result != CKR_OK) |
| return result; |
| if (!copy_from_object) { |
| result = object->FinalizeNewObject(); |
| if (result != CKR_OK) |
| return result; |
| } |
| ObjectPool* pool = token_object_pool_; |
| if (object->IsTokenObject()) { |
| result = WrapPrivateKey(object.get()); |
| if (result != CKR_OK) |
| return result; |
| } else { |
| pool = session_object_pool_.get(); |
| } |
| if (!pool->Insert(object.get())) |
| return CKR_GENERAL_ERROR; |
| *new_object_handle = object.release()->handle(); |
| return CKR_OK; |
| } |
| |
| bool SessionImpl::GenerateDESKey(string* key_material) { |
| static const int kDESKeySizeBytes = 8; |
| bool done = false; |
| while (!done) { |
| string tmp = GenerateRandomSoftware(kDESKeySizeBytes); |
| DES_cblock des; |
| memcpy(&des, tmp.data(), kDESKeySizeBytes); |
| if (!DES_is_weak_key(&des)) { |
| DES_set_odd_parity(&des); |
| *key_material = string(reinterpret_cast<char*>(des), kDESKeySizeBytes); |
| done = true; |
| } |
| } |
| return true; |
| } |
| |
| bool SessionImpl::GenerateKeyPairSoftware(int modulus_bits, |
| const string& public_exponent, |
| Object* public_object, |
| Object* private_object) { |
| if (public_exponent.length() > sizeof(uint32_t) || |
| public_exponent.empty()) |
| return false; |
| BIGNUM* e = ConvertToBIGNUM(public_exponent); |
| RSA* key = RSA_generate_key(modulus_bits, BN_get_word(e), NULL, NULL); |
| CHECK(key); |
| string n = ConvertFromBIGNUM(key->n); |
| string d = ConvertFromBIGNUM(key->d); |
| string p = ConvertFromBIGNUM(key->p); |
| string q = ConvertFromBIGNUM(key->q); |
| string dmp1 = ConvertFromBIGNUM(key->dmp1); |
| string dmq1 = ConvertFromBIGNUM(key->dmq1); |
| string iqmp = ConvertFromBIGNUM(key->iqmp); |
| public_object->SetAttributeString(CKA_MODULUS, n); |
| private_object->SetAttributeString(CKA_MODULUS, n); |
| private_object->SetAttributeString(CKA_PRIVATE_EXPONENT, d); |
| private_object->SetAttributeString(CKA_PRIME_1, p); |
| private_object->SetAttributeString(CKA_PRIME_2, q); |
| private_object->SetAttributeString(CKA_EXPONENT_1, dmp1); |
| private_object->SetAttributeString(CKA_EXPONENT_2, dmq1); |
| private_object->SetAttributeString(CKA_COEFFICIENT, iqmp); |
| RSA_free(key); |
| BN_free(e); |
| return true; |
| } |
| |
| string SessionImpl::GenerateRandomSoftware(int num_bytes) { |
| string random(num_bytes, 0); |
| RAND_bytes(ConvertStringToByteBuffer(random.data()), num_bytes); |
| return random; |
| } |
| |
| string SessionImpl::GetDERDigestInfo(CK_MECHANISM_TYPE mechanism) { |
| // These strings are the DER encodings of the DigestInfo values for the |
| // supported digest algorithms. See PKCS #1 v2.1: 9.2. |
| static const string kMD5DigestInfo( |
| "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04" |
| "\x10", |
| 18); |
| static const string kSHA1DigestInfo( |
| "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14", |
| 15); |
| static const string kSHA256DigestInfo( |
| "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04" |
| "\x20", |
| 19); |
| static const string kSHA384DigestInfo( |
| "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04" |
| "\x30", |
| 19); |
| static const string kSHA512DigestInfo( |
| "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04" |
| "\x40", |
| 19); |
| const EVP_MD* md = GetOpenSSLDigest(mechanism); |
| if (md == EVP_md5()) { |
| return kMD5DigestInfo; |
| } else if (md == EVP_sha1()) { |
| return kSHA1DigestInfo; |
| } else if (md == EVP_sha256()) { |
| return kSHA256DigestInfo; |
| } else if (md == EVP_sha384()) { |
| return kSHA384DigestInfo; |
| } else if (md == EVP_sha512()) { |
| return kSHA512DigestInfo; |
| } |
| // This is valid in some cases (e.g. CKM_RSA_PKCS). |
| return string(); |
| } |
| |
| CK_RV SessionImpl::GetOperationOutput(OperationContext* context, |
| int* required_out_length, |
| string* data_out) { |
| int out_length = context->data_.length(); |
| int max_length = *required_out_length; |
| *required_out_length = out_length; |
| if (max_length < out_length) |
| return CKR_BUFFER_TOO_SMALL; |
| *data_out = context->data_; |
| context->data_.clear(); |
| return CKR_OK; |
| } |
| |
| CK_ATTRIBUTE_TYPE SessionImpl::GetRequiredKeyUsage(OperationType operation) { |
| switch (operation) { |
| case kEncrypt: |
| return CKA_ENCRYPT; |
| case kDecrypt: |
| return CKA_DECRYPT; |
| case kSign: |
| return CKA_SIGN; |
| case kVerify: |
| return CKA_VERIFY; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| bool SessionImpl::GetTPMKeyHandle(const Object* key, int* key_handle) { |
| map<const Object*, int>::iterator it = object_tpm_handle_map_.find(key); |
| if (it == object_tpm_handle_map_.end()) { |
| // Only private keys are loaded into the TPM. All public key operations do |
| // not use the TPM (and use OpenSSL instead). |
| if (key->GetObjectClass() == CKO_PRIVATE_KEY) { |
| string auth_data = key->GetAttributeString(kAuthDataAttribute); |
| if (key->GetAttributeBool(kLegacyAttribute, false)) { |
| // This is a legacy key and it needs to be loaded with the legacy root |
| // key. |
| if (!LoadLegacyRootKeys()) |
| return false; |
| bool is_private = key->GetAttributeBool(CKA_PRIVATE, true); |
| int root_key_handle = is_private ? private_root_key_ : public_root_key_; |
| if (!tpm_utility_->LoadKeyWithParent( |
| slot_id_, |
| key->GetAttributeString(kKeyBlobAttribute), |
| SecureBlob(auth_data.data(), auth_data.length()), |
| root_key_handle, |
| key_handle)) |
| return false; |
| } else { |
| if (!tpm_utility_->LoadKey( |
| slot_id_, |
| key->GetAttributeString(kKeyBlobAttribute), |
| SecureBlob(auth_data.data(), auth_data.length()), |
| key_handle)) |
| return false; |
| } |
| } else { |
| LOG(ERROR) << "Invalid object class for loading into TPM."; |
| return false; |
| } |
| object_tpm_handle_map_[key] = *key_handle; |
| } else { |
| *key_handle = it->second; |
| } |
| return true; |
| } |
| |
| bool SessionImpl::LoadLegacyRootKeys() { |
| if (is_legacy_loaded_) |
| return true; |
| |
| // Load the legacy root keys. See http://trousers.sourceforge.net/pkcs11.html |
| // for details on where these come from. |
| string private_blob; |
| if (!token_object_pool_->GetInternalBlob(kLegacyPrivateRootKey, |
| &private_blob)) { |
| LOG(ERROR) << "Failed to read legacy private root key blob."; |
| return false; |
| } |
| if (!tpm_utility_->LoadKey(slot_id_, |
| private_blob, |
| SecureBlob(), |
| &private_root_key_)) { |
| LOG(ERROR) << "Failed to load legacy private root key."; |
| return false; |
| } |
| string public_blob; |
| if (!token_object_pool_->GetInternalBlob(kLegacyPublicRootKey, |
| &public_blob)) { |
| LOG(ERROR) << "Failed to read legacy public root key blob."; |
| return false; |
| } |
| if (!tpm_utility_->LoadKey(slot_id_, public_blob, SecureBlob(), |
| &public_root_key_)) { |
| LOG(ERROR) << "Failed to load legacy public root key."; |
| return false; |
| } |
| is_legacy_loaded_ = true; |
| return true; |
| } |
| |
| bool SessionImpl::IsHMAC(CK_MECHANISM_TYPE mechanism) { |
| switch (mechanism) { |
| case CKM_MD5_HMAC: |
| case CKM_SHA_1_HMAC: |
| case CKM_SHA256_HMAC: |
| case CKM_SHA384_HMAC: |
| case CKM_SHA512_HMAC: |
| return true; |
| } |
| return false; |
| } |
| |
| bool SessionImpl::IsPaddingEnabled(CK_MECHANISM_TYPE mechanism) { |
| switch (mechanism) { |
| case CKM_DES_CBC_PAD: |
| case CKM_DES3_CBC_PAD: |
| case CKM_AES_CBC_PAD: |
| return true; |
| } |
| return false; |
| } |
| |
| bool SessionImpl::IsRSA(CK_MECHANISM_TYPE mechanism) { |
| switch (mechanism) { |
| case CKM_RSA_PKCS: |
| case CKM_MD5_RSA_PKCS: |
| case CKM_SHA1_RSA_PKCS: |
| case CKM_SHA256_RSA_PKCS: |
| case CKM_SHA384_RSA_PKCS: |
| case CKM_SHA512_RSA_PKCS: |
| return true; |
| } |
| return false; |
| } |
| |
| // Both PKCS #11 and OpenSSL use big-endian binary representations of big |
| // integers. To convert we can just use the OpenSSL converters. |
| string SessionImpl::ConvertFromBIGNUM(const BIGNUM* bignum) { |
| string big_integer(BN_num_bytes(bignum), 0); |
| BN_bn2bin(bignum, ConvertStringToByteBuffer(big_integer.data())); |
| return big_integer; |
| } |
| |
| BIGNUM* SessionImpl::ConvertToBIGNUM(const string& big_integer) { |
| if (big_integer.empty()) |
| return NULL; |
| BIGNUM* b = BN_bin2bn(ConvertStringToByteBuffer(big_integer.data()), |
| big_integer.length(), |
| NULL); |
| CHECK(b); |
| return b; |
| } |
| |
| RSA* SessionImpl::CreateKeyFromObject(const Object* key_object) { |
| RSA* rsa = RSA_new(); |
| CHECK(rsa); |
| if (key_object->GetObjectClass() == CKO_PUBLIC_KEY) { |
| string e = key_object->GetAttributeString(CKA_PUBLIC_EXPONENT); |
| rsa->e = ConvertToBIGNUM(e); |
| string n = key_object->GetAttributeString(CKA_MODULUS); |
| rsa->n = ConvertToBIGNUM(n); |
| } else { |
| string n = key_object->GetAttributeString(CKA_MODULUS); |
| rsa->n = ConvertToBIGNUM(n); |
| string d = key_object->GetAttributeString(CKA_PRIVATE_EXPONENT); |
| rsa->d = ConvertToBIGNUM(d); |
| string p = key_object->GetAttributeString(CKA_PRIME_1); |
| rsa->p = ConvertToBIGNUM(p); |
| string q = key_object->GetAttributeString(CKA_PRIME_2); |
| rsa->q = ConvertToBIGNUM(q); |
| string dmp1 = key_object->GetAttributeString(CKA_EXPONENT_1); |
| rsa->dmp1 = ConvertToBIGNUM(dmp1); |
| string dmq1 = key_object->GetAttributeString(CKA_EXPONENT_2); |
| rsa->dmq1 = ConvertToBIGNUM(dmq1); |
| string iqmp = key_object->GetAttributeString(CKA_COEFFICIENT); |
| rsa->iqmp = ConvertToBIGNUM(iqmp); |
| } |
| return rsa; |
| } |
| |
| const EVP_CIPHER* SessionImpl::GetOpenSSLCipher(CK_MECHANISM_TYPE mechanism, |
| size_t key_size) { |
| switch (mechanism) { |
| case CKM_DES_ECB: |
| return EVP_des_ecb(); |
| case CKM_DES_CBC: |
| case CKM_DES_CBC_PAD: |
| return EVP_des_cbc(); |
| case CKM_DES3_ECB: |
| return EVP_des_ede3(); |
| case CKM_DES3_CBC: |
| case CKM_DES3_CBC_PAD: |
| return EVP_des_ede3_cbc(); |
| case CKM_AES_ECB: |
| switch (key_size) { |
| case 16: |
| return EVP_aes_128_ecb(); |
| case 24: |
| return EVP_aes_192_ecb(); |
| default: |
| return EVP_aes_256_ecb(); |
| } |
| break; |
| case CKM_AES_CBC: |
| case CKM_AES_CBC_PAD: |
| switch (key_size) { |
| case 16: |
| return EVP_aes_128_cbc(); |
| case 24: |
| return EVP_aes_192_cbc(); |
| default: |
| return EVP_aes_256_cbc(); |
| } |
| break; |
| } |
| return NULL; |
| } |
| |
| const EVP_MD* SessionImpl::GetOpenSSLDigest(CK_MECHANISM_TYPE mechanism) { |
| switch (mechanism) { |
| case CKM_MD5: |
| case CKM_MD5_HMAC: |
| case CKM_MD5_RSA_PKCS: |
| return EVP_md5(); |
| case CKM_SHA_1: |
| case CKM_SHA_1_HMAC: |
| case CKM_SHA1_RSA_PKCS: |
| return EVP_sha1(); |
| case CKM_SHA256: |
| case CKM_SHA256_HMAC: |
| case CKM_SHA256_RSA_PKCS: |
| return EVP_sha256(); |
| case CKM_SHA384: |
| case CKM_SHA384_HMAC: |
| case CKM_SHA384_RSA_PKCS: |
| return EVP_sha384(); |
| case CKM_SHA512: |
| case CKM_SHA512_HMAC: |
| case CKM_SHA512_RSA_PKCS: |
| return EVP_sha512(); |
| } |
| return NULL; |
| } |
| |
| bool SessionImpl::RSADecrypt(OperationContext* context) { |
| if (context->key_->IsTokenObject() && |
| context->key_->IsAttributePresent(kKeyBlobAttribute)) { |
| int tpm_key_handle = 0; |
| if (!GetTPMKeyHandle(context->key_, &tpm_key_handle)) |
| return false; |
| string encrypted_data = context->data_; |
| context->data_.clear(); |
| if (!tpm_utility_->Unbind(tpm_key_handle, encrypted_data, &context->data_)) |
| return false; |
| } else { |
| RSA* rsa = CreateKeyFromObject(context->key_); |
| uint8_t buffer[kMaxRSAOutputBytes]; |
| CHECK(RSA_size(rsa) <= kMaxRSAOutputBytes); |
| int length = RSA_private_decrypt( |
| context->data_.length(), |
| ConvertStringToByteBuffer(context->data_.data()), |
| buffer, |
| rsa, |
| RSA_PKCS1_PADDING); // Strips PKCS #1 type 2 padding. |
| RSA_free(rsa); |
| if (length == -1) { |
| LOG(ERROR) << "RSA_private_decrypt failed: " << GetOpenSSLError(); |
| return false; |
| } |
| context->data_ = ConvertByteBufferToString(buffer, length); |
| } |
| return true; |
| } |
| |
| bool SessionImpl::RSAEncrypt(OperationContext* context) { |
| RSA* rsa = CreateKeyFromObject(context->key_); |
| uint8_t buffer[kMaxRSAOutputBytes]; |
| CHECK(RSA_size(rsa) <= kMaxRSAOutputBytes); |
| int length = RSA_public_encrypt( |
| context->data_.length(), |
| ConvertStringToByteBuffer(context->data_.data()), |
| buffer, |
| rsa, |
| RSA_PKCS1_PADDING); // Adds PKCS #1 type 2 padding. |
| RSA_free(rsa); |
| if (length == -1) { |
| LOG(ERROR) << "RSA_public_encrypt failed: " << GetOpenSSLError(); |
| return false; |
| } |
| context->data_ = ConvertByteBufferToString(buffer, length); |
| return true; |
| } |
| |
| bool SessionImpl::RSASign(OperationContext* context) { |
| string data_to_sign = GetDERDigestInfo(context->mechanism_) + context->data_; |
| string signature; |
| if (context->key_->IsTokenObject() && |
| context->key_->IsAttributePresent(kKeyBlobAttribute)) { |
| int tpm_key_handle = 0; |
| if (!GetTPMKeyHandle(context->key_, &tpm_key_handle)) |
| return false; |
| if (!tpm_utility_->Sign(tpm_key_handle, data_to_sign, &signature)) |
| return false; |
| } else { |
| RSA* rsa = CreateKeyFromObject(context->key_); |
| CHECK(RSA_size(rsa) <= kMaxRSAOutputBytes); |
| uint8_t buffer[kMaxRSAOutputBytes]; |
| int length = RSA_private_encrypt( |
| data_to_sign.length(), |
| ConvertStringToByteBuffer(data_to_sign.data()), |
| buffer, |
| rsa, |
| RSA_PKCS1_PADDING); // Adds PKCS #1 type 1 padding. |
| RSA_free(rsa); |
| if (length == -1) { |
| LOG(ERROR) << "RSA_private_encrypt failed: " << GetOpenSSLError(); |
| return false; |
| } |
| signature = string(reinterpret_cast<char*>(buffer), length); |
| } |
| context->data_ = signature; |
| return true; |
| } |
| |
| CK_RV SessionImpl::RSAVerify(OperationContext* context, |
| const string& digest, |
| const string& signature) { |
| if (context->key_->GetAttributeString(CKA_MODULUS).length() != |
| signature.length()) |
| return CKR_SIGNATURE_LEN_RANGE; |
| RSA* rsa = CreateKeyFromObject(context->key_); |
| CHECK(RSA_size(rsa) <= kMaxRSAOutputBytes); |
| uint8_t buffer[kMaxRSAOutputBytes]; |
| int length = RSA_public_decrypt( |
| signature.length(), |
| ConvertStringToByteBuffer(signature.data()), |
| buffer, |
| rsa, |
| RSA_PKCS1_PADDING); // Strips PKCS #1 type 1 padding. |
| RSA_free(rsa); |
| if (length == -1) { |
| LOG(ERROR) << "RSA_public_decrypt failed: " << GetOpenSSLError(); |
| return CKR_SIGNATURE_INVALID; |
| } |
| string signed_data = GetDERDigestInfo(context->mechanism_) + digest; |
| if (static_cast<size_t>(length) != signed_data.length() || |
| 0 != chromeos::SecureMemcmp(buffer, signed_data.data(), length)) |
| return CKR_SIGNATURE_INVALID; |
| return CKR_OK; |
| } |
| |
| CK_RV SessionImpl::WrapPrivateKey(Object* object) { |
| if (!tpm_utility_->IsTPMAvailable() || |
| object->GetObjectClass() != CKO_PRIVATE_KEY || |
| object->IsAttributePresent(kKeyBlobAttribute)) { |
| // This object does not need to be wrapped. |
| return CKR_OK; |
| } |
| if (!object->IsAttributePresent(CKA_PUBLIC_EXPONENT) || |
| !object->IsAttributePresent(CKA_MODULUS) || |
| !(object->IsAttributePresent(CKA_PRIME_1) || |
| object->IsAttributePresent(CKA_PRIME_2))) |
| return CKR_TEMPLATE_INCOMPLETE; |
| string prime; |
| if (object->IsAttributePresent(CKA_PRIME_1)) { |
| prime = object->GetAttributeString(CKA_PRIME_1); |
| } else { |
| prime = object->GetAttributeString(CKA_PRIME_2); |
| } |
| int key_size_bits = object->GetAttributeString(CKA_MODULUS).length() * 8; |
| if (key_size_bits > kMaxRSAKeyBitsHW || key_size_bits < kMinRSAKeyBits) { |
| LOG(WARNING) << "WARNING: " << key_size_bits |
| << "-bit private key cannot be wrapped by the TPM."; |
| // Fall back to software. |
| return CKR_OK; |
| } |
| string auth_data = GenerateRandomSoftware(kDefaultAuthDataBytes); |
| string key_blob; |
| int tpm_key_handle = 0; |
| if (!tpm_utility_->WrapKey(slot_id_, |
| object->GetAttributeString(CKA_PUBLIC_EXPONENT), |
| object->GetAttributeString(CKA_MODULUS), |
| prime, |
| SecureBlob(auth_data.data(), auth_data.length()), |
| &key_blob, |
| &tpm_key_handle)) |
| return CKR_FUNCTION_FAILED; |
| object->SetAttributeString(kAuthDataAttribute, auth_data); |
| object->SetAttributeString(kKeyBlobAttribute, key_blob); |
| object->RemoveAttribute(CKA_PRIVATE_EXPONENT); |
| object->RemoveAttribute(CKA_PRIME_1); |
| object->RemoveAttribute(CKA_PRIME_2); |
| object->RemoveAttribute(CKA_EXPONENT_1); |
| object->RemoveAttribute(CKA_EXPONENT_2); |
| object->RemoveAttribute(CKA_COEFFICIENT); |
| return CKR_OK; |
| } |
| |
| SessionImpl::OperationContext::OperationContext() : is_valid_(false), |
| is_cipher_(false), |
| is_digest_(false), |
| is_hmac_(false), |
| is_finished_(false), |
| key_(NULL) {} |
| |
| SessionImpl::OperationContext::~OperationContext() { |
| Clear(); |
| } |
| |
| void SessionImpl::OperationContext::Clear() { |
| if (is_valid_ && !is_finished_) { |
| if (is_cipher_) { |
| EVP_CIPHER_CTX_cleanup(&cipher_context_); |
| } else if (is_digest_) { |
| EVP_MD_CTX_cleanup(&digest_context_); |
| } else if (is_hmac_) { |
| HMAC_CTX_cleanup(&hmac_context_); |
| } |
| } |
| is_valid_ = false; |
| is_cipher_ = false; |
| is_digest_ = false; |
| is_hmac_ = false; |
| is_incremental_ = false; |
| is_finished_ = false; |
| key_ = NULL; |
| data_.clear(); |
| parameter_.clear(); |
| } |
| |
| } // namespace chaps |