// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chromeos/ash/components/chaps_util/chaps_util_impl.h"
#include <pkcs11t.h>
#include <secmodt.h>
#include <stdint.h>
#include <map>
#include <optional>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "chromeos/ash/components/chaps_util/chaps_slot_session.h"
#include "crypto/nss_key_util.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/scoped_test_nss_db.h"
#include "net/cert/x509_util_nss.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/base.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/stack.h"
#include "third_party/boringssl/src/include/openssl/x509.h"
namespace chromeos {
namespace {
const size_t kKeySizeBits = 2048;
// TODO(b/202374261): Move these into a shared header.
// Signals to chaps that a generated key should be software-backed.
constexpr CK_ATTRIBUTE_TYPE kForceSoftwareAttribute = CKA_VENDOR_DEFINED + 4;
// Chaps sets this for keys that are software-backed.
constexpr CK_ATTRIBUTE_TYPE kKeyInSoftware = CKA_VENDOR_DEFINED + 5;
enum AttrValueType { kNotDefined, kCkBool, kCkUlong, kCkBytes };
const std::optional<std::vector<CK_BYTE>> default_encoded_cert_label =
base::Base64Decode("dGVzdHVzZXJjZXJ0");
// python print(base64.b64encode("default nickname".encode('utf-8'))).
const std::optional<std::vector<CK_BYTE>> default_encoded_label =
base::Base64Decode("VW5rbm93biBvcmc=");
const std::optional<std::vector<CK_BYTE>> cka_id_for_ec_key =
base::Base64Decode("9kVFdOhn8yYso7a/wG2uC0wdHWo=");
const std::optional<std::vector<CK_BYTE>> cka_ex_point_ec_key =
base::Base64Decode(
"BP+"
"IQBEPm3e3ABQMhQaZlE0w8qIjn0tKH6jTEekQvtKoUhFo2nM4Q9VA3MLljVF7vabV8CuH9"
"/"
"UkKt2FMg2iHGM=");
const std::optional<std::vector<CK_BYTE>> cka_ec_params_ec_key =
base::Base64Decode("BggqhkjOPQMBBw==");
const std::optional<std::vector<CK_BYTE>> cka_value_ec_key =
base::Base64Decode("fvWtrgVAq5JApBuCPK92IUAQQnnEoLUrBgZ/KGFhz7E=");
// Class helper to keep relations between all possible attribute's types,
// attribute's names and attribute's value types.
class AttributesParsingOptions {
public:
AttributesParsingOptions() = default;
~AttributesParsingOptions() = default;
static std::string GetName(const CK_ATTRIBUTE& attribute) {
if (!GetPkcs12ObjectAttrMap().contains(attribute.type)) {
ADD_FAILURE() << "Attribute value's type is unknown hex:" << std::hex
<< attribute.type;
return "";
}
return std::get<std::string>(GetPkcs12ObjectAttrMap().at(attribute.type));
}
static AttrValueType GetValueType(const CK_ATTRIBUTE& attribute) {
if (!GetPkcs12ObjectAttrMap().contains(attribute.type)) {
ADD_FAILURE() << "Attribute value's type is unknown hex:" << std::hex
<< attribute.type;
return AttrValueType::kNotDefined;
}
return std::get<AttrValueType>(GetPkcs12ObjectAttrMap().at(attribute.type));
}
private:
static const std::map<CK_ATTRIBUTE_TYPE,
std::pair<AttrValueType, std::string>>&
GetPkcs12ObjectAttrMap() {
// Map which keeps relation between PKCS12 object attribute type, attribute
// name and attribute value's type.
static std::map<CK_ATTRIBUTE_TYPE, std::pair<AttrValueType, std::string>>
attr_map;
if (attr_map.empty()) {
attr_map[CKA_TOKEN] = {kCkBool, "CKA_TOKEN"};
attr_map[CKA_PRIVATE] = {kCkBool, "CKA_PRIVATE"};
attr_map[CKA_VERIFY] = {kCkBool, "CKA_VERIFY"};
attr_map[CKA_MODULUS_BITS] = {kCkUlong, "CKA_MODULUS_BITS"};
attr_map[CKA_PUBLIC_EXPONENT] = {kCkBytes, "CKA_PUBLIC_EXPONENT"};
attr_map[CKA_SENSITIVE] = {kCkBool, "CKA_SENSITIVE"};
attr_map[CKA_EXTRACTABLE] = {kCkBool, "CKA_EXTRACTABLE"};
attr_map[CKA_SIGN] = {kCkBool, "CKA_SIGN"};
attr_map[kForceSoftwareAttribute] = {kCkBool, "kForceSoftwareAttribute"};
attr_map[CKA_CLASS] = {kCkUlong, "CKA_CLASS"};
attr_map[CKA_KEY_TYPE] = {kCkUlong, "CKA_KEY_TYPE"};
attr_map[CKA_UNWRAP] = {kCkBool, "CKA_UNWRAP"};
attr_map[CKA_DECRYPT] = {kCkBool, "CKA_DECRYPT"};
attr_map[CKA_MODULUS] = {kCkBytes, "CKA_MODULUS"};
attr_map[CKA_SIGN_RECOVER] = {kCkBool, "CKA_SIGN_RECOVER"};
attr_map[CKA_ID] = {kCkBytes, "CKA_ID"};
attr_map[CKA_PUBLIC_EXPONENT] = {kCkBytes, "CKA_PUBLIC_EXPONENT"};
attr_map[CKA_PRIVATE_EXPONENT] = {kCkBytes, "CKA_PRIVATE_EXPONENT"};
attr_map[CKA_PRIME_1] = {kCkBytes, "CKA_PRIME_1"};
attr_map[CKA_PRIME_2] = {kCkBytes, "CKA_PRIME_2"};
attr_map[CKA_EXPONENT_1] = {kCkBytes, "CKA_EXPONENT_1"};
attr_map[CKA_EXPONENT_2] = {kCkBytes, "CKA_EXPONENT_2"};
attr_map[CKA_COEFFICIENT] = {kCkBytes, "CKA_COEFFICIENT"};
attr_map[CKA_LABEL] = {kCkBytes, "CKA_LABEL"};
attr_map[CKA_VALUE] = {kCkBytes, "CKA_VALUE"};
attr_map[CKA_ISSUER] = {kCkBytes, "CKA_ISSUER"};
attr_map[CKA_SUBJECT] = {kCkBytes, "CKA_SUBJECT"};
attr_map[CKA_SERIAL_NUMBER] = {kCkBytes, "CKA_SERIAL_NUMBER"};
attr_map[CKA_NSS_EMAIL] = {kCkBytes, "CKA_NSS_EMAIL"};
attr_map[CKA_CERTIFICATE_TYPE] = {kCkBytes, "CKA_CERTIFICATE_TYPE"};
attr_map[CKA_EC_POINT] = {kCkBytes, "CKA_EC_POINT"};
attr_map[CKA_DERIVE] = {kCkBool, "CKA_DERIVE"};
attr_map[CKA_EC_PARAMS] = {kCkBytes, "CKA_EC_PARAMS"};
}
return attr_map;
}
};
// Generic holder for single parsed attribute with all parsing methods.
class AttributeData {
public:
AttributeData() = default;
explicit AttributeData(const CK_ATTRIBUTE& attribute) {
AttrValueType attr_value_type =
AttributesParsingOptions::GetValueType(attribute);
name_ = AttributesParsingOptions::GetName(attribute);
switch (attr_value_type) {
case kCkBool:
ck_bool_value_ = ParseCkBBool(attribute, name_);
break;
case kCkUlong:
ck_ulong_value_ = ParseCkULong(attribute, name_);
break;
case kCkBytes:
ck_bytes_value_ = ParseCkBytes(attribute);
break;
case kNotDefined:
ADD_FAILURE() << "Parser is not defined for attribute type:" << std::hex
<< attribute.type;
break;
}
}
~AttributeData() = default;
std::optional<CK_BBOOL> CkBool() { return ck_bool_value_; }
std::optional<CK_ULONG> CkULong() { return ck_ulong_value_; }
std::optional<std::vector<CK_BYTE>> CkByte() { return ck_bytes_value_; }
private:
std::string name_;
std::optional<CK_BBOOL> ck_bool_value_;
std::optional<CK_ULONG> ck_ulong_value_;
std::optional<std::vector<CK_BYTE>> ck_bytes_value_;
static std::optional<CK_BBOOL> ParseCkBBool(
const CK_ATTRIBUTE& attribute,
const std::string& attribute_name) {
if (attribute.ulValueLen < sizeof(CK_BBOOL)) {
ADD_FAILURE() << "Size to small for CK_BBOOL for attribute "
<< attribute_name << ": " << attribute.ulValueLen;
return std::nullopt;
}
CK_BBOOL value;
memcpy(&value, attribute.pValue, sizeof(CK_BBOOL));
return value;
}
static std::optional<CK_ULONG> ParseCkULong(
const CK_ATTRIBUTE& attribute,
const std::string& attribute_name) {
if (attribute.ulValueLen < sizeof(CK_ULONG)) {
ADD_FAILURE() << "Size to small for CK_ULONG for attribute "
<< attribute_name << ": " << attribute.ulValueLen;
return std::nullopt;
}
CK_ULONG value;
memcpy(&value, attribute.pValue, sizeof(CK_ULONG));
return value;
}
static std::optional<std::vector<CK_BYTE>> ParseCkBytes(
const CK_ATTRIBUTE& attribute) {
std::vector<CK_BYTE> result(attribute.ulValueLen);
memcpy(result.data(), attribute.pValue, result.size());
return result;
}
};
// Holds PKCS#11 attributes passed by the code under test.
struct ObjectAttributes {
ObjectAttributes() = default;
~ObjectAttributes() = default;
static ObjectAttributes ParseFrom(CK_ATTRIBUTE_PTR attributes,
CK_ULONG attributes_count) {
ObjectAttributes result;
for (CK_ULONG i = 0; i < attributes_count; ++i) {
const CK_ATTRIBUTE& attr = attributes[i];
if (result.parsed_attributes_map.contains(attr.type)) {
ADD_FAILURE() << "Already stored attribute type:" << attr.type;
}
result.parsed_attributes_map[attr.type] = AttributeData(attr);
}
return result;
}
std::optional<CK_BBOOL> GetCkBool(const CK_ATTRIBUTE_TYPE attribute_type) {
return parsed_attributes_map[attribute_type].CkBool();
}
std::optional<CK_ULONG> GetCkULong(const CK_ATTRIBUTE_TYPE attribute_type) {
return parsed_attributes_map[attribute_type].CkULong();
}
std::optional<std::vector<CK_BYTE>> GetCkByte(
const CK_ATTRIBUTE_TYPE attribute_type) {
return parsed_attributes_map[attribute_type].CkByte();
}
int Size() { return parsed_attributes_map.size(); }
std::map<CK_ATTRIBUTE_TYPE, AttributeData> parsed_attributes_map;
};
// Holds
// - flags triggering how FakeChapsSlotSession should behave.
// - data passed by the code under test to FakeChapsSlotSessionFactory and
// FakeChapsSlotSession.
struct PassedData {
// Controls whether ChapsSlotSessionFactory::CreateChapsSlotSession succeeds.
bool factory_success = true;
// Assigns results to operations. The key is the operation index, i.e. the
// sequence number of an operation performed on the ChapsSlotSession.
// The value is the operation result. CKR_INVALID_SESSION_HANDLE and
// CKR_SESSION_CLOSED have special meaning.
std::map<int, CK_RV> operation_results;
// If set to false, calls to ChapsSlotSession::ReopenSession will fail.
bool reopen_session_success = true;
// Counts how often the code under test called
// ChapsSlotSession::ReopenSession.
int reopen_session_call_count = 0;
// The slot_id passed into FakeChapsSlotSessionFactory.
std::optional<CK_SLOT_ID> slot_id;
// Attributes passed for the public key template to GenerateKeyPair.
ObjectAttributes public_key_gen_attributes;
// Attributes passed for the private key template to GenerateKeyPair.
ObjectAttributes private_key_gen_attributes;
// The data passed into FakeChapsSlotSession::SetAttributeValue for the
// CKA_ID attribute of the public key. Empty if SetAttributeValue was never
// called for that attribute.
std::vector<uint8_t> public_key_cka_id;
// The data passed into FakeChapsSlotSession::SetAttributeValue for the
// CKA_ID attribute of the private key. Empty if SetAttributeValue was never
// called for that attribute.
std::vector<uint8_t> private_key_cka_id;
// The data passed into FakeChapsSlotSession::SetAttributeValue for creation
// of key object from PKCS12 container.
ObjectAttributes pkcs12_key_attributes;
// The data passed into FakeChapsSlotSession::SetAttributeValue for creation
// of certificates objects from PKCS12 container. PKCS12 container can hold
// multiple certificates.
std::vector<ObjectAttributes> pkcs12_cert_attributes;
};
// FakeChapsSlotSession actually generates a key pair on a NSS slot. This is
// useful so it's possible to test whether the CKA_ID that the code under test
// would assign matches the CKA_ID that NSS computed for the key.
class FakeChapsSlotSession : public ChapsSlotSession {
public:
explicit FakeChapsSlotSession(PK11SlotInfo* slot, PassedData* passed_data)
: slot_(slot), passed_data_(passed_data) {}
~FakeChapsSlotSession() override = default;
bool ReopenSession() override {
++passed_data_->reopen_session_call_count;
// The code under test should only call this if it was given an indication
// that the session handle it uses is not valid anymore.
EXPECT_FALSE(session_ok_);
if (passed_data_->reopen_session_success) {
session_ok_ = true;
return true;
}
return false;
}
CK_RV CreateObject(CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulCount,
CK_OBJECT_HANDLE_PTR phObject) override {
EXPECT_TRUE(session_ok_);
CK_RV configured_result = ApplyConfiguredResult();
if (configured_result != CKR_OK) {
return configured_result;
}
ObjectAttributes parsing_result =
ObjectAttributes::ParseFrom(pTemplate, ulCount);
AttributeData parsed_object_type =
parsing_result.parsed_attributes_map[CKA_CLASS];
if (parsed_object_type.CkULong() == CKO_PRIVATE_KEY) {
passed_data_->pkcs12_key_attributes = parsing_result;
}
if (parsed_object_type.CkULong() == CKO_CERTIFICATE) {
passed_data_->pkcs12_cert_attributes.push_back(parsing_result);
}
return CKR_OK;
}
CK_RV GenerateKeyPair(CK_MECHANISM_PTR pMechanism,
CK_ATTRIBUTE_PTR pPublicKeyTemplate,
CK_ULONG ulPublicKeyAttributeCount,
CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
CK_ULONG ulPrivateKeyAttributeCount,
CK_OBJECT_HANDLE_PTR phPublicKey,
CK_OBJECT_HANDLE_PTR phPrivateKey) override {
EXPECT_TRUE(session_ok_);
CK_RV configured_result = ApplyConfiguredResult();
if (configured_result != CKR_OK) {
return configured_result;
}
passed_data_->public_key_gen_attributes = ObjectAttributes::ParseFrom(
pPublicKeyTemplate, ulPublicKeyAttributeCount);
passed_data_->private_key_gen_attributes = ObjectAttributes::ParseFrom(
pPrivateKeyTemplate, ulPrivateKeyAttributeCount);
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_TRUE(crypto::GenerateRSAKeyPairNSS(slot_, kKeySizeBits,
/*permanent=*/true, &public_key,
&private_key));
*phPublicKey = public_key->pkcs11ID;
public_key_handle_ = public_key->pkcs11ID;
*phPrivateKey = private_key->pkcs11ID;
private_key_handle_ = private_key->pkcs11ID;
// Remember the modulus.
SECItem* modulus = &(public_key->u.rsa.modulus);
public_key_modulus_.assign(modulus->data, modulus->data + modulus->len);
return CKR_OK;
}
CK_RV GetAttributeValue(CK_OBJECT_HANDLE hObject,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulCount) override {
EXPECT_TRUE(session_ok_);
CK_RV configured_result = ApplyConfiguredResult();
if (configured_result != CKR_OK) {
return configured_result;
}
if (hObject == public_key_handle_) {
const size_t kModulusBytes = kKeySizeBits / 8;
if (ulCount != 1 || pTemplate[0].type != CKA_MODULUS) {
return CKR_ATTRIBUTE_TYPE_INVALID;
}
if (pTemplate[0].ulValueLen < kModulusBytes) {
return CKR_BUFFER_TOO_SMALL;
}
memcpy(pTemplate[0].pValue, public_key_modulus_.data(), kModulusBytes);
return CKR_OK;
}
if (hObject == private_key_handle_) {
if (ulCount != 1 || pTemplate[0].type != kKeyInSoftware) {
return CKR_ATTRIBUTE_TYPE_INVALID;
}
const CK_BBOOL key_in_software_value = true;
if (pTemplate[0].ulValueLen < sizeof(key_in_software_value)) {
return CKR_BUFFER_TOO_SMALL;
}
memcpy(pTemplate[0].pValue, &key_in_software_value,
sizeof(key_in_software_value));
return CKR_OK;
}
return CKR_OBJECT_HANDLE_INVALID;
}
CK_RV SetAttributeValue(CK_OBJECT_HANDLE hObject,
CK_ATTRIBUTE_PTR pTemplate,
CK_ULONG ulCount) override {
EXPECT_TRUE(session_ok_);
CK_RV configured_result = ApplyConfiguredResult();
if (configured_result != CKR_OK) {
return configured_result;
}
if (ulCount != 1 || pTemplate[0].type != CKA_ID) {
return CKR_ATTRIBUTE_TYPE_INVALID;
}
uint8_t* data = reinterpret_cast<uint8_t*>(pTemplate[0].pValue);
size_t length = pTemplate[0].ulValueLen;
if (hObject == public_key_handle_) {
passed_data_->public_key_cka_id.assign(data, data + length);
return CKR_OK;
} else if (hObject == private_key_handle_) {
passed_data_->private_key_cka_id.assign(data, data + length);
return CKR_OK;
}
return CKR_OBJECT_HANDLE_INVALID;
}
private:
// Applies a result configured for the current operation, if any.
CK_RV ApplyConfiguredResult() {
int cur_operation = operation_count_;
++operation_count_;
auto operation_result = passed_data_->operation_results.find(cur_operation);
if (operation_result == passed_data_->operation_results.end()) {
return CKR_OK;
}
CK_RV result = operation_result->second;
// CKR_SESSION_HANDLE_INVALID and CKR_SESSION_CLOSED have a special meaning
// - also flag that the session handle is not usable (until the next call to
// ReopenSession).
if (result == CKR_SESSION_HANDLE_INVALID || result == CKR_SESSION_CLOSED) {
session_ok_ = false;
}
return result;
}
// Keeps track of how many operations were already performed. Used to keep
// track of the operation sequence number for PassedData::operation_results.
int operation_count_ = 0;
// If false, the session is not usable until ReopenSession has been called.
bool session_ok_ = true;
// Unowned.
const raw_ptr<PK11SlotInfo> slot_;
// Unowned.
const raw_ptr<PassedData> passed_data_;
// Cached modulus of the generated public key so GetAttributeValue with
// CKA_MODULUS is supported.
std::vector<uint8_t> public_key_modulus_;
CK_OBJECT_HANDLE public_key_handle_ = CKR_OBJECT_HANDLE_INVALID;
CK_OBJECT_HANDLE private_key_handle_ = CKR_OBJECT_HANDLE_INVALID;
};
class FakeChapsSlotSessionFactory : public ChapsSlotSessionFactory {
public:
FakeChapsSlotSessionFactory(PK11SlotInfo* slot, PassedData* passed_data)
: slot_(slot), passed_data_(passed_data) {}
~FakeChapsSlotSessionFactory() override = default;
std::unique_ptr<ChapsSlotSession> CreateChapsSlotSession(
CK_SLOT_ID slot_id) override {
passed_data_->slot_id = slot_id;
if (!passed_data_->factory_success) {
return nullptr;
}
return std::make_unique<FakeChapsSlotSession>(slot_, passed_data_);
}
private:
// Unowned.
const raw_ptr<PK11SlotInfo> slot_;
// Unowned.
const raw_ptr<PassedData> passed_data_;
};
class ChapsUtilImplTest : public ::testing::Test {
public:
ChapsUtilImplTest() {
auto chaps_slot_session_factory =
std::make_unique<FakeChapsSlotSessionFactory>(nss_test_db_.slot(),
&passed_data_);
chaps_util_impl_ =
std::make_unique<ChapsUtilImpl>(std::move(chaps_slot_session_factory));
chaps_util_impl_->SetIsChapsProvidedSlotForTesting(true);
}
ChapsUtilImplTest(const ChapsUtilImplTest&) = delete;
ChapsUtilImplTest& operator=(const ChapsUtilImplTest&) = delete;
~ChapsUtilImplTest() override = default;
protected:
static std::vector<uint8_t> ReadTestFile(const std::string& file_name) {
base::FilePath file_path =
net::GetTestCertsDirectory().AppendASCII(file_name);
std::optional<std::vector<uint8_t>> file_data = ReadFileToBytes(file_path);
EXPECT_TRUE(file_data.has_value());
if (!file_data.has_value()) {
return {};
}
return file_data.value();
}
static std::vector<uint8_t>& GetPkcs12Data(std::string file_name) {
static std::vector<uint8_t> pkcs12_data_;
pkcs12_data_ = ReadTestFile(file_name);
return pkcs12_data_;
}
static std::vector<uint8_t>& GetPkcs12Data() {
return GetPkcs12Data("client.p12");
}
static std::vector<uint8_t>& GetPkcs12WithEcKeyData() {
return GetPkcs12Data("client_with_ec_key.p12");
}
bool KeyImportNeverDone() const {
ObjectAttributes data = passed_data_.pkcs12_key_attributes;
return data.Size() == 0;
}
bool CertImportNeverDone() const {
return passed_data_.pkcs12_cert_attributes.empty();
}
bool KeyImportDone() const {
ObjectAttributes data = passed_data_.pkcs12_key_attributes;
return data.Size() == 19; // valid only for ReadTestFile("client.p12").
}
bool CertImportDone() const {
ObjectAttributes data = passed_data_.pkcs12_cert_attributes[0];
return data.Size() == 10; // valid only for ReadTestFile("client.p12").
}
crypto::ScopedTestNSSDB nss_test_db_;
PassedData passed_data_;
std::unique_ptr<ChapsUtilImpl> chaps_util_impl_;
};
// Returns the CKA_ID of |private_key|. This is the CKA_ID that NSS assigned to
// the private key, and thus it should be the same CKA_ID that ChapsUtil
// attempts to assign to the private and public key.
std::vector<uint8_t> GetExpectedCkaId(SECKEYPrivateKey* private_key) {
crypto::ScopedSECItem cka_id_secitem(
PK11_GetLowLevelKeyIDForPrivateKey(private_key));
uint8_t* cka_id_data = reinterpret_cast<uint8_t*>(cka_id_secitem->data);
return {cka_id_data, cka_id_data + cka_id_secitem->len};
}
// Successfully generates a software-backed key pair. Also verifies CKA_ID
// assignment.
TEST_F(ChapsUtilImplTest, GenerateSoftwareKeyPairSuccess) {
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
ASSERT_TRUE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
// Verify that ChapsUtil passed the correct slot id to the factory.
EXPECT_EQ(passed_data_.slot_id, PK11_GetSlotID(nss_test_db_.slot()));
// Verify that ChapsUtil passed the expected attributes.
// Check attributes for public key.
ObjectAttributes public_key_data = passed_data_.public_key_gen_attributes;
const int expected_public_key_attributes = 5;
EXPECT_EQ(public_key_data.Size(), expected_public_key_attributes);
EXPECT_EQ(public_key_data.GetCkBool(CKA_TOKEN), CK_TRUE);
EXPECT_EQ(public_key_data.GetCkBool(CKA_PRIVATE), CK_FALSE);
EXPECT_EQ(public_key_data.GetCkBool(CKA_VERIFY), CK_TRUE);
EXPECT_EQ(public_key_data.GetCkULong(CKA_MODULUS_BITS), (CK_ULONG)2048);
EXPECT_EQ(public_key_data.GetCkByte(CKA_PUBLIC_EXPONENT),
(std::vector<CK_BYTE>{0x01, 0x00, 0x01}));
// Check attributes for private key.
ObjectAttributes private_key_data = passed_data_.private_key_gen_attributes;
const int expected_private_key_attributes = 6;
EXPECT_EQ(private_key_data.Size(), expected_private_key_attributes);
EXPECT_EQ(private_key_data.GetCkBool(CKA_TOKEN), CK_TRUE);
EXPECT_EQ(private_key_data.GetCkBool(CKA_PRIVATE), CK_TRUE);
EXPECT_EQ(private_key_data.GetCkBool(CKA_SENSITIVE), CK_TRUE);
EXPECT_EQ(private_key_data.GetCkBool(CKA_EXTRACTABLE), CK_FALSE);
EXPECT_EQ(private_key_data.GetCkBool(kForceSoftwareAttribute), CK_TRUE);
EXPECT_EQ(private_key_data.GetCkBool(CKA_SIGN), CK_TRUE);
// Verify that ChapsUtil attempted to assign the correct CKA_ID to the public
// and private key objects.
std::vector<uint8_t> expected_cka_id = GetExpectedCkaId(private_key.get());
EXPECT_EQ(passed_data_.public_key_cka_id, expected_cka_id);
EXPECT_EQ(passed_data_.private_key_cka_id, expected_cka_id);
}
// The passed slot is not provided by chaps. The operation fails.
TEST_F(ChapsUtilImplTest, NotChapsProvidedSlot) {
chaps_util_impl_->SetIsChapsProvidedSlotForTesting(false);
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_FALSE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
}
// A ChapsSlotSession can not be created, so the operation fails.
TEST_F(ChapsUtilImplTest, ChapsSlotSessionFactoryFailure) {
passed_data_.factory_success = false;
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_FALSE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
// Verify that ChapsUtil passed the correct slot id to the factory.
EXPECT_EQ(passed_data_.slot_id, PK11_GetSlotID(nss_test_db_.slot()));
}
// A PKCS11 operation fails with a generic failure. The operation fails.
TEST_F(ChapsUtilImplTest, OperationFails) {
passed_data_.operation_results[0] = CKR_FUNCTION_FAILED;
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_FALSE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
}
// A PKCS11 operation fails with CKR_SESSION_HANDLE_INVALID. ChapsUtilImpl
// re-opens the session and retries the operation.
TEST_F(ChapsUtilImplTest, HandlesInvalidSessionHandle_ReopenOk) {
passed_data_.operation_results[0] = CKR_SESSION_HANDLE_INVALID;
passed_data_.reopen_session_success = true;
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_TRUE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
EXPECT_EQ(passed_data_.reopen_session_call_count, 1);
}
// A PKCS11 operation fails with CKR_SESSION_HANDLE_INVALID twice. ChapsUtilImpl
// re-opens the session and retries the operation.
TEST_F(ChapsUtilImplTest, HandlesInvalidSessionHandle_ReopenTwiceOk) {
passed_data_.operation_results[0] = CKR_SESSION_HANDLE_INVALID;
passed_data_.operation_results[1] = CKR_SESSION_CLOSED;
passed_data_.reopen_session_success = true;
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_TRUE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
EXPECT_EQ(passed_data_.reopen_session_call_count, 2);
}
// A PKCS11 operation fails with CKR_SESSION_HANDLE_INVALID many times.
// ChapsUtilImpl gives up attempts to retry after 5 times.
TEST_F(ChapsUtilImplTest, HandlesInvalidSessionHandle_ReopenGivesUp) {
passed_data_.operation_results[0] = CKR_SESSION_HANDLE_INVALID;
passed_data_.operation_results[1] = CKR_SESSION_HANDLE_INVALID;
passed_data_.operation_results[2] = CKR_SESSION_HANDLE_INVALID;
passed_data_.operation_results[3] = CKR_SESSION_HANDLE_INVALID;
passed_data_.operation_results[4] = CKR_SESSION_HANDLE_INVALID;
passed_data_.operation_results[5] = CKR_SESSION_HANDLE_INVALID;
passed_data_.reopen_session_success = true;
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_FALSE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
EXPECT_EQ(passed_data_.reopen_session_call_count, 5);
}
// A PKCS11 operation fails with CKR_SESSION_HANDLE_INVALID and the session can
// not be re-opened. The operation fails.
TEST_F(ChapsUtilImplTest, HandlesInvalidSessionHandle_ReopenFails) {
passed_data_.operation_results[0] = CKR_SESSION_HANDLE_INVALID;
passed_data_.reopen_session_success = false;
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
EXPECT_FALSE(chaps_util_impl_->GenerateSoftwareBackedRSAKey(
nss_test_db_.slot(), kKeySizeBits, &public_key, &private_key));
EXPECT_EQ(passed_data_.reopen_session_call_count, 1);
}
} // namespace
} // namespace chromeos