// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2016 Mirantis * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #include #include "global/global_init.h" #include "common/ceph_argparse.h" #include "rgw/rgw_common.h" #include "rgw/rgw_rados.h" #include "rgw/rgw_crypt.h" #include #include "include/assert.h" #define dout_subsys ceph_subsys_rgw using namespace std; std::unique_ptr AES_256_CBC_create(CephContext* cct, const uint8_t* key, size_t len); class ut_get_sink : public RGWGetDataCB { std::stringstream sink; public: ut_get_sink() {} virtual ~ut_get_sink() {} int handle_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) override { sink << boost::string_ref(bl.c_str()+bl_ofs, bl_len); return 0; } std::string get_sink() { return sink.str(); } }; class ut_put_sink: public RGWPutObjDataProcessor { std::stringstream sink; public: ut_put_sink(){} virtual ~ut_put_sink(){} int handle_data(bufferlist& bl, off_t ofs, void **phandle, rgw_raw_obj *pobj, bool *again) override { sink << boost::string_ref(bl.c_str(),bl.length()); *again = false; return 0; } int throttle_data(void *handle, const rgw_raw_obj& obj, uint64_t size, bool need_to_wait) override { return 0; } std::string get_sink() { return sink.str(); } }; class BlockCryptNone: public BlockCrypt { public: BlockCryptNone(){}; virtual ~BlockCryptNone(){}; size_t get_block_size() override { return 256; } bool encrypt(bufferlist& input, off_t in_ofs, size_t size, bufferlist& output, off_t stream_offset) override { output.clear(); output.append(input.c_str(), input.length()); return true; } bool decrypt(bufferlist& input, off_t in_ofs, size_t size, bufferlist& output, off_t stream_offset) override { output.clear(); output.append(input.c_str(), input.length()); return true; } }; TEST(TestRGWCrypto, verify_AES_256_CBC_identity) { //create some input for encryption const off_t test_range = 1024*1024; buffer::ptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); for (unsigned int step : {1, 2, 3, 5, 7, 11, 13, 17}) { //make some random key uint8_t key[32]; for(size_t i=0;iget_block_size(); ASSERT_NE(block_size, 0u); for (size_t r = 97; r < 123 ; r++) { off_t begin = (r*r*r*r*r % test_range); begin = begin - begin % block_size; off_t end = begin + r*r*r*r*r*r*r % (test_range - begin); if (r % 3) end = end - end % block_size; off_t offset = r*r*r*r*r*r*r*r % (1000*1000*1000); offset = offset - offset % block_size; ASSERT_EQ(begin % block_size, 0u); ASSERT_LE(end, test_range); ASSERT_EQ(offset % block_size, 0u); bufferlist encrypted; ASSERT_TRUE(aes->encrypt(input, begin, end - begin, encrypted, offset)); bufferlist decrypted; ASSERT_TRUE(aes->decrypt(encrypted, 0, end - begin, decrypted, offset)); ASSERT_EQ(decrypted.length(), end - begin); ASSERT_EQ(boost::string_ref(input.c_str() + begin, end - begin), boost::string_ref(decrypted.c_str(), end - begin) ); } } } TEST(TestRGWCrypto, verify_AES_256_CBC_identity_2) { //create some input for encryption const off_t test_range = 1024*1024; buffer::ptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); for (unsigned int step : {1, 2, 3, 5, 7, 11, 13, 17}) { //make some random key uint8_t key[32]; for(size_t i=0;iget_block_size(); ASSERT_NE(block_size, 0u); for (off_t end = 1; end < 6096 ; end+=3) { off_t begin = 0; off_t offset = end*end*end*end*end % (1000*1000*1000); offset = offset - offset % block_size; ASSERT_EQ(begin % block_size, 0u); ASSERT_LE(end, test_range); ASSERT_EQ(offset % block_size, 0u); bufferlist encrypted; ASSERT_TRUE(aes->encrypt(input, begin, end, encrypted, offset)); bufferlist decrypted; ASSERT_TRUE(aes->decrypt(encrypted, 0, end, decrypted, offset)); ASSERT_EQ(decrypted.length(), end); ASSERT_EQ(boost::string_ref(input.c_str(), end), boost::string_ref(decrypted.c_str(), end) ); } } } TEST(TestRGWCrypto, verify_AES_256_CBC_identity_3) { //create some input for encryption const off_t test_range = 1024*1024; buffer::ptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); for (unsigned int step : {1, 2, 3, 5, 7, 11, 13, 17}) { //make some random key uint8_t key[32]; for(size_t i=0;iget_block_size(); ASSERT_NE(block_size, 0u); size_t rr = 111; for (size_t r = 97; r < 123 ; r++) { off_t begin = 0; off_t end = begin + r*r*r*r*r*r*r % (test_range - begin); //sometimes make aligned if (r % 3) end = end - end % block_size; off_t offset = r*r*r*r*r*r*r*r % (1000*1000*1000); offset = offset - offset % block_size; ASSERT_EQ(begin % block_size, 0u); ASSERT_LE(end, test_range); ASSERT_EQ(offset % block_size, 0u); bufferlist encrypted1; bufferlist encrypted2; off_t pos = begin; off_t chunk; while (pos < end) { chunk = block_size + (rr/3)*(rr+17)*(rr+71)*(rr+123)*(rr+131) % 50000; chunk = chunk - chunk % block_size; if (pos + chunk > end) chunk = end - pos; bufferlist tmp; ASSERT_TRUE(aes->encrypt(input, pos, chunk, tmp, offset + pos)); encrypted1.append(tmp); pos += chunk; rr++; } pos = begin; while (pos < end) { chunk = block_size + (rr/3)*(rr+97)*(rr+151)*(rr+213)*(rr+251) % 50000; chunk = chunk - chunk % block_size; if (pos + chunk > end) chunk = end - pos; bufferlist tmp; ASSERT_TRUE(aes->encrypt(input, pos, chunk, tmp, offset + pos)); encrypted2.append(tmp); pos += chunk; rr++; } ASSERT_EQ(encrypted1.length(), end); ASSERT_EQ(encrypted2.length(), end); ASSERT_EQ(boost::string_ref(encrypted1.c_str(), end), boost::string_ref(encrypted2.c_str(), end) ); } } } TEST(TestRGWCrypto, verify_AES_256_CBC_size_0_15) { //create some input for encryption const off_t test_range = 1024*1024; buffer::ptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); for (unsigned int step : {1, 2, 3, 5, 7, 11, 13, 17}) { //make some random key uint8_t key[32]; for(size_t i=0;iget_block_size(); ASSERT_NE(block_size, 0u); for (size_t r = 97; r < 123 ; r++) { off_t begin = 0; off_t end = begin + r*r*r*r*r*r*r % (16); off_t offset = r*r*r*r*r*r*r*r % (1000*1000*1000); offset = offset - offset % block_size; ASSERT_EQ(begin % block_size, 0u); ASSERT_LE(end, test_range); ASSERT_EQ(offset % block_size, 0u); bufferlist encrypted; bufferlist decrypted; ASSERT_TRUE(aes->encrypt(input, 0, end, encrypted, offset)); ASSERT_TRUE(aes->encrypt(encrypted, 0, end, decrypted, offset)); ASSERT_EQ(encrypted.length(), end); ASSERT_EQ(decrypted.length(), end); ASSERT_EQ(boost::string_ref(input.c_str(), end), boost::string_ref(decrypted.c_str(), end) ); } } } TEST(TestRGWCrypto, verify_AES_256_CBC_identity_last_block) { //create some input for encryption const off_t test_range = 1024*1024; buffer::ptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); for (unsigned int step : {1, 2, 3, 5, 7, 11, 13, 17}) { //make some random key uint8_t key[32]; for(size_t i=0;iget_block_size(); ASSERT_NE(block_size, 0u); size_t rr = 111; for (size_t r = 97; r < 123 ; r++) { off_t begin = 0; off_t end = r*r*r*r*r*r*r % (test_range - 16); end = end - end % block_size; end = end + (r+3)*(r+5)*(r+7) % 16; off_t offset = r*r*r*r*r*r*r*r % (1000*1000*1000); offset = offset - offset % block_size; ASSERT_EQ(begin % block_size, 0u); ASSERT_LE(end, test_range); ASSERT_EQ(offset % block_size, 0u); bufferlist encrypted1; bufferlist encrypted2; off_t pos = begin; off_t chunk; while (pos < end) { chunk = block_size + (rr/3)*(rr+17)*(rr+71)*(rr+123)*(rr+131) % 50000; chunk = chunk - chunk % block_size; if (pos + chunk > end) chunk = end - pos; bufferlist tmp; ASSERT_TRUE(aes->encrypt(input, pos, chunk, tmp, offset + pos)); encrypted1.append(tmp); pos += chunk; rr++; } pos = begin; while (pos < end) { chunk = block_size + (rr/3)*(rr+97)*(rr+151)*(rr+213)*(rr+251) % 50000; chunk = chunk - chunk % block_size; if (pos + chunk > end) chunk = end - pos; bufferlist tmp; ASSERT_TRUE(aes->encrypt(input, pos, chunk, tmp, offset + pos)); encrypted2.append(tmp); pos += chunk; rr++; } ASSERT_EQ(encrypted1.length(), end); ASSERT_EQ(encrypted2.length(), end); ASSERT_EQ(boost::string_ref(encrypted1.c_str(), end), boost::string_ref(encrypted2.c_str(), end) ); } } } TEST(TestRGWCrypto, verify_RGWGetObj_BlockDecrypt_ranges) { //create some input for encryption const off_t test_range = 1024*1024; bufferptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); uint8_t key[32]; for(size_t i=0;iencrypt(input, 0, test_range, encrypted, 0)); for (off_t r = 93; r < 150; r++ ) { ut_get_sink get_sink; auto cbc = AES_256_CBC_create(g_ceph_context, &key[0], 32); ASSERT_NE(cbc.get(), nullptr); RGWGetObj_BlockDecrypt decrypt(g_ceph_context, &get_sink, std::move(cbc) ); //random ranges off_t begin = (r/3)*r*(r+13)*(r+23)*(r+53)*(r+71) % test_range; off_t end = begin + (r/5)*(r+7)*(r+13)*(r+101)*(r*103) % (test_range - begin) - 1; off_t f_begin = begin; off_t f_end = end; decrypt.fixup_range(f_begin, f_end); decrypt.handle_data(encrypted, f_begin, f_end - f_begin + 1); decrypt.flush(); const std::string& decrypted = get_sink.get_sink(); size_t expected_len = end - begin + 1; ASSERT_EQ(decrypted.length(), expected_len); ASSERT_EQ(decrypted, boost::string_ref(input.c_str()+begin, expected_len)); } } TEST(TestRGWCrypto, verify_RGWGetObj_BlockDecrypt_chunks) { //create some input for encryption const off_t test_range = 1024*1024; bufferptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); uint8_t key[32]; for(size_t i=0;iencrypt(input, 0, test_range, encrypted, 0)); for (off_t r = 93; r < 150; r++ ) { ut_get_sink get_sink; auto cbc = AES_256_CBC_create(g_ceph_context, &key[0], 32); ASSERT_NE(cbc.get(), nullptr); RGWGetObj_BlockDecrypt decrypt(g_ceph_context, &get_sink, std::move(cbc) ); //random off_t begin = (r/3)*r*(r+13)*(r+23)*(r+53)*(r+71) % test_range; off_t end = begin + (r/5)*(r+7)*(r+13)*(r+101)*(r*103) % (test_range - begin) - 1; off_t f_begin = begin; off_t f_end = end; decrypt.fixup_range(f_begin, f_end); off_t pos = f_begin; do { off_t size = 2 << ((pos * 17 + pos / 113 + r) % 16); size = (pos + 1117) * (pos + 2229) % size + 1; if (pos + size > f_end + 1) size = f_end + 1 - pos; decrypt.handle_data(encrypted, pos, size); pos = pos + size; } while (pos < f_end + 1); decrypt.flush(); const std::string& decrypted = get_sink.get_sink(); size_t expected_len = end - begin + 1; ASSERT_EQ(decrypted.length(), expected_len); ASSERT_EQ(decrypted, boost::string_ref(input.c_str()+begin, expected_len)); } } using range_t = std::pair; // call filter->fixup_range() and return the range as a pair. this makes it easy // to fit on a single line for ASSERT_EQ() range_t fixup_range(RGWGetObj_BlockDecrypt *decrypt, off_t ofs, off_t end) { decrypt->fixup_range(ofs, end); return {ofs, end}; } TEST(TestRGWCrypto, check_RGWGetObj_BlockDecrypt_fixup) { ut_get_sink get_sink; auto nonecrypt = std::unique_ptr(new BlockCryptNone); RGWGetObj_BlockDecrypt decrypt(g_ceph_context, &get_sink, std::move(nonecrypt)); ASSERT_EQ(fixup_range(&decrypt,0,0), range_t(0,255)); ASSERT_EQ(fixup_range(&decrypt,1,256), range_t(0,511)); ASSERT_EQ(fixup_range(&decrypt,0,255), range_t(0,255)); ASSERT_EQ(fixup_range(&decrypt,255,256), range_t(0,511)); ASSERT_EQ(fixup_range(&decrypt,511,1023), range_t(256,1023)); ASSERT_EQ(fixup_range(&decrypt,513,1024), range_t(512,1024+255)); } TEST(TestRGWCrypto, verify_RGWPutObj_BlockEncrypt_chunks) { //create some input for encryption const off_t test_range = 1024*1024; bufferptr buf(test_range); char* p = buf.c_str(); for(size_t i = 0; i < buf.length(); i++) p[i] = i + i*i + (i >> 2); bufferlist input; input.append(buf); uint8_t key[32]; for(size_t i=0;i test_size) size = test_size - pos; bufferlist bl; bl.append(input.c_str()+pos, size); void* handle; bool again = false; rgw_raw_obj ro; encrypt.handle_data(bl, 0, &handle, nullptr, &again); encrypt.throttle_data(handle, ro, size, false); pos = pos + size; } while (pos < test_size); bufferlist bl; void* handle; bool again = false; encrypt.handle_data(bl, 0, &handle, nullptr, &again); ASSERT_EQ(put_sink.get_sink().length(), static_cast(test_size)); cbc = AES_256_CBC_create(g_ceph_context, &key[0], 32); ASSERT_NE(cbc.get(), nullptr); bufferlist encrypted; bufferlist decrypted; encrypted.append(put_sink.get_sink()); ASSERT_TRUE(cbc->decrypt(encrypted, 0, test_size, decrypted, 0)); ASSERT_EQ(decrypted.length(), test_size); ASSERT_EQ(boost::string_ref(decrypted.c_str(), test_size), boost::string_ref(input.c_str(), test_size)); } } TEST(TestRGWCrypto, verify_Encrypt_Decrypt) { uint8_t key[32]; for(size_t i=0;i args; argv_to_vec(argc, (const char **)argv, args); auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0); common_init_finish(g_ceph_context); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }