summaryrefslogtreecommitdiffstats
path: root/src/rgw/rgw_crypt.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/rgw/rgw_crypt.cc
parentInitial commit. (diff)
downloadceph-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/rgw/rgw_crypt.cc')
-rw-r--r--src/rgw/rgw_crypt.cc1537
1 files changed, 1537 insertions, 0 deletions
diff --git a/src/rgw/rgw_crypt.cc b/src/rgw/rgw_crypt.cc
new file mode 100644
index 000000000..69b1b8bc6
--- /dev/null
+++ b/src/rgw/rgw_crypt.cc
@@ -0,0 +1,1537 @@
+// -*- 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 std;
+using namespace rgw;
+
+template<typename M>
+class canonical_char_sorter {
+private:
+ const DoutPrefixProvider *dpp;
+ const icu::Normalizer2* normalizer;
+ CephContext *cct;
+public:
+ canonical_char_sorter(const DoutPrefixProvider *dpp, CephContext *cct) : dpp(dpp), cct(cct) {
+ UErrorCode status = U_ZERO_ERROR;
+ normalizer = icu::Normalizer2::getNFCInstance(status);
+ if (U_FAILURE(status)) {
+ ldpp_dout(this->dpp, -1) << "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)) {
+ ldpp_dout(this->dpp, 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(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, 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)) {
+ ldpp_dout(s, -1) << "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) {
+ ldpp_dout(s, -1) << "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) {
+ ldpp_dout(s, 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(const DoutPrefixProvider* dpp, 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) {
+ ldpp_dout(dpp, -1) << __func__ << " cannot load crypto accelerator of type " << crypto_accel_type << dendl;
+ return nullptr;
+ }
+ int err = factory->factory(&ca_impl, &ss);
+ if (err) {
+ ldpp_dout(dpp, -1) << __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(const DoutPrefixProvider* dpp,
+ 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)) {
+ ldpp_dout(dpp, 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)) {
+ ldpp_dout(dpp, 5) << "EVP: failed to 2nd initialization stage" << dendl;
+ return false;
+ }
+
+ // disable padding
+ if (1 != EVP_CIPHER_CTX_set_padding(pctx.get(), 0)) {
+ ldpp_dout(dpp, 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)) {
+ ldpp_dout(dpp, 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)) {
+ ldpp_dout(dpp, 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;
+ const DoutPrefixProvider* dpp;
+private:
+ static const uint8_t IV[AES_256_IVSIZE];
+ CephContext* cct;
+ uint8_t key[AES_256_KEYSIZE];
+public:
+ explicit AES_256_CBC(const DoutPrefixProvider* dpp, CephContext* cct): dpp(dpp), 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>(
+ dpp, 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(this->dpp, 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) {
+ ldpp_dout(this->dpp, 25) << "Encrypted " << size << " bytes"<< dendl;
+ buf.set_length(size);
+ output.append(buf);
+ } else {
+ ldpp_dout(this->dpp, 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) {
+ ldpp_dout(this->dpp, 25) << "Decrypted " << size << " bytes"<< dendl;
+ buf.set_length(size);
+ output.append(buf);
+ } else {
+ ldpp_dout(this->dpp, 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(const DoutPrefixProvider* dpp, CephContext* cct, const uint8_t* key, size_t len)
+{
+ auto cbc = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(dpp, 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(const DoutPrefixProvider* dpp,
+ 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 */>(
+ dpp, cct, EVP_aes_256_ecb(), data_out, data_in, data_size,
+ nullptr /* no IV in ECB */, key, true /* encrypt */);
+ } else {
+ ldpp_dout(dpp, 5) << "Key size must be 256 bits long" << dendl;
+ return false;
+ }
+}
+
+
+RGWGetObj_BlockDecrypt::RGWGetObj_BlockDecrypt(const DoutPrefixProvider *dpp,
+ CephContext* cct,
+ RGWGetObj_Filter* next,
+ std::unique_ptr<BlockCrypt> crypt,
+ std::vector<size_t> parts_len)
+ :
+ RGWGetObj_Filter(next),
+ dpp(dpp),
+ cct(cct),
+ crypt(std::move(crypt)),
+ enc_begin_skip(0),
+ ofs(0),
+ end(0),
+ cache(),
+ parts_len(std::move(parts_len))
+{
+ block_size = this->crypt->get_block_size();
+}
+
+RGWGetObj_BlockDecrypt::~RGWGetObj_BlockDecrypt() {
+}
+
+int RGWGetObj_BlockDecrypt::read_manifest_parts(const DoutPrefixProvider *dpp,
+ const bufferlist& manifest_bl,
+ std::vector<size_t>& parts_len)
+{
+ 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();
+ }
+ 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);
+ }
+ ldpp_dout(this->dpp, 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) {
+ ldpp_dout(this->dpp, 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() {
+ ldpp_dout(this->dpp, 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(const DoutPrefixProvider *dpp,
+ CephContext* cct,
+ rgw::sal::DataProcessor *next,
+ std::unique_ptr<BlockCrypt> crypt)
+ : Pipe(next),
+ dpp(dpp),
+ cct(cct),
+ crypt(std::move(crypt)),
+ block_size(this->crypt->get_block_size())
+{
+}
+
+int RGWPutObj_BlockEncrypt::process(bufferlist&& data, uint64_t logical_offset)
+{
+ ldpp_dout(this->dpp, 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;
+struct crypt_option_names {
+ const std::string post_part_name;
+};
+
+static const crypt_option_names crypt_options[] = {
+ { "x-amz-server-side-encryption-customer-algorithm"},
+ { "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"},
+};
+
+struct CryptAttributes {
+ meta_map_t &x_meta_map;
+
+ CryptAttributes(req_state *s)
+ : x_meta_map(s->info.crypt_attribute_map) {
+ }
+
+ std::string_view get(crypt_option_e option)
+ {
+ static_assert(
+ X_AMZ_SERVER_SIDE_ENCRYPTION_LAST == sizeof(crypt_options)/sizeof(*crypt_options),
+ "Missing items in crypt_options");
+ auto hdr { x_meta_map.find(crypt_options[option].post_part_name) };
+ if (hdr != x_meta_map.end()) {
+ return std::string_view(hdr->second);
+ } else {
+ return std::string_view();
+ }
+ }
+};
+
+std::string fetch_bucket_key_id(req_state *s)
+{
+ auto kek_iter = s->bucket_attrs.find(RGW_ATTR_BUCKET_ENCRYPTION_KEY_ID);
+ if (kek_iter == s->bucket_attrs.end())
+ return std::string();
+ std::string a_key { kek_iter->second.to_str() };
+ // early code appends a nul; pretend that didn't happen
+ auto l { a_key.length() };
+ if (l > 0 && a_key[l-1] == '\0') {
+ a_key.resize(--l);
+ }
+ return a_key;
+}
+
+const std::string cant_expand_key{ "\uFFFD" };
+std::string expand_key_name(req_state *s, const std::string_view&t)
+{
+ std::string r;
+ size_t i, j;
+ for (i = 0;;) {
+ i = t.find('%', (j = i));
+ if (i != j) {
+ if (i == std::string_view::npos)
+ r.append( t.substr(j) );
+ else
+ r.append( t.substr(j, i-j) );
+ }
+ if (i == std::string_view::npos) {
+ break;
+ }
+ if (t[i+1] == '%') {
+ r.append("%");
+ i += 2;
+ continue;
+ }
+ if (t.compare(i+1, 9, "bucket_id") == 0) {
+ r.append(s->bucket->get_marker());
+ i += 10;
+ continue;
+ }
+ if (t.compare(i+1, 8, "owner_id") == 0) {
+ r.append(s->bucket->get_info().owner.id);
+ i += 9;
+ continue;
+ }
+ return cant_expand_key;
+ }
+ return r;
+}
+
+static int get_sse_s3_bucket_key(req_state *s,
+ std::string &key_id)
+{
+ int res;
+ std::string saved_key;
+
+ key_id = expand_key_name(s, s->cct->_conf->rgw_crypt_sse_s3_key_template);
+
+ if (key_id == cant_expand_key) {
+ ldpp_dout(s, 5) << "ERROR: unable to expand key_id " <<
+ s->cct->_conf->rgw_crypt_sse_s3_key_template << " on bucket" << dendl;
+ s->err.message = "Server side error - unable to expand key_id";
+ return -EINVAL;
+ }
+
+ saved_key = fetch_bucket_key_id(s);
+ if (saved_key != "") {
+ ldpp_dout(s, 5) << "Found KEK ID: " << key_id << dendl;
+ }
+ if (saved_key != key_id) {
+ res = create_sse_s3_bucket_key(s, s->cct, key_id);
+ if (res != 0) {
+ return res;
+ }
+ bufferlist key_id_bl;
+ key_id_bl.append(key_id.c_str(), key_id.length());
+ for (int count = 0; count < 15; ++count) {
+ rgw::sal::Attrs attrs = s->bucket->get_attrs();
+ attrs[RGW_ATTR_BUCKET_ENCRYPTION_KEY_ID] = key_id_bl;
+ res = s->bucket->merge_and_store_attrs(s, attrs, s->yield);
+ if (res != -ECANCELED) {
+ break;
+ }
+ res = s->bucket->try_refresh_info(s, nullptr);
+ if (res != 0) {
+ break;
+ }
+ }
+ if (res != 0) {
+ ldpp_dout(s, 5) << "ERROR: unable to save new key_id on bucket" << dendl;
+ s->err.message = "Server side error - unable to save key_id";
+ return res;
+ }
+ }
+ return 0;
+}
+
+int rgw_s3_prepare_encrypt(req_state* s,
+ std::map<std::string, ceph::bufferlist>& attrs,
+ std::unique_ptr<BlockCrypt>* block_crypt,
+ std::map<std::string, std::string>& crypt_http_responses)
+{
+ int res = 0;
+ CryptAttributes crypt_attributes { s };
+ crypt_http_responses.clear();
+
+ {
+ std::string_view req_sse_ca =
+ crypt_attributes.get(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(
+ crypt_attributes.get(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 =
+ crypt_attributes.get(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, 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 =
+ crypt_attributes.get(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 =
+ crypt_attributes.get(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 =
+ crypt_attributes.get(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 =
+ crypt_attributes.get(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 =
+ crypt_attributes.get(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, 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, 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") {
+ 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;
+ }
+
+ if (s->cct->_conf->rgw_crypt_sse_s3_backend != "vault") {
+ s->err.message = "Request specifies Server Side Encryption "
+ "but server configuration does not support this.";
+ return -EINVAL;
+ }
+
+ ldpp_dout(s, 5) << "RGW_ATTR_BUCKET_ENCRYPTION ALGO: "
+ << req_sse << dendl;
+ std::string_view context = "";
+ std::string cooked_context;
+ if ((res = make_canonical_context(s, context, cooked_context)))
+ return res;
+
+ std::string key_id;
+ res = get_sse_s3_bucket_key(s, key_id);
+ if (res != 0) {
+ return res;
+ }
+ std::string key_selector = create_random_key_selector(s->cct);
+
+ set_attr(attrs, RGW_ATTR_CRYPT_KEYSEL, key_selector);
+ set_attr(attrs, RGW_ATTR_CRYPT_CONTEXT, cooked_context);
+ set_attr(attrs, RGW_ATTR_CRYPT_MODE, "AES256");
+ set_attr(attrs, RGW_ATTR_CRYPT_KEYID, key_id);
+ std::string actual_key;
+ res = make_actual_key_from_sse_s3(s, 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";
+ 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 = "SSE-S3 provided an invalid key for the given keyid.";
+ return -EINVAL;
+ }
+
+ if (block_crypt) {
+ auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s, 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"] = "AES256";
+
+ return 0;
+ } else 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, 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, 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;
+ }
+ }
+ return 0;
+}
+
+
+int rgw_s3_prepare_decrypt(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, 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, 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 -EINVAL;
+ }
+
+ auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s, 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, 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, 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;
+ }
+
+ /* SSE-S3 */
+ if (stored_mode == "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;
+ }
+ /* 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_sse_s3(s, s->cct, attrs, actual_key);
+ if (res != 0) {
+ ldpp_dout(s, 10) << "ERROR: failed to retrieve actual key" << dendl;
+ s->err.message = "Failed to retrieve the actual key";
+ return res;
+ }
+ if (actual_key.size() != AES_256_KEYSIZE) {
+ ldpp_dout(s, 0) << "ERROR: key obtained " <<
+ "is not 256 bit size" << dendl;
+ s->err.message = "SSE-S3 provided an invalid key for the given keyid.";
+ return -EINVAL;
+ }
+
+ auto aes = std::unique_ptr<AES_256_CBC>(new AES_256_CBC(s, 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"] = "AES256";
+ return 0;
+ }
+
+
+ /*no decryption*/
+ return 0;
+}
+
+int rgw_remove_sse_s3_bucket_key(req_state *s)
+{
+ int res;
+ auto key_id { expand_key_name(s, s->cct->_conf->rgw_crypt_sse_s3_key_template) };
+ auto saved_key { fetch_bucket_key_id(s) };
+ size_t i;
+
+ if (key_id == cant_expand_key) {
+ ldpp_dout(s, 5) << "ERROR: unable to expand key_id " <<
+ s->cct->_conf->rgw_crypt_sse_s3_key_template << " on bucket" << dendl;
+ s->err.message = "Server side error - unable to expand key_id";
+ return -EINVAL;
+ }
+
+ if (saved_key == "") {
+ return 0;
+ } else if (saved_key != key_id) {
+ ldpp_dout(s, 5) << "Found but will not delete strange KEK ID: " << saved_key << dendl;
+ return 0;
+ }
+ i = s->cct->_conf->rgw_crypt_sse_s3_key_template.find("%bucket_id");
+ if (i == std::string_view::npos) {
+ ldpp_dout(s, 5) << "Kept valid KEK ID: " << saved_key << dendl;
+ return 0;
+ }
+ ldpp_dout(s, 5) << "Removing valid KEK ID: " << saved_key << dendl;
+ res = remove_sse_s3_bucket_key(s, s->cct, saved_key);
+ if (res != 0) {
+ ldpp_dout(s, 0) << "ERROR: Unable to remove KEK ID: " << saved_key << " got " << res << dendl;
+ }
+ return res;
+}
+
+/*********************************************************************
+* "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
+*********************************************************************/