blob: ac8c2e11e3514b7a3394b6ea3d548f372be3e261 [file] [log] [blame]
// Copyright 2012 The ChromiumOS Authors
// 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 <iterator>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/check_op.h>
#include <base/functional/bind.h>
#include <base/functional/callback_helpers.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <brillo/secure_blob.h>
#include <chaps/proto_bindings/attributes.pb.h>
#include <chaps/proto_bindings/chaps_wrapped_key_info.pb.h>
#include <chaps/proto_bindings/ck_structs.pb.h>
#include <crypto/libcrypto-compat.h>
#include <crypto/scoped_openssl_types.h>
#include <dbus/chaps/dbus-constants.h>
#include <libhwsec/frontend/chaps/frontend.h>
#include <libhwsec-foundation/status/status_chain_macros.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 <openssl/params.h>
#include <openssl/ec.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 "pkcs11/cryptoki.h"
using brillo::SecureBlob;
using hwsec::TPMError;
using hwsec_foundation::status::MakeStatus;
using ScopedASN1_OCTET_STRING =
crypto::ScopedOpenSSL<ASN1_OCTET_STRING, ASN1_OCTET_STRING_free>;
using std::hex;
using std::map;
using std::set;
using std::string;
using std::vector;
using AllowSoftwareGen = hwsec::ChapsFrontend::AllowSoftwareGen;
using AllowDecrypt = hwsec::ChapsFrontend::AllowDecrypt;
using AllowSign = hwsec::ChapsFrontend::AllowSign;
namespace chaps {
namespace {
using chaps::OperationType::kDecrypt;
using chaps::OperationType::kDigest;
using chaps::OperationType::kEncrypt;
using chaps::OperationType::kSign;
using chaps::OperationType::kVerify;
const int kDefaultAuthDataBytes = 20;
const int kMaxCipherBlockBytes = 16;
const int kMaxRSAOutputBytes = 2048;
const int kMaxDigestOutputBytes = EVP_MAX_MD_SIZE;
const int kMinRSAKeyBits = 512;
const int kMaxRSAKeyBits = kMaxRSAOutputBytes * 8;
// We are willing to generate random data up to 512MiB, this is a tradeoff
// between servicing as much requests as possible and not causing out of memory
// events.
const size_t kMaxRandomBufferSize = 1024 * 1024 * 512;
string GenerateRandomSoftware(size_t num_bytes) {
string random(num_bytes, 0);
RAND_bytes(ConvertStringToByteBuffer(random.data()), num_bytes);
return random;
}
brillo::SecureBlob GenerateRandomSecureBlobSoftware(int num_bytes) {
brillo::SecureBlob random(num_bytes);
RAND_bytes(random.data(), num_bytes);
return random;
}
bool ParsePrfDataParamByteArray(const PrfDataParam& prf_param,
const string* value) {
if (prf_param.type() != CK_SP800_108_BYTE_ARRAY) {
return false;
}
value = &prf_param.value();
return true;
}
CK_RV DeriveKeySP800108Software(const Object& base_key,
Object& derived_key,
const string& mechanism_parameter,
string* key_material) {
CHECK(key_material);
// For simplicity, our implementation only support generating AES or general
// secret keys.
const int key_type =
derived_key.GetAttributeInt(CKA_KEY_TYPE, CK_UNAVAILABLE_INFORMATION);
if (key_type != CKK_AES && key_type != CKK_GENERIC_SECRET) {
return CKR_FUNCTION_NOT_SUPPORTED;
}
// For AES and general secret keys we read the key length from the supplied
// template.
if (!derived_key.IsAttributePresent(CKA_VALUE_LEN))
return CKR_TEMPLATE_INCOMPLETE;
CK_ULONG key_length = derived_key.GetAttributeInt(CKA_VALUE_LEN, 0);
if (key_length < 1)
return CKR_KEY_SIZE_RANGE;
// Parse CK_PRF_DATA_PARAM assuming that it has only two entries, representing
// the label, and context in order.
string label, context;
Sp800108KdfParams kdf_params;
LOG_CK_RV_AND_RETURN_IF(!kdf_params.ParseFromString(mechanism_parameter),
CKR_MECHANISM_PARAM_INVALID);
LOG_CK_RV_AND_RETURN_IF(kdf_params.data_params().size() != 2,
CKR_MECHANISM_PARAM_INVALID);
LOG_CK_RV_AND_RETURN_IF(
!ParsePrfDataParamByteArray(kdf_params.data_params().at(0), &label),
CKR_MECHANISM_PARAM_INVALID);
LOG_CK_RV_AND_RETURN_IF(
!ParsePrfDataParamByteArray(kdf_params.data_params().at(1), &context),
CKR_MECHANISM_PARAM_INVALID);
string base_key_str = base_key.GetAttributeString(CKA_VALUE);
switch (kdf_params.prf_type()) {
case CKK_SHA256_HMAC: {
LOG_CK_RV_AND_RETURN_IF(
!DeriveKeyHmacSha256CounterMode(key_length, base_key_str, label,
context, key_material),
CKR_FUNCTION_FAILED);
break;
}
default: {
LOG(ERROR) << "We only support DeriveKey with CKK_SHA256_HMAC";
return CKR_FUNCTION_NOT_SUPPORTED;
}
}
return CKR_OK;
}
CK_RV ResultToRV(chaps::ObjectPool::Result result, CK_RV fail_rv) {
switch (result) {
case chaps::ObjectPool::Result::Success:
return CKR_OK;
case chaps::ObjectPool::Result::Failure:
return fail_rv;
case chaps::ObjectPool::Result::WaitForPrivateObjects:
return CKR_WOULD_BLOCK_FOR_PRIVATE_OBJECTS;
}
}
const char* OperationToString(OperationType operation) {
switch (operation) {
case kEncrypt:
return "Encrypt";
case kDecrypt:
return "Decrypt";
case kDigest:
return "Digest";
case kSign:
return "Sign";
case kVerify:
return "Verify";
case kNumOperationTypes:
default:
return "Unknown";
}
}
bool IsSuccess(chaps::ObjectPool::Result result) {
return result == chaps::ObjectPool::Result::Success;
}
class MechanismInfo {
public:
explicit MechanismInfo(CK_MECHANISM_TYPE mechanism);
bool IsSupported() const;
bool IsOperationValid(chaps::OperationType op) const;
bool IsForKeyType(CK_KEY_TYPE keytype) const;
private:
struct MechanismInfoData {
bool is_supported;
set<chaps::OperationType> operation;
CK_KEY_TYPE key_type;
};
static MechanismInfoData GetSupportedMechanismInfo(
CK_MECHANISM_TYPE mechanism);
MechanismInfoData data_;
};
MechanismInfo::MechanismInfo(CK_MECHANISM_TYPE mechanism)
: data_(GetSupportedMechanismInfo(mechanism)) {}
bool MechanismInfo::IsSupported() const {
return data_.is_supported;
}
bool MechanismInfo::IsOperationValid(chaps::OperationType op) const {
return IsSupported() && data_.operation.count(op) > 0;
}
bool MechanismInfo::IsForKeyType(CK_KEY_TYPE keytype) const {
return IsSupported() && data_.key_type == keytype;
}
MechanismInfo::MechanismInfoData MechanismInfo::GetSupportedMechanismInfo(
CK_MECHANISM_TYPE mechanism) {
switch (mechanism) {
// DES
case CKM_DES_ECB:
case CKM_DES_CBC:
case CKM_DES_CBC_PAD:
return {true, {kEncrypt, kDecrypt}, CKK_DES};
// DES3
case CKM_DES3_ECB:
case CKM_DES3_CBC:
case CKM_DES3_CBC_PAD:
return {true, {kEncrypt, kDecrypt}, CKK_DES3};
// AES
case CKM_AES_ECB:
case CKM_AES_CBC:
case CKM_AES_CBC_PAD:
return {true, {kEncrypt, kDecrypt}, CKK_AES};
// RSA PKCS v1.5
case CKM_RSA_PKCS:
return {true, {kEncrypt, kDecrypt, kSign, kVerify}, CKK_RSA};
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, {kSign, kVerify}, CKK_RSA};
// RSA RSS
case CKM_RSA_PKCS_PSS:
case CKM_SHA1_RSA_PKCS_PSS:
case CKM_SHA256_RSA_PKCS_PSS:
case CKM_SHA384_RSA_PKCS_PSS:
case CKM_SHA512_RSA_PKCS_PSS:
case CKM_SHA224_RSA_PKCS_PSS:
return {true, {kSign, kVerify}, CKK_RSA};
// ECC
case CKM_ECDSA:
case CKM_ECDSA_SHA1:
case CKM_ECDSA_SHA256:
case CKM_ECDSA_SHA384:
case CKM_ECDSA_SHA512:
return {true, {kSign, kVerify}, CKK_EC};
// HMAC
case CKM_MD5_HMAC:
case CKM_SHA_1_HMAC:
case CKM_SHA256_HMAC:
case CKM_SHA384_HMAC:
case CKM_SHA512_HMAC:
return {true, {kSign, kVerify}, CKK_GENERIC_SECRET};
// Digest
case CKM_MD5:
case CKM_SHA_1:
case CKM_SHA256:
case CKM_SHA384:
case CKM_SHA512:
return {true, {kDigest}, CKK_INVALID_KEY_TYPE};
default:
return {false, {}, CKK_INVALID_KEY_TYPE};
}
}
bool IsHMAC(CK_MECHANISM_TYPE mechanism) {
return MechanismInfo(mechanism).IsForKeyType(CKK_GENERIC_SECRET);
}
// Returns true if the given block cipher (AES/DES) mechanism uses padding.
bool IsPaddingEnabled(CK_MECHANISM_TYPE mechanism) {
switch (mechanism) {
case CKM_DES_CBC_PAD:
case CKM_DES3_CBC_PAD:
case CKM_AES_CBC_PAD:
return true;
default:
return false;
}
}
bool IsRSA(CK_MECHANISM_TYPE mechanism) {
return MechanismInfo(mechanism).IsForKeyType(CKK_RSA);
}
bool IsECC(CK_MECHANISM_TYPE mechanism) {
return MechanismInfo(mechanism).IsForKeyType(CKK_EC);
}
bool IsMechanismValidForOperation(chaps::OperationType operation,
CK_MECHANISM_TYPE mechanism) {
return MechanismInfo(mechanism).IsOperationValid(operation);
}
CK_OBJECT_CLASS GetExpectedObjectClass(chaps::OperationType operation,
CK_KEY_TYPE key_type) {
bool use_private_key = operation == kSign || operation == kDecrypt;
switch (key_type) {
case CKK_DES:
case CKK_DES3:
case CKK_AES:
return CKO_SECRET_KEY;
case CKK_RSA:
case CKK_EC:
return use_private_key ? CKO_PRIVATE_KEY : CKO_PUBLIC_KEY;
case CKK_GENERIC_SECRET:
return CKO_SECRET_KEY;
default:
// Never used
NOTREACHED();
return -1;
}
}
// Check |object_class| and |key_type| is valid for |mechanism| and |operation|
bool IsValidKeyType(chaps::OperationType operation,
CK_MECHANISM_TYPE mechanism,
CK_OBJECT_CLASS object_class,
CK_KEY_TYPE key_type) {
return MechanismInfo(mechanism).IsForKeyType(key_type) &&
object_class == GetExpectedObjectClass(operation, key_type);
}
const EVP_CIPHER* 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 nullptr;
}
string GetDERDigestInfo(CK_MECHANISM_TYPE mechanism) {
return GetDigestAlgorithmEncoding(chaps::GetDigestAlgorithm(mechanism));
}
// TODO(menghuan): Move Create*KeyFromObject to the member function of object.
crypto::ScopedEC_KEY CreateECCPublicKeyFromObject(const Object* key_object) {
// Start parsing EC_PARAMS
string ec_params = key_object->GetAttributeString(CKA_EC_PARAMS);
crypto::ScopedEC_KEY key = chaps::CreateECCKeyFromEC_PARAMS(ec_params);
if (key == nullptr)
return nullptr;
// Start parsing EC_POINT
// DER decode EC_POINT to OCT_STRING
string pub_data = key_object->GetAttributeString(CKA_EC_POINT);
const unsigned char* buf = chaps::ConvertStringToByteBuffer(pub_data.data());
ScopedASN1_OCTET_STRING os(
d2i_ASN1_OCTET_STRING(nullptr, &buf, pub_data.size()));
if (os == nullptr)
return nullptr;
// Convert OCT_STRING to *EC_KEY
buf = os->data;
EC_KEY* key_ptr = key.get();
key_ptr = o2i_ECPublicKey(&key_ptr, &buf, os->length);
if (key_ptr == nullptr)
return nullptr;
CHECK_EQ(key_ptr, key.get());
if (!EC_KEY_check_key(key.get())) {
LOG(ERROR) << __func__
<< ": Bad key created from object. OpenSSL key check fail.";
return nullptr;
}
return key;
}
crypto::ScopedEC_KEY CreateECCPrivateKeyFromObject(const Object& key_object) {
// Parse EC_PARAMS
string ec_params = key_object.GetAttributeString(CKA_EC_PARAMS);
crypto::ScopedEC_KEY key = chaps::CreateECCKeyFromEC_PARAMS(ec_params);
if (key == nullptr)
return nullptr;
crypto::ScopedBIGNUM d(BN_new());
if (!d) {
LOG(ERROR) << "Failed to allocate BIGNUM.";
return nullptr;
}
if (!chaps::ConvertToBIGNUM(key_object.GetAttributeString(CKA_VALUE),
d.get())) {
LOG(ERROR) << "Failed to convert CKA_VALUE to BIGNUM.";
return nullptr;
}
if (!EC_KEY_set_private_key(key.get(), d.get()))
return nullptr;
// OpenSSL will not set public key field. Need to manually compute.
const EC_GROUP* group = EC_KEY_get0_group(key.get());
if (group == nullptr)
return nullptr;
crypto::ScopedEC_POINT pub_key(EC_POINT_new(group));
EC_POINT_mul(group, pub_key.get(), d.get(), nullptr, nullptr, nullptr);
if (!EC_KEY_set_public_key(key.get(), pub_key.get()))
return nullptr;
if (!EC_KEY_check_key(key.get())) {
LOG(ERROR) << __func__
<< ": Bad key created from object. OpenSSL key check fail.";
return nullptr;
}
return key;
}
crypto::ScopedRSA CreateRSAKeyFromObject(const chaps::Object* key_object) {
crypto::ScopedRSA rsa(RSA_new());
if (!rsa) {
LOG(ERROR) << "Failed to allocate RSA or BIGNUM for key.";
return nullptr;
}
if (key_object->GetObjectClass() == CKO_PUBLIC_KEY) {
crypto::ScopedBIGNUM rsa_n(BN_new()), rsa_e(BN_new());
if (!rsa_n || !rsa_e) {
LOG(ERROR) << "Failed to allocate RSA or BIGNUM for key.";
return nullptr;
}
string n = key_object->GetAttributeString(CKA_MODULUS);
string e = key_object->GetAttributeString(CKA_PUBLIC_EXPONENT);
if (!chaps::ConvertToBIGNUM(n, rsa_n.get()) ||
!chaps::ConvertToBIGNUM(e, rsa_e.get())) {
LOG(ERROR) << "Failed to convert modulus or exponent for key.";
return nullptr;
}
if (!RSA_set0_key(rsa.get(), rsa_n.release(), rsa_e.release(), nullptr)) {
LOG(ERROR) << "Failed to set modulus or exponent for RSA.";
return nullptr;
}
} else { // key_object->GetObjectClass() == CKO_PRIVATE_KEY
crypto::ScopedBIGNUM rsa_n(BN_new()), rsa_e(BN_new()), rsa_d(BN_new()),
rsa_p(BN_new()), rsa_q(BN_new()), rsa_dmp1(BN_new()),
rsa_dmq1(BN_new()), rsa_iqmp(BN_new());
if (!rsa_n || !rsa_e || !rsa_d || !rsa_p || !rsa_q || !rsa_dmp1 ||
!rsa_dmq1 || !rsa_iqmp) {
LOG(ERROR) << "Failed to allocate BIGNUM for private key.";
return nullptr;
}
string n = key_object->GetAttributeString(CKA_MODULUS);
string e = key_object->GetAttributeString(CKA_PUBLIC_EXPONENT);
string d = key_object->GetAttributeString(CKA_PRIVATE_EXPONENT);
string p = key_object->GetAttributeString(CKA_PRIME_1);
string q = key_object->GetAttributeString(CKA_PRIME_2);
string dmp1 = key_object->GetAttributeString(CKA_EXPONENT_1);
string dmq1 = key_object->GetAttributeString(CKA_EXPONENT_2);
string iqmp = key_object->GetAttributeString(CKA_COEFFICIENT);
if (!chaps::ConvertToBIGNUM(n, rsa_n.get()) ||
!chaps::ConvertToBIGNUM(e, rsa_e.get()) ||
!chaps::ConvertToBIGNUM(d, rsa_d.get()) ||
!chaps::ConvertToBIGNUM(p, rsa_p.get()) ||
!chaps::ConvertToBIGNUM(q, rsa_q.get()) ||
!chaps::ConvertToBIGNUM(dmp1, rsa_dmp1.get()) ||
!chaps::ConvertToBIGNUM(dmq1, rsa_dmq1.get()) ||
!chaps::ConvertToBIGNUM(iqmp, rsa_iqmp.get())) {
LOG(ERROR) << "Failed to convert parameters for private key.";
return nullptr;
}
if (!RSA_set0_key(rsa.get(), rsa_n.release(), rsa_e.release(),
rsa_d.release()) ||
!RSA_set0_factors(rsa.get(), rsa_p.release(), rsa_q.release()) ||
!RSA_set0_crt_params(rsa.get(), rsa_dmp1.release(), rsa_dmq1.release(),
rsa_iqmp.release())) {
LOG(ERROR) << "Failed to set parameters for private key RSA.";
return nullptr;
}
}
return rsa;
}
// TODO(crbug/916023): Move OpenSSL utility to cross daemon library.
// Return the length (in bytes) of group order of the EC key |key| or 0 on error
// which is aligned with OpenSSL design.
size_t GetGroupOrderLengthFromEcKey(const crypto::ScopedEC_KEY& key) {
crypto::ScopedBIGNUM order(BN_new());
if (!order) {
LOG(ERROR) << "Failed to allocate BIGNUM for EC key order.";
return 0;
}
const EC_GROUP* group = EC_KEY_get0_group(key.get());
if (group == nullptr)
return 0;
if (!EC_GROUP_get_order(group, order.get(), nullptr))
return 0;
return BN_num_bytes(order.get());
}
// RSA Sign/Verify Helper
class RSASignerVerifier {
public:
// Just a default destructor, nothing more, nothing less.
// Note: It's here because this is a base class, and we want to avoid having
// non-virtual destructor in base class.
virtual ~RSASignerVerifier() = default;
// Sign |context->data_| with |rsa|.
virtual bool Sign(crypto::ScopedRSA rsa,
SessionImpl::OperationContext* context) = 0;
// Verify |signature| of |digest| against |rsa|.
virtual CK_RV Verify(crypto::ScopedRSA rsa,
SessionImpl::OperationContext* context,
const string& digest,
const string& signature) = 0;
static std::unique_ptr<RSASignerVerifier> GetForMechanism(
CK_MECHANISM_TYPE mechanism);
};
// Sign/Verify helper for PKCS#1 v1.5
class RSASignerVerifierImplPKCS115 : public RSASignerVerifier {
public:
virtual ~RSASignerVerifierImplPKCS115() = default;
bool Sign(crypto::ScopedRSA rsa,
SessionImpl::OperationContext* context) final {
if (RSA_size(rsa.get()) > kMaxRSAOutputBytes) {
LOG(ERROR) << __func__ << ": RSA Key size is too large for PKCS#1 v1.5.";
return false;
}
uint8_t buffer[kMaxRSAOutputBytes];
// Emulate RSASSA by performing raw RSA (decrypting) with RSA_PKCS1_PADDING
string input = GetDERDigestInfo(context->mechanism_) + context->data_;
int length = RSA_private_encrypt(
input.length(), ConvertStringToByteBuffer(input.data()), buffer,
rsa.get(), RSA_PKCS1_PADDING); // Adds PKCS #1 type 1 padding.
if (length == -1) {
LOG(ERROR) << __func__ << ": RSA_private_encrypt failed for PKCS#1 v1.5: "
<< GetOpenSSLError();
return false;
}
// Set the signature in context->data_.
context->data_ = string(reinterpret_cast<char*>(buffer), length);
return true;
};
CK_RV Verify(crypto::ScopedRSA rsa,
SessionImpl::OperationContext* context,
const string& digest,
const string& signature) final {
if (RSA_size(rsa.get()) > kMaxRSAOutputBytes) {
LOG(ERROR) << __func__ << ": RSA Key size is too large for PKCS#1 v1.5.";
return CKR_KEY_SIZE_RANGE;
}
uint8_t buffer[kMaxRSAOutputBytes];
int length = RSA_public_decrypt(
signature.length(), ConvertStringToByteBuffer(signature.data()), buffer,
rsa.get(), RSA_PKCS1_PADDING); // Strips PKCS #1 type 1 padding.
if (length == -1) {
LOG(ERROR) << __func__ << ": RSA_public_decrypt failed for PKCS#1 v1.5: "
<< GetOpenSSLError();
return CKR_SIGNATURE_INVALID;
}
string signed_data = GetDERDigestInfo(context->mechanism_) + digest;
if (static_cast<size_t>(length) != signed_data.length() ||
0 != brillo::SecureMemcmp(buffer, signed_data.data(), length)) {
return CKR_SIGNATURE_INVALID;
}
return CKR_OK;
};
};
// Sign/Verify helper for RSA PSS
class RSASignerVerifierImplPSS : public RSASignerVerifier {
public:
virtual ~RSASignerVerifierImplPSS() = default;
bool Sign(crypto::ScopedRSA rsa,
SessionImpl::OperationContext* context) final {
if (RSA_size(rsa.get()) > kMaxRSAOutputBytes) {
LOG(ERROR) << __func__ << ": RSA Key size is too large for RSA PSS.";
return false;
}
uint8_t buffer[kMaxRSAOutputBytes];
DigestAlgorithm digest_algorithm = GetDigestAlgorithm(context->mechanism_);
// Parse the RSA PSS Parameters.
const CK_RSA_PKCS_PSS_PARAMS* pss_params = nullptr;
const EVP_MD* mgf1_hash = nullptr;
if (!ParseRSAPSSParams(context->parameter_, digest_algorithm, &pss_params,
&mgf1_hash, &digest_algorithm)) {
LOG(ERROR) << __func__ << ": Failed to parse RSA PSS parameters.";
return false;
}
string padded_data(RSA_size(rsa.get()), 0);
if (RSA_padding_add_PKCS1_PSS_mgf1(
rsa.get(), reinterpret_cast<unsigned char*>(std::data(padded_data)),
reinterpret_cast<const unsigned char*>(std::data(context->data_)),
GetOpenSSLDigest(digest_algorithm), mgf1_hash,
pss_params->sLen) != 1) {
LOG(ERROR) << __func__ << ": Failed to produce the PSA PSS paddings.";
return false;
}
int length = RSA_private_encrypt(
padded_data.length(), ConvertStringToByteBuffer(padded_data.data()),
buffer, rsa.get(), RSA_NO_PADDING);
if (length == -1) {
LOG(ERROR) << __func__
<< ": RSA_private_encrypt failed: " << GetOpenSSLError();
return false;
}
// Set the signature in context->data_.
context->data_ = string(reinterpret_cast<char*>(buffer), length);
return true;
};
CK_RV Verify(crypto::ScopedRSA rsa,
SessionImpl::OperationContext* context,
const string& digest,
const string& signature) final {
if (RSA_size(rsa.get()) > kMaxRSAOutputBytes) {
LOG(ERROR) << __func__ << ": RSA Key size is too large for RSA PSS.";
return CKR_KEY_SIZE_RANGE;
}
DigestAlgorithm digest_algorithm = GetDigestAlgorithm(context->mechanism_);
uint8_t buffer[kMaxRSAOutputBytes];
// Parse the RSA PSS Parameters.
const CK_RSA_PKCS_PSS_PARAMS* pss_params = nullptr;
const EVP_MD* mgf1_hash = nullptr;
if (!ParseRSAPSSParams(context->parameter_, digest_algorithm, &pss_params,
&mgf1_hash, &digest_algorithm)) {
LOG(ERROR) << __func__ << ": Failed to parse RSA PSS parameters.";
return CKR_SIGNATURE_INVALID;
}
int expected_size = EVP_MD_size(GetOpenSSLDigest(digest_algorithm));
if (digest.size() != expected_size) {
LOG(ERROR) << __func__ << ": Size mismatch with RSAPSS, expected "
<< expected_size << ", actual " << digest.size();
return CKR_SIGNATURE_INVALID;
}
int length = RSA_public_decrypt(signature.length(),
ConvertStringToByteBuffer(signature.data()),
buffer, rsa.get(), RSA_NO_PADDING);
if (length == -1) {
LOG(ERROR) << __func__
<< ": RSA_public_decrypt failed: " << GetOpenSSLError();
return CKR_SIGNATURE_INVALID;
}
if (RSA_verify_PKCS1_PSS_mgf1(
rsa.get(), reinterpret_cast<const unsigned char*>(digest.data()),
GetOpenSSLDigest(digest_algorithm), mgf1_hash, buffer,
pss_params->sLen) != 1) {
LOG(ERROR) << __func__ << ": Incorrect PSS padding.";
return CKR_SIGNATURE_INVALID;
}
return CKR_OK;
};
};
std::unique_ptr<RSASignerVerifier> RSASignerVerifier::GetForMechanism(
CK_MECHANISM_TYPE mechanism) {
auto scheme = GetSigningSchemeForMechanism(mechanism);
switch (scheme) {
case RsaPaddingScheme::RSASSA_PKCS1_V1_5:
return std::make_unique<RSASignerVerifierImplPKCS115>();
case RsaPaddingScheme::RSASSA_PSS:
return std::make_unique<RSASignerVerifierImplPSS>();
default:
LOG(ERROR) << __func__ << ": Invalid mechanism "
<< static_cast<int64_t>(mechanism);
return nullptr;
}
}
hwsec::DigestAlgorithm ChapsToHwsecDigestAlg(DigestAlgorithm alg) {
switch (alg) {
case DigestAlgorithm::MD5:
return hwsec::DigestAlgorithm::kMd5;
case DigestAlgorithm::SHA1:
return hwsec::DigestAlgorithm::kSha1;
case DigestAlgorithm::SHA256:
return hwsec::DigestAlgorithm::kSha256;
case DigestAlgorithm::SHA384:
return hwsec::DigestAlgorithm::kSha384;
case DigestAlgorithm::SHA512:
return hwsec::DigestAlgorithm::kSha512;
default:
return hwsec::DigestAlgorithm::kNoDigest;
}
}
std::optional<hwsec::SigningOptions::RsaPaddingScheme>
ChapsToHwsecRsaPaddingScheme(RsaPaddingScheme scheme) {
switch (scheme) {
case RsaPaddingScheme::RSASSA_PKCS1_V1_5:
return hwsec::SigningOptions::RsaPaddingScheme::kPkcs1v15;
case RsaPaddingScheme::RSASSA_PSS:
return hwsec::SigningOptions::RsaPaddingScheme::kRsassaPss;
default:
return std::nullopt;
}
}
hwsec::DigestAlgorithm GetHwsecDigestForMGF(const CK_RSA_PKCS_MGF_TYPE mgf) {
switch (mgf) {
case CKG_MGF1_SHA1:
return hwsec::DigestAlgorithm::kSha1;
case CKG_MGF1_SHA224:
return hwsec::DigestAlgorithm::kSha224;
case CKG_MGF1_SHA256:
return hwsec::DigestAlgorithm::kSha256;
case CKG_MGF1_SHA384:
return hwsec::DigestAlgorithm::kSha384;
case CKG_MGF1_SHA512:
return hwsec::DigestAlgorithm::kSha512;
default:
return hwsec::DigestAlgorithm::kNoDigest;
}
}
std::optional<hwsec::SigningOptions::PssParams> GetHwsecPssParams(
const std::string& mechanism_parameter, DigestAlgorithm& digest_algorithm) {
const CK_RSA_PKCS_PSS_PARAMS* pss_params = nullptr;
const EVP_MD* mgf1_hash = nullptr;
// Check the parameters
if (!ParseRSAPSSParams(mechanism_parameter, digest_algorithm, &pss_params,
&mgf1_hash, &digest_algorithm)) {
LOG(ERROR) << "Failed to parse RSA PSS parameters.";
return std::nullopt;
}
return hwsec::SigningOptions::PssParams{
.mgf1_algorithm = GetHwsecDigestForMGF(pss_params->mgf),
.salt_length = pss_params->sLen,
};
}
hwsec::SigningOptions ToHwsecSigningOptions(
CK_MECHANISM_TYPE signing_mechanism,
const std::string& mechanism_parameter) {
// Parse the various parameters for this method.
DigestAlgorithm digest_algorithm = GetDigestAlgorithm(signing_mechanism);
// Parse RSA PSS Parameters if applicable.
const RsaPaddingScheme padding_scheme =
GetSigningSchemeForMechanism(signing_mechanism);
std::optional<hwsec::SigningOptions::PssParams> pss_params;
if (padding_scheme == RsaPaddingScheme::RSASSA_PSS) {
pss_params = GetHwsecPssParams(mechanism_parameter, digest_algorithm);
}
return hwsec::SigningOptions{
.digest_algorithm = ChapsToHwsecDigestAlg(digest_algorithm),
.rsa_padding_scheme = ChapsToHwsecRsaPaddingScheme(padding_scheme),
.pss_params = std::move(pss_params),
};
}
AllowDecrypt IsHwsecObjectAllowDecrypt(const Object& object) {
// For a hwsec key unwrapping is same as decrypting.
if (object.GetAttributeBool(CKA_DECRYPT, false) ||
object.GetAttributeBool(CKA_UNWRAP, false))
return AllowDecrypt::kAllow;
else
return AllowDecrypt::kNotAllow;
}
std::optional<string> AESKeyWrapWithPadding(bool is_encrypt,
const string& key_material,
const string& data_in) {
crypto::ScopedEVP_CIPHER_CTX ctx(EVP_CIPHER_CTX_new());
// Reasons for using AES wrap pad here:
// - AES Key Wrap provides both confidentiality and authentication. There are
// other schemes that provides the same but they requires the use of a
// unique IV, adding unnecessary complexity.
// - Performance is not critical in this use case (otherwise AES GCM would
// standout), so it's OK to use a slower scheme such as AES Key Wrap.
// - AES Key Wrap guarantees that each bit of output can be expected to depend
// in a nontrivial fashion on each bit of input.
const EVP_CIPHER* cipher = EVP_aes_256_wrap_pad();
EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr,
ConvertStringToByteBuffer(key_material.data()),
nullptr, is_encrypt)) {
LOG(ERROR) << "EVP_CipherInit failed: " << GetOpenSSLError();
return std::nullopt;
}
int update_length = data_in.length() + kMaxCipherBlockBytes;
string data_out;
data_out.resize(update_length + 2 * kMaxCipherBlockBytes);
if (!EVP_CipherUpdate(
ctx.get(), ConvertStringToByteBuffer(data_out.data()), &update_length,
ConvertStringToByteBuffer(data_in.data()), data_in.length())) {
LOG(ERROR) << "EVP_CipherUpdate failed: " << GetOpenSSLError();
return std::nullopt;
}
int final_length;
if (!EVP_CipherFinal_ex(
ctx.get(), ConvertStringToByteBuffer(data_out.data()) + update_length,
&final_length)) {
LOG(ERROR) << "EVP_CipherFinal failed: " << GetOpenSSLError();
return std::nullopt;
}
data_out.resize(update_length + final_length);
return data_out;
}
} // namespace
SessionImpl::SessionImpl(int slot_id,
ObjectPool* token_object_pool,
const hwsec::ChapsFrontend* hwsec,
ChapsFactory* factory,
HandleGenerator* handle_generator,
bool is_read_only,
ChapsMetrics* chaps_metrics)
: factory_(factory),
find_results_valid_(false),
is_read_only_(is_read_only),
slot_id_(slot_id),
token_object_pool_(token_object_pool),
hwsec_(hwsec),
chaps_metrics_(chaps_metrics) {
CHECK(token_object_pool_);
CHECK(factory_);
CHECK(chaps_metrics_);
// If the hwsec is nullptr, means it's not ready to use.
if (hwsec_ == nullptr) {
LOG(WARNING) << "HWSec is not available";
}
session_object_pool_.reset(
factory_->CreateObjectPool(handle_generator, nullptr, nullptr));
CHECK(session_object_pool_.get());
}
SessionImpl::~SessionImpl() {
for (OperationContext& context : operation_context_) {
if (context.is_valid_) {
LOG(WARNING) << "Valid context exists when session is closing.";
if (context.cleanup_) {
context.cleanup_.RunAndReset();
}
}
}
if (!object_count_map_.empty()) {
LOG(WARNING) << "Remaining object exists.";
}
}
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, nullptr,
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 = nullptr;
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 = nullptr;
if (!GetObject(object_handle, &object))
return CKR_OBJECT_HANDLE_INVALID;
CHECK(object);
ObjectPool* pool =
object->IsTokenObject() ? token_object_pool_ : session_object_pool_.get();
return ResultToRV(pool->Delete(object), CKR_GENERAL_ERROR);
}
bool SessionImpl::GetObject(int object_handle, const Object** object) {
CHECK(object);
if (token_object_pool_->FindByHandle(object_handle, object) ==
ObjectPool::Result::Success)
return true;
return session_object_pool_->FindByHandle(object_handle, object) ==
ObjectPool::Result::Success;
}
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;
}
CK_RV SessionImpl::FlushModifiableObject(Object* object) {
CHECK(object);
ObjectPool* pool =
object->IsTokenObject() ? token_object_pool_ : session_object_pool_.get();
return ResultToRV(pool->Flush(object), CKR_FUNCTION_FAILED);
}
CK_RV SessionImpl::FindObjectsInit(const CK_ATTRIBUTE_PTR attributes,
int num_attributes) {
if (find_results_valid_)
return CKR_OPERATION_ACTIVE;
std::unique_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()) {
auto res = token_object_pool_->Find(search_template.get(), &objects);
if (!IsSuccess(res))
return ResultToRV(res, CKR_GENERAL_ERROR);
}
if (!search_template->IsAttributePresent(CKA_TOKEN) ||
!search_template->IsTokenObject()) {
auto res = session_object_pool_->Find(search_template.get(), &objects);
if (!IsSuccess(res))
return ResultToRV(res, 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);
CK_RV result =
OperationInitRaw(operation, mechanism, mechanism_parameter, key);
chaps_metrics_->ReportChapsSessionStatus(OperationToString(operation),
static_cast<int>(result));
return result;
}
CK_RV SessionImpl::OperationInitRaw(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 (!IsMechanismValidForOperation(operation, mechanism)) {
LOG(ERROR) << "Mechanism not supported: 0x" << hex << mechanism;
return CKR_MECHANISM_INVALID;
}
if (operation == kSign || operation == kVerify || operation == kEncrypt ||
operation == kDecrypt) {
// Make sure the key is valid for the mechanism.
CHECK(key);
if (!IsValidKeyType(
operation, mechanism, key->GetObjectClass(),
key->GetAttributeInt(CKA_KEY_TYPE, CK_UNAVAILABLE_INFORMATION))) {
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)) {
// Refuse to use RSA keys with unsupported sizes that may have been
// created in an earlier version of chaps.
int key_size = key->GetAttributeString(CKA_MODULUS).length() * 8;
if (key_size < kMinRSAKeyBits || key_size > kMaxRSAKeyBits) {
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 if (operation == kSign || operation == kVerify ||
operation == kDigest) {
// It is valid for GetOpenSSLDigestForMechanism to return NULL (e.g.
// CKM_RSA_PKCS).
const EVP_MD* digest = GetOpenSSLDigestForMechanism(mechanism);
if (IsHMAC(mechanism)) {
string key_material = key->GetAttributeString(CKA_VALUE);
context->hmac_context_.reset(HMAC_CTX_new());
if (!context->hmac_context_) {
LOG(ERROR) << "Failed to allocate HMAC context";
return CKR_FUNCTION_FAILED;
}
HMAC_Init_ex(context->hmac_context_.get(), key_material.data(),
key_material.length(), digest, nullptr);
context->is_hmac_ = true;
} else if (digest) {
context->digest_context_.reset(EVP_MD_CTX_new());
if (!context->digest_context_) {
LOG(ERROR) << "Failed to allocate EVP_MD context";
return CKR_FUNCTION_FAILED;
}
EVP_DigestInit_ex(context->digest_context_.get(), digest, nullptr);
context->is_digest_ = true;
}
if (IsRSA(mechanism) || IsECC(mechanism))
context->key_ = key;
context->is_valid_ = true;
} else {
NOTREACHED();
return CKR_FUNCTION_FAILED;
}
UpdateObjectCount(context);
return CKR_OK;
}
CK_RV SessionImpl::OperationUpdate(OperationType operation,
const string& data_in,
int* required_out_length,
string* data_out) {
CHECK(operation < kNumOperationTypes);
CK_RV result =
OperationUpdateRaw(operation, data_in, required_out_length, data_out);
chaps_metrics_->ReportChapsSessionStatus(OperationToString(operation),
static_cast<int>(result));
return result;
}
CK_RV SessionImpl::OperationUpdateRaw(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_) {
return CKR_OPERATION_NOT_INITIALIZED;
}
if (context->is_finished_) {
LOG(ERROR) << "Operation is finished.";
OperationCancel(operation);
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_) {
CK_RV rv = CipherUpdate(context, data_in, required_out_length, data_out);
if ((rv != CKR_OK) && (rv != CKR_BUFFER_TOO_SMALL))
OperationCancel(operation);
return rv;
} else if (context->is_digest_) {
EVP_DigestUpdate(context->digest_context_.get(), data_in.data(),
data_in.length());
} else if (context->is_hmac_) {
HMAC_Update(context->hmac_context_.get(),
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;
}
void SessionImpl::OperationCancel(OperationType operation) {
CHECK(operation < kNumOperationTypes);
OperationContext* context = &operation_context_[operation];
if (!context->is_valid_) {
LOG(ERROR) << "Operation is not initialized.";
return;
}
// Drop the context and any associated data.
context->Clear();
}
CK_RV SessionImpl::OperationFinal(OperationType operation,
int* required_out_length,
string* data_out) {
CHECK(required_out_length);
CHECK(data_out);
CHECK(operation < kNumOperationTypes);
CK_RV result = OperationFinalRaw(operation, required_out_length, data_out);
chaps_metrics_->ReportChapsSessionStatus(OperationToString(operation),
static_cast<int>(result));
return result;
}
CK_RV SessionImpl::OperationFinalRaw(OperationType operation,
int* required_out_length,
string* data_out,
bool clear_context) {
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.";
OperationCancel(operation);
return CKR_OPERATION_ACTIVE;
}
context->is_incremental_ = true;
return OperationFinalInternal(operation, required_out_length, data_out,
clear_context);
}
CK_RV SessionImpl::OperationFinalInternal(OperationType operation,
int* required_out_length,
string* data_out,
bool clear_context) {
CHECK(operation < kNumOperationTypes);
OperationContext* context = &operation_context_[operation];
base::ScopedClosureRunner context_clear_runner(base::DoNothing());
if (clear_context) {
context_clear_runner.ReplaceClosure(
base::BindOnce(&OperationContext::Clear, base::Unretained(context)));
}
// 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_ex(context->digest_context_.get(), 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_.get(), buffer, &out_length);
context->data_ = string(reinterpret_cast<char*>(buffer), out_length);
}
// Some RSA/ECC mechanisms use a digest so it's important to finish the
// digest before finishing the RSA/ECC 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;
}
} else if (IsECC(context->mechanism_)) {
if (operation == kSign) {
if (!ECCSign(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;
context_clear_runner.ReplaceClosure(base::DoNothing());
}
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 = std::numeric_limits<int>::max();
string data_out;
CK_RV result = OperationFinalRaw(kVerify, &max_out_length, &data_out,
/*clear_context=*/false);
chaps_metrics_->ReportChapsSessionStatus(OperationToString(kVerify),
static_cast<int>(result));
if (result != CKR_OK)
return result;
base::ScopedClosureRunner context_clear_runner(
base::BindOnce(&OperationContext::Clear, base::Unretained(context)));
// We only support 3 Verify mechanisms, HMAC, RSA and ECC.
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 != brillo::SecureMemcmp(signature.data(), data_out.data(),
signature.length()))
return CKR_SIGNATURE_INVALID;
return CKR_OK;
} else if (IsRSA(context->mechanism_)) {
// The data_out contents will be the computed digest.
return RSAVerify(context, data_out, signature);
} else if (IsECC(context->mechanism_)) {
// The data_out contents will be the computed digest.
return ECCVerify(context, data_out, signature);
} else {
NOTREACHED();
return false;
}
}
CK_RV SessionImpl::OperationSinglePart(OperationType operation,
const string& data_in,
int* required_out_length,
string* data_out) {
CHECK(operation < kNumOperationTypes);
CK_RV result =
OperationSinglePartRaw(operation, data_in, required_out_length, data_out);
chaps_metrics_->ReportChapsSessionStatus(OperationToString(operation),
static_cast<int>(result));
return result;
}
CK_RV SessionImpl::OperationSinglePartRaw(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.";
OperationCancel(operation);
return CKR_OPERATION_ACTIVE;
}
CK_RV result = CKR_OK;
if (!context->is_finished_) {
string update, final;
int max = std::numeric_limits<int>::max();
result = OperationUpdateInternal(operation, data_in, &max, &update);
if (result != CKR_OK) {
return result;
}
max = std::numeric_limits<int>::max();
result = OperationFinalInternal(operation, &max, &final);
if (result != CKR_OK) {
return result;
}
context->data_ = update + final;
context->is_finished_ = true;
}
base::ScopedClosureRunner context_clear_runner(
base::BindOnce(&OperationContext::Clear, base::Unretained(context)));
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;
context_clear_runner.ReplaceClosure(base::DoNothing());
}
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);
std::unique_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;
CK_ULONG 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;
CK_ULONG 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();
auto pool_res = pool->Insert(object.get());
if (!IsSuccess(pool_res))
return ResultToRV(pool_res, 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);
// Create public/private key objects
std::unique_ptr<Object> public_object(factory_->CreateObject());
CHECK(public_object.get());
std::unique_ptr<Object> private_object(factory_->CreateObject());
CHECK(private_object.get());
// copy attributes
// TODO(menghuan): don't copy the attribute that doesn't support
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;
// Get the object pool
ObjectPool* public_pool =
(public_object->IsTokenObject() ? token_object_pool_
: session_object_pool_.get());
ObjectPool* private_pool =
(private_object->IsTokenObject() ? token_object_pool_
: session_object_pool_.get());
switch (mechanism) {
case CKM_RSA_PKCS_KEY_PAIR_GEN:
result = GenerateRSAKeyPair(public_object.get(), private_object.get());
break;
case CKM_EC_KEY_PAIR_GEN:
result = GenerateECCKeyPair(public_object.get(), private_object.get());
break;
default:
LOG(ERROR) << __func__ << ": Mechanism not supported: " << hex
<< mechanism;
return CKR_MECHANISM_INVALID;
}
if (result != CKR_OK) {
return result;
}
// Set the general attributes for public / private key
public_object->SetAttributeInt(CKA_CLASS, CKO_PUBLIC_KEY);
private_object->SetAttributeInt(CKA_CLASS, CKO_PRIVATE_KEY);
// The CKA_KEY_GEN_MECHANISM attribute identifies the key generation mechanism
// used to generate the key material. It contains a valid value only if the
// CKA_LOCAL attribute has the value CK_TRUE. If CKA_LOCAL has the value
// CK_FALSE, the value of the attribute is CK_UNAVAILABLE_INFORMATION.
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);
// Finalize the objects
result = public_object->FinalizeNewObject();
if (result != CKR_OK) {
LOG(ERROR) << __func__ << ": Fail to finalize public object.";
return result;
}
result = private_object->FinalizeNewObject();
if (result != CKR_OK) {
LOG(ERROR) << __func__ << ": Fail to finalize private object.";
return result;
}
auto pool_res = public_pool->Insert(public_object.get());
if (!IsSuccess(pool_res)) {
LOG(ERROR) << __func__ << ": Fail to insert public object to public pool.";
return ResultToRV(pool_res, CKR_FUNCTION_FAILED);
}
pool_res = private_pool->Insert(private_object.get());
if (!IsSuccess(pool_res)) {
LOG(ERROR) << __func__
<< ": Fail to insert private object to private pool.";
// Remove inserted public object.
// The object will be destroy in Delete(), we should release uniptr.
public_pool->Delete(public_object.release());
return ResultToRV(pool_res, 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::WrapKey(CK_MECHANISM_TYPE mechanism,
const std::string& mechanism_parameter,
const Object* wrapping_key,
const Object& key,
int* required_out_length,
string* wrapped_key) {
CHECK(required_out_length);
CHECK(wrapped_key);
string data;
// kChapsKeyWrapMechanism is a chaps-specific mechanism that doesn't require
// a wrapping key.
CHECK_EQ(mechanism == kChapsKeyWrapMechanism, !wrapping_key);
CK_RV result = mechanism == kChapsKeyWrapMechanism
? WrapKeyWithChaps(mechanism_parameter, key, &data)
: WrapKeyInternal(mechanism, mechanism_parameter,
*wrapping_key, key, &data);
if (result != CKR_OK) {
return result;
}
int out_length = data.size();
int max_length = *required_out_length;
*required_out_length = out_length;
if (max_length < out_length) {
return CKR_BUFFER_TOO_SMALL;
}
*wrapped_key = data;
return CKR_OK;
}
CK_RV SessionImpl::WrapKeyInternal(CK_MECHANISM_TYPE mechanism,
const std::string& mechanism_parameter,
const Object& wrapping_key,
const Object& key,
string* wrapped_key) {
LOG_CK_RV_AND_RETURN_IF(!key.GetAttributeBool(CKA_EXTRACTABLE, false),
CKR_KEY_UNEXTRACTABLE);
LOG_CK_RV_AND_RETURN_IF(!wrapping_key.GetAttributeBool(CKA_WRAP, false),
CKR_KEY_FUNCTION_NOT_PERMITTED);
LOG_CK_RV_AND_RETURN_IF(
key.GetAttributeBool(CKA_WRAP_WITH_TRUSTED, false) &&
!wrapping_key.GetAttributeBool(CKA_TRUSTED, false),
CKR_KEY_NOT_WRAPPABLE);
switch (mechanism) {
case CKM_RSA_PKCS_OAEP: {
LOG_CK_RV_AND_RETURN_IF(key.GetObjectClass() != CKO_SECRET_KEY,
CKR_KEY_NOT_WRAPPABLE);
LOG_CK_RV_AND_RETURN_IF_ERR(
WrapKeyRSAOAEP(wrapping_key, key, wrapped_key));
break;
}
default: {
LOG(ERROR) << "WrapKey: mechanism not supported: " << hex << mechanism;
return CKR_MECHANISM_INVALID;
}
}
return CKR_OK;
}
CK_RV SessionImpl::WrapKeyRSAOAEP(const Object& wrapping_key,
const Object& key,
std::string* wrapped_key) {
if (wrapping_key.GetObjectClass() != CKO_PUBLIC_KEY ||
wrapping_key.GetAttributeInt(CKA_KEY_TYPE, CK_UNAVAILABLE_INFORMATION) !=
CKK_RSA) {
LOG(ERROR) << __func__ << "The wrapping key should be a RSA public key.";
return CKR_WRAPPING_KEY_TYPE_INCONSISTENT;
}
crypto::ScopedRSA rsa = CreateRSAKeyFromObject(&wrapping_key);
if (!rsa) {
LOG(ERROR) << "Failed to create RSA key for encryption.";
return CKR_FUNCTION_FAILED;
}
if (RSA_size(rsa.get()) > kMaxRSAOutputBytes) {
LOG(ERROR) << __func__ << ": RSA Key size is too large for RSA OAEP.";
return CKR_WRAPPING_KEY_SIZE_RANGE;
}
uint8_t buffer[kMaxRSAOutputBytes];
int length = RSA_public_encrypt(
key.GetAttributeInt(CKA_VALUE_LEN, 0),
chaps::ConvertStringToByteBuffer(
key.GetAttributeString(CKA_VALUE).data()),
buffer, rsa.get(),
RSA_PKCS1_OAEP_PADDING); // using EME-OAEP for padding.
if (length == -1) {
LOG(ERROR) << __func__
<< "RSA_public_encrypt failed: " << GetOpenSSLError();
return CKR_FUNCTION_FAILED;
}
CHECK(length <= std::size(buffer));
*wrapped_key = chaps::ConvertByteBufferToString(buffer, length);
return CKR_OK;
}
CK_RV SessionImpl::WrapKeyWithChaps(const std::string& mechanism_parameter,
const Object& key,
string* wrapped_key) {
LOG_CK_RV_AND_RETURN_IF(
!key.GetAttributeBool(kChapsWrappableAttribute, false),
CKR_KEY_NOT_WRAPPABLE);
// Generates a temporary random AES key of length=32 (not accessible to the
// user).
string base_aes_key = GenerateRandomSoftware(32);
// Using the base_aes_key and Chaps' secret random seed to derive the actual
// wrapping key.
string derived_aes_key = HmacSha512(base_aes_key, factory_->GetRandomSeed());
// Wraps the target key with derived_aes_key using CKM_AES_KEY_WRAP_KWP
const AttributeMap* attribute_map = key.GetAttributeMap();
AttributeMap::const_iterator it;
AttributeList attribute_list;
for (const auto& entry : *attribute_map) {
Attribute* next = attribute_list.add_attributes();
next->set_type(entry.first);
next->set_length(entry.second.length());
next->set_value(entry.second);
}
string serialized_key_object;
if (!attribute_list.SerializeToString(&serialized_key_object)) {
LOG(ERROR) << "Failed to serialize object.";
return CKR_FUNCTION_FAILED;
}
auto data = AESKeyWrapWithPadding(/* is_encrypt */ true, derived_aes_key,
serialized_key_object);
if (data == std::nullopt)
return CKR_FUNCTION_FAILED;
const string& wrapped_target_key = data.value();
// Zeroizes the temporary AES key
derived_aes_key.clear();
// Fill the sealed AES key and the wrapped target key into some protobuf and
// output the serialized result.
ChapsWrappedKeyInfo key_info;
key_info.set_base_aes_key(base_aes_key);
key_info.set_wrapped_attribute_list(wrapped_target_key);
*wrapped_key = key_info.SerializeAsString();
return CKR_OK;
}
CK_RV SessionImpl::UnwrapKey(CK_MECHANISM_TYPE mechanism,
const std::string& mechanism_parameter,
const Object* unwrapping_key,
const std::string& wrapped_key,
const CK_ATTRIBUTE_PTR attributes,
int num_attributes,
int* new_key_handle) {
CHECK(new_key_handle);
// kChapsKeyWrapMechanism is a chaps-specific mechanism that doesn't require
// an unwrapping key.
CHECK_EQ(mechanism == kChapsKeyWrapMechanism, !unwrapping_key);
return mechanism == kChapsKeyWrapMechanism
? UnwrapKeyWithChaps(mechanism_parameter, wrapped_key,
new_key_handle)
: UnwrapKeyInternal(mechanism, mechanism_parameter,
*unwrapping_key, wrapped_key, attributes,
num_attributes, new_key_handle);
}
CK_RV SessionImpl::UnwrapKeyInternal(CK_MECHANISM_TYPE mechanism,
const std::string& mechanism_parameter,
const Object& unwrapping_key,
const string& wrapped_key,
const CK_ATTRIBUTE_PTR attributes,
int num_attributes,
int* new_key_handle) {
LOG_CK_RV_AND_RETURN_IF(!unwrapping_key.GetAttributeBool(CKA_UNWRAP, false),
CKR_KEY_FUNCTION_NOT_PERMITTED);
std::unique_ptr<Object> object(factory_->CreateObject());
CHECK(object.get());
LOG_CK_RV_AND_RETURN_IF_ERR(
object->SetAttributes(attributes, num_attributes));
switch (mechanism) {
case CKM_RSA_PKCS_OAEP: {
LOG_CK_RV_AND_RETURN_IF_ERR(
UnwrapKeyRSAOAEP(unwrapping_key, *object.get(), wrapped_key));
break;
}
default: {
LOG(ERROR) << "UnwrapKey: mechanism not supported: " << hex << mechanism;
return CKR_MECHANISM_INVALID;
}
}
object->SetAttributeBool(CKA_LOCAL, false);
object->SetAttributeBool(CKA_ALWAYS_SENSITIVE, false);
object->SetAttributeBool(CKA_NEVER_EXTRACTABLE, false);
if (!object->IsAttributePresent(CKA_EXTRACTABLE))
object->SetAttributeBool(CKA_EXTRACTABLE, true);
LOG_CK_RV_AND_RETURN_IF_ERR(object->FinalizeNewObject());
ObjectPool* pool =
object->IsTokenObject() ? token_object_pool_ : session_object_pool_.get();
auto pool_res = pool->Insert(object.get());
if (!IsSuccess(pool_res))
return ResultToRV(pool_res, CKR_FUNCTION_FAILED);
*new_key_handle = object.release()->handle();
return CKR_OK;
}
CK_RV SessionImpl::UnwrapKeyRSAOAEP(const Object& unwrapping_key,
Object& object,
const std::string& wrapped_key) {
brillo::SecureBlob data;
if (unwrapping_key.GetObjectClass() != CKO_PRIVATE_KEY ||
unwrapping_key.GetAttributeInt(CKA_KEY_TYPE,
CK_UNAVAILABLE_INFORMATION) != CKK_RSA) {
LOG(ERROR) << __func__ << "The unwrapping key should be a RSA private key.";
return CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT;
}
if (unwrapping_key.IsTokenObject() &&
unwrapping_key.IsAttributePresent(kKeyBlobAttribute)) {
if (!hwsec_) {
LOG(ERROR) << __func__ << "No HWSec frontend available.";
return CKR_FUNCTION_FAILED;
}
ASSIGN_OR_RETURN(hwsec::Key key, GetHwsecKey(&unwrapping_key),
_.LogError().As(CKR_FUNCTION_FAILED));
ASSIGN_OR_RETURN(
data, hwsec_->Decrypt(key, brillo::BlobFromString(wrapped_key)),
_.WithStatus<TPMError>(
"Failed to decrypt the wrapped key in UnwrapKeyRSAOAEP")
.LogError()
.As(CKR_FUNCTION_FAILED));
} else {
crypto::ScopedRSA rsa = CreateRSAKeyFromObject(&unwrapping_key);
if (!rsa) {
LOG(ERROR) << __func__ << "Failed to create RSA key for decryption.";
return CKR_FUNCTION_FAILED;
}
if (RSA_size(rsa.get()) > kMaxRSAOutputBytes) {
LOG(ERROR) << __func__ << ": RSA Key size is too large for RSA OAEP.";
return CKR_UNWRAPPING_KEY_SIZE_RANGE;
}
uint8_t buffer[kMaxRSAOutputBytes];
int length = RSA_private_decrypt(
wrapped_key.size(), ConvertStringToByteBuffer(wrapped_key.data()),
buffer, rsa.get(),
RSA_PKCS1_OAEP_PADDING); // using EME-OAEP for padding.
if (length == -1) {
LOG(ERROR) << __func__
<< "RSA_private_decrypt failed: " << GetOpenSSLError();
return CKR_FUNCTION_FAILED;
}
CHECK(length <= std::size(buffer));
data = SecureBlob(ConvertByteBufferToString(buffer, length));
}
object.SetAttributeInt(CKA_CLASS, CKO_SECRET_KEY);
object.SetAttributeString(CKA_VALUE, data.to_string());
object.SetAttributeInt(CKA_VALUE_LEN, data.size());
return CKR_OK;
}
CK_RV SessionImpl::UnwrapKeyWithChaps(const std::string& mechanism_parameter,
const string& wrapped_key,
int* new_key_handle) {
std::unique_ptr<Object> object(factory_->CreateObject());
CHECK(object.get());
// Deserializes the input protobuf and obtains the sealed AES key and the
// wrapped target key.
ChapsWrappedKeyInfo key_info;
key_info.ParseFromString(wrapped_key);
string base_aes_key = key_info.base_aes_key();
string wrapped_target_key = key_info.wrapped_attribute_list();
// Using the base_aes_key and Chaps' secret random seed to derive the actual
// unwrapping key.
string derived_aes_key = HmacSha512(base_aes_key, factory_->GetRandomSeed());
// Unwraps the target key from the second part with derived_aes_key using
// CKM_AES_KEY_WRAP_KWP.
auto data = AESKeyWrapWithPadding(/* is_encrypt */ false, derived_aes_key,
wrapped_target_key);
if (data == std::nullopt)
return CKR_FUNCTION_FAILED;
AttributeList attribute_list;
attribute_list.ParseFromString(data.value());
for (int i = 0; i < attribute_list.attributes_size(); ++i) {
const Attribute& attribute = attribute_list.attributes(i);
if (!attribute.has_value()) {
LOG(WARNING) << "No value found for attribute: " << attribute.type();
continue;
}
object->SetAttributeString(attribute.type(), attribute.value());
// Correct the length of integral attributes since they may have been
// serialized with a different sizeof(CK_ULONG).
if (IsIntegralAttribute(attribute.type()) &&
attribute.value().length() != sizeof(CK_ULONG)) {
CK_ULONG int_value = object->GetAttributeInt(attribute.type(), 0);
object->SetAttributeInt(attribute.type(), int_value);
}
}
LOG_CK_RV_AND_RETURN_IF_ERR(object->FinalizeNewObject());
ObjectPool* pool =
object->IsTokenObject() ? token_object_pool_ : session_object_pool_.get();
auto pool_res = pool->Insert(object.get());
if (!IsSuccess(pool_res))
return ResultToRV(pool_res, CKR_FUNCTION_FAILED);
*new_key_handle = object.release()->handle();
// Zeroizes the temporary AES key.
derived_aes_key.clear();
return CKR_OK;
}
CK_RV SessionImpl::DeriveKey(CK_MECHANISM_TYPE mechanism,
const std::string& mechanism_parameter,
const Object& base_key,
const CK_ATTRIBUTE_PTR attributes,
int num_attributes,
int* new_key_handle) {
CHECK(new_key_handle);
if (!base_key.GetAttributeBool(CKA_DERIVE, false)) {
LOG(ERROR) << __func__ << ": Base key not permitted to derive anoher key.";
return CKR_KEY_FUNCTION_NOT_PERMITTED;
}
std::unique_ptr<Object> object(factory_->CreateObject());
CHECK(object.get());
LOG_CK_RV_AND_RETURN_IF_ERR(
object->SetAttributes(attributes, num_attributes));
if (object->GetObjectClass() == CK_UNAVAILABLE_INFORMATION) {
return CKR_TEMPLATE_INCOMPLETE;
}
string key_material;
switch (mechanism) {
case CKM_SP800_108_COUNTER_KDF: {
LOG_CK_RV_AND_RETURN_IF(base_key.GetObjectClass() != CKO_SECRET_KEY,
CKR_KEY_TYPE_INCONSISTENT);
LOG_CK_RV_AND_RETURN_IF(object->GetObjectClass() != CKO_SECRET_KEY,
CKR_TEMPLATE_INCONSISTENT);
LOG_CK_RV_AND_RETURN_IF_ERR(DeriveKeySP800108Software(
base_key, *object.get(), mechanism_parameter, &key_material));
if (base_key.GetAttributeBool(CKA_ALWAYS_SENSITIVE, false))
object->SetAttributeBool(
CKA_ALWAYS_SENSITIVE,
base_key.GetAttributeBool(CKA_SENSITIVE, false));
if (base_key.GetAttributeBool(CKA_NEVER_EXTRACTABLE, false))
object->SetAttributeBool(
CKA_NEVER_EXTRACTABLE,
!base_key.GetAttributeBool(CKA_EXTRACTABLE, false));
break;
}
default: {
LOG(ERROR) << "DeriveKey: Mechanism not supported: " << hex << mechanism;
return CKR_MECHANISM_INVALID;
}
}
object->SetAttributeString(CKA_VALUE, key_material);
object->SetAttributeBool(CKA_LOCAL, false);
LOG_CK_RV_AND_RETURN_IF_ERR(object->FinalizeNewObject());
ObjectPool* pool =
object->IsTokenObject() ? token_object_pool_ : session_object_pool_.get();
auto pool_res = pool->Insert(object.get());
if (!IsSuccess(pool_res))
return ResultToRV(pool_res, CKR_FUNCTION_FAILED);
*new_key_handle = 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(size_t num_bytes, string* random_data) {
if (num_bytes > kMaxRandomBufferSize) {
return CKR_HOST_MEMORY;
}
*random_data = GenerateRandomSoftware(num_bytes);
return CKR_OK;
}
bool SessionImpl::IsPrivateLoaded() {
return token_object_pool_->IsPrivateLoaded();
}
CK_RV SessionImpl::CipherInit(bool is_encrypt,
CK_MECHANISM_TYPE mechanism,
const string& mechanism_parameter,
const Object& key) {
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;
}
OperationType operation = is_encrypt ? kEncrypt : kDecrypt;
OperationContext* context = &operation_context_[operation];
context->cipher_context_.reset(EVP_CIPHER_CTX_new());
if (!context->cipher_context_) {
LOG(ERROR) << "Failed to allocate EVP_CIPHER context";
return CKR_FUNCTION_FAILED;
}
if (!EVP_CipherInit_ex(context->cipher_context_.get(), cipher_type, nullptr,
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->cipher_context_.get(),
IsPaddingEnabled(mechanism));
context->is_valid_ = true;
context->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_.get(),
ConvertStringToByteBuffer(context->data_.c_str()), &out_length,
ConvertStringToByteBuffer(data_in.c_str()), in_length)) {
context->Clear();
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_ex(context->cipher_context_.get(),
ConvertStringToByteBuffer(context->data_.c_str()),
&out_length)) {
LOG(ERROR) << "EVP_CipherFinal failed: " << GetOpenSSLError();
return CKR_FUNCTION_FAILED;
}
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);
std::unique_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;
bool is_token_object = object->IsTokenObject();
if (is_token_object) {
result = WrapPrivateKey(*object.get());
if (result != CKR_OK)
return result;
}
// Finalize the object, whether it's new or copied.
if (copy_from_object) {
result = object->FinalizeCopyObject();
} else {
result = object->FinalizeNewObject();
}
if (result != CKR_OK) {
return result;
}
ObjectPool* pool =
is_token_object ? token_object_pool_ : session_object_pool_.get();
auto pool_res = pool->Insert(object.get());
if (!IsSuccess(pool_res))
return ResultToRV(pool_res, 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;
}
CK_RV SessionImpl::GenerateRSAKeyPair(Object* public_object,
Object* private_object) {
// 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);
// CKA_MODULUS_BITS is requried
if (!public_object->IsAttributePresent(CKA_MODULUS_BITS))
return CKR_TEMPLATE_INCOMPLETE;
CK_ULONG modulus_bits = public_object->GetAttributeInt(CKA_MODULUS_BITS, 0);
if (modulus_bits < kMinRSAKeyBits || modulus_bits > kMaxRSAKeyBits)
return CKR_KEY_SIZE_RANGE;
// Set CKA_KEY_TYPE
public_object->SetAttributeInt(CKA_KEY_TYPE, CKK_RSA);
private_object->SetAttributeInt(CKA_KEY_TYPE, CKK_RSA);
bool is_using_hwsec =
private_object->IsTokenObject() &&
!private_object->GetAttributeBool(kForceSoftwareAttribute, false) &&
hwsec_ && hwsec_->IsRSAModulusSupported(modulus_bits).ok();
// Check if we are able to back this key with the HWSec.
if (is_using_hwsec) {
// Use HWSec to generate RSA key
if (!GenerateRSAKeyPairHwsec(modulus_bits, public_exponent, public_object,
private_object))
return CKR_FUNCTION_FAILED;
} else {
// Use software to generate RSA key
if (!GenerateRSAKeyPairSoftware(modulus_bits, public_exponent,
public_object, private_object))
return CKR_FUNCTION_FAILED;
}
return CKR_OK;
}
bool SessionImpl::GenerateRSAKeyPairSoftware(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;
crypto::ScopedRSA key(RSA_new());
crypto::ScopedBIGNUM e(BN_new());
if (!key || !e) {
LOG(ERROR) << "Failed to allocate RSA or BIGNUM for exponent.";
return false;
}
if (!ConvertToBIGNUM(public_exponent, e.get())) {
LOG(ERROR) << "Failed to convert exponent to BIGNUM.";
return false;
}
if (!RSA_generate_key_ex(key.get(), modulus_bits, e.get(), nullptr)) {
LOG(ERROR) << "Failed to generate key pair.";
return false;
}
const BIGNUM* key_n;
const BIGNUM* key_d;
const BIGNUM* key_p;
const BIGNUM* key_q;
const BIGNUM* key_dmp1;
const BIGNUM* key_dmq1;
const BIGNUM* key_iqmp;
RSA_get0_key(key.get(), &key_n, nullptr, &key_d);
RSA_get0_factors(key.get(), &key_p, &key_q);
RSA_get0_crt_params(key.get(), &key_dmp1, &key_dmq1, &key_iqmp);
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);
return true;
}
bool SessionImpl::GenerateRSAKeyPairHwsec(int modulus_bits,
const string& public_exponent,
Object* public_object,
Object* private_object) {
if (!hwsec_) {
LOG(ERROR) << "No HWSec frontend available in GenerateRSAKeyPairHwsec.";
return false;
}
brillo::Blob exponent = brillo::BlobFromString(public_exponent);
brillo::SecureBlob auth_data =
GenerateRandomSecureBlobSoftware(kDefaultAuthDataBytes);
AllowSoftwareGen allow_soft_gen =
private_object->GetAttributeBool(kAllowSoftwareGenAttribute, false)
? AllowSoftwareGen::kAllow
: AllowSoftwareGen::kNotAllow;
AllowDecrypt allow_decrypt = IsHwsecObjectAllowDecrypt(*private_object);
AllowSign allow_sign = private_object->GetAttributeBool(CKA_SIGN, false)
? AllowSign::kAllow
: AllowSign::kNotAllow;
ASSIGN_OR_RETURN(
const hwsec::ChapsFrontend::CreateKeyResult& result,
hwsec_->GenerateRSAKey(modulus_bits, exponent, auth_data, allow_soft_gen,
allow_decrypt, allow_sign),
_.WithStatus<TPMError>(
"Failed to generate RSA key in GenerateRSAKeyPairHwsec")
.LogError()
.As(false));
// Get public key information from HWSec
ASSIGN_OR_RETURN(const hwsec::RSAPublicInfo& info,
hwsec_->GetRSAPublicKey(result.key.GetKey()),
_.WithStatus<TPMError>(
"Failed to get RSA key info in GenerateRSAKeyPairHwsec")
.LogError()
.As(false));
public_object->SetAttributeString(CKA_MODULUS,
brillo::BlobToString(info.modulus));
private_object->SetAttributeString(CKA_MODULUS,
brillo::BlobToString(info.modulus));
private_object->SetAttributeString(kAuthDataAttribute, auth_data.to_string());
private_object->SetAttributeString(kKeyBlobAttribute,
brillo::BlobToString(result.key_blob));
return true;
}
CK_RV SessionImpl::GenerateECCKeyPair(Object* public_object,
Object* private_object) {
// CKA_EC_PARAMS is requried
if (!public_object->IsAttributePresent(CKA_EC_PARAMS))
return CKR_TEMPLATE_INCOMPLETE;
crypto::ScopedEC_KEY key = CreateECCKeyFromEC_PARAMS(
public_object->GetAttributeString(CKA_EC_PARAMS));
if (key == nullptr) {
LOG(ERROR) << __func__ << ": CKA_EC_PARAMS parse fail.";
return CKR_DOMAIN_PARAMS_INVALID;
}
// Set CKA_KEY_TYPE
public_object->SetAttributeInt(CKA_KEY_TYPE, CKK_EC);
private_object->SetAttributeInt(CKA_KEY_TYPE, CKK_EC);
// reset CKA_EC_PARAMS for both key
const string ec_params = GetECParametersAsString(key.get());
if (ec_params.empty()) {
LOG(ERROR) << __func__ << ": Fail to dump CKA_EC_PARAMS";
return CKR_FUNCTION_FAILED;
}
public_object->SetAttributeString(CKA_EC_PARAMS, ec_params);
private_object->SetAttributeString(CKA_EC_PARAMS, ec_params);
// Get NID from key
const EC_GROUP* group = EC_KEY_get0_group(key.get());
if (group == nullptr)
return CKR_FUNCTION_FAILED;
int curve_nid = EC_GROUP_get_curve_name(group);
bool is_using_hwsec =
private_object->IsTokenObject() &&
!private_object->GetAttributeBool(kForceSoftwareAttribute, false) &&
hwsec_ && hwsec_->IsECCurveSupported(curve_nid).ok();
bool result = false;
if (is_using_hwsec) {
result =
GenerateECCKeyPairHwsec(key, curve_nid, public_object, private_object);
} else {
result = GenerateECCKeyPairSoftware(key, public_object, private_object);
}
return result ? CKR_OK : CKR_FUNCTION_FAILED;
}
bool SessionImpl::GenerateECCKeyPairHwsec(const crypto::ScopedEC_KEY& key,
int curve_nid,
Object* public_object,
Object* private_object) {
if (!hwsec_) {
LOG(ERROR) << "No HWSec frontend available in GenerateECCKeyPairHwsec.";
return false;
}
brillo::SecureBlob auth_data =
GenerateRandomSecureBlobSoftware(kDefaultAuthDataBytes);
AllowDecrypt allow_decrypt = IsHwsecObjectAllowDecrypt(*private_object);
AllowSign allow_sign = private_object->GetAttributeBool(CKA_SIGN, false)
? AllowSign::kAllow
: AllowSign::kNotAllow;
ASSIGN_OR_RETURN(
const hwsec::ChapsFrontend::CreateKeyResult& result,
hwsec_->GenerateECCKey(curve_nid, auth_data, allow_decrypt, allow_sign),
_.WithStatus<TPMError>(
"Failed to generate ECC key in GenerateECCKeyPairHwsec")
.LogError()
.As(false));
// Get public key information from HWSec
ASSIGN_OR_RETURN(const hwsec::ECCPublicInfo& info,
hwsec_->GetECCPublicKey(result.key.GetKey()),
_.WithStatus<TPMError>(
"Failed to get ECC key info in GenerateECCKeyPairHwsec")
.LogError()
.As(false));
// Convert the ECC public into the DER-encoded format.
crypto::ScopedEC_Key ecc(EC_KEY_new_by_curve_name(info.nid));
if (!ecc) {
LOG(ERROR) << "Failed to create EC_KEY from curve name " << info.nid << ".";
return false;
}
crypto::ScopedBIGNUM x(BN_new()), y(BN_new());
if (!x || !y) {
LOG(ERROR) << "Failed to allocate BIGNUM.";
return false;
}
if (!chaps::ConvertBlobToBIGNUM(info.x_point, x.get()) ||
!chaps::ConvertBlobToBIGNUM(info.y_point, y.get())) {
LOG(ERROR) << "Failed to convert to BIGNUM.";
return false;
}
// EC_KEY_set_public_key_affine_coordinates will check the pointer is valid
if (!EC_KEY_set_public_key_affine_coordinates(ecc.get(), x.get(), y.get())) {
LOG(ERROR) << "Invalid point.";
return false;
}
std::string ec_point = GetECPointAsString(ecc.get());
// Set CKA_EC_POINT for public key
public_object->SetAttributeString(CKA_EC_POINT, ec_point);
// Set HWSec information for private key
private_object->SetAttributeString(kAuthDataAttribute, auth_data.to_string());
private_object->SetAttributeString(kKeyBlobAttribute,
brillo::BlobToString(result.key_blob));
return true;
}
bool SessionImpl::GenerateECCKeyPairSoftware(const crypto::ScopedEC_KEY& key,
Object* public_object,
Object* private_object) {
if (!EC_KEY_generate_key(key.get())) {
LOG(ERROR) << __func__
<< ": Software generate key fail. Perhaps it is not supported "
"by OpenSSL.";
return false;
}
// Set CKA_EC_POINT for public key
const string ec_point = GetECPointAsString(key.get());
if (ec_point.empty()) {
LOG(ERROR) << __func__ << ": Fail to dump EC_POINT.";
return false;
}
public_object->SetAttributeString(CKA_EC_POINT, ec_point);
// Set CKA_VALUE for private key
const BIGNUM* privkey = EC_KEY_get0_private_key(key.get());
private_object->SetAttributeString(CKA_VALUE, ConvertFromBIGNUM(privkey));
return true;
}
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;
}
hwsec::StatusOr<hwsec::Key> SessionImpl::GetHwsecKey(const Object* key) {
if (!hwsec_) {
return MakeStatus<TPMError>("No HWSec in GetHwsecKey",
hwsec::TPMRetryAction::kNoRetry);
}
map<const Object*, hwsec::ScopedKey>::iterator it = object_key_map_.find(key);
if (it != object_key_map_.end()) {
return it->second.GetKey();
}
// Only private keys are loaded into the HWSec. All public key operations do
// not use the HWSec (and use OpenSSL instead).
if (key->GetObjectClass() != CKO_PRIVATE_KEY) {
return MakeStatus<TPMError>("Invalid object class for loading into HWSec",
hwsec::TPMRetryAction::kNoRetry);
}
brillo::Blob key_blob =
brillo::BlobFromString(key->GetAttributeString(kKeyBlobAttribute));
brillo::SecureBlob auth_value(key->GetAttributeString(kAuthDataAttribute));
ASSIGN_OR_RETURN(
hwsec::ScopedKey hwsec_key, hwsec_->LoadKey(key_blob, auth_value),
_.WithStatus<TPMError>("Failed to load the key in GetHwsecKey"));
hwsec::Key key_handle = hwsec_key.GetKey();
object_key_map_.emplace(key, std::move(hwsec_key));
return key_handle;
}
void SessionImpl::UpdateObjectCount(OperationContext* context) {
if (context->key_ != nullptr) {
IncreaseObjectCount(context->key_);
// We stored the context inside the session, and there is no way to transfer
// the ownership of context outside of session. So base::Unretained(this) is
// safe here.
context->cleanup_ = base::ScopedClosureRunner(
base::BindOnce(&SessionImpl::DecreaseObjectCount,
base::Unretained(this), context->key_));
}
}
void SessionImpl::IncreaseObjectCount(const Object* key) {
if (key == nullptr) {
return;
}
object_count_map_[key]++;
}
void SessionImpl::DecreaseObjectCount(const Object* key) {
if (key == nullptr) {
return;
}
if ((--object_count_map_[key]) == 0) {
object_count_map_.erase(key);
object_key_map_.erase(key);
}
}
bool SessionImpl::RSADecrypt(OperationContext* context) {
if (context->key_->IsTokenObject() &&
context->key_->IsAttributePresent(kKeyBlobAttribute)) {
if (!hwsec_) {
LOG(ERROR) << "No HWSec frontend available in RSADecrypt.";
return false;
}
ASSIGN_OR_RETURN(hwsec::Key key, GetHwsecKey(context->key_),
_.LogError().As(false));
brillo::Blob encrypted_data = brillo::BlobFromString(context->data_);
context->data_.clear();
ASSIGN_OR_RETURN(brillo::SecureBlob data,
hwsec_->Unbind(key, encrypted_data),
_.WithStatus<TPMError>(
"Failed to unbind the encrypted data in RSADecrypt")
.LogError()
.As(false));
context->data_ = data.to_string();
} else {
crypto::ScopedRSA rsa = CreateRSAKeyFromObject(context->key_);
if (!rsa) {
LOG(ERROR) << "Failed to create RSA key for decryption.";
return false;
}
uint8_t buffer[kMaxRSAOutputBytes];
CHECK(RSA_size(rsa.get()) <= kMaxRSAOutputBytes);
int length = RSA_private_decrypt(
context->data_.length(),
ConvertStringToByteBuffer(context->data_.data()), buffer, rsa.get(),
RSA_PKCS1_PADDING); // Strips PKCS #1 type 2 padding.
if (length == -1) {
LOG(ERROR) << "RSA_private_decrypt failed: " << GetOpenSSLError();
return false;
}
context->data_ = ConvertByteBufferToString(buffer, length);
}
return true;
}
bool SessionImpl::RSAEncrypt(OperationContext* context) {
crypto::ScopedRSA rsa = CreateRSAKeyFromObject(context->key_);
if (!rsa) {
LOG(ERROR) << "Failed to create RSA key for encryption.";
return false;
}
uint8_t buffer[kMaxRSAOutputBytes];
CHECK(RSA_size(rsa.get()) <= kMaxRSAOutputBytes);
int length = RSA_public_encrypt(
context->data_.length(), ConvertStringToByteBuffer(context->data_.data()),
buffer, rsa.get(),
RSA_PKCS1_PADDING); // Adds PKCS #1 type 2 padding.
if (length == -1) {
LOG(ERROR) << "RSA_public_encrypt failed: " << GetOpenSSLError();
return false;
}
context->data_ = ConvertByteBufferToString(buffer, length);
return true;
}
bool SessionImpl::RSASign(OperationContext* context) {
if (context->key_->IsTokenObject() &&
context->key_->IsAttributePresent(kKeyBlobAttribute)) {
if (!hwsec_) {
LOG(ERROR) << "No HWSec frontend available in RSASign.";
return false;
}
ASSIGN_OR_RETURN(hwsec::Key key, GetHwsecKey(context->key_),
_.LogError().As(false));
ASSIGN_OR_RETURN(
brillo::Blob data,
hwsec_->Sign(
key, brillo::BlobFromString(context->data_),
ToHwsecSigningOptions(context->mechanism_, context->parameter_)),
_.WithStatus<TPMError>("Failed to RSA sign the data in RASSign")
.LogError()
.As(false));
context->data_ = brillo::BlobToString(data);
return true;
}
// Sign the data without HWSec.
crypto::ScopedRSA rsa = CreateRSAKeyFromObject(context->key_);
if (!rsa) {
LOG(ERROR) << "Failed to create RSA key for signing.";
return false;
}
std::unique_ptr<RSASignerVerifier> signer =
RSASignerVerifier::GetForMechanism(context->mechanism_);
if (!signer) {
return false;
}
return signer->Sign(std::move(rsa), context);
}
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;
crypto::ScopedRSA rsa = CreateRSAKeyFromObject(context->key_);
if (!rsa) {
LOG(ERROR) << "Failed to create RSA key for verification.";
return CKR_KEY_HANDLE_INVALID;
}
std::unique_ptr<RSASignerVerifier> verifier =
RSASignerVerifier::GetForMechanism(context->mechanism_);
if (!verifier)
return CKR_ARGUMENTS_BAD;
return verifier->Verify(std::move(rsa), context, digest, signature);
}
bool SessionImpl::ECCSign(OperationContext* context) {
string signature;
if (context->key_->IsTokenObject() &&
context->key_->IsAttributePresent(kKeyBlobAttribute)) {
if (!ECCSignHwsec(context->data_, context->mechanism_, *context->key_,
&signature))
return false;
} else {
if (!ECCSignSoftware(context->data_, *context->key_, &signature))
return false;
}
context->data_ = signature;
return true;
}
bool SessionImpl::ECCSignHwsec(const std::string& input,
CK_MECHANISM_TYPE signing_mechanism,
const Object& key_object,
std::string* signature) {
if (!hwsec_) {
LOG(ERROR) << "No HWSec frontend available in ECCSignHwsec.";
return false;
}
if (!(signing_mechanism == CKM_ECDSA || signing_mechanism == CKM_ECDSA_SHA1 ||
signing_mechanism == CKM_ECDSA_SHA256 ||
signing_mechanism == CKM_ECDSA_SHA384 ||
signing_mechanism == CKM_ECDSA_SHA512)) {
LOG(ERROR)
<< "Failed to sign with ECCSignHwsec because mechanism is unsupported: "
<< signing_mechanism;
return false;
}
ASSIGN_OR_RETURN(hwsec::Key key, GetHwsecKey(&key_object),
_.LogError().As(false));
ASSIGN_OR_RETURN(
brillo::Blob data,
hwsec_->Sign(key, brillo::BlobFromString(input),
ToHwsecSigningOptions(signing_mechanism, "")),
_.WithStatus<TPMError>("Failed to ECC sign the data in ECCSignHwsec")
.LogError()
.As(false));
*signature = brillo::BlobToString(data);
return true;
}
bool SessionImpl::ECCSignSoftware(const std::string& input,
const Object& key_object,
std::string* signature) {
crypto::ScopedEC_KEY key = CreateECCPrivateKeyFromObject(key_object);
if (key == nullptr) {
LOG(ERROR) << __func__ << ": Load key failed.";
return false;
}
// We don't use ECDSA_sign here since the output format of PKCS#11 is
// different from OpenSSL's.
crypto::ScopedECDSA_SIG sig(ECDSA_do_sign(
ConvertStringToByteBuffer(input.data()), input.size(), key.get()));
if (sig == nullptr) {
LOG(ERROR) << __func__ << ": ECDSA failed: " << GetOpenSSLError();
return false;
}
// The resulting signature is always of length |2 * nLen|, where nLen is the
// maximum size of the EC group. The first half of the signature is r and the
// second half is s.
int max_length = GetGroupOrderLengthFromEcKey(key);
if (max_length <= 0) {
LOG(ERROR) << __func__ << ": Get the group order fail.";
return false;
}
const BIGNUM* r;
const BIGNUM* s;
ECDSA_SIG_get0(sig.get(), &r, &s);
*signature =
ConvertFromBIGNUM(r, max_length) + ConvertFromBIGNUM(s, max_length);
return true;
}
CK_RV SessionImpl::ECCVerify(OperationContext* context,
const string& signed_data,
const string& signature) {
// Software verify with ECC key
crypto::ScopedEC_KEY key = CreateECCPublicKeyFromObject(context->key_);
if (key == nullptr) {
LOG(ERROR) << __func__ << ": Load key failed.";
return CKR_FUNCTION_FAILED;
}
// Parse signature back to ECDSA_SIG
int sign_size = signature.size();
if (sign_size % 2 != 0) {
return CKR_SIGNATURE_LEN_RANGE;
}
crypto::ScopedECDSA_SIG sig(ECDSA_SIG_new());
crypto::ScopedBIGNUM r(BN_new()), s(BN_new());
if (!sig || !r || !s) {
LOG(ERROR) << "Failed to allocate ECDSA_SIG or BIGNUM.";
return CKR_FUNCTION_FAILED;
}
if (!ConvertToBIGNUM(signature.substr(0, sign_size / 2), r.get()) ||
!ConvertToBIGNUM(signature.substr(sign_size / 2), s.get())) {
LOG(ERROR) << "Failed to convert BIGNUM for ECDSA_SIG.";
return CKR_FUNCTION_FAILED;
}
if (!ECDSA_SIG_set0(sig.get(), r.release(), s.release())) {
LOG(ERROR) << "Failed to set ECDSA_SIG parameters.";
return CKR_FUNCTION_FAILED;
}
// 1 for a valid signature, 0 for an invalid signature and -1 on error.
int result = ECDSA_do_verify(ConvertStringToByteBuffer(signed_data.data()),
signed_data.size(), sig.get(), key.get());
if (result < 0) {
LOG(ERROR) << __func__ << ": ECDSA verify failed: " << GetOpenSSLError();
return CKR_FUNCTION_FAILED;
}
return result ? CKR_OK : CKR_SIGNATURE_INVALID;
}
CK_RV SessionImpl::WrapRSAPrivateKey(Object& object) {
if (!object.IsAttributePresent(CKA_PUBLIC_EXPONENT) ||
!object.IsAttributePresent(CKA_MODULUS) ||
!(object.IsAttributePresent(CKA_PRIME_1) ||
object.IsAttributePresent(CKA_PRIME_2)))
return CKR_TEMPLATE_INCOMPLETE;
// If HWSec doesn't support, fall back to software.
int key_size_bits = object.GetAttributeString(CKA_MODULUS).length() * 8;
if (!hwsec_ || !hwsec_->IsRSAModulusSupported(key_size_bits).ok()) {
LOG(WARNING) << "WARNING: " << key_size_bits
<< "-bit private key cannot be wrapped by the HWSec.";
return CKR_OK;
}
// Get prime p or q
string prime = object.IsAttributePresent(CKA_PRIME_1)
? object.GetAttributeString(CKA_PRIME_1)
: object.GetAttributeString(CKA_PRIME_2);
brillo::Blob exponent =
brillo::BlobFromString(object.GetAttributeString(CKA_PUBLIC_EXPONENT));
brillo::Blob modulus =
brillo::BlobFromString(object.GetAttributeString(CKA_MODULUS));
brillo::SecureBlob prime_blob(prime);
brillo::SecureBlob auth_data =
GenerateRandomSecureBlobSoftware(kDefaultAuthDataBytes);
AllowDecrypt allow_decrypt = IsHwsecObjectAllowDecrypt(object);
AllowSign allow_sign = object.GetAttributeBool(CKA_SIGN, false)
? AllowSign::kAllow
: AllowSign::kNotAllow;
// TODO(menghuan): Use Software key but report and have an auto-rewrapping
// when WrapRSAKey() fail
ASSIGN_OR_RETURN(
const hwsec::ChapsFrontend::CreateKeyResult& result,
hwsec_->WrapRSAKey(exponent, modulus, prime_blob, auth_data,
allow_decrypt, allow_sign),
_.WithStatus<TPMError>("Failed to wrap RSA key in WrapRSAPrivateKey")
.LogError()
.As(CKR_FUNCTION_FAILED));
object.SetAttributeString(kAuthDataAttribute, auth_data.to_string());
object.SetAttributeString(kKeyBlobAttribute,
brillo::BlobToString(result.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;
}
CK_RV SessionImpl::WrapECCPrivateKey(Object& object) {
if (!object.IsAttributePresent(CKA_EC_PARAMS) ||
!object.IsAttributePresent(CKA_VALUE)) {
return CKR_TEMPLATE_INCOMPLETE;
}
// Get OpenSSL NID
crypto::ScopedEC_Key key = CreateECCPrivateKeyFromObject(object);
const EC_GROUP* group = EC_KEY_get0_group(key.get());
if (group == nullptr) {
return CKR_FUNCTION_FAILED;
}
int curve_nid = EC_GROUP_get_curve_name(group);
// If HWSec doesn't support, fall back to software.
if (!hwsec_ || !hwsec_->IsECCurveSupported(curve_nid).ok()) {
return CKR_OK;
}
// Get public key value
crypto::ScopedBIGNUM x(BN_new()), y(BN_new());
if (!x || !y) {
LOG(ERROR) << "Failed to allocate BIGNUM.";
return CKR_FUNCTION_FAILED;
}
const EC_POINT* ec_point = EC_KEY_get0_public_key(key.get());
if (ec_point == nullptr) {
return CKR_FUNCTION_FAILED;
}
if (!EC_POINT_get_affine_coordinates_GF2m(group, ec_point, x.get(), y.get(),
nullptr)) {
return CKR_FUNCTION_FAILED;
}
brillo::Blob x_point = brillo::BlobFromString(ConvertFromBIGNUM(x.get()));
brillo::Blob y_point = brillo::BlobFromString(ConvertFromBIGNUM(y.get()));
brillo::SecureBlob private_value(object.GetAttributeString(CKA_VALUE));
brillo::SecureBlob auth_data =
GenerateRandomSecureBlobSoftware(kDefaultAuthDataBytes);
AllowDecrypt allow_decrypt = IsHwsecObjectAllowDecrypt(object);
AllowSign allow_sign = object.GetAttributeBool(CKA_SIGN, false)
? AllowSign::kAllow
: AllowSign::kNotAllow;
ASSIGN_OR_RETURN(
const hwsec::ChapsFrontend::CreateKeyResult& result,
hwsec_->WrapECCKey(curve_nid, x_point, y_point, private_value, auth_data,
allow_decrypt, allow_sign),
_.WithStatus<TPMError>("Failed to wrap ECC key in WrapECCPrivateKey")
.LogError()
.As(CKR_FUNCTION_FAILED));
object.SetAttributeString(kAuthDataAttribute, auth_data.to_string());
object.SetAttributeString(kKeyBlobAttribute,
brillo::BlobToString(result.key_blob));
object.RemoveAttribute(CKA_VALUE);
return CKR_OK;
}
CK_RV SessionImpl::WrapPrivateKey(Object& object) {
if (!hwsec_ || object.GetAttributeBool(kForceSoftwareAttribute, false) ||
object.GetObjectClass() != CKO_PRIVATE_KEY ||
object.IsAttributePresent(kKeyBlobAttribute)) {
// This object does not need to be wrapped.
return CKR_OK;
}
int key_type = object.GetAttributeInt(CKA_KEY_TYPE, 0);
if (key_type == CKK_RSA) {
return WrapRSAPrivateKey(object);
} else if (key_type == CKK_EC) {
return WrapECCPrivateKey(object);
} else {
// If HWSec doesn't support, fall back to software.
LOG(WARNING) << __func__ << ": Key type " << key_type
<< " private key cannot be wrapped by the HWSec.";
return CKR_OK;
}
}
SessionImpl::OperationContext::OperationContext()
: is_valid_(false),
is_cipher_(false),
is_digest_(false),
is_hmac_(false),
is_finished_(false),
key_(nullptr) {}
SessionImpl::OperationContext::~OperationContext() {
Clear();
}
void SessionImpl::OperationContext::Clear() {
cipher_context_.reset();
digest_context_.reset();
hmac_context_.reset();
is_valid_ = false;
is_cipher_ = false;
is_digest_ = false;
is_hmac_ = false;
is_incremental_ = false;
is_finished_ = false;
key_ = nullptr;
data_.clear();
parameter_.clear();
cleanup_.RunAndReset();
}
} // namespace chaps