// -*- 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 #include #include #include #include #include "include/assert.h" #include #include #include "include/str_map.h" #include "crypto/crypto_accel.h" #include "crypto/crypto_plugin.h" #ifdef USE_NSS # include # include # include #endif #ifdef USE_CRYPTOPP #include #include #include using namespace CryptoPP; #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. */ #warning "TODO: move this code to auth/Crypto for others to reuse." 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: AES_256_CTR(CephContext* cct): cct(cct) { } ~AES_256_CTR() { memset(key, 0, 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_CRYPTOPP bool encrypt(bufferlist& input, off_t in_ofs, size_t size, bufferlist& output, off_t stream_offset) { byte iv[AES_256_IVSIZE]; ldout(cct, 25) << "Encrypt in_ofs " << in_ofs << " size=" << size << " stream_offset=" << stream_offset << dendl; if (input.length() < in_ofs + size) { return false; } output.clear(); buffer::ptr buf((size + AES_256_KEYSIZE - 1) / AES_256_KEYSIZE * AES_256_KEYSIZE); /*create CTR mask*/ prepare_iv(iv, stream_offset); CTR_Mode< AES >::Encryption e; e.SetKeyWithIV(key, AES_256_KEYSIZE, iv, AES_256_IVSIZE); buf.zero(); e.ProcessData((byte*)buf.c_str(), (byte*)buf.c_str(), buf.length()); buf.set_length(size); off_t plaintext_pos = in_ofs; off_t crypt_pos = 0; auto iter = input.buffers().begin(); //skip unaffected begin while ((iter != input.buffers().end()) && (plaintext_pos >= iter->length())) { plaintext_pos -= iter->length(); ++iter; } while (iter != input.buffers().end()) { off_t cnt = std::min(iter->length() - plaintext_pos, size - crypt_pos); byte* src = (byte*)iter->c_str() + plaintext_pos; byte* dst = (byte*)buf.c_str() + crypt_pos; for (off_t i = 0; i < cnt; i++) { dst[i] ^= src[i]; } ++iter; plaintext_pos = 0; crypt_pos += cnt; } output.append(buf); return true; } #elif defined(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(&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 Must define USE_CRYPTOPP or USE_NSS #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(byte 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(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 */ #warning "TODO: use auth/Crypto instead of reimplementing." 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: AES_256_CBC(CephContext* cct): cct(cct) { } ~AES_256_CBC() { memset(key, 0, 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_CRYPTOPP 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) { if (encrypt) { CBC_Mode< AES >::Encryption e; e.SetKeyWithIV(key, AES_256_KEYSIZE, iv, AES_256_IVSIZE); e.ProcessData((byte*)out, (byte*)in, size); } else { CBC_Mode< AES >::Decryption d; d.SetKeyWithIV(key, AES_256_KEYSIZE, iv, AES_256_IVSIZE); d.ProcessData((byte*)out, (byte*)in, size); } return true; } #elif defined(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(&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 Must define USE_CRYPTOPP or USE_NSS #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 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(buf.c_str()); const unsigned char* input_raw = reinterpret_cast(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(buf.c_str()); unsigned char* input_raw = reinterpret_cast(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(byte (&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 AES_256_CBC_create(CephContext* cct, const uint8_t* key, size_t len) { auto cbc = std::unique_ptr(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_CRYPTOPP 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 res = false; if (key_size == AES_256_KEYSIZE) { try { ECB_Mode< AES >::Encryption e; e.SetKey( key, key_size ); e.ProcessData(data_out, data_in, data_size); res = true; } catch( CryptoPP::Exception& ex ) { ldout(cct, 5) << "AES-ECB encryption failed with: " << ex.GetWhat() << dendl; } } return res; } #elif defined 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(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 Must define USE_CRYPTOPP or USE_NSS #endif RGWGetObj_BlockDecrypt::RGWGetObj_BlockDecrypt(CephContext* cct, RGWGetDataCB* next, std::unique_ptr 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()) { bufferlist::iterator miter = manifest_bl.begin(); 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 [" << bl_ofs << "," << bl_end << "]" << dendl; return 0; } int RGWGetObj_BlockDecrypt::handle_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { int res = 0; ldout(cct, 25) << "Decrypt " << bl_len << " bytes" << dendl; size_t part_ofs = ofs; size_t i = 0; while (i= parts_len[i])) { part_ofs -= parts_len[i]; i++; } bl.copy(bl_ofs, bl_len, cache); off_t aligned_size = cache.length() & ~(block_size - 1); if (aligned_size > 0) { bufferlist data; if (! crypt->decrypt(cache, 0, aligned_size, data, part_ofs) ) { return -ERR_INTERNAL_ERROR; } off_t send_size = aligned_size - enc_begin_skip; if (ofs + enc_begin_skip + send_size > end + 1) { send_size = end + 1 - ofs - enc_begin_skip; } res = next->handle_data(data, enc_begin_skip, send_size); enc_begin_skip = 0; ofs += aligned_size; cache.splice(0, aligned_size); } return res; } /** * flush remainder of data to output */ int RGWGetObj_BlockDecrypt::flush() { int res = 0; size_t part_ofs = ofs; size_t i = 0; while (i parts_len[i])) { part_ofs -= parts_len[i]; i++; } if (cache.length() > 0) { bufferlist data; if (! crypt->decrypt(cache, 0, cache.length(), data, part_ofs) ) { return -ERR_INTERNAL_ERROR; } off_t send_size = cache.length() - enc_begin_skip; if (ofs + enc_begin_skip + send_size > end + 1) { send_size = end + 1 - ofs - enc_begin_skip; } res = next->handle_data(data, enc_begin_skip, send_size); enc_begin_skip = 0; ofs += send_size; } return res; } RGWPutObj_BlockEncrypt::RGWPutObj_BlockEncrypt(CephContext* cct, RGWPutObjDataProcessor* next, std::unique_ptr crypt): RGWPutObj_Filter(next), cct(cct), crypt(std::move(crypt)), ofs(0), cache() { block_size = this->crypt->get_block_size(); } RGWPutObj_BlockEncrypt::~RGWPutObj_BlockEncrypt() { } int RGWPutObj_BlockEncrypt::handle_data(bufferlist& bl, off_t in_ofs, void **phandle, rgw_raw_obj *pobj, bool *again) { int res = 0; ldout(cct, 25) << "Encrypt " << bl.length() << " bytes" << dendl; if (*again) { bufferlist no_data; res = next->handle_data(no_data, in_ofs, phandle, pobj, again); //if *again is not set to false, we will have endless loop //drop info on log if (*again) { ldout(cct, 20) << "*again==true" << dendl; } return res; } cache.append(bl); off_t proc_size = cache.length() & ~(block_size - 1); if (bl.length() == 0) { proc_size = cache.length(); } if (proc_size > 0) { bufferlist data; if (! crypt->encrypt(cache, 0, proc_size, data, ofs) ) { return -ERR_INTERNAL_ERROR; } res = next->handle_data(data, ofs, phandle, pobj, again); ofs += proc_size; cache.splice(0, proc_size); if (res < 0) return res; } if (bl.length() == 0) { /*replicate 0-sized handle_data*/ res = next->handle_data(bl, ofs, phandle, pobj, again); } return res; } int RGWPutObj_BlockEncrypt::throttle_data(void *handle, const rgw_raw_obj& obj, uint64_t size, bool need_to_wait) { return next->throttle_data(handle, obj, size, need_to_wait); } std::string create_random_key_selector(CephContext * const cct) { char random[AES_256_KEYSIZE]; if (get_random_bytes(&random[0], sizeof(random)) != 0) { ldout(cct, 0) << "ERROR: cannot get_random_bytes. " << dendl; for (char& v:random) v=rand(); } 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, &secret_bl); secret_req.append_header("Accept", "application/octet-stream"); secret_req.append_header("X-Auth-Token", barbican_token); res = secret_req.process("GET", secret_url.c_str()); 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()); memset(secret_bl.c_str(), 0, secret_bl.length()); } else { res = -EACCES; } return res; } static map get_str_map(const string &str) { map 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 str_map = get_str_map( cct->_conf->rgw_crypt_s3_kms_encryption_keys); map::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(master_key.c_str()), AES_256_KEYSIZE, reinterpret_cast(key_selector.data()), _actual_key, AES_256_KEYSIZE)) { actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE); } else { res = -EIO; } memset(_actual_key, 0, 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& 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& 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* 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& attrs, std::map* parts, std::unique_ptr* block_crypt, std::map& 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 && !s->info.env->exists("SERVER_PORT_SECURE")) { 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; byte key_hash_res[CEPH_CRYPTO_MD5_DIGESTSIZE]; key_hash.Update(reinterpret_cast(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(new AES_256_CBC(s->cct)); aes->set_key(reinterpret_cast(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 (req_sse != "aws:kms") { 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"; return -EINVAL; } if (s->cct->_conf->rgw_crypt_require_ssl && !s->info.env->exists("SERVER_PORT_SECURE")) { ldout(s->cct, 5) << "ERROR: insecure request, rgw_crypt_require_ssl is set" << dendl; return -ERR_INVALID_REQUEST; } 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(new AES_256_CBC(s->cct)); aes->set_key(reinterpret_cast(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 { 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(master_encryption_key.c_str()), AES_256_KEYSIZE, reinterpret_cast(key_selector.c_str()), actual_key, AES_256_KEYSIZE) != true) { memset(actual_key, 0, sizeof(actual_key)); return -EIO; } if (block_crypt) { auto aes = std::unique_ptr(new AES_256_CBC(s->cct)); aes->set_key(reinterpret_cast(actual_key), AES_256_KEYSIZE); *block_crypt = std::move(aes); } memset(actual_key, 0, sizeof(actual_key)); return 0; } } /*no encryption*/ return 0; } int rgw_s3_prepare_decrypt(struct req_state* s, map& attrs, std::unique_ptr* block_crypt, std::map& 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 && !s->info.env->exists("SERVER_PORT_SECURE")) { 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(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(new AES_256_CBC(s->cct)); aes->set_key(reinterpret_cast(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 && !s->info.env->exists("SERVER_PORT_SECURE")) { 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(new AES_256_CBC(s->cct)); aes->set_key(reinterpret_cast(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(master_encryption_key.c_str()), AES_256_KEYSIZE, reinterpret_cast(attr_key_selector.c_str()), actual_key, AES_256_KEYSIZE) != true) { memset(actual_key, 0, sizeof(actual_key)); return -EIO; } auto aes = std::unique_ptr(new AES_256_CBC(s->cct)); aes->set_key(actual_key, AES_256_KEYSIZE); memset(actual_key, 0, sizeof(actual_key)); if (block_crypt) *block_crypt = std::move(aes); return 0; } /*no decryption*/ return 0; }