// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "test/librbd/test_mock_fixture.h" #include "test/librbd/test_support.h" #include "test/librbd/mock/MockImageCtx.h" namespace librbd { namespace util { inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { return image_ctx->image_ctx; } } // namespace util } // namespace librbd #include "librbd/crypto/luks/LoadRequest.cc" namespace librbd { namespace crypto { namespace luks { using ::testing::_; using ::testing::Invoke; using ::testing::Return; struct TestMockCryptoLuksLoadRequest : public TestMockFixture { typedef LoadRequest MockLoadRequest; const size_t OBJECT_SIZE = 4 * 1024 * 1024; const char* passphrase_cstr = "password"; std::string passphrase = passphrase_cstr; MockImageCtx* mock_image_ctx; std::unique_ptr crypto; MockLoadRequest* mock_load_request; C_SaferCond finished_cond; Context *on_finish = &finished_cond; Context* image_read_request; ceph::bufferlist header_bl; uint64_t data_offset; std::string detected_format_name; void SetUp() override { TestMockFixture::SetUp(); librbd::ImageCtx *ictx; ASSERT_EQ(0, open_image(m_image_name, &ictx)); mock_image_ctx = new MockImageCtx(*ictx); mock_load_request = MockLoadRequest::create( mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, std::move(passphrase), &crypto, &detected_format_name, on_finish); detected_format_name = ""; } void TearDown() override { delete mock_image_ctx; TestMockFixture::TearDown(); } // returns data offset in bytes void generate_header(const char* type, const char* alg, size_t key_size, const char* cipher_mode, uint32_t sector_size, bool magic_switched) { Header header(mock_image_ctx->cct); ASSERT_EQ(0, header.init()); ASSERT_EQ(0, header.format(type, alg, nullptr, key_size, cipher_mode, sector_size, OBJECT_SIZE, true)); ASSERT_EQ(0, header.add_keyslot(passphrase_cstr, strlen(passphrase_cstr))); ASSERT_LT(0, header.read(&header_bl)); if (magic_switched) { ASSERT_EQ(0, Magic::replace_magic(mock_image_ctx->cct, header_bl)); } data_offset = header.get_data_offset(); } void expect_image_read(uint64_t offset, uint64_t length) { EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)) .WillOnce(Invoke([this, offset, length](io::ImageDispatchSpec* spec) { auto* read = boost::get( &spec->request); ASSERT_TRUE(read != nullptr); ASSERT_EQ(1, spec->image_extents.size()); ASSERT_EQ(offset, spec->image_extents[0].first); ASSERT_EQ(length, spec->image_extents[0].second); spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; auto aio_comp = spec->aio_comp; aio_comp->set_request_count(1); aio_comp->read_result = std::move(read->read_result); aio_comp->read_result.set_image_extents(spec->image_extents); auto ctx = new io::ReadResult::C_ImageReadRequest( aio_comp, 0, spec->image_extents); if (header_bl.length() < offset + length) { header_bl.append_zero(offset + length - header_bl.length()); } ctx->bl.substr_of(header_bl, offset, length); image_read_request = ctx; })); } void expect_get_image_size(uint64_t size) { EXPECT_CALL(*mock_image_ctx, get_image_size(_)).WillOnce( Return(size)); } void expect_get_stripe_period(uint64_t period) { EXPECT_CALL(*mock_image_ctx, get_stripe_period()).WillOnce( Return(period)); } }; TEST_F(TestMockCryptoLuksLoadRequest, AES128) { generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, AES256) { generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, LUKS1) { delete mock_load_request; mock_load_request = MockLoadRequest::create( mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, {passphrase_cstr}, &crypto, &detected_format_name, on_finish); generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS1", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, LUKS1ViaLUKS) { delete mock_load_request; mock_load_request = MockLoadRequest::create( mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS, {passphrase_cstr}, &crypto, &detected_format_name, on_finish); generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS1", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, UnknownFormat) { header_bl.append_zero(MAXIMUM_HEADER_SIZE); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, WrongFormat) { generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); mock_load_request->send(); expect_image_read(DEFAULT_INITIAL_READ_SIZE, MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); image_read_request->complete(MAXIMUM_HEADER_SIZE - DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("LUKS", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedAlgorithm) { generate_header(CRYPT_LUKS2, "twofish", 32, "xts-plain64", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-ENOTSUP, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, UnsupportedCipherMode) { generate_header(CRYPT_LUKS2, "aes", 32, "cbc-essiv:sha256", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-ENOTSUP, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, BadSize) { generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE - 1); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, BadStripePattern) { generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE * 3); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-EINVAL, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, HeaderBiggerThanInitialRead) { generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false); mock_load_request->set_initial_read_size(4096); expect_image_read(0, 4096); mock_load_request->send(); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); expect_image_read(4096, MAXIMUM_HEADER_SIZE - 4096); image_read_request->complete(4096); // complete initial read image_read_request->complete(MAXIMUM_HEADER_SIZE - 4096); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, LUKS1FormattedClone) { mock_image_ctx->parent = mock_image_ctx; delete mock_load_request; mock_load_request = MockLoadRequest::create( mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, {passphrase_cstr}, &crypto, &detected_format_name, on_finish); generate_header(CRYPT_LUKS1, "aes", 64, "xts-plain64", 512, true); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS1", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, LUKS2FormattedClone) { mock_image_ctx->parent = mock_image_ctx; generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, true); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, KeyslotsBiggerThanInitialRead) { generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false); mock_load_request->set_initial_read_size(16384); expect_image_read(0, 16384); mock_load_request->send(); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); expect_image_read(16384, data_offset - 16384); image_read_request->complete(16384); // complete initial read image_read_request->complete(data_offset - 16384); ASSERT_EQ(0, finished_cond.wait()); ASSERT_NE(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } TEST_F(TestMockCryptoLuksLoadRequest, WrongPassphrase) { delete mock_load_request; mock_load_request = MockLoadRequest::create( mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, "wrong", &crypto, &detected_format_name, on_finish); generate_header(CRYPT_LUKS2, "aes", 64, "xts-plain64", 4096, false); expect_image_read(0, DEFAULT_INITIAL_READ_SIZE); expect_get_image_size(OBJECT_SIZE << 5); expect_get_stripe_period(OBJECT_SIZE); mock_load_request->send(); // crypt_volume_key_get will fail, we will retry reading more expect_image_read(DEFAULT_INITIAL_READ_SIZE, data_offset - DEFAULT_INITIAL_READ_SIZE); image_read_request->complete(DEFAULT_INITIAL_READ_SIZE); image_read_request->complete(data_offset - DEFAULT_INITIAL_READ_SIZE); ASSERT_EQ(-EPERM, finished_cond.wait()); ASSERT_EQ(crypto.get(), nullptr); ASSERT_EQ("LUKS2", detected_format_name); } } // namespace luks } // namespace crypto } // namespace librbd