From 19617e49565e7e1e81e1a4120fed5850e787a72c Mon Sep 17 00:00:00 2001 From: Yehuda Sadeh Date: Fri, 21 Feb 2025 16:18:58 -0500 Subject: [PATCH] auth/crypto: add support for aes256-hmac384-192 Using the encryption standard set in RFC 8009. This is the encryption that is used in Kerberos 5, so naming this variation as AES256KRB5. Signed-off-by: Yehuda Sadeh --- src/auth/Crypto.cc | 418 +++++++++++++++++++++++++++++++++++++ src/common/ceph_context.cc | 5 + src/common/ceph_context.h | 1 + src/include/ceph_fs.h | 5 +- src/test/crypto.cc | 109 +++++++++- 5 files changed, 527 insertions(+), 11 deletions(-) diff --git a/src/auth/Crypto.cc b/src/auth/Crypto.cc index e47997a7be1..20dfe06b0b6 100644 --- a/src/auth/Crypto.cc +++ b/src/auth/Crypto.cc @@ -17,6 +17,7 @@ #include #include +#include #include "Crypto.h" @@ -34,6 +35,10 @@ #include "common/debug.h" #include +#include + +#define dout_subsys ceph_subsys_auth + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" @@ -47,6 +52,8 @@ using ceph::bufferlist; using ceph::bufferptr; using ceph::Formatter; +using boost::endian::native_to_big; + // use getentropy() if available. it uses the same source of randomness // as /dev/urandom without the filesystem overhead @@ -483,6 +490,415 @@ CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret, } +// --------------------------------------------------- + +/* + * AES256CTS-HMAC384-192 + */ +class CryptoAES256KRB5 : public CryptoHandler { +public: + CryptoAES256KRB5() { } + ~CryptoAES256KRB5() override {} + int get_type() const override { + return CEPH_CRYPTO_AES256KRB5; + } + int create(CryptoRandom *random, bufferptr& secret) override; + int validate_secret(const bufferptr& secret) override; + CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override; +}; + +static constexpr const std::size_t AES256KRB5_KEY_LEN{32}; +static constexpr const std::size_t AES256KRB5_BLOCK_LEN{16}; +static constexpr const std::size_t AES256KRB5_HASH_LEN{24}; +static constexpr const std::size_t SHA384_LEN{48}; + +class CryptoAES256KRB5KeyHandler : public CryptoKeyHandler { + EVP_CIPHER *cipher{nullptr}; + + ceph::bufferlist ki; + const unsigned char *ki_raw; + ceph::bufferlist ke; + const unsigned char *ke_raw; + +static void dump_buf(CephContext *cct, string title, const unsigned char *buf, int len) +{ + std::stringstream ss; + ss << std::endl << title << std::endl; + for (int i = 0; i < len; ++i) { + if (i != 0 && i % 16 == 0) { + ss << std::endl; + } + ss << fmt::format("{:02x} ", buf[i]); + } + ss << std::endl; + ldout(cct, 0) << ss.str() << dendl; +} + + int calc_hmac_sha384(const unsigned char *data, + int data_len, + const unsigned char* hmac_key, + int key_size, + const unsigned char *iv, + int iv_size, + char *out, + int out_size, + ostringstream& err) const { + unsigned int len = 0; + char _out[SHA384_LEN]; + char *pout; + bool need_trim = (out_size < (int)sizeof(_out)); + if (need_trim) { + pout = _out; + } else { + pout = out; + } + + /* IV is prepended to the plaintext */ + ceph::bufferptr iv_buf(reinterpret_cast(iv), iv_size); + ceph::bufferlist source; + source.push_back(iv_buf); + source.append((const char *)data, data_len); + + HMAC(EVP_sha384(), hmac_key, key_size, + reinterpret_cast(source.c_str()), source.length(), + (unsigned char *)pout, &len); + + if (len != SHA384_LEN) { + err << "Unexpected calculated SHA384 length"; + return -EIO; + } + + if (need_trim) { + memcpy(out, pout, out_size); + len = out_size; + } + + return len; + } + + int calc_kx(const ceph::bufferptr& secret, + uint32_t usage, + uint8_t type, + int k, + ceph::bufferlist& out, + ostringstream& err) { + + struct plain_data { + unsigned char prefix[4] = { 0, 0, 0, 1 }; + uint32_t usage; + uint8_t type; + uint8_t c = 0; + uint32_t k; + + plain_data(uint32_t _usage, uint8_t _type, uint32_t _k) : + usage(native_to_big(_usage)), + type(_type), + k(native_to_big(_k * 8)) {} + } __attribute__((packed)) data(usage, type, k); + + ceph::bufferptr bp(reinterpret_cast(&data), sizeof(data)); + + ceph::bufferptr sha384(SHA384_LEN); + int r = calc_hmac_sha384((const unsigned char *)bp.c_str(), bp.length(), + reinterpret_cast(secret.c_str()), + secret.length(), + nullptr, 0, /* no IV */ + sha384.c_str(), + SHA384_LEN, + err); + + bufferlist bl; + bl.append(sha384); + bl.splice(0, k, &out); + + return r; + } + + int encrypt_AES256_CTS(CephContext *cct, + ceph::bufferlist& plaintext, + const unsigned char* iv, int iv_size, + unsigned char *ciphertext, + int ciphertext_len) const { + if (!cipher) { + return -EINVAL; /* initialization error */ + } + + if ((size_t)ciphertext_len < plaintext.length()) { + return -EINVAL; + } + + OSSL_PARAM params[2] = { OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, (char *)"CS3", 0), + OSSL_PARAM_construct_end()}; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + ldout(cct, 20) << "EVP_CIPHER_CTX_new() returned null" << dendl; + return -EIO; + } + + if (!EVP_EncryptInit_ex2(ctx, cipher, ke_raw, iv, params)) { + ldout(cct, 20) << "EVP_EncryptInit() failed" << dendl; + return -EIO; + } + + int encrypted_len = 0; + int len; + + auto ret = EVP_EncryptUpdate(ctx, ciphertext + encrypted_len, &len, (const unsigned char *)plaintext.c_str(), plaintext.length()); + if (ret != 1) { + ldout(cct, 20) << "EVP_EncryptUpdate(len=" << plaintext.length() << ") returned " << ret << dendl; + return -EIO; + } + encrypted_len += len; + + ret = EVP_EncryptFinal_ex(ctx, ciphertext + encrypted_len, &len); + if (ret != 1) { + ldout(cct, 20) << "EVP_EncryptFinal_ex() returned " << ret << dendl; + return -EIO; + } + encrypted_len += len; + + EVP_CIPHER_CTX_free(ctx); + + return encrypted_len; + } + + int decrypt_AES256_CTS(ceph::bufferlist& ciphertext, + const unsigned char* key, const unsigned char* iv, + int iv_size, + ceph::bufferptr& plaintext) const { + if (!cipher) { + return -EINVAL; /* initialization error really */ + } + + OSSL_PARAM params[2] = { OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, (char *)"CS3", 0), + OSSL_PARAM_construct_end()}; + + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + return -EIO; + } + + if (!EVP_DecryptInit_ex2(ctx, cipher, key, iv, params)) { + return -EIO; + } + + int len; + auto dest = reinterpret_cast(plaintext.c_str()); + int plaintext_len = 0; + + int max = ciphertext.length(); + auto iter = ciphertext.cbegin(); + while (!iter.end()) { + const char *p; + int chunk_len = iter.get_ptr_and_advance(max, &p); + + if (EVP_DecryptUpdate(ctx, dest + plaintext_len, &len, (const unsigned char *)p, chunk_len) != 1) { + return -EIO; + } + plaintext_len += len; + } + + if (EVP_DecryptFinal_ex(ctx, dest + plaintext_len, &len) != 1) { + return -EIO; + } + plaintext_len += len; + + EVP_CIPHER_CTX_free(ctx); + + return 0; + } + +public: + CryptoAES256KRB5KeyHandler() : CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_16B()) { + } + + using CryptoKeyHandler::encrypt; + using CryptoKeyHandler::decrypt; + + int init(const ceph::bufferptr& s, ostringstream& err) { + cipher = EVP_CIPHER_fetch(NULL, "AES-256-CBC-CTS", NULL); + secret = s; + + int r = calc_kx(secret, 0x2 /* usage */, + 0x55 /* Ki type */, + AES256KRB5_HASH_LEN /* 192 bit */, + ki, + err); + if (r < 0) { + return r; + } + ki_raw = reinterpret_cast(ki.c_str()); /* needed so that we can use ki in const methods */ + + r = calc_kx(secret, 0x2 /* usage */, + 0xAA /* Ke type */, + 32 /* 256 bit */, + ke, + err); + if (r < 0) { + return r; + } + ke_raw = reinterpret_cast(ke.c_str()); /* same reason as with ki */ + + return 0; + } + + int encrypt(CephContext *cct, const ceph::bufferlist& in, + ceph::bufferlist& out, + std::string* /* unused */) const override { + // encrypted (confounder | data) | hash + ceph::bufferptr out_tmp{static_cast( + AES256KRB5_BLOCK_LEN + in.length() + AES256KRB5_HASH_LEN)}; + + /* encrypted (confounder data) */ + char *aes_enc = out_tmp.c_str(); + int aes_enc_len = AES256KRB5_BLOCK_LEN + in.length(); + + /* plaintext confounder */ + bufferptr confounder(AES256KRB5_BLOCK_LEN); + cct->random()->get_bytes(confounder.c_str(), confounder.length()); + + // combine confounder with input data + ceph::bufferlist incopy; + incopy.append(confounder); + incopy.append(in); + + // reinitialize IV each time. It might be unnecessary depending on + // actual implementation but at the interface layer we are obliged + // to deliver IV as non-const. + static_assert(strlen_ct(CEPH_AES_IV) == AES256KRB5_BLOCK_LEN); + unsigned char iv[AES_BLOCK_LEN]; + memset(iv, 0, sizeof(iv)); + + int r = encrypt_AES256_CTS(cct, incopy, iv, sizeof(iv), (unsigned char *)aes_enc, aes_enc_len); + if (r < 0) { + return r; + } + aes_enc_len = r; + + char *hmac = out_tmp.c_str() + AES256KRB5_BLOCK_LEN + in.length(); + + ostringstream err; + r = calc_hmac_sha384((const unsigned char *)aes_enc, aes_enc_len, + ki_raw, ki.length(), + iv, sizeof(iv), + hmac, AES256KRB5_HASH_LEN, err); + if (r < 0) { + return r; + } + + out.append(out_tmp); + return 0; + } + + int decrypt(CephContext *cct, const ceph::bufferlist& in, + ceph::bufferlist& out, + std::string* /* unused */) const override { + + if (in.length() < AES256KRB5_BLOCK_LEN + AES256KRB5_HASH_LEN) { /* minimum size: confounder + hmac */ + return -EINVAL; + } + + // needed because of .c_str() on const. It's a shallow copy. + bufferlist incopy(in); + + ceph::bufferlist indata; + + /* after this: + * indata holds: encrypted (confounder | plaintext) + * incopy holds: hmac hash of indata + */ + incopy.splice(0, in.length() - AES256KRB5_HASH_LEN, &indata); + + auto& inhash = incopy; + + // make a local, modifiable copy of IV. + static_assert(strlen_ct(CEPH_AES_IV) == AES_BLOCK_LEN); + unsigned char iv[AES_BLOCK_LEN]; + memset(iv, 0, sizeof(iv)); + + + /* first need to compare hmac to calculated hmac */ + char hmac[AES256KRB5_HASH_LEN]; + ostringstream err; + int r = calc_hmac_sha384((const unsigned char *)indata.c_str(), indata.length(), + ki_raw, ki.length(), + iv, sizeof(iv), + hmac, sizeof(hmac), err); + if (r < 0) { + return r; + } + + int len = r; + + if ((size_t)len != inhash.length()) { + return -EPERM; + } + + if (memcmp(hmac, inhash.c_str(), sizeof(hmac)) != 0) { + return -EPERM; + } + + /* will consist of confounder | plaintext */ + bufferptr tmp_out(indata.length()); + + r = decrypt_AES256_CTS(indata, + ke_raw, iv, sizeof(iv), + tmp_out); + if (r < 0) { + return r; + } + + auto confounder_len = AES256KRB5_BLOCK_LEN; + + if (tmp_out.length() < confounder_len) { + /* should at least consist of the confounder */ + return -EPERM; + } + + int data_len = tmp_out.length() - AES256KRB5_BLOCK_LEN; + + out.append(tmp_out.c_str() + AES256KRB5_BLOCK_LEN, data_len); + + return 0; + } + +}; + + +// ------------------------------------------------------------ + +int CryptoAES256KRB5::create(CryptoRandom *random, bufferptr& secret) +{ + bufferptr buf(AES256KRB5_KEY_LEN); + random->get_bytes(buf.c_str(), buf.length()); + secret = std::move(buf); + return 0; +} + +int CryptoAES256KRB5::validate_secret(const bufferptr& secret) +{ + if (secret.length() < AES256KRB5_KEY_LEN) { + return -EINVAL; + } + + return 0; +} + +CryptoKeyHandler *CryptoAES256KRB5::get_key_handler(const bufferptr& secret, + string& error) +{ + CryptoAES256KRB5KeyHandler *ckh = new CryptoAES256KRB5KeyHandler; + ostringstream oss; + if (ckh->init(secret, oss) < 0) { + error = oss.str(); + delete ckh; + return NULL; + } + return ckh; +} + + // -- @@ -628,6 +1044,8 @@ CryptoHandler *CryptoHandler::create(int type) return new CryptoNone; case CEPH_CRYPTO_AES: return new CryptoAES; + case CEPH_CRYPTO_AES256KRB5: + return new CryptoAES256KRB5; default: return NULL; } diff --git a/src/common/ceph_context.cc b/src/common/ceph_context.cc index db4cae2db0e..064adecc51d 100644 --- a/src/common/ceph_context.cc +++ b/src/common/ceph_context.cc @@ -721,6 +721,7 @@ CephContext::CephContext(uint32_t module_type_, _heartbeat_map(NULL), _crypto_none(NULL), _crypto_aes(NULL), + _crypto_aes256krb5(NULL), _plugin_registry(NULL), #ifdef CEPH_DEBUG_MUTEX _lockdep_obs(NULL), @@ -783,6 +784,7 @@ CephContext::CephContext(uint32_t module_type_, _crypto_none = CryptoHandler::create(CEPH_CRYPTO_NONE); _crypto_aes = CryptoHandler::create(CEPH_CRYPTO_AES); + _crypto_aes256krb5 = CryptoHandler::create(CEPH_CRYPTO_AES256KRB5); _crypto_random.reset(new CryptoRandom()); lookup_or_create_singleton_object("mempool_obs", false, this); @@ -845,6 +847,7 @@ CephContext::~CephContext() delete _crypto_none; delete _crypto_aes; + delete _crypto_aes256krb5; if (_crypto_inited > 0) { ceph_assert(_crypto_inited == 1); // or else someone explicitly did // init but not shutdown @@ -1050,6 +1053,8 @@ CryptoHandler *CephContext::get_crypto_handler(int type) return _crypto_none; case CEPH_CRYPTO_AES: return _crypto_aes; + case CEPH_CRYPTO_AES256KRB5: + return _crypto_aes256krb5; default: return NULL; } diff --git a/src/common/ceph_context.h b/src/common/ceph_context.h index 5ecacd4b67c..60af31a2c35 100644 --- a/src/common/ceph_context.h +++ b/src/common/ceph_context.h @@ -379,6 +379,7 @@ private: // crypto CryptoHandler *_crypto_none; CryptoHandler *_crypto_aes; + CryptoHandler *_crypto_aes256krb5; std::unique_ptr _crypto_random; // experimental diff --git a/src/include/ceph_fs.h b/src/include/ceph_fs.h index e0e91a0554e..24ad58addf1 100644 --- a/src/include/ceph_fs.h +++ b/src/include/ceph_fs.h @@ -91,8 +91,9 @@ struct ceph_dir_layout { } __attribute__ ((packed)); /* crypto algorithms */ -#define CEPH_CRYPTO_NONE 0x0 -#define CEPH_CRYPTO_AES 0x1 +#define CEPH_CRYPTO_NONE 0x0 +#define CEPH_CRYPTO_AES 0x1 +#define CEPH_CRYPTO_AES256KRB5 0x2 /* AES256-CTS-HMAC384-192 */ #define CEPH_AES_IV "cephsageyudagreg" diff --git a/src/test/crypto.cc b/src/test/crypto.cc index 9d4531b8c88..d09d301e283 100644 --- a/src/test/crypto.cc +++ b/src/test/crypto.cc @@ -60,7 +60,7 @@ TEST(AES, Encrypt) { bufferlist cipher; std::string error; CryptoKeyHandler *kh = h->get_key_handler(secret, error); - int r = kh->encrypt(plaintext, cipher, &error); + int r = kh->encrypt(g_ceph_context, plaintext, cipher, &error); ASSERT_EQ(r, 0); ASSERT_EQ(error, ""); @@ -102,7 +102,7 @@ TEST(AES, EncryptNoBl) { // we need to deduce size first const CryptoKey::out_slice_t probe_slice { 0, nullptr }; - const auto needed = kh->encrypt(plain_slice, probe_slice); + const auto needed = kh->encrypt(g_ceph_context, plain_slice, probe_slice); ASSERT_GE(needed, plain_slice.length); boost::container::small_vector< @@ -110,7 +110,7 @@ TEST(AES, EncryptNoBl) { //unsigned char, sizeof(plaintext) + kh->get_block_size()> buf; unsigned char, sizeof(plaintext) + 16> buf(needed); const CryptoKey::out_slice_t cipher_slice { needed, buf.data() }; - const auto cipher_size = kh->encrypt(plain_slice, cipher_slice); + const auto cipher_size = kh->encrypt(g_ceph_context, plain_slice, cipher_slice); ASSERT_EQ(cipher_size, needed); const unsigned char want_cipher[] = { @@ -152,7 +152,7 @@ TEST(AES, Decrypt) { std::string error; bufferlist plaintext; CryptoKeyHandler *kh = h->get_key_handler(secret, error); - int r = kh->decrypt(cipher, plaintext, &error); + int r = kh->decrypt(g_ceph_context, cipher, plaintext, &error); ASSERT_EQ(r, 0); ASSERT_EQ(error, ""); @@ -194,7 +194,7 @@ TEST(AES, DecryptNoBl) { CryptoKey::in_slice_t cipher_slice { sizeof(ciphertext), ciphertext }; CryptoKey::out_slice_t plain_slice { sizeof(plaintext), plaintext }; - const auto plain_size = kh->decrypt(cipher_slice, plain_slice); + const auto plain_size = kh->decrypt(g_ceph_context, cipher_slice, plain_slice); ASSERT_EQ(plain_size, sizeof(want_plaintext)); @@ -220,7 +220,7 @@ static void aes_loop_cephx() { // we need to deduce size first const CryptoKey::out_slice_t probe_slice { 0, nullptr }; - const auto needed = kh->encrypt(plain_slice, probe_slice); + const auto needed = kh->encrypt(g_ceph_context, plain_slice, probe_slice); ASSERT_GE(needed, plain_slice.length); boost::container::small_vector< @@ -231,7 +231,7 @@ static void aes_loop_cephx() { std::size_t cipher_size; for (std::size_t i = 0; i < 1000000; i++) { const CryptoKey::out_slice_t cipher_slice { needed, buf.data() }; - cipher_size = kh->encrypt(plain_slice, cipher_slice); + cipher_size = kh->encrypt(g_ceph_context, plain_slice, cipher_slice); ASSERT_EQ(cipher_size, needed); } } @@ -265,7 +265,7 @@ static void aes_loop(const std::size_t text_size) { std::string error; CryptoKeyHandler *kh = h->get_key_handler(secret, error); - int r = kh->encrypt(plaintext, cipher, &error); + int r = kh->encrypt(g_ceph_context, plaintext, cipher, &error); ASSERT_EQ(r, 0); ASSERT_EQ(error, ""); @@ -277,7 +277,7 @@ static void aes_loop(const std::size_t text_size) { CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES); std::string error; CryptoKeyHandler *ckh = h->get_key_handler(secret, error); - int r = ckh->decrypt(cipher, plaintext, &error); + int r = ckh->decrypt(g_ceph_context, cipher, plaintext, &error); ASSERT_EQ(r, 0); ASSERT_EQ(error, ""); @@ -343,3 +343,94 @@ TEST(AES, LoopKey_29) { TEST(AES, LoopKey_32) { aes_loopkey(32); } + +static void dump_buf(string title, const unsigned char *buf, int len) +{ + std::cout << title << std::endl; + for (int i = 0; i < len; ++i) { + if (i != 0 && i % 16 == 0) { + cout << std::endl; + } + std::cout << std::format("{:02x} ", buf[i]); + } + std::cout << std::endl << std::endl; +} + + +TEST(AES256KRB5, Encrypt) { + CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); + unsigned char secret_s[] = { + 0x6D, 0x40, 0x4D, 0x37, 0xFA, 0xF7, 0x9F, 0x9D, 0xF0, 0xD3, 0x35, 0x68, 0xD3, 0x20, 0x66, 0x98, + 0x00, 0xEB, 0x48, 0x36, 0x47, 0x2E, 0xA8, 0xA0, 0x26, 0xD1, 0x6B, 0x71, 0x82, 0x46, 0x0C, 0x52 }; + + bufferptr secret((char *)secret_s, sizeof(secret_s)); + + unsigned char plaintext_s[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + + bufferlist plaintext; + plaintext.append((char *)plaintext_s, sizeof(plaintext_s)); + + bufferlist cipher; + std::string error; + CryptoKeyHandler *kh = h->get_key_handler(secret, error); + int r = kh->encrypt(g_ceph_context, plaintext, cipher, &error); + ASSERT_EQ(r, 0); + ASSERT_EQ(error, ""); + + unsigned char want_cipher[] = { + 0x4E, 0xD7, 0xB3, 0x7C, 0x2B, 0xCA, 0xC8, 0xF7, 0x4F, 0x23, 0xC1, 0xCF, 0x07, 0xE6, 0x2B, 0xC7, + 0xB7, 0x5F, 0xB3, 0xF6, 0x37, 0xB9, 0xF5, 0x59, 0xC7, 0xF6, 0x64, 0xF6, 0x9E, 0xAB, 0x7B, 0x60, + 0x92, 0x23, 0x75, 0x26, 0xEA, 0x0D, 0x1F, 0x61, 0xCB, 0x20, 0xD6, 0x9D, 0x10, 0xF2 + }; + char cipher_s[sizeof(want_cipher)]; + + ASSERT_EQ(sizeof(cipher_s), cipher.length()); + cipher.cbegin().copy(sizeof(cipher_s), &cipher_s[0]); + + dump_buf("ENCRYPTED:", (unsigned char *)cipher.c_str(), cipher.length()); + dump_buf("EXPECTED:", want_cipher, sizeof(want_cipher)); + + int err; + err = memcmp(cipher_s, want_cipher, sizeof(want_cipher)); + ASSERT_EQ(0, err); + + delete kh; +} + +TEST(AES256KRB5, Decrypt) { + CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5); + unsigned char secret_s[] = { + 0x6D, 0x40, 0x4D, 0x37, 0xFA, 0xF7, 0x9F, 0x9D, 0xF0, 0xD3, 0x35, 0x68, 0xD3, 0x20, 0x66, 0x98, + 0x00, 0xEB, 0x48, 0x36, 0x47, 0x2E, 0xA8, 0xA0, 0x26, 0xD1, 0x6B, 0x71, 0x82, 0x46, 0x0C, 0x52 }; + + bufferptr secret((const char *)secret_s, sizeof(secret_s)); + + unsigned char cipher_s[] = { + 0x4E, 0xD7, 0xB3, 0x7C, 0x2B, 0xCA, 0xC8, 0xF7, 0x4F, 0x23, 0xC1, 0xCF, 0x07, 0xE6, 0x2B, 0xC7, + 0xB7, 0x5F, 0xB3, 0xF6, 0x37, 0xB9, 0xF5, 0x59, 0xC7, 0xF6, 0x64, 0xF6, 0x9E, 0xAB, 0x7B, 0x60, + 0x92, 0x23, 0x75, 0x26, 0xEA, 0x0D, 0x1F, 0x61, 0xCB, 0x20, 0xD6, 0x9D, 0x10, 0xF2 + }; + + bufferlist cipher; + cipher.append((char *)cipher_s, sizeof(cipher_s)); + + unsigned char want_plaintext[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }; + char plaintext_s[sizeof(want_plaintext)]; + + std::string error; + bufferlist plaintext; + CryptoKeyHandler *kh = h->get_key_handler(secret, error); + int r = kh->decrypt(g_ceph_context, cipher, plaintext, &error); + ASSERT_EQ(r, 0); + ASSERT_EQ(error, ""); + + ASSERT_EQ(sizeof(plaintext_s), plaintext.length()); + plaintext.cbegin().copy(sizeof(plaintext_s), &plaintext_s[0]); + + int err; + err = memcmp(plaintext_s, want_plaintext, sizeof(want_plaintext)); + ASSERT_EQ(0, err); + + delete kh; +} + -- 2.39.5