diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/rgw/rgw_crypt.cc | |
parent | Initial commit. (diff) | |
download | ceph-upstream.tar.xz ceph-upstream.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/rgw/rgw_crypt.cc')
-rw-r--r-- | src/rgw/rgw_crypt.cc | 1344 |
1 files changed, 1344 insertions, 0 deletions
diff --git a/src/rgw/rgw_crypt.cc b/src/rgw/rgw_crypt.cc new file mode 100644 index 000000000..08837d39e --- /dev/null +++ b/src/rgw/rgw_crypt.cc @@ -0,0 +1,1344 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/** + * Crypto filters for Put/Post/Get operations. + */ + +#include <string_view> + +#include <rgw/rgw_op.h> +#include <rgw/rgw_crypt.h> +#include <auth/Crypto.h> +#include <rgw/rgw_b64.h> +#include <rgw/rgw_rest_s3.h> +#include "include/ceph_assert.h" +#include "crypto/crypto_accel.h" +#include "crypto/crypto_plugin.h" +#include "rgw/rgw_kms.h" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/error/error.h" +#include "rapidjson/error/en.h" +#include <unicode/normalizer2.h> // libicu + +#include <openssl/evp.h> + +#define dout_context g_ceph_context +#define dout_subsys ceph_subsys_rgw + +using namespace rgw; + + +template<typename M> +class canonical_char_sorter { +private: + const icu::Normalizer2* normalizer; + CephContext *cct; + +public: + canonical_char_sorter(CephContext *cct) : cct(cct) { + UErrorCode status = U_ZERO_ERROR; + normalizer = icu::Normalizer2::getNFCInstance(status); + if (U_FAILURE(status)) { +lderr(cct) << "ERROR: can't get nfc instance, error = " << status << dendl; + normalizer = 0; + } + } + bool compare_helper (const M *, const M *); + bool make_string_canonical(rapidjson::Value &, + rapidjson::Document::AllocatorType&); +}; + +template<typename M> +bool +canonical_char_sorter<M>::compare_helper (const M*a, const M*b) +{ + UErrorCode status = U_ZERO_ERROR; + const std::string as{a->name.GetString(), a->name.GetStringLength()}, + bs{b->name.GetString(), b->name.GetStringLength()}; + icu::UnicodeString aw{icu::UnicodeString::fromUTF8(as)}, bw{icu::UnicodeString::fromUTF8(bs)}; + int32_t afl{ aw.countChar32()}, bfl{bw.countChar32()}; + std::u32string af, bf; + af.resize(afl); bf.resize(bfl); + auto *astr{af.c_str()}, *bstr{bf.c_str()}; + aw.toUTF32((int32_t*)astr, afl, status); + bw.toUTF32((int32_t*)bstr, bfl, status); + bool r{af < bf}; + return r; +} + +template<typename M> +bool +canonical_char_sorter<M>::make_string_canonical (rapidjson::Value &v, rapidjson::Document::AllocatorType&a) +{ + UErrorCode status = U_ZERO_ERROR; + const std::string as{v.GetString(), v.GetStringLength()}; + + if (!normalizer) + return false; + const icu::UnicodeString aw{icu::UnicodeString::fromUTF8(as)}; + icu::UnicodeString an{normalizer->normalize(aw, status)}; + if (U_FAILURE(status)) { + ldout(cct, 5) << "conversion error; code=" << status << + " on string " << as << dendl; + return false; + } + std::string ans; + an.toUTF8String(ans); + v.SetString(ans.c_str(), ans.length(), a); + return true; +} + +typedef +rapidjson::GenericMember<rapidjson::UTF8<>, rapidjson::MemoryPoolAllocator<> > +MyMember; + +template<typename H> +bool +sort_and_write(rapidjson::Value &d, H &writer, canonical_char_sorter<MyMember>& ccs) +{ + bool r; + switch(d.GetType()) { + case rapidjson::kObjectType: { + struct comparer { + canonical_char_sorter<MyMember> &r; + comparer(canonical_char_sorter<MyMember> &r) : r(r) {}; + bool operator()(const MyMember*a, const MyMember*b) { + return r.compare_helper(a,b); + } + } cmp_functor{ccs}; + if (!(r = writer.StartObject())) + break; + std::vector<MyMember*> q; + for (auto &m: d.GetObject()) + q.push_back(&m); + std::sort(q.begin(), q.end(), cmp_functor); + for (auto m: q) { + assert(m->name.IsString()); + if (!(r = writer.Key(m->name.GetString(), m->name.GetStringLength()))) + goto Done; + if (!(r = sort_and_write(m->value, writer, ccs))) + goto Done; + } + r = writer.EndObject(); + break; } + case rapidjson::kArrayType: + if (!(r = writer.StartArray())) + break; + for (auto &v: d.GetArray()) { + if (!(r = sort_and_write(v, writer, ccs))) + goto Done; + } + r = writer.EndArray(); + break; + default: + r = d.Accept(writer); + break; + } +Done: + return r; +} + +enum struct mec_option { +empty = 0, number_ok = 1 +}; + +enum struct mec_error { +success = 0, conversion, number +}; + +mec_error +make_everything_canonical(rapidjson::Value &d, rapidjson::Document::AllocatorType&a, canonical_char_sorter<MyMember>& ccs, mec_option f = mec_option::empty ) +{ + mec_error r; + switch(d.GetType()) { + case rapidjson::kObjectType: + for (auto &m: d.GetObject()) { + assert(m.name.IsString()); + if (!ccs.make_string_canonical(m.name, a)) { + r = mec_error::conversion; + goto Error; + } + if ((r = make_everything_canonical(m.value, a, ccs, f)) != mec_error::success) + goto Error; + } + break; + case rapidjson::kArrayType: + for (auto &v: d.GetArray()) { + if ((r = make_everything_canonical(v, a, ccs, f)) != mec_error::success) + goto Error; + } + break; + case rapidjson::kStringType: + if (!ccs.make_string_canonical(d, a)) { + r = mec_error::conversion; + goto Error; + } + break; + case rapidjson::kNumberType: + if (static_cast<int>(f) & static_cast<int>(mec_option::number_ok)) + break; + r = mec_error::number; + goto Error; + default: + break; + } + r = mec_error::success; +Error: + return r; +} + +bool +add_object_to_context(rgw_obj &obj, rapidjson::Document &d) +{ + ARN a{obj}; + const char aws_s3_arn[] { "aws:s3:arn" }; + std::string as{a.to_string()}; + rapidjson::Document::AllocatorType &allocator { d.GetAllocator() }; + rapidjson::Value name, val; + + if (!d.IsObject()) + return false; + if (d.HasMember(aws_s3_arn)) + return true; + val.SetString(as.c_str(), as.length(), allocator); + name.SetString(aws_s3_arn, sizeof aws_s3_arn - 1, allocator); + d.AddMember(name, val, allocator); + return true; +} + +static inline const std::string & +get_tenant_or_id(req_state *s) +{ + const std::string &tenant{ s->user->get_tenant() }; + if (!tenant.empty()) return tenant; + return s->user->get_id().id; +} + +int +make_canonical_context(struct req_state *s, + std::string_view &context, + std::string &cooked_context) +{ + rapidjson::Document d; + bool b = false; +mec_option options { +//mec_option::number_ok : SEE BOTTOM OF FILE +mec_option::empty }; + rgw_obj obj; + std::ostringstream oss; + canonical_char_sorter<MyMember> ccs{s->cct}; + + obj.bucket.tenant = get_tenant_or_id(s); + obj.bucket.name = s->bucket->get_name(); + obj.key.name = s->object->get_name(); + std::string iline; + rapidjson::Document::AllocatorType &allocator { d.GetAllocator() }; + + try { + iline = rgw::from_base64(context); + } catch (const std::exception& e) { + oss << "bad context: " << e.what(); + s->err.message = oss.str(); + return -ERR_INVALID_REQUEST; + } + rapidjson::StringStream isw(iline.c_str()); + if (!iline.length()) + d.SetObject(); +// else if (qflag) SEE BOTTOM OF FILE +// d.ParseStream<rapidjson::kParseNumbersAsStringsFlag>(isw); + else + d.ParseStream<rapidjson::kParseFullPrecisionFlag>(isw); + if (isw.Tell() != iline.length()) { + oss << "bad context: did not consume all of input: @ " + << isw.Tell(); + s->err.message = oss.str(); + return -ERR_INVALID_REQUEST; + } + if (d.HasParseError()) { + oss << "bad context: parse error: @ " << d.GetErrorOffset() + << " " << rapidjson::GetParseError_En(d.GetParseError()); + s->err.message = oss.str(); + return -ERR_INVALID_REQUEST; + } + rapidjson::StringBuffer buf; + rapidjson::Writer<rapidjson::StringBuffer> writer(buf); + if (!add_object_to_context(obj, d)) { + lderr(s->cct) << "ERROR: can't add default value to context" << dendl; + s->err.message = "context: internal error adding defaults"; + return -ERR_INVALID_REQUEST; + } + b = make_everything_canonical(d, allocator, ccs, options) == mec_error::success; + if (!b) { + lderr(s->cct) << "ERROR: can't make canonical json <" + << context << ">" << dendl; + s->err.message = "context: can't make canonical"; + return -ERR_INVALID_REQUEST; + } + b = sort_and_write(d, writer, ccs); + if (!b) { + ldout(s->cct, 5) << "format error <" << context + << ">: partial.results=" << buf.GetString() << dendl; + s->err.message = "unable to reformat json"; + return -ERR_INVALID_REQUEST; + } + cooked_context = rgw::to_base64(buf.GetString()); + return 0; +} + + +CryptoAccelRef get_crypto_accel(CephContext *cct) +{ + CryptoAccelRef ca_impl = nullptr; + stringstream ss; + PluginRegistry *reg = cct->get_plugin_registry(); + string crypto_accel_type = cct->_conf->plugin_crypto_accelerator; + + CryptoPlugin *factory = dynamic_cast<CryptoPlugin*>(reg->get_with_load("crypto", crypto_accel_type)); + if (factory == nullptr) { + lderr(cct) << __func__ << " cannot load crypto accelerator of type " << crypto_accel_type << dendl; + return nullptr; + } + int err = factory->factory(&ca_impl, &ss); + if (err) { + lderr(cct) << __func__ << " factory return error " << err << + " with description: " << ss.str() << dendl; + } + return ca_impl; +} + + +template <std::size_t KeySizeV, std::size_t IvSizeV> +static inline +bool evp_sym_transform(CephContext* const cct, + const EVP_CIPHER* const type, + unsigned char* const out, + const unsigned char* const in, + const size_t size, + const unsigned char* const iv, + const unsigned char* const key, + const bool encrypt) +{ + using pctx_t = \ + std::unique_ptr<EVP_CIPHER_CTX, decltype(&::EVP_CIPHER_CTX_free)>; + pctx_t pctx{ EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free }; + + if (!pctx) { + return false; + } + + if (1 != EVP_CipherInit_ex(pctx.get(), type, nullptr, + nullptr, nullptr, encrypt)) { + ldout(cct, 5) << "EVP: failed to 1st initialization stage" << dendl; + return false; + } + + // we want to support ciphers that don't use IV at all like AES-256-ECB + if constexpr (static_cast<bool>(IvSizeV)) { + ceph_assert(EVP_CIPHER_CTX_iv_length(pctx.get()) == IvSizeV); + ceph_assert(EVP_CIPHER_CTX_block_size(pctx.get()) == IvSizeV); + } + ceph_assert(EVP_CIPHER_CTX_key_length(pctx.get()) == KeySizeV); + + if (1 != EVP_CipherInit_ex(pctx.get(), nullptr, nullptr, key, iv, encrypt)) { + ldout(cct, 5) << "EVP: failed to 2nd initialization stage" << dendl; + return false; + } + + // disable padding + if (1 != EVP_CIPHER_CTX_set_padding(pctx.get(), 0)) { + ldout(cct, 5) << "EVP: cannot disable PKCS padding" << dendl; + return false; + } + + // operate! + int written = 0; + ceph_assert(size <= static_cast<size_t>(std::numeric_limits<int>::max())); + if (1 != EVP_CipherUpdate(pctx.get(), out, &written, in, size)) { + ldout(cct, 5) << "EVP: EVP_CipherUpdate failed" << dendl; + return false; + } + + int finally_written = 0; + static_assert(sizeof(*out) == 1); + if (1 != EVP_CipherFinal_ex(pctx.get(), out + written, &finally_written)) { + ldout(cct, 5) << "EVP: EVP_CipherFinal_ex failed" << dendl; + return false; + } + + // padding is disabled so EVP_CipherFinal_ex should not append anything + ceph_assert(finally_written == 0); + return (written + finally_written) == static_cast<int>(size); +} + + +/** + * Encryption in CBC mode. Chunked to 4K blocks. Offset is used as IV for each 4K block. + * + * + * + * A. Encryption + * 1. Input is split to 4K chunks + remainder in one, smaller chunk + * 2. Each full chunk is encrypted separately with CBC chained mode, with initial IV derived from offset + * 3. Last chunk is 16*m + n. + * 4. 16*m bytes are encrypted with CBC chained mode, with initial IV derived from offset + * 5. Last n bytes are xor-ed with pattern obtained by CBC encryption of + * last encrypted 16 byte block <16m-16, 16m-15) with IV = {0}. + * 6. (Special case) If m == 0 then last n bytes are xor-ed with pattern + * obtained by CBC encryption of {0} with IV derived from offset + * + * B. Decryption + * 1. Input is split to 4K chunks + remainder in one, smaller chunk + * 2. Each full chunk is decrypted separately with CBC chained mode, with initial IV derived from offset + * 3. Last chunk is 16*m + n. + * 4. 16*m bytes are decrypted with CBC chained mode, with initial IV derived from offset + * 5. Last n bytes are xor-ed with pattern obtained by CBC ENCRYPTION of + * last (still encrypted) 16 byte block <16m-16,16m-15) with IV = {0} + * 6. (Special case) If m == 0 then last n bytes are xor-ed with pattern + * obtained by CBC ENCRYPTION of {0} with IV derived from offset + */ +class AES_256_CBC : public BlockCrypt { +public: + static const size_t AES_256_KEYSIZE = 256 / 8; + static const size_t AES_256_IVSIZE = 128 / 8; + static const size_t CHUNK_SIZE = 4096; +private: + static const uint8_t IV[AES_256_IVSIZE]; + CephContext* cct; + uint8_t key[AES_256_KEYSIZE]; +public: + explicit AES_256_CBC(CephContext* cct): cct(cct) { + } + ~AES_256_CBC() { + ::ceph::crypto::zeroize_for_security(key, AES_256_KEYSIZE); + } + bool set_key(const uint8_t* _key, size_t key_size) { + if (key_size != AES_256_KEYSIZE) { + return false; + } + memcpy(key, _key, AES_256_KEYSIZE); + return true; + } + size_t get_block_size() { + return CHUNK_SIZE; + } + + bool cbc_transform(unsigned char* out, + const unsigned char* in, + const size_t size, + const unsigned char (&iv)[AES_256_IVSIZE], + const unsigned char (&key)[AES_256_KEYSIZE], + bool encrypt) + { + return evp_sym_transform<AES_256_KEYSIZE, AES_256_IVSIZE>( + cct, EVP_aes_256_cbc(), out, in, size, iv, key, encrypt); + } + + bool cbc_transform(unsigned char* out, + const unsigned char* in, + size_t size, + off_t stream_offset, + const unsigned char (&key)[AES_256_KEYSIZE], + bool encrypt) + { + static std::atomic<bool> failed_to_get_crypto(false); + CryptoAccelRef crypto_accel; + if (! failed_to_get_crypto.load()) + { + crypto_accel = get_crypto_accel(cct); + if (!crypto_accel) + failed_to_get_crypto = true; + } + bool result = true; + unsigned char iv[AES_256_IVSIZE]; + for (size_t offset = 0; result && (offset < size); offset += CHUNK_SIZE) { + size_t process_size = offset + CHUNK_SIZE <= size ? CHUNK_SIZE : size - offset; + prepare_iv(iv, stream_offset + offset); + if (crypto_accel != nullptr) { + if (encrypt) { + result = crypto_accel->cbc_encrypt(out + offset, in + offset, + process_size, iv, key); + } else { + result = crypto_accel->cbc_decrypt(out + offset, in + offset, + process_size, iv, key); + } + } else { + result = cbc_transform( + out + offset, in + offset, process_size, + iv, key, encrypt); + } + } + return result; + } + + + bool encrypt(bufferlist& input, + off_t in_ofs, + size_t size, + bufferlist& output, + off_t stream_offset) + { + bool result = false; + size_t aligned_size = size / AES_256_IVSIZE * AES_256_IVSIZE; + size_t unaligned_rest_size = size - aligned_size; + output.clear(); + buffer::ptr buf(aligned_size + AES_256_IVSIZE); + unsigned char* buf_raw = reinterpret_cast<unsigned char*>(buf.c_str()); + const unsigned char* input_raw = reinterpret_cast<const unsigned char*>(input.c_str()); + + /* encrypt main bulk of data */ + result = cbc_transform(buf_raw, + input_raw + in_ofs, + aligned_size, + stream_offset, key, true); + if (result && (unaligned_rest_size > 0)) { + /* remainder to encrypt */ + if (aligned_size % CHUNK_SIZE > 0) { + /* use last chunk for unaligned part */ + unsigned char iv[AES_256_IVSIZE] = {0}; + result = cbc_transform(buf_raw + aligned_size, + buf_raw + aligned_size - AES_256_IVSIZE, + AES_256_IVSIZE, + iv, key, true); + } else { + /* 0 full blocks in current chunk, use IV as base for unaligned part */ + unsigned char iv[AES_256_IVSIZE] = {0}; + unsigned char data[AES_256_IVSIZE]; + prepare_iv(data, stream_offset + aligned_size); + result = cbc_transform(buf_raw + aligned_size, + data, + AES_256_IVSIZE, + iv, key, true); + } + if (result) { + for(size_t i = aligned_size; i < size; i++) { + *(buf_raw + i) ^= *(input_raw + in_ofs + i); + } + } + } + if (result) { + ldout(cct, 25) << "Encrypted " << size << " bytes"<< dendl; + buf.set_length(size); + output.append(buf); + } else { + ldout(cct, 5) << "Failed to encrypt" << dendl; + } + return result; + } + + + bool decrypt(bufferlist& input, + off_t in_ofs, + size_t size, + bufferlist& output, + off_t stream_offset) + { + bool result = false; + size_t aligned_size = size / AES_256_IVSIZE * AES_256_IVSIZE; + size_t unaligned_rest_size = size - aligned_size; + output.clear(); + buffer::ptr buf(aligned_size + AES_256_IVSIZE); + unsigned char* buf_raw = reinterpret_cast<unsigned char*>(buf.c_str()); + unsigned char* input_raw = reinterpret_cast<unsigned char*>(input.c_str()); + + /* decrypt main bulk of data */ + result = cbc_transform(buf_raw, + input_raw + in_ofs, + aligned_size, + stream_offset, key, false); + if (result && unaligned_rest_size > 0) { + /* remainder to decrypt */ + if (aligned_size % CHUNK_SIZE > 0) { + /*use last chunk for unaligned part*/ + unsigned char iv[AES_256_IVSIZE] = {0}; + result = cbc_transform(buf_raw + aligned_size, + input_raw + in_ofs + aligned_size - AES_256_IVSIZE, + AES_256_IVSIZE, + iv, key, true); + } else { + /* 0 full blocks in current chunk, use IV as base for unaligned part */ + unsigned char iv[AES_256_IVSIZE] = {0}; + unsigned char data[AES_256_IVSIZE]; + prepare_iv(data, stream_offset + aligned_size); + result = cbc_transform(buf_raw + aligned_size, + data, + AES_256_IVSIZE, + iv, key, true); + } + if (result) { + for(size_t i = aligned_size; i < size; i++) { + *(buf_raw + i) ^= *(input_raw + in_ofs + i); + } + } + } + if (result) { + ldout(cct, 25) << "Decrypted " << size << " bytes"<< dendl; + buf.set_length(size); + output.append(buf); + } else { + ldout(cct, 5) << "Failed to decrypt" << dendl; + } + return result; + } + + + void prepare_iv(unsigned char (&iv)[AES_256_IVSIZE], off_t offset) { + off_t index = offset / AES_256_IVSIZE; + off_t i = AES_256_IVSIZE - 1; + unsigned int val; + unsigned int carry = 0; + while (i>=0) { + val = (index & 0xff) + IV[i] + carry; + iv[i] = val; + carry = val >> 8; + index = index >> 8; + i--; + } + } +}; + + +std::unique_ptr<BlockCrypt> AES_256_CBC_create(CephContext* cct, const uint8_t* key, size_t len) +{ + auto cbc = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(cct)); + cbc->set_key(key, AES_256_KEYSIZE); + return cbc; +} + + +const uint8_t AES_256_CBC::IV[AES_256_CBC::AES_256_IVSIZE] = + { 'a', 'e', 's', '2', '5', '6', 'i', 'v', '_', 'c', 't', 'r', '1', '3', '3', '7' }; + + +bool AES_256_ECB_encrypt(CephContext* cct, + const uint8_t* key, + size_t key_size, + const uint8_t* data_in, + uint8_t* data_out, + size_t data_size) +{ + if (key_size == AES_256_KEYSIZE) { + return evp_sym_transform<AES_256_KEYSIZE, 0 /* no IV in ECB */>( + cct, EVP_aes_256_ecb(), data_out, data_in, data_size, + nullptr /* no IV in ECB */, key, true /* encrypt */); + } else { + ldout(cct, 5) << "Key size must be 256 bits long" << dendl; + return false; + } +} + + +RGWGetObj_BlockDecrypt::RGWGetObj_BlockDecrypt(CephContext* cct, + RGWGetObj_Filter* next, + std::unique_ptr<BlockCrypt> crypt): + RGWGetObj_Filter(next), + cct(cct), + crypt(std::move(crypt)), + enc_begin_skip(0), + ofs(0), + end(0), + cache() +{ + block_size = this->crypt->get_block_size(); +} + +RGWGetObj_BlockDecrypt::~RGWGetObj_BlockDecrypt() { +} + +int RGWGetObj_BlockDecrypt::read_manifest(const DoutPrefixProvider *dpp, bufferlist& manifest_bl) { + parts_len.clear(); + RGWObjManifest manifest; + if (manifest_bl.length()) { + auto miter = manifest_bl.cbegin(); + try { + decode(manifest, miter); + } catch (buffer::error& err) { + ldpp_dout(dpp, 0) << "ERROR: couldn't decode manifest" << dendl; + return -EIO; + } + RGWObjManifest::obj_iterator mi; + for (mi = manifest.obj_begin(dpp); mi != manifest.obj_end(dpp); ++mi) { + if (mi.get_cur_stripe() == 0) { + parts_len.push_back(0); + } + parts_len.back() += mi.get_stripe_size(); + } + if (cct->_conf->subsys.should_gather<ceph_subsys_rgw, 20>()) { + for (size_t i = 0; i<parts_len.size(); i++) { + ldpp_dout(dpp, 20) << "Manifest part " << i << ", size=" << parts_len[i] << dendl; + } + } + } + return 0; +} + +int RGWGetObj_BlockDecrypt::fixup_range(off_t& bl_ofs, off_t& bl_end) { + off_t inp_ofs = bl_ofs; + off_t inp_end = bl_end; + if (parts_len.size() > 0) { + off_t in_ofs = bl_ofs; + off_t in_end = bl_end; + + size_t i = 0; + while (i<parts_len.size() && (in_ofs >= (off_t)parts_len[i])) { + in_ofs -= parts_len[i]; + i++; + } + //in_ofs is inside block i + size_t j = 0; + while (j<(parts_len.size() - 1) && (in_end >= (off_t)parts_len[j])) { + in_end -= parts_len[j]; + j++; + } + //in_end is inside part j, OR j is the last part + + size_t rounded_end = ( in_end & ~(block_size - 1) ) + (block_size - 1); + if (rounded_end > parts_len[j]) { + rounded_end = parts_len[j] - 1; + } + + enc_begin_skip = in_ofs & (block_size - 1); + ofs = bl_ofs - enc_begin_skip; + end = bl_end; + bl_end += rounded_end - in_end; + bl_ofs = std::min(bl_ofs - enc_begin_skip, bl_end); + } + else + { + enc_begin_skip = bl_ofs & (block_size - 1); + ofs = bl_ofs & ~(block_size - 1); + end = bl_end; + bl_ofs = bl_ofs & ~(block_size - 1); + bl_end = ( bl_end & ~(block_size - 1) ) + (block_size - 1); + } + ldout(cct, 20) << "fixup_range [" << inp_ofs << "," << inp_end + << "] => [" << bl_ofs << "," << bl_end << "]" << dendl; + return 0; +} + +int RGWGetObj_BlockDecrypt::process(bufferlist& in, size_t part_ofs, size_t size) +{ + bufferlist data; + if (!crypt->decrypt(in, 0, size, data, part_ofs)) { + return -ERR_INTERNAL_ERROR; + } + off_t send_size = size - enc_begin_skip; + if (ofs + enc_begin_skip + send_size > end + 1) { + send_size = end + 1 - ofs - enc_begin_skip; + } + int res = next->handle_data(data, enc_begin_skip, send_size); + enc_begin_skip = 0; + ofs += size; + in.splice(0, size); + return res; +} + +int RGWGetObj_BlockDecrypt::handle_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { + ldout(cct, 25) << "Decrypt " << bl_len << " bytes" << dendl; + bl.begin(bl_ofs).copy(bl_len, cache); + + int res = 0; + size_t part_ofs = ofs; + for (size_t part : parts_len) { + if (part_ofs >= part) { + part_ofs -= part; + } else if (part_ofs + cache.length() >= part) { + // flush data up to part boundaries, aligned or not + res = process(cache, part_ofs, part - part_ofs); + if (res < 0) { + return res; + } + part_ofs = 0; + } else { + break; + } + } + // write up to block boundaries, aligned only + off_t aligned_size = cache.length() & ~(block_size - 1); + if (aligned_size > 0) { + res = process(cache, part_ofs, aligned_size); + } + return res; +} + +/** + * flush remainder of data to output + */ +int RGWGetObj_BlockDecrypt::flush() { + ldout(cct, 25) << "Decrypt flushing " << cache.length() << " bytes" << dendl; + int res = 0; + size_t part_ofs = ofs; + for (size_t part : parts_len) { + if (part_ofs >= part) { + part_ofs -= part; + } else if (part_ofs + cache.length() >= part) { + // flush data up to part boundaries, aligned or not + res = process(cache, part_ofs, part - part_ofs); + if (res < 0) { + return res; + } + part_ofs = 0; + } else { + break; + } + } + // flush up to block boundaries, aligned or not + if (cache.length() > 0) { + res = process(cache, part_ofs, cache.length()); + } + return res; +} + +RGWPutObj_BlockEncrypt::RGWPutObj_BlockEncrypt(CephContext* cct, + rgw::putobj::DataProcessor *next, + std::unique_ptr<BlockCrypt> crypt) + : Pipe(next), + cct(cct), + crypt(std::move(crypt)), + block_size(this->crypt->get_block_size()) +{ +} + +int RGWPutObj_BlockEncrypt::process(bufferlist&& data, uint64_t logical_offset) +{ + ldout(cct, 25) << "Encrypt " << data.length() << " bytes" << dendl; + + // adjust logical offset to beginning of cached data + ceph_assert(logical_offset >= cache.length()); + logical_offset -= cache.length(); + + const bool flush = (data.length() == 0); + cache.claim_append(data); + + uint64_t proc_size = cache.length() & ~(block_size - 1); + if (flush) { + proc_size = cache.length(); + } + if (proc_size > 0) { + bufferlist in, out; + cache.splice(0, proc_size, &in); + if (!crypt->encrypt(in, 0, proc_size, out, logical_offset)) { + return -ERR_INTERNAL_ERROR; + } + int r = Pipe::process(std::move(out), logical_offset); + logical_offset += proc_size; + if (r < 0) + return r; + } + + if (flush) { + /*replicate 0-sized handle_data*/ + return Pipe::process({}, logical_offset); + } + return 0; +} + + +std::string create_random_key_selector(CephContext * const cct) { + char random[AES_256_KEYSIZE]; + cct->random()->get_bytes(&random[0], sizeof(random)); + return std::string(random, sizeof(random)); +} + +typedef enum { + X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM=0, + X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY, + X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5, + X_AMZ_SERVER_SIDE_ENCRYPTION, + X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID, + X_AMZ_SERVER_SIDE_ENCRYPTION_CONTEXT, + X_AMZ_SERVER_SIDE_ENCRYPTION_LAST +} crypt_option_e; + +typedef struct { + const char* http_header_name; + const std::string post_part_name; +} crypt_option_names; + +static const crypt_option_names crypt_options[] = { + {"HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM", "x-amz-server-side-encryption-customer-algorithm"}, + {"HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY", "x-amz-server-side-encryption-customer-key"}, + {"HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5", "x-amz-server-side-encryption-customer-key-md5"}, + {"HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION", "x-amz-server-side-encryption"}, + {"HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID", "x-amz-server-side-encryption-aws-kms-key-id"}, + {"HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CONTEXT", "x-amz-server-side-encryption-context"}, +}; + +static std::string_view get_crypt_attribute( + const RGWEnv* env, + std::map<std::string, + RGWPostObj_ObjStore::post_form_part, + const ltstr_nocase>* parts, + crypt_option_e option) +{ + static_assert( + X_AMZ_SERVER_SIDE_ENCRYPTION_LAST == sizeof(crypt_options)/sizeof(*crypt_options), + "Missing items in crypt_options"); + if (parts != nullptr) { + auto iter + = parts->find(crypt_options[option].post_part_name); + if (iter == parts->end()) + return std::string_view(); + bufferlist& data = iter->second.data; + std::string_view str = std::string_view(data.c_str(), data.length()); + return rgw_trim_whitespace(str); + } else { + const char* hdr = env->get(crypt_options[option].http_header_name, nullptr); + if (hdr != nullptr) { + return std::string_view(hdr); + } else { + return std::string_view(); + } + } +} + + +int rgw_s3_prepare_encrypt(struct req_state* s, + std::map<std::string, ceph::bufferlist>& attrs, + std::map<std::string, + RGWPostObj_ObjStore::post_form_part, + const ltstr_nocase>* parts, + std::unique_ptr<BlockCrypt>* block_crypt, + std::map<std::string, std::string>& crypt_http_responses) +{ + int res = 0; + crypt_http_responses.clear(); + { + std::string_view req_sse_ca = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM); + if (! req_sse_ca.empty()) { + if (req_sse_ca != "AES256") { + ldpp_dout(s, 5) << "ERROR: Invalid value for header " + << "x-amz-server-side-encryption-customer-algorithm" + << dendl; + s->err.message = "The requested encryption algorithm is not valid, must be AES256."; + return -ERR_INVALID_ENCRYPTION_ALGORITHM; + } + if (s->cct->_conf->rgw_crypt_require_ssl && + !rgw_transport_is_secure(s->cct, *s->info.env)) { + ldpp_dout(s, 5) << "ERROR: Insecure request, rgw_crypt_require_ssl is set" << dendl; + return -ERR_INVALID_REQUEST; + } + + std::string key_bin; + try { + key_bin = from_base64( + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY) ); + } catch (...) { + ldpp_dout(s, 5) << "ERROR: rgw_s3_prepare_encrypt invalid encryption " + << "key which contains character that is not base64 encoded." + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key."; + return -EINVAL; + } + + if (key_bin.size() != AES_256_CBC::AES_256_KEYSIZE) { + ldpp_dout(s, 5) << "ERROR: invalid encryption key size" << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key."; + return -EINVAL; + } + + std::string_view keymd5 = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5); + + std::string keymd5_bin; + try { + keymd5_bin = from_base64(keymd5); + } catch (...) { + ldpp_dout(s, 5) << "ERROR: rgw_s3_prepare_encrypt invalid encryption key " + << "md5 which contains character that is not base64 encoded." + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key md5."; + return -EINVAL; + } + + if (keymd5_bin.size() != CEPH_CRYPTO_MD5_DIGESTSIZE) { + ldpp_dout(s, 5) << "ERROR: Invalid key md5 size" << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key md5."; + return -EINVAL; + } + + MD5 key_hash; + // Allow use of MD5 digest in FIPS mode for non-cryptographic purposes + key_hash.SetFlags(EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); + unsigned char key_hash_res[CEPH_CRYPTO_MD5_DIGESTSIZE]; + key_hash.Update(reinterpret_cast<const unsigned char*>(key_bin.c_str()), key_bin.size()); + key_hash.Final(key_hash_res); + + if (memcmp(key_hash_res, keymd5_bin.c_str(), CEPH_CRYPTO_MD5_DIGESTSIZE) != 0) { + ldpp_dout(s, 5) << "ERROR: Invalid key md5 hash" << dendl; + s->err.message = "The calculated MD5 hash of the key did not match the hash that was provided."; + return -EINVAL; + } + + set_attr(attrs, RGW_ATTR_CRYPT_MODE, "SSE-C-AES256"); + set_attr(attrs, RGW_ATTR_CRYPT_KEYMD5, keymd5_bin); + + if (block_crypt) { + auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s->cct)); + aes->set_key(reinterpret_cast<const uint8_t*>(key_bin.c_str()), AES_256_KEYSIZE); + *block_crypt = std::move(aes); + } + + crypt_http_responses["x-amz-server-side-encryption-customer-algorithm"] = "AES256"; + crypt_http_responses["x-amz-server-side-encryption-customer-key-MD5"] = std::string(keymd5); + return 0; + } else { + std::string_view customer_key = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY); + if (!customer_key.empty()) { + ldpp_dout(s, 5) << "ERROR: SSE-C encryption request is missing the header " + << "x-amz-server-side-encryption-customer-algorithm" + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide a valid encryption algorithm."; + return -EINVAL; + } + + std::string_view customer_key_md5 = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5); + if (!customer_key_md5.empty()) { + ldpp_dout(s, 5) << "ERROR: SSE-C encryption request is missing the header " + << "x-amz-server-side-encryption-customer-algorithm" + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide a valid encryption algorithm."; + return -EINVAL; + } + } + + /* AMAZON server side encryption with KMS (key management service) */ + std::string_view req_sse = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION); + if (! req_sse.empty()) { + + if (s->cct->_conf->rgw_crypt_require_ssl && + !rgw_transport_is_secure(s->cct, *s->info.env)) { + ldpp_dout(s, 5) << "ERROR: insecure request, rgw_crypt_require_ssl is set" << dendl; + return -ERR_INVALID_REQUEST; + } + + if (req_sse == "aws:kms") { + std::string_view context = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CONTEXT); + std::string cooked_context; + if ((res = make_canonical_context(s, context, cooked_context))) + return res; + std::string_view key_id = + get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID); + if (key_id.empty()) { + ldpp_dout(s, 5) << "ERROR: not provide a valid key id" << dendl; + s->err.message = "Server Side Encryption with KMS managed key requires " + "HTTP header x-amz-server-side-encryption-aws-kms-key-id"; + return -EINVAL; + } + /* try to retrieve actual key */ + std::string key_selector = create_random_key_selector(s->cct); + set_attr(attrs, RGW_ATTR_CRYPT_MODE, "SSE-KMS"); + set_attr(attrs, RGW_ATTR_CRYPT_KEYID, key_id); + set_attr(attrs, RGW_ATTR_CRYPT_KEYSEL, key_selector); + set_attr(attrs, RGW_ATTR_CRYPT_CONTEXT, cooked_context); + std::string actual_key; + res = make_actual_key_from_kms(s->cct, attrs, actual_key); + if (res != 0) { + ldpp_dout(s, 5) << "ERROR: failed to retrieve actual key from key_id: " << key_id << dendl; + s->err.message = "Failed to retrieve the actual key, kms-keyid: " + std::string(key_id); + return res; + } + if (actual_key.size() != AES_256_KEYSIZE) { + ldpp_dout(s, 5) << "ERROR: key obtained from key_id:" << + key_id << " is not 256 bit size" << dendl; + s->err.message = "KMS provided an invalid key for the given kms-keyid."; + return -EINVAL; + } + + if (block_crypt) { + auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s->cct)); + aes->set_key(reinterpret_cast<const uint8_t*>(actual_key.c_str()), AES_256_KEYSIZE); + *block_crypt = std::move(aes); + } + ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length()); + + crypt_http_responses["x-amz-server-side-encryption"] = "aws:kms"; + crypt_http_responses["x-amz-server-side-encryption-aws-kms-key-id"] = std::string(key_id); + crypt_http_responses["x-amz-server-side-encryption-context"] = std::move(cooked_context); + return 0; + } else if (req_sse == "AES256") { + /* if a default encryption key was provided, we will use it for SSE-S3 */ + } else { + ldpp_dout(s, 5) << "ERROR: Invalid value for header x-amz-server-side-encryption" + << dendl; + s->err.message = "Server Side Encryption with KMS managed key requires " + "HTTP header x-amz-server-side-encryption : aws:kms or AES256"; + return -EINVAL; + } + } else { + /* x-amz-server-side-encryption not present or empty */ + std::string_view key_id = + get_crypt_attribute(s->info.env, parts, + X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID); + if (!key_id.empty()) { + ldpp_dout(s, 5) << "ERROR: SSE-KMS encryption request is missing the header " + << "x-amz-server-side-encryption" + << dendl; + s->err.message = "Server Side Encryption with KMS managed key requires " + "HTTP header x-amz-server-side-encryption : aws:kms"; + return -EINVAL; + } + } + + /* no other encryption mode, check if default encryption is selected */ + if (s->cct->_conf->rgw_crypt_default_encryption_key != "") { + std::string master_encryption_key; + try { + master_encryption_key = from_base64(s->cct->_conf->rgw_crypt_default_encryption_key); + } catch (...) { + ldpp_dout(s, 5) << "ERROR: rgw_s3_prepare_encrypt invalid default encryption key " + << "which contains character that is not base64 encoded." + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key."; + return -EINVAL; + } + + if (master_encryption_key.size() != 256 / 8) { + ldpp_dout(s, 0) << "ERROR: failed to decode 'rgw crypt default encryption key' to 256 bit string" << dendl; + /* not an error to return; missing encryption does not inhibit processing */ + return 0; + } + + set_attr(attrs, RGW_ATTR_CRYPT_MODE, "RGW-AUTO"); + std::string key_selector = create_random_key_selector(s->cct); + set_attr(attrs, RGW_ATTR_CRYPT_KEYSEL, key_selector); + + uint8_t actual_key[AES_256_KEYSIZE]; + if (AES_256_ECB_encrypt(s->cct, + reinterpret_cast<const uint8_t*>(master_encryption_key.c_str()), AES_256_KEYSIZE, + reinterpret_cast<const uint8_t*>(key_selector.c_str()), + actual_key, AES_256_KEYSIZE) != true) { + ::ceph::crypto::zeroize_for_security(actual_key, sizeof(actual_key)); + return -EIO; + } + if (block_crypt) { + auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s->cct)); + aes->set_key(reinterpret_cast<const uint8_t*>(actual_key), AES_256_KEYSIZE); + *block_crypt = std::move(aes); + } + ::ceph::crypto::zeroize_for_security(actual_key, sizeof(actual_key)); + return 0; + } + } + /*no encryption*/ + return 0; +} + + +int rgw_s3_prepare_decrypt(struct req_state* s, + map<string, bufferlist>& attrs, + std::unique_ptr<BlockCrypt>* block_crypt, + std::map<std::string, std::string>& crypt_http_responses) +{ + int res = 0; + std::string stored_mode = get_str_attribute(attrs, RGW_ATTR_CRYPT_MODE); + ldpp_dout(s, 15) << "Encryption mode: " << stored_mode << dendl; + + const char *req_sse = s->info.env->get("HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION", NULL); + if (nullptr != req_sse && (s->op == OP_GET || s->op == OP_HEAD)) { + return -ERR_INVALID_REQUEST; + } + + if (stored_mode == "SSE-C-AES256") { + if (s->cct->_conf->rgw_crypt_require_ssl && + !rgw_transport_is_secure(s->cct, *s->info.env)) { + ldpp_dout(s, 5) << "ERROR: Insecure request, rgw_crypt_require_ssl is set" << dendl; + return -ERR_INVALID_REQUEST; + } + const char *req_cust_alg = + s->info.env->get("HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_ALGORITHM", NULL); + + if (nullptr == req_cust_alg) { + ldpp_dout(s, 5) << "ERROR: Request for SSE-C encrypted object missing " + << "x-amz-server-side-encryption-customer-algorithm" + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide a valid encryption algorithm."; + return -EINVAL; + } else if (strcmp(req_cust_alg, "AES256") != 0) { + ldpp_dout(s, 5) << "ERROR: The requested encryption algorithm is not valid, must be AES256." << dendl; + s->err.message = "The requested encryption algorithm is not valid, must be AES256."; + return -ERR_INVALID_ENCRYPTION_ALGORITHM; + } + + std::string key_bin; + try { + key_bin = from_base64(s->info.env->get("HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY", "")); + } catch (...) { + ldpp_dout(s, 5) << "ERROR: rgw_s3_prepare_decrypt invalid encryption key " + << "which contains character that is not base64 encoded." + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key."; + return -EINVAL; + } + + if (key_bin.size() != AES_256_CBC::AES_256_KEYSIZE) { + ldpp_dout(s, 5) << "ERROR: Invalid encryption key size" << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key."; + return -EINVAL; + } + + std::string keymd5 = + s->info.env->get("HTTP_X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5", ""); + std::string keymd5_bin; + try { + keymd5_bin = from_base64(keymd5); + } catch (...) { + ldpp_dout(s, 5) << "ERROR: rgw_s3_prepare_decrypt invalid encryption key md5 " + << "which contains character that is not base64 encoded." + << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key md5."; + return -EINVAL; + } + + + if (keymd5_bin.size() != CEPH_CRYPTO_MD5_DIGESTSIZE) { + ldpp_dout(s, 5) << "ERROR: Invalid key md5 size " << dendl; + s->err.message = "Requests specifying Server Side Encryption with Customer " + "provided keys must provide an appropriate secret key md5."; + return -EINVAL; + } + + MD5 key_hash; + // Allow use of MD5 digest in FIPS mode for non-cryptographic purposes + key_hash.SetFlags(EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); + uint8_t key_hash_res[CEPH_CRYPTO_MD5_DIGESTSIZE]; + key_hash.Update(reinterpret_cast<const unsigned char*>(key_bin.c_str()), key_bin.size()); + key_hash.Final(key_hash_res); + + if ((memcmp(key_hash_res, keymd5_bin.c_str(), CEPH_CRYPTO_MD5_DIGESTSIZE) != 0) || + (get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYMD5) != keymd5_bin)) { + s->err.message = "The calculated MD5 hash of the key did not match the hash that was provided."; + return -EINVAL; + } + auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s->cct)); + aes->set_key(reinterpret_cast<const uint8_t*>(key_bin.c_str()), AES_256_CBC::AES_256_KEYSIZE); + if (block_crypt) *block_crypt = std::move(aes); + + crypt_http_responses["x-amz-server-side-encryption-customer-algorithm"] = "AES256"; + crypt_http_responses["x-amz-server-side-encryption-customer-key-MD5"] = keymd5; + return 0; + } + + if (stored_mode == "SSE-KMS") { + if (s->cct->_conf->rgw_crypt_require_ssl && + !rgw_transport_is_secure(s->cct, *s->info.env)) { + ldpp_dout(s, 5) << "ERROR: Insecure request, rgw_crypt_require_ssl is set" << dendl; + return -ERR_INVALID_REQUEST; + } + /* try to retrieve actual key */ + std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); + std::string actual_key; + res = reconstitute_actual_key_from_kms(s->cct, attrs, actual_key); + if (res != 0) { + ldpp_dout(s, 10) << "ERROR: failed to retrieve actual key from key_id: " << key_id << dendl; + s->err.message = "Failed to retrieve the actual key, kms-keyid: " + key_id; + return res; + } + if (actual_key.size() != AES_256_KEYSIZE) { + ldpp_dout(s, 0) << "ERROR: key obtained from key_id:" << + key_id << " is not 256 bit size" << dendl; + s->err.message = "KMS provided an invalid key for the given kms-keyid."; + return -ERR_INVALID_ACCESS_KEY; + } + + auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s->cct)); + aes->set_key(reinterpret_cast<const uint8_t*>(actual_key.c_str()), AES_256_KEYSIZE); + actual_key.replace(0, actual_key.length(), actual_key.length(), '\000'); + if (block_crypt) *block_crypt = std::move(aes); + + crypt_http_responses["x-amz-server-side-encryption"] = "aws:kms"; + crypt_http_responses["x-amz-server-side-encryption-aws-kms-key-id"] = key_id; + return 0; + } + + if (stored_mode == "RGW-AUTO") { + std::string master_encryption_key; + try { + master_encryption_key = from_base64(std::string(s->cct->_conf->rgw_crypt_default_encryption_key)); + } catch (...) { + ldpp_dout(s, 5) << "ERROR: rgw_s3_prepare_decrypt invalid default encryption key " + << "which contains character that is not base64 encoded." + << dendl; + s->err.message = "The default encryption key is not valid base64."; + return -EINVAL; + } + + if (master_encryption_key.size() != 256 / 8) { + ldpp_dout(s, 0) << "ERROR: failed to decode 'rgw crypt default encryption key' to 256 bit string" << dendl; + return -EIO; + } + std::string attr_key_selector = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYSEL); + if (attr_key_selector.size() != AES_256_CBC::AES_256_KEYSIZE) { + ldpp_dout(s, 0) << "ERROR: missing or invalid " RGW_ATTR_CRYPT_KEYSEL << dendl; + return -EIO; + } + uint8_t actual_key[AES_256_KEYSIZE]; + if (AES_256_ECB_encrypt(s->cct, + reinterpret_cast<const uint8_t*>(master_encryption_key.c_str()), + AES_256_KEYSIZE, + reinterpret_cast<const uint8_t*>(attr_key_selector.c_str()), + actual_key, AES_256_KEYSIZE) != true) { + ::ceph::crypto::zeroize_for_security(actual_key, sizeof(actual_key)); + return -EIO; + } + auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s->cct)); + aes->set_key(actual_key, AES_256_KEYSIZE); + ::ceph::crypto::zeroize_for_security(actual_key, sizeof(actual_key)); + if (block_crypt) *block_crypt = std::move(aes); + return 0; + } + /*no decryption*/ + return 0; +} + +/********************************************************************* +* "BOTTOM OF FILE" +* I've left some commented out lines above. They are there for +* a reason, which I will explain. The "canonical" json constructed +* by the code above as a crypto context must take a json object and +* turn it into a unique determinstic fixed form. For most json +* types this is easy. The hardest problem that is handled above is +* detailing with unicode strings; they must be turned into +* NFC form and sorted in a fixed order. Numbers, however, +* are another story. Json makes no distinction between integers +* and floating point, and both types have their problems. +* Integers can overflow, so very large numbers are a problem. +* Floating point is even worse; not all floating point numbers +* can be represented accurately in c++ data types, and there +* are many quirks regarding how overflow, underflow, and loss +* of significance are handled. +* +* In this version of the code, I took the simplest answer, I +* reject all numbers altogether. This is not ideal, but it's +* the only choice that is guaranteed to be future compatible. +* AWS S3 does not guarantee to support numbers at all; but it +* actually converts all numbers into strings right off. +* This has the interesting property that 7 and 007 are different, +* but that 007 and "007" are the same. I would rather +* treat numbers as a string of digits and have logic +* to produce the "most compact" equivalent form. This can +* fix all the overflow/underflow problems, but it requires +* fixing the json parser part, and I put that problem off. +* +* The commented code above indicates places in this code that +* will need to be revised depending on future work in this area. +* Removing those comments makes that work harder. +* February 25, 2021 +*********************************************************************/ |