blob: 420beb4be4d14724bdca73039ddc60f5ead066c4 [file] [log] [blame]
// Copyright 2020 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 "arc/keymaster/context/chaps_client.h"
#include <vector>
#include <brillo/secure_blob.h>
#include <chaps/attributes.h>
#include <chaps/chaps_proxy_mock.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "arc/keymaster/context/context_adaptor.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace arc {
namespace keymaster {
namespace context {
namespace {
// Arbitrary non-zero CK_SESSION_HANDLE.
constexpr uint64_t kSessionId = 9;
// Arbitrary single-element list.
const std::vector<uint64_t> kObjectList = {7};
const std::vector<uint64_t> kEmptyObjectList = {};
// Arbitrary 32 byte key.
const std::vector<uint8_t> kKeyBlob(32, 99);
} // anonymous namespace
// Fixture for chaps client tests.
class ChapsClientTest : public ::testing::Test {
public:
ChapsClientTest()
: chaps_mock_(/* is_initialized */ true),
chaps_client_(context_adaptor_.GetWeakPtr()) {}
uint32_t FakeGetAttributeValue(const brillo::SecureBlob& isolate_credential,
uint64_t session_id,
uint64_t object_handle,
const std::vector<uint8_t>& attributes_in,
std::vector<uint8_t>* attributes_out) {
chaps::Attributes parsed;
parsed.Parse(attributes_in);
parsed.attributes()[0].ulValueLen = kKeyBlob.size();
if (parsed.attributes()[0].pValue) {
memcpy(parsed.attributes()[0].pValue, kKeyBlob.data(), kKeyBlob.size());
}
parsed.Serialize(attributes_out);
return CKR_OK;
}
protected:
void SetUp() override {
context_adaptor_.set_slot_for_tests(1);
ON_CALL(chaps_mock_, OpenSession(_, _, _, _))
.WillByDefault(DoAll(SetArgPointee<3>(kSessionId), Return(CKR_OK)));
ON_CALL(chaps_mock_, CloseSession(_, _)).WillByDefault(Return(CKR_OK));
ON_CALL(chaps_mock_, FindObjectsInit(_, _, _))
.WillByDefault(Return(CKR_OK));
ON_CALL(chaps_mock_, FindObjects(_, _, _, _))
.WillByDefault(DoAll(SetArgPointee<3>(kObjectList), Return(CKR_OK)));
ON_CALL(chaps_mock_, FindObjectsFinal(_, _)).WillByDefault(Return(CKR_OK));
ON_CALL(chaps_mock_, GetAttributeValue(_, _, _, _, _))
.WillByDefault(Invoke(/* obj_ptr */ this,
&ChapsClientTest::FakeGetAttributeValue));
}
::testing::NiceMock<::chaps::ChapsProxyMock> chaps_mock_;
ContextAdaptor context_adaptor_;
ChapsClient chaps_client_;
};
TEST_F(ChapsClientTest, UsesSlotFromAdaptor) {
// Setup a fake slot in the cache.
uint64_t slot = 42;
context_adaptor_.set_slot_for_tests(slot);
// Expect chaps client will use the given slot.
EXPECT_CALL(chaps_mock_, OpenSession(_, Eq(slot), _, _));
// Call an operation that triggers slot usage.
chaps_client_.session_handle();
}
TEST_F(ChapsClientTest, ExportExistingEncryptionKey) {
// An existing key is prepared in fixture setup, expect no generation happens.
EXPECT_CALL(chaps_mock_, GenerateKey(_, _, _, _, _, _)).Times(0);
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify output.
ASSERT_TRUE(encryption_key.has_value());
std::vector<uint8_t> key(encryption_key->begin(), encryption_key->end());
EXPECT_EQ(kKeyBlob, key);
}
TEST_F(ChapsClientTest, ExportGeneratedEncryptionKey) {
// Expect no existing key is found and generation is called.
EXPECT_CALL(chaps_mock_, FindObjects(_, _, _, _))
.WillOnce(DoAll(SetArgPointee<3>(kEmptyObjectList), Return(CKR_OK)));
EXPECT_CALL(chaps_mock_, GenerateKey(_, _, _, _, _, _))
.WillOnce(Return(CKR_OK));
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify output.
ASSERT_TRUE(encryption_key.has_value());
std::vector<uint8_t> key(encryption_key->begin(), encryption_key->end());
EXPECT_EQ(kKeyBlob, key);
}
TEST_F(ChapsClientTest, CachesExportedEncryptionKey) {
// Expect chaps is queried only once.
EXPECT_CALL(chaps_mock_, FindObjects(_, _, _, _)).Times(1);
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify exported key is cached in adaptor
ASSERT_TRUE(context_adaptor_.encryption_key().has_value());
std::vector<uint8_t> key(context_adaptor_.encryption_key()->begin(),
context_adaptor_.encryption_key()->end());
EXPECT_EQ(kKeyBlob, key);
// Verify exporting key again won't trigger more FindObject calls.
for (int i = 0; i < 10; ++i)
chaps_client_.ExportOrGenerateEncryptionKey();
}
TEST_F(ChapsClientTest, ReturnsCachedEncryptionKey) {
// Prepare the adaptor cache with a key.
brillo::SecureBlob in_key(kKeyBlob.begin(), kKeyBlob.end());
context_adaptor_.set_encryption_key(in_key);
// Expect chaps is never asked to find nor generate a key.
EXPECT_CALL(chaps_mock_, FindObjects(_, _, _, _)).Times(0);
EXPECT_CALL(chaps_mock_, GenerateKey(_, _, _, _, _, _)).Times(0);
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify exported key is what we prepared in adaptor cache.
ASSERT_TRUE(encryption_key.has_value());
std::vector<uint8_t> key(encryption_key->begin(), encryption_key->end());
EXPECT_EQ(kKeyBlob, key);
}
TEST_F(ChapsClientTest, FindKeyHandlesInvalidSession) {
// Expect a retry if FindObjects returns CKR_SESSION_HANDLE_INVALID.
EXPECT_CALL(chaps_mock_, FindObjects(_, _, _, _))
.WillOnce(Return(CKR_SESSION_HANDLE_INVALID))
.WillOnce(Return(CKR_SESSION_HANDLE_INVALID))
.WillOnce(DoAll(SetArgPointee<3>(kObjectList), Return(CKR_OK)));
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify key is exported successfully.
ASSERT_TRUE(encryption_key.has_value());
std::vector<uint8_t> key(encryption_key->begin(), encryption_key->end());
EXPECT_EQ(kKeyBlob, key);
}
TEST_F(ChapsClientTest, GenerateKeyHandlesInvalidSession) {
// Expect a retry if GenerateKey returns CKR_SESSION_HANDLE_INVALID.
EXPECT_CALL(chaps_mock_, FindObjects(_, _, _, _))
.WillOnce(DoAll(SetArgPointee<3>(kEmptyObjectList), Return(CKR_OK)));
EXPECT_CALL(chaps_mock_, GenerateKey(_, _, _, _, _, _))
.WillOnce(Return(CKR_SESSION_HANDLE_INVALID))
.WillOnce(Return(CKR_SESSION_HANDLE_INVALID))
.WillOnce(Return(CKR_OK));
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify output.
ASSERT_TRUE(encryption_key.has_value());
std::vector<uint8_t> key(encryption_key->begin(), encryption_key->end());
EXPECT_EQ(kKeyBlob, key);
}
TEST_F(ChapsClientTest, GetAttributeHandlesInvalidSession) {
// Expect a retry if GetAttribute returns CKR_SESSION_HANDLE_INVALID.
EXPECT_CALL(chaps_mock_, GetAttributeValue(_, _, _, _, _))
.WillOnce(Return(CKR_SESSION_HANDLE_INVALID))
.WillOnce(Return(CKR_SESSION_HANDLE_INVALID))
.WillRepeatedly(
Invoke(/* obj_ptr */ this, &ChapsClientTest::FakeGetAttributeValue));
// Call export key.
base::Optional<brillo::SecureBlob> encryption_key =
chaps_client_.ExportOrGenerateEncryptionKey();
// Verify output.
ASSERT_TRUE(encryption_key.has_value());
std::vector<uint8_t> key(encryption_key->begin(), encryption_key->end());
EXPECT_EQ(kKeyBlob, key);
}
} // namespace context
} // namespace keymaster
} // namespace arc