#include <fcntl.h>
#include <openssl/aes.h>
+#include <openssl/core_names.h>
#include "Crypto.h"
#include "common/debug.h"
#include <errno.h>
+#include <boost/endian/conversion.hpp>
+
+#define dout_subsys ceph_subsys_auth
+
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
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
}
+// ---------------------------------------------------
+
+/*
+ * 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<const char *>(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<const unsigned char *>(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<uint32_t>(_usage)),
+ type(_type),
+ k(native_to_big<uint32_t>(_k * 8)) {}
+ } __attribute__((packed)) data(usage, type, k);
+
+ ceph::bufferptr bp(reinterpret_cast<char *>(&data), sizeof(data));
+
+ ceph::bufferptr sha384(SHA384_LEN);
+ int r = calc_hmac_sha384((const unsigned char *)bp.c_str(), bp.length(),
+ reinterpret_cast<const unsigned char *>(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<unsigned char *>(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<const unsigned char *>(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<const unsigned char *>(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<unsigned>(
+ 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;
+}
+
+
// --
return new CryptoNone;
case CEPH_CRYPTO_AES:
return new CryptoAES;
+ case CEPH_CRYPTO_AES256KRB5:
+ return new CryptoAES256KRB5;
default:
return NULL;
}
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, "");
// 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<
//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[] = {
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, "");
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));
// 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<
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);
}
}
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, "");
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, "");
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;
+}
+