summaryrefslogtreecommitdiffstats
path: root/src/rgw/rgw_crypt.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
commit483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch)
treee5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/rgw/rgw_crypt.cc
parentInitial commit. (diff)
downloadceph-483eb2f56657e8e7f419ab1a4fab8dce9ade8609.tar.xz
ceph-483eb2f56657e8e7f419ab1a4fab8dce9ade8609.zip
Adding upstream version 14.2.21.upstream/14.2.21upstream
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.cc1317
1 files changed, 1317 insertions, 0 deletions
diff --git a/src/rgw/rgw_crypt.cc b/src/rgw/rgw_crypt.cc
new file mode 100644
index 00000000..08f28552
--- /dev/null
+++ b/src/rgw/rgw_crypt.cc
@@ -0,0 +1,1317 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+/**
+ * Crypto filters for Put/Post/Get operations.
+ */
+
+#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 <boost/utility/string_view.hpp>
+#include <rgw/rgw_keystone.h>
+#include "include/str_map.h"
+#include "crypto/crypto_accel.h"
+#include "crypto/crypto_plugin.h"
+#ifdef USE_NSS
+# include <nspr.h>
+# include <nss.h>
+# include <pk11pub.h>
+#endif
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rgw
+
+using namespace rgw;
+
+/**
+ * Encryption in CTR mode. offset is used as IV for each block.
+ */
+class AES_256_CTR : public BlockCrypt {
+public:
+ static const size_t AES_256_KEYSIZE = 256 / 8;
+ static const size_t AES_256_IVSIZE = 128 / 8;
+private:
+ static const uint8_t IV[AES_256_IVSIZE];
+ CephContext* cct;
+ uint8_t key[AES_256_KEYSIZE];
+public:
+ explicit AES_256_CTR(CephContext* cct): cct(cct) {
+ }
+ ~AES_256_CTR() {
+ ::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 AES_256_IVSIZE;
+ }
+
+#ifdef USE_NSS
+
+ bool encrypt(bufferlist& input, off_t in_ofs, size_t size, bufferlist& output, off_t stream_offset)
+ {
+ bool result = false;
+ PK11SlotInfo *slot;
+ SECItem keyItem;
+ PK11SymKey *symkey;
+ CK_AES_CTR_PARAMS ctr_params = {0};
+ SECItem ivItem;
+ SECItem *param;
+ SECStatus ret;
+ PK11Context *ectx;
+ int written;
+ unsigned int written2;
+
+ slot = PK11_GetBestSlot(CKM_AES_CTR, NULL);
+ if (slot) {
+ keyItem.type = siBuffer;
+ keyItem.data = key;
+ keyItem.len = AES_256_KEYSIZE;
+
+ symkey = PK11_ImportSymKey(slot, CKM_AES_CTR, PK11_OriginUnwrap, CKA_UNWRAP, &keyItem, NULL);
+ if (symkey) {
+ static_assert(sizeof(ctr_params.cb) >= AES_256_IVSIZE, "Must fit counter");
+ ctr_params.ulCounterBits = 128;
+ prepare_iv(reinterpret_cast<unsigned char*>(&ctr_params.cb), stream_offset);
+
+ ivItem.type = siBuffer;
+ ivItem.data = (unsigned char*)&ctr_params;
+ ivItem.len = sizeof(ctr_params);
+
+ param = PK11_ParamFromIV(CKM_AES_CTR, &ivItem);
+ if (param) {
+ ectx = PK11_CreateContextBySymKey(CKM_AES_CTR, CKA_ENCRYPT, symkey, param);
+ if (ectx) {
+ buffer::ptr buf((size + AES_256_KEYSIZE - 1) / AES_256_KEYSIZE * AES_256_KEYSIZE);
+ ret = PK11_CipherOp(ectx,
+ (unsigned char*)buf.c_str(), &written, buf.length(),
+ (unsigned char*)input.c_str() + in_ofs, size);
+ if (ret == SECSuccess) {
+ ret = PK11_DigestFinal(ectx,
+ (unsigned char*)buf.c_str() + written, &written2,
+ buf.length() - written);
+ if (ret == SECSuccess) {
+ buf.set_length(written + written2);
+ output.append(buf);
+ result = true;
+ }
+ }
+ PK11_DestroyContext(ectx, PR_TRUE);
+ }
+ SECITEM_FreeItem(param, PR_TRUE);
+ }
+ PK11_FreeSymKey(symkey);
+ }
+ PK11_FreeSlot(slot);
+ }
+ if (result == false) {
+ ldout(cct, 5) << "Failed to perform AES-CTR encryption: " << PR_GetError() << dendl;
+ }
+ return result;
+ }
+
+#else
+# error "No supported crypto implementation found."
+#endif
+ /* in CTR encrypt is the same as decrypt */
+ bool decrypt(bufferlist& input, off_t in_ofs, size_t size, bufferlist& output, off_t stream_offset) {
+ return encrypt(input, in_ofs, size, output, stream_offset);
+ }
+
+ 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--;
+ }
+ }
+};
+
+const uint8_t AES_256_CTR::IV[AES_256_CTR::AES_256_IVSIZE] =
+ { 'a', 'e', 's', '2', '5', '6', 'i', 'v', '_', 'c', 't', 'r', '1', '3', '3', '7' };
+
+
+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;
+}
+
+
+/**
+ * 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;
+ }
+
+#ifdef USE_NSS
+
+ bool cbc_transform(unsigned char* out,
+ const unsigned char* in,
+ size_t size,
+ const unsigned char (&iv)[AES_256_IVSIZE],
+ const unsigned char (&key)[AES_256_KEYSIZE],
+ bool encrypt)
+ {
+ bool result = false;
+ PK11SlotInfo *slot;
+ SECItem keyItem;
+ PK11SymKey *symkey;
+ CK_AES_CBC_ENCRYPT_DATA_PARAMS ctr_params = {0};
+ SECItem ivItem;
+ SECItem *param;
+ SECStatus ret;
+ PK11Context *ectx;
+ int written;
+
+ slot = PK11_GetBestSlot(CKM_AES_CBC, NULL);
+ if (slot) {
+ keyItem.type = siBuffer;
+ keyItem.data = const_cast<unsigned char*>(&key[0]);
+ keyItem.len = AES_256_KEYSIZE;
+ symkey = PK11_ImportSymKey(slot, CKM_AES_CBC, PK11_OriginUnwrap, CKA_UNWRAP, &keyItem, NULL);
+ if (symkey) {
+ memcpy(ctr_params.iv, iv, AES_256_IVSIZE);
+ ivItem.type = siBuffer;
+ ivItem.data = (unsigned char*)&ctr_params;
+ ivItem.len = sizeof(ctr_params);
+
+ param = PK11_ParamFromIV(CKM_AES_CBC, &ivItem);
+ if (param) {
+ ectx = PK11_CreateContextBySymKey(CKM_AES_CBC, encrypt?CKA_ENCRYPT:CKA_DECRYPT, symkey, param);
+ if (ectx) {
+ ret = PK11_CipherOp(ectx,
+ out, &written, size,
+ in, size);
+ if ((ret == SECSuccess) && (written == (int)size)) {
+ result = true;
+ }
+ PK11_DestroyContext(ectx, PR_TRUE);
+ }
+ SECITEM_FreeItem(param, PR_TRUE);
+ }
+ PK11_FreeSymKey(symkey);
+ }
+ PK11_FreeSlot(slot);
+ }
+ if (result == false) {
+ ldout(cct, 5) << "Failed to perform AES-CBC encryption: " << PR_GetError() << dendl;
+ }
+ return result;
+ }
+
+#else
+# error "No supported crypto implementation found."
+#endif
+
+ 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 std::move(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' };
+
+
+#ifdef USE_NSS
+
+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) {
+ bool result = false;
+ PK11SlotInfo *slot;
+ SECItem keyItem;
+ PK11SymKey *symkey;
+ SECItem *param;
+ SECStatus ret;
+ PK11Context *ectx;
+ int written;
+ unsigned int written2;
+ if (key_size == AES_256_KEYSIZE) {
+ slot = PK11_GetBestSlot(CKM_AES_ECB, NULL);
+ if (slot) {
+ keyItem.type = siBuffer;
+ keyItem.data = const_cast<uint8_t*>(key);
+ keyItem.len = AES_256_KEYSIZE;
+
+ param = PK11_ParamFromIV(CKM_AES_ECB, NULL);
+ if (param) {
+ symkey = PK11_ImportSymKey(slot, CKM_AES_ECB, PK11_OriginUnwrap, CKA_UNWRAP, &keyItem, NULL);
+ if (symkey) {
+ ectx = PK11_CreateContextBySymKey(CKM_AES_ECB, CKA_ENCRYPT, symkey, param);
+ if (ectx) {
+ ret = PK11_CipherOp(ectx,
+ data_out, &written, data_size,
+ data_in, data_size);
+ if (ret == SECSuccess) {
+ ret = PK11_DigestFinal(ectx,
+ data_out + written, &written2,
+ data_size - written);
+ if (ret == SECSuccess) {
+ result = true;
+ }
+ }
+ PK11_DestroyContext(ectx, PR_TRUE);
+ }
+ PK11_FreeSymKey(symkey);
+ }
+ SECITEM_FreeItem(param, PR_TRUE);
+ }
+ PK11_FreeSlot(slot);
+ }
+ if (result == false) {
+ ldout(cct, 5) << "Failed to perform AES-ECB encryption: " << PR_GetError() << dendl;
+ }
+ } else {
+ ldout(cct, 5) << "Key size must be 256 bits long" << dendl;
+ }
+ return result;
+}
+
+#else
+# error "No supported crypto implementation found."
+#endif
+
+
+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(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) {
+ ldout(cct, 0) << "ERROR: couldn't decode manifest" << dendl;
+ return -EIO;
+ }
+ RGWObjManifest::obj_iterator mi;
+ for (mi = manifest.obj_begin(); mi != manifest.obj_end(); ++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++) {
+ ldout(cct, 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.copy(bl_ofs, 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));
+}
+
+static int get_barbican_url(CephContext * const cct,
+ std::string& url)
+{
+ url = cct->_conf->rgw_barbican_url;
+ if (url.empty()) {
+ ldout(cct, 0) << "ERROR: conf rgw_barbican_url is not set" << dendl;
+ return -EINVAL;
+ }
+
+ if (url.back() != '/') {
+ url.append("/");
+ }
+
+ return 0;
+}
+
+static int request_key_from_barbican(CephContext *cct,
+ boost::string_view key_id,
+ boost::string_view key_selector,
+ const std::string& barbican_token,
+ std::string& actual_key) {
+ std::string secret_url;
+ int res;
+ res = get_barbican_url(cct, secret_url);
+ if (res < 0) {
+ return res;
+ }
+ secret_url += "v1/secrets/" + std::string(key_id);
+
+ bufferlist secret_bl;
+ RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
+ secret_req.append_header("Accept", "application/octet-stream");
+ secret_req.append_header("X-Auth-Token", barbican_token);
+
+ res = secret_req.process();
+ if (res < 0) {
+ return res;
+ }
+ if (secret_req.get_http_status() ==
+ RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
+ return -EACCES;
+ }
+
+ if (secret_req.get_http_status() >=200 &&
+ secret_req.get_http_status() < 300 &&
+ secret_bl.length() == AES_256_KEYSIZE) {
+ actual_key.assign(secret_bl.c_str(), secret_bl.length());
+ ::ceph::crypto::zeroize_for_security(secret_bl.c_str(), secret_bl.length());
+ } else {
+ res = -EACCES;
+ }
+ return res;
+}
+
+static map<string,string> get_str_map(const string &str) {
+ map<string,string> m;
+ get_str_map(str, &m, ";, \t");
+ return m;
+}
+
+static int get_actual_key_from_kms(CephContext *cct,
+ boost::string_view key_id,
+ boost::string_view key_selector,
+ std::string& actual_key)
+{
+ int res = 0;
+ ldout(cct, 20) << "Getting KMS encryption key for key=" << key_id << dendl;
+ static map<string,string> str_map = get_str_map(
+ cct->_conf->rgw_crypt_s3_kms_encryption_keys);
+
+ map<string, string>::iterator it = str_map.find(std::string(key_id));
+ if (it != str_map.end() ) {
+ std::string master_key;
+ try {
+ master_key = from_base64((*it).second);
+ } catch (...) {
+ ldout(cct, 5) << "ERROR: get_actual_key_from_kms invalid encryption key id "
+ << "which contains character that is not base64 encoded."
+ << dendl;
+ return -EINVAL;
+ }
+
+ if (master_key.length() == AES_256_KEYSIZE) {
+ uint8_t _actual_key[AES_256_KEYSIZE];
+ if (AES_256_ECB_encrypt(cct,
+ reinterpret_cast<const uint8_t*>(master_key.c_str()), AES_256_KEYSIZE,
+ reinterpret_cast<const uint8_t*>(key_selector.data()),
+ _actual_key, AES_256_KEYSIZE)) {
+ actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE);
+ } else {
+ res = -EIO;
+ }
+ ::ceph::crypto::zeroize_for_security(_actual_key, sizeof(_actual_key));
+ } else {
+ ldout(cct, 20) << "Wrong size for key=" << key_id << dendl;
+ res = -EIO;
+ }
+ } else {
+ std::string token;
+ if (rgw::keystone::Service::get_keystone_barbican_token(cct, token) < 0) {
+ ldout(cct, 5) << "Failed to retrieve token for barbican" << dendl;
+ res = -EINVAL;
+ return res;
+ }
+
+ res = request_key_from_barbican(cct, key_id, key_selector, token, actual_key);
+ if (res != 0) {
+ ldout(cct, 5) << "Failed to retrieve secret from barbican:" << key_id << dendl;
+ }
+ }
+ return res;
+}
+
+static inline void set_attr(map<string, bufferlist>& attrs,
+ const char* key,
+ boost::string_view value)
+{
+ bufferlist bl;
+ bl.append(value.data(), value.size());
+ attrs[key] = std::move(bl);
+}
+
+static inline std::string get_str_attribute(map<string, bufferlist>& attrs,
+ const char *name)
+{
+ auto iter = attrs.find(name);
+ if (iter == attrs.end()) {
+ return {};
+ }
+ return iter->second.to_str();
+}
+
+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_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"},
+};
+
+static boost::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 boost::string_view();
+ bufferlist& data = iter->second.data;
+ boost::string_view str = boost::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 boost::string_view(hdr);
+ } else {
+ return boost::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();
+ {
+ boost::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") {
+ ldout(s->cct, 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)) {
+ ldout(s->cct, 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 (...) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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;
+ }
+
+ boost::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 (...) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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;
+ 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) {
+ ldout(s->cct, 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"] = keymd5.to_string();
+ return 0;
+ } else {
+ boost::string_view customer_key =
+ get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY);
+ if (!customer_key.empty()) {
+ ldout(s->cct, 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;
+ }
+
+ boost::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()) {
+ ldout(s->cct, 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) */
+ boost::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)) {
+ ldout(s->cct, 5) << "ERROR: insecure request, rgw_crypt_require_ssl is set" << dendl;
+ return -ERR_INVALID_REQUEST;
+ }
+
+ if (req_sse == "aws:kms") {
+ boost::string_view key_id =
+ get_crypt_attribute(s->info.env, parts, X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID);
+ if (key_id.empty()) {
+ ldout(s->cct, 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 -ERR_INVALID_ACCESS_KEY;
+ }
+ /* try to retrieve actual key */
+ std::string key_selector = create_random_key_selector(s->cct);
+ std::string actual_key;
+ res = get_actual_key_from_kms(s->cct, key_id, key_selector, actual_key);
+ if (res != 0) {
+ ldout(s->cct, 5) << "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.to_string();
+ return res;
+ }
+ if (actual_key.size() != AES_256_KEYSIZE) {
+ ldout(s->cct, 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 -ERR_INVALID_ACCESS_KEY;
+ }
+ 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);
+
+ 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);
+ }
+ actual_key.replace(0, actual_key.length(), actual_key.length(), '\000');
+
+ crypt_http_responses["x-amz-server-side-encryption"] = "aws:kms";
+ crypt_http_responses["x-amz-server-side-encryption-aws-kms-key-id"] = key_id.to_string();
+ return 0;
+ } else if (req_sse == "AES256") {
+ /* if a default encryption key was provided, we will use it for SSE-S3 */
+ } else {
+ ldout(s->cct, 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 */
+ boost::string_view key_id =
+ get_crypt_attribute(s->info.env, parts,
+ X_AMZ_SERVER_SIDE_ENCRYPTION_AWS_KMS_KEY_ID);
+ if (!key_id.empty()) {
+ ldout(s->cct, 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 (...) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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);
+ ldout(s->cct, 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)) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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 (...) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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 (...) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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;
+ 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)) {
+ ldout(s->cct, 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 key_selector = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYSEL);
+ std::string actual_key;
+ res = get_actual_key_from_kms(s->cct, key_id, key_selector, actual_key);
+ if (res != 0) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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 (...) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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) {
+ ldout(s->cct, 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;
+}