diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/test/librbd/crypto/luks | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/test/librbd/crypto/luks')
-rw-r--r-- | src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc | 266 | ||||
-rw-r--r-- | src/test/librbd/crypto/luks/test_mock_FormatRequest.cc | 230 | ||||
-rw-r--r-- | src/test/librbd/crypto/luks/test_mock_LoadRequest.cc | 333 |
3 files changed, 829 insertions, 0 deletions
diff --git a/src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc b/src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc new file mode 100644 index 000000000..bc615bcf7 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_FlattenRequest.cc @@ -0,0 +1,266 @@ +// -*- 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" +#include "test/librbd/mock/crypto/MockCryptoInterface.h" +#include "test/librbd/mock/crypto/MockEncryptionFormat.h" + +namespace librbd { + +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace util { + +inline ImageCtx *get_image_ctx(MockImageCtx *image_ctx) { + return image_ctx->image_ctx; +} + +} // namespace util +} // namespace librbd + +#include "librbd/crypto/luks/FlattenRequest.cc" + +namespace librbd { +namespace crypto { +namespace luks { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +struct TestMockCryptoLuksFlattenRequest : public TestMockFixture { + typedef FlattenRequest<MockTestImageCtx> MockFlattenRequest; + + const size_t OBJECT_SIZE = 4 * 1024 * 1024; + const uint64_t DATA_OFFSET = MockCryptoInterface::DATA_OFFSET; + const char* passphrase_cstr = "password"; + std::string passphrase = passphrase_cstr; + + MockTestImageCtx* mock_image_ctx; + MockFlattenRequest* mock_flatten_request; + MockEncryptionFormat* mock_encryption_format; + MockCryptoInterface mock_crypto; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + Context* image_read_request; + io::AioCompletion* aio_comp; + ceph::bufferlist header_bl; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + mock_image_ctx = new MockTestImageCtx(*ictx); + mock_encryption_format = new MockEncryptionFormat(); + mock_image_ctx->encryption_format.reset(mock_encryption_format); + mock_flatten_request = MockFlattenRequest::create( + mock_image_ctx, on_finish); + } + + void TearDown() override { + delete mock_image_ctx; + TestMockFixture::TearDown(); + } + + 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)); + } + } + + void expect_get_crypto() { + EXPECT_CALL(*mock_encryption_format, get_crypto()).WillOnce( + Return(&mock_crypto)); + } + + 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<io::ImageDispatchSpec::Read>( + &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_image_write() { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)) + .WillOnce(Invoke([this](io::ImageDispatchSpec* spec) { + auto* write = boost::get<io::ImageDispatchSpec::Write>( + &spec->request); + ASSERT_TRUE(write != nullptr); + + ASSERT_EQ(1, spec->image_extents.size()); + ASSERT_EQ(0, spec->image_extents[0].first); + ASSERT_GT(spec->image_extents[0].second, 0); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + aio_comp = spec->aio_comp; + + // patch header_bl with write + bufferlist bl; + bl.substr_of(header_bl, write->bl.length(), + header_bl.length() - write->bl.length()); + header_bl = write->bl; + header_bl.claim_append(bl); + })); + } + + void expect_image_flush(int r) { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)).WillOnce( + Invoke([r](io::ImageDispatchSpec* spec) { + ASSERT_TRUE(boost::get<io::ImageDispatchSpec::Flush>( + &spec->request) != nullptr); + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + spec->aio_comp->set_request_count(1); + spec->aio_comp->add_request(); + spec->aio_comp->complete_request(r); + })); + } + + void complete_aio(int r) { + if (r < 0) { + aio_comp->fail(r); + } else { + aio_comp->set_request_count(1); + aio_comp->add_request(); + aio_comp->complete_request(r); + } + } + + void verify_header(const char* expected_format) { + Header header(mock_image_ctx->cct); + + ASSERT_EQ(0, header.init()); + ASSERT_EQ(0, header.write(header_bl)); + ASSERT_EQ(0, header.load(expected_format)); + } +}; + +TEST_F(TestMockCryptoLuksFlattenRequest, LUKS1) { + generate_header(CRYPT_LUKS1, "aes", 32, "xts-plain64", 512, true); + expect_get_crypto(); + expect_image_read(0, DATA_OFFSET); + mock_flatten_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_write(); + image_read_request->complete(DATA_OFFSET); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(0); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS1)); + ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); +} + +TEST_F(TestMockCryptoLuksFlattenRequest, LUKS2) { + generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true); + expect_get_crypto(); + expect_image_read(0, DATA_OFFSET); + mock_flatten_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_write(); + image_read_request->complete(DATA_OFFSET); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(0); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2)); + ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); +} + +TEST_F(TestMockCryptoLuksFlattenRequest, FailedRead) { + generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true); + expect_get_crypto(); + expect_image_read(0, DATA_OFFSET); + mock_flatten_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + image_read_request->complete(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); +} + +TEST_F(TestMockCryptoLuksFlattenRequest, AlreadyFlattened) { + generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, false); + expect_get_crypto(); + expect_image_read(0, DATA_OFFSET); + mock_flatten_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_write(); + image_read_request->complete(DATA_OFFSET); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(0); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2)); + ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); +} + +TEST_F(TestMockCryptoLuksFlattenRequest, FailedWrite) { + generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true); + expect_get_crypto(); + expect_image_read(0, DATA_OFFSET); + mock_flatten_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_write(); + image_read_request->complete(DATA_OFFSET); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(-EIO); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); +} + +TEST_F(TestMockCryptoLuksFlattenRequest, FailedFlush) { + generate_header(CRYPT_LUKS2, "aes", 32, "xts-plain64", 4096, true); + expect_get_crypto(); + expect_image_read(0, DATA_OFFSET); + mock_flatten_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_write(); + image_read_request->complete(DATA_OFFSET); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + expect_image_flush(-EIO); + complete_aio(0); + ASSERT_EQ(-EIO, finished_cond.wait()); + ASSERT_EQ(mock_encryption_format, mock_image_ctx->encryption_format.get()); +} + +} // namespace luks +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc new file mode 100644 index 000000000..86026b456 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_FormatRequest.cc @@ -0,0 +1,230 @@ +// -*- 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/FormatRequest.cc" + +namespace librbd { +namespace crypto { +namespace luks { + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +struct TestMockCryptoLuksFormatRequest : public TestMockFixture { + typedef FormatRequest<librbd::MockImageCtx> MockFormatRequest; + + const size_t OBJECT_SIZE = 4 * 1024 * 1024; + const size_t IMAGE_SIZE = 1024 * 1024 * 1024; + const char* passphrase_cstr = "password"; + std::string passphrase = passphrase_cstr; + + MockImageCtx* mock_image_ctx; + C_SaferCond finished_cond; + Context *on_finish = &finished_cond; + io::AioCompletion* aio_comp; + ceph::bufferlist header_bl; + std::unique_ptr<CryptoInterface> crypto; + + void SetUp() override { + TestMockFixture::SetUp(); + + librbd::ImageCtx *ictx; + ASSERT_EQ(0, open_image(m_image_name, &ictx)); + mock_image_ctx = new MockImageCtx(*ictx); + } + + void TearDown() override { + delete mock_image_ctx; + TestMockFixture::TearDown(); + } + + void expect_get_stripe_period() { + EXPECT_CALL(*mock_image_ctx, get_stripe_period()).WillOnce(Return( + OBJECT_SIZE)); + } + + void expect_get_image_size(uint64_t image_size) { + EXPECT_CALL(*mock_image_ctx, get_image_size(CEPH_NOSNAP)).WillOnce(Return( + image_size)); + } + + void expect_image_write() { + EXPECT_CALL(*mock_image_ctx->io_image_dispatcher, send(_)) + .WillOnce(Invoke([this](io::ImageDispatchSpec* spec) { + auto* write = boost::get<io::ImageDispatchSpec::Write>( + &spec->request); + ASSERT_TRUE(write != nullptr); + + ASSERT_EQ(1, spec->image_extents.size()); + ASSERT_EQ(0, spec->image_extents[0].first); + ASSERT_GT(spec->image_extents[0].second, 0); + + spec->dispatch_result = io::DISPATCH_RESULT_COMPLETE; + aio_comp = spec->aio_comp; + header_bl = write->bl; + })); + } + + void complete_aio(int r) { + if (r < 0) { + aio_comp->fail(r); + } else { + aio_comp->set_request_count(1); + aio_comp->add_request(); + aio_comp->complete_request(r); + } + } + + void verify_header(const char* expected_format, size_t expected_key_length, + uint64_t expected_sector_size, bool magic_switched) { + Header header(mock_image_ctx->cct); + + ASSERT_EQ(0, header.init()); + + if (magic_switched) { + Header non_switched_header(mock_image_ctx->cct); + ASSERT_EQ(0, non_switched_header.init()); + ASSERT_EQ(0, non_switched_header.write(header_bl)); + ASSERT_EQ(-EINVAL, non_switched_header.load(expected_format)); + ASSERT_EQ(0, Magic::replace_magic(mock_image_ctx->cct, header_bl)); + } + ASSERT_EQ(0, header.write(header_bl)); + ASSERT_EQ(0, header.load(expected_format)); + + ASSERT_EQ(expected_sector_size, header.get_sector_size()); + ASSERT_EQ(0, header.get_data_offset() % OBJECT_SIZE); + + char volume_key[64]; + size_t volume_key_size = sizeof(volume_key); + ASSERT_EQ(0, header.read_volume_key( + passphrase_cstr, strlen(passphrase_cstr), + reinterpret_cast<char*>(volume_key), &volume_key_size)); + + ASSERT_EQ(expected_key_length, crypto->get_key_length()); + ASSERT_EQ(0, std::memcmp( + volume_key, crypto->get_key(), expected_key_length)); + ASSERT_EQ(expected_sector_size, crypto->get_block_size()); + ASSERT_EQ(header.get_data_offset(), crypto->get_data_offset()); + } +}; + +TEST_F(TestMockCryptoLuksFormatRequest, LUKS1) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, + RBD_ENCRYPTION_ALGORITHM_AES128, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS1, 32, 512, false)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, AES128) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES128, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 32, 4096, false)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, AES256) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 64, 4096, false)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, LUKS1OnCloned) { + mock_image_ctx->parent = mock_image_ctx; + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS1, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS1, 64, 512, true)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, LUKS2OnCloned) { + mock_image_ctx->parent = mock_image_ctx; + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(0); + ASSERT_EQ(0, finished_cond.wait()); + ASSERT_NO_FATAL_FAILURE(verify_header(CRYPT_LUKS2, 64, 4096, true)); +} + +TEST_F(TestMockCryptoLuksFormatRequest, ImageTooSmall) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(1024*1024); + mock_format_request->send(); + ASSERT_EQ(-ENOSPC, finished_cond.wait()); +} + +TEST_F(TestMockCryptoLuksFormatRequest, WriteFail) { + auto mock_format_request = MockFormatRequest::create( + mock_image_ctx, RBD_ENCRYPTION_FORMAT_LUKS2, + RBD_ENCRYPTION_ALGORITHM_AES256, std::move(passphrase), &crypto, + on_finish, true); + expect_get_stripe_period(); + expect_get_image_size(IMAGE_SIZE); + expect_image_write(); + mock_format_request->send(); + ASSERT_EQ(ETIMEDOUT, finished_cond.wait_for(0)); + complete_aio(-123); + ASSERT_EQ(-123, finished_cond.wait()); +} + +} // namespace luks +} // namespace crypto +} // namespace librbd diff --git a/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc new file mode 100644 index 000000000..5fb566a07 --- /dev/null +++ b/src/test/librbd/crypto/luks/test_mock_LoadRequest.cc @@ -0,0 +1,333 @@ +// -*- 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<librbd::MockImageCtx> 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<CryptoInterface> 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<io::ImageDispatchSpec::Read>( + &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("<unknown>", 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 |