blob: 1f5dfbbc35a1ce1da3f1a43668ba62d87744fa57 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/pkcs11_data_store.h"
#include <map>
#include <string>
#include <vector>
#include <base/check.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <brillo/cryptohome.h>
#include <brillo/map_utils.h>
#include <chaps/attributes.h>
#include <chaps/chaps_proxy_mock.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using ::testing::_;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace {
const uint64_t kSession = 7; // Arbitrary non-zero value.
const CK_SLOT_ID kDefaultSlot = 1;
class ScopedFakeSalt {
public:
ScopedFakeSalt() : salt_(128, 0) {
brillo::cryptohome::home::SetSystemSalt(&salt_);
}
~ScopedFakeSalt() { brillo::cryptohome::home::SetSystemSalt(nullptr); }
private:
std::string salt_;
};
class ScopedDisableVerboseLogging {
public:
ScopedDisableVerboseLogging()
: original_severity_(logging::GetMinLogLevel()) {
logging::SetMinLogLevel(logging::LOGGING_INFO);
}
~ScopedDisableVerboseLogging() {
logging::SetMinLogLevel(original_severity_);
}
private:
logging::LogSeverity original_severity_;
};
} // namespace
namespace shill {
typedef chaps::ChapsProxyMock Pkcs11Mock;
// Implements a fake PKCS #11 object store. Labeled data blobs can be stored
// and later retrieved. The mocked interface is ChapsInterface so these
// tests must be linked with the Chaps PKCS #11 library. The mock class itself
// is part of the Chaps package; it is reused here to avoid duplication (see
// chaps_proxy_mock.h).
class PKCS11DataStoreTest : public testing::Test {
public:
PKCS11DataStoreTest()
: pkcs11_(false), // Do not pre-initialize the mock PKCS #11 library.
// This just controls whether the first call to
// C_Initialize returns 'already initialized'.
next_handle_(1) {}
PKCS11DataStoreTest(const PKCS11DataStoreTest&) = delete;
PKCS11DataStoreTest& operator=(const PKCS11DataStoreTest&) = delete;
~PKCS11DataStoreTest() override = default;
void SetUp() override {
std::vector<uint64_t> slot_list = {0, 1};
ON_CALL(pkcs11_, GetSlotList(_, _, _))
.WillByDefault(DoAll(SetArgPointee<2>(slot_list), Return(0)));
ON_CALL(pkcs11_, OpenSession(_, _, _, _))
.WillByDefault(DoAll(SetArgPointee<3>(kSession), Return(0)));
ON_CALL(pkcs11_, CloseSession(_, _)).WillByDefault(Return(0));
ON_CALL(pkcs11_, CreateObject(_, _, _, _))
.WillByDefault(Invoke(this, &PKCS11DataStoreTest::CreateObject));
ON_CALL(pkcs11_, DestroyObject(_, _, _))
.WillByDefault(Invoke(this, &PKCS11DataStoreTest::DestroyObject));
ON_CALL(pkcs11_, GetAttributeValue(_, _, _, _, _))
.WillByDefault(Invoke(this, &PKCS11DataStoreTest::GetAttributeValue));
ON_CALL(pkcs11_, SetAttributeValue(_, _, _, _))
.WillByDefault(Invoke(this, &PKCS11DataStoreTest::SetAttributeValue));
ON_CALL(pkcs11_, FindObjectsInit(_, _, _))
.WillByDefault(Invoke(this, &PKCS11DataStoreTest::FindObjectsInit));
ON_CALL(pkcs11_, FindObjects(_, _, _, _))
.WillByDefault(Invoke(this, &PKCS11DataStoreTest::FindObjects));
ON_CALL(pkcs11_, FindObjectsFinal(_, _)).WillByDefault(Return(0));
}
// Stores a new labeled object, only CKA_LABEL and CKA_VALUE are relevant.
virtual uint32_t CreateObject(const brillo::SecureBlob& isolate,
uint64_t session_id,
const std::vector<uint8_t>& attributes,
uint64_t* new_object_handle) {
*new_object_handle = next_handle_++;
std::string label = GetValue(attributes, CKA_LABEL);
handles_[*new_object_handle] = label;
values_[label] = GetValue(attributes, CKA_VALUE);
labels_[label] = *new_object_handle;
return CKR_OK;
}
// Deletes a labeled object.
virtual uint32_t DestroyObject(const brillo::SecureBlob& isolate,
uint64_t session_id,
uint64_t object_handle) {
std::string label = handles_[object_handle];
handles_.erase(object_handle);
values_.erase(label);
labels_.erase(label);
return CKR_OK;
}
// Supports reading CKA_VALUE.
virtual uint32_t GetAttributeValue(const brillo::SecureBlob& isolate,
uint64_t session_id,
uint64_t object_handle,
const std::vector<uint8_t>& attributes_in,
std::vector<uint8_t>* attributes_out) {
std::string label = handles_[object_handle];
std::string value = values_[label];
chaps::Attributes parsed;
parsed.Parse(attributes_in);
if (parsed.num_attributes() == 1 &&
parsed.attributes()[0].type == CKA_LABEL)
value = label;
if (parsed.num_attributes() != 1 ||
(parsed.attributes()[0].type != CKA_VALUE &&
parsed.attributes()[0].type != CKA_LABEL) ||
(parsed.attributes()[0].pValue &&
parsed.attributes()[0].ulValueLen != value.size()))
return CKR_GENERAL_ERROR;
parsed.attributes()[0].ulValueLen = value.size();
if (parsed.attributes()[0].pValue)
memcpy(parsed.attributes()[0].pValue, value.data(), value.size());
parsed.Serialize(attributes_out);
return CKR_OK;
}
// Supports writing CKA_VALUE.
virtual uint32_t SetAttributeValue(const brillo::SecureBlob& isolate,
uint64_t session_id,
uint64_t object_handle,
const std::vector<uint8_t>& attributes) {
values_[handles_[object_handle]] = GetValue(attributes, CKA_VALUE);
return CKR_OK;
}
// Finds stored objects by CKA_LABEL or CKA_VALUE. If no CKA_LABEL or
// CKA_VALUE, find all objects.
virtual uint32_t FindObjectsInit(const brillo::SecureBlob& isolate,
uint64_t session_id,
const std::vector<uint8_t>& attributes) {
std::string label = GetValue(attributes, CKA_LABEL);
std::string value = GetValue(attributes, CKA_VALUE);
found_objects_.clear();
if (label.empty() && value.empty()) {
// Find all objects.
found_objects_ = brillo::GetMapKeysAsVector(handles_);
} else if (!label.empty() && labels_.count(label) > 0) {
// Find only the object with |label|.
found_objects_.push_back(labels_[label]);
} else {
// Find all objects with |value|.
for (const auto& item : values_) {
if (item.second == value && labels_.count(item.first) > 0) {
found_objects_.push_back(labels_[item.first]);
}
}
}
return CKR_OK;
}
// Reports a 'found' object based on find_status_.
virtual uint32_t FindObjects(const brillo::SecureBlob& isolate,
uint64_t session_id,
uint64_t max_object_count,
std::vector<uint64_t>* object_list) {
while (!found_objects_.empty() && object_list->size() < max_object_count) {
object_list->push_back(found_objects_.back());
found_objects_.pop_back();
}
return CKR_OK;
}
protected:
NiceMock<Pkcs11Mock> pkcs11_;
private:
// A helper to pull the value for a given attribute out of a serialized
// template.
std::string GetValue(const std::vector<uint8_t>& attributes,
CK_ATTRIBUTE_TYPE type) {
chaps::Attributes parsed;
parsed.Parse(attributes);
CK_ATTRIBUTE_PTR array = parsed.attributes();
for (CK_ULONG i = 0; i < parsed.num_attributes(); ++i) {
if (array[i].type == type) {
if (!array[i].pValue)
return "";
return std::string(reinterpret_cast<char*>(array[i].pValue),
array[i].ulValueLen);
}
}
return "";
}
std::map<std::string, std::string> values_; // The fake store: label->value
std::map<uint64_t, std::string> handles_; // The fake store: handle->label
std::map<std::string, uint64_t> labels_; // The fake store: label->handle
std::vector<uint64_t> found_objects_; // The most recent search results
uint64_t next_handle_; // Tracks handle assignment
ScopedFakeSalt fake_system_salt_;
// We want to avoid all the Chaps verbose logging.
ScopedDisableVerboseLogging no_verbose_logging;
};
// Exercises the key store when PKCS #11 returns success. This exercises all
// non-error-handling code paths.
TEST_F(PKCS11DataStoreTest, Pkcs11Success) {
Pkcs11DataStore key_store;
std::string blob;
EXPECT_FALSE(key_store.Read(kDefaultSlot, "test", &blob));
EXPECT_TRUE(key_store.Write(kDefaultSlot, "test", "test_data"));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "test", &blob));
EXPECT_EQ("test_data", blob);
// Try with a different key name.
EXPECT_FALSE(key_store.Read(kDefaultSlot, "test2", &blob));
EXPECT_TRUE(key_store.Write(kDefaultSlot, "test2", "test_data2"));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "test2", &blob));
EXPECT_EQ("test_data2", blob);
// Read the original key again.
EXPECT_TRUE(key_store.Read(kDefaultSlot, "test", &blob));
EXPECT_EQ("test_data", blob);
// Replace key data.
EXPECT_TRUE(key_store.Write(kDefaultSlot, "test", "test_data3"));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "test", &blob));
EXPECT_EQ("test_data3", blob);
// Delete key data.
EXPECT_TRUE(key_store.Delete(kDefaultSlot, "test2"));
EXPECT_FALSE(key_store.Read(kDefaultSlot, "test2", &blob));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "test", &blob));
}
// Tests the key store when PKCS #11 fails to open a session.
TEST_F(PKCS11DataStoreTest, NoSession) {
EXPECT_CALL(pkcs11_, OpenSession(_, _, _, _))
.WillRepeatedly(Return(CKR_GENERAL_ERROR));
Pkcs11DataStore key_store;
std::string blob;
EXPECT_FALSE(key_store.Write(kDefaultSlot, "test", "test_data"));
EXPECT_FALSE(key_store.Read(kDefaultSlot, "test", &blob));
}
// Tests the key store when PKCS #11 fails to create an object.
TEST_F(PKCS11DataStoreTest, CreateObjectFail) {
EXPECT_CALL(pkcs11_, CreateObject(_, _, _, _))
.WillRepeatedly(Return(CKR_GENERAL_ERROR));
Pkcs11DataStore key_store;
std::string blob;
EXPECT_FALSE(key_store.Write(kDefaultSlot, "test", "test_data"));
EXPECT_FALSE(key_store.Read(kDefaultSlot, "test", &blob));
}
// Tests the key store when PKCS #11 fails to read attribute values.
TEST_F(PKCS11DataStoreTest, ReadValueFail) {
EXPECT_CALL(pkcs11_, GetAttributeValue(_, _, _, _, _))
.WillRepeatedly(Return(CKR_GENERAL_ERROR));
Pkcs11DataStore key_store;
std::string blob;
EXPECT_TRUE(key_store.Write(kDefaultSlot, "test", "test_data"));
EXPECT_FALSE(key_store.Read(kDefaultSlot, "test", &blob));
}
// Tests the key store when PKCS #11 fails to delete key data.
TEST_F(PKCS11DataStoreTest, DeleteValueFail) {
EXPECT_CALL(pkcs11_, DestroyObject(_, _, _))
.WillRepeatedly(Return(CKR_GENERAL_ERROR));
Pkcs11DataStore key_store;
EXPECT_TRUE(key_store.Write(kDefaultSlot, "test", "test_data"));
EXPECT_FALSE(key_store.Write(kDefaultSlot, "test", "test_data2"));
EXPECT_FALSE(key_store.Delete(kDefaultSlot, "test"));
}
// Tests that the DeleteByPrefix() method removes the correct objects and only
// the correct objects.
TEST_F(PKCS11DataStoreTest, DeleteByPrefix) {
Pkcs11DataStore key_store;
// Test with no keys.
ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultSlot, "prefix"));
// Test with a single matching key.
ASSERT_TRUE(key_store.Write(kDefaultSlot, "prefix_test", "test"));
ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultSlot, "prefix"));
std::string blob;
EXPECT_FALSE(key_store.Read(kDefaultSlot, "prefix_test", &blob));
// Test with a single non-matching key.
ASSERT_TRUE(key_store.Write(kDefaultSlot, "_prefix_", "test"));
ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultSlot, "prefix"));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "_prefix_", &blob));
// Test with an empty prefix.
ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultSlot, ""));
EXPECT_FALSE(key_store.Read(kDefaultSlot, "_prefix_", &blob));
// Test with multiple matching and non-matching keys.
const int kNumKeys = 110; // Pkcs11DataStore max is 100 for FindObjects.
key_store.Write(kDefaultSlot, "other1", "test");
for (int i = 0; i < kNumKeys; ++i) {
std::string key_name = std::string("prefix") + base::NumberToString(i);
key_store.Write(kDefaultSlot, key_name, std::string(key_name));
}
ASSERT_TRUE(key_store.Write(kDefaultSlot, "other2", "test"));
ASSERT_TRUE(key_store.DeleteByPrefix(kDefaultSlot, "prefix"));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "other1", &blob));
EXPECT_TRUE(key_store.Read(kDefaultSlot, "other2", &blob));
for (int i = 0; i < kNumKeys; ++i) {
std::string key_name = std::string("prefix") + base::NumberToString(i);
EXPECT_FALSE(key_store.Read(kDefaultSlot, key_name, &blob));
}
}
} // namespace shill