mirror of
https://github.com/ceph/ceph
synced 2024-12-19 01:46:00 +00:00
Merge PR #21540 into wip-sage-testing-20180521.120735
* refs/pull/21540/head: tests/crypto: print compile warning when NSS is unavailable. tests/crypto: add tests for the no-bl encrypt/decrypt, part 2. tests/crypto: add tests for the no-bl encrypt/decrypt. auth: use OpenSSL for CryptoAESKeyHandler's no-bl encrypt/decrypt. auth: extend CryptoKey with no-bl encrypt/decrypt. auth: CryptoAESKeyHandler switches from NSS to OpenSSL. auth: the outbuf of AES should be multiple of block size auth: cache the PK11Context for CryptoAESKeyHandler
This commit is contained in:
commit
ea205e1cbb
@ -341,9 +341,12 @@ CHECK_SYMBOL_EXISTS(curl_multi_wait curl/curl.h HAVE_CURL_MULTI_WAIT)
|
||||
|
||||
find_package(NSS REQUIRED)
|
||||
find_package(NSPR REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
# TODO: use NSS only for validation of the OpenSSL-based implementations
|
||||
set(USE_NSS 1)
|
||||
set(CRYPTO_LIBS ${NSS_LIBRARIES} ${NSPR_LIBRARIES})
|
||||
set(SSL_LIBRARIES ${NSS_LIBRARIES})
|
||||
set(USE_OPENSSL 1)
|
||||
set(CRYPTO_LIBS ${NSS_LIBRARIES} ${NSPR_LIBRARIES} ${OPENSSL_LIBRARIES})
|
||||
set(SSL_LIBRARIES ${NSS_LIBRARIES} ${OPENSSL_LIBRARIES})
|
||||
|
||||
option(WITH_XIO "Enable XIO messaging" OFF)
|
||||
if(WITH_XIO)
|
||||
|
@ -11,12 +11,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <limits>
|
||||
|
||||
#include "Crypto.h"
|
||||
#ifdef USE_NSS
|
||||
# include <nspr.h>
|
||||
# include <nss.h>
|
||||
# include <pk11pub.h>
|
||||
#ifdef USE_OPENSSL
|
||||
# include <openssl/aes.h>
|
||||
#endif
|
||||
|
||||
#include "include/assert.h"
|
||||
@ -75,10 +76,69 @@ void CryptoRandom::get_bytes(char *buf, int len)
|
||||
#endif
|
||||
|
||||
|
||||
// ---------------------------------------------------
|
||||
// fallback implementation of the bufferlist-free
|
||||
// interface.
|
||||
|
||||
std::size_t CryptoKeyHandler::encrypt(
|
||||
const CryptoKeyHandler::in_slice_t& in,
|
||||
const CryptoKeyHandler::out_slice_t& out) const
|
||||
{
|
||||
ceph::bufferptr inptr(reinterpret_cast<const char*>(in.buf), in.length);
|
||||
ceph::bufferlist plaintext;
|
||||
plaintext.append(std::move(inptr));
|
||||
|
||||
ceph::bufferlist ciphertext;
|
||||
std::string error;
|
||||
const int ret = encrypt(plaintext, ciphertext, &error);
|
||||
if (ret != 0 || !error.empty()) {
|
||||
throw std::runtime_error(std::move(error));
|
||||
}
|
||||
|
||||
// we need to specify the template parameter explicitly as ::length()
|
||||
// returns unsigned int, not size_t.
|
||||
const auto todo_len = \
|
||||
std::min<std::size_t>(ciphertext.length(), out.max_length);
|
||||
memcpy(out.buf, ciphertext.c_str(), todo_len);
|
||||
|
||||
return todo_len;
|
||||
}
|
||||
|
||||
std::size_t CryptoKeyHandler::decrypt(
|
||||
const CryptoKeyHandler::in_slice_t& in,
|
||||
const CryptoKeyHandler::out_slice_t& out) const
|
||||
{
|
||||
ceph::bufferptr inptr(reinterpret_cast<const char*>(in.buf), in.length);
|
||||
ceph::bufferlist ciphertext;
|
||||
ciphertext.append(std::move(inptr));
|
||||
|
||||
ceph::bufferlist plaintext;
|
||||
std::string error;
|
||||
const int ret = decrypt(ciphertext, plaintext, &error);
|
||||
if (ret != 0 || !error.empty()) {
|
||||
throw std::runtime_error(std::move(error));
|
||||
}
|
||||
|
||||
// we need to specify the template parameter explicitly as ::length()
|
||||
// returns unsigned int, not size_t.
|
||||
const auto todo_len = \
|
||||
std::min<std::size_t>(plaintext.length(), out.max_length);
|
||||
memcpy(out.buf, plaintext.c_str(), todo_len);
|
||||
|
||||
return todo_len;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
class CryptoNoneKeyHandler : public CryptoKeyHandler {
|
||||
public:
|
||||
CryptoNoneKeyHandler()
|
||||
: CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_0B()) {
|
||||
}
|
||||
|
||||
using CryptoKeyHandler::encrypt;
|
||||
using CryptoKeyHandler::decrypt;
|
||||
|
||||
int encrypt(const bufferlist& in,
|
||||
bufferlist& out, std::string *error) const override {
|
||||
out = in;
|
||||
@ -125,128 +185,181 @@ public:
|
||||
CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override;
|
||||
};
|
||||
|
||||
#ifdef USE_NSS
|
||||
#ifdef USE_OPENSSL
|
||||
// when we say AES, we mean AES-128
|
||||
# define AES_KEY_LEN 16
|
||||
# define AES_BLOCK_LEN 16
|
||||
|
||||
static int nss_aes_operation(CK_ATTRIBUTE_TYPE op,
|
||||
CK_MECHANISM_TYPE mechanism,
|
||||
PK11SymKey *key,
|
||||
SECItem *param,
|
||||
const bufferlist& in, bufferlist& out,
|
||||
std::string *error)
|
||||
{
|
||||
// sample source said this has to be at least size of input + 8,
|
||||
// but i see 15 still fail with SEC_ERROR_OUTPUT_LEN
|
||||
bufferptr out_tmp(in.length()+16);
|
||||
bufferlist incopy;
|
||||
|
||||
SECStatus ret;
|
||||
int written;
|
||||
unsigned char *in_buf;
|
||||
|
||||
PK11Context *ectx;
|
||||
ectx = PK11_CreateContextBySymKey(mechanism, op, key, param);
|
||||
assert(ectx);
|
||||
|
||||
incopy = in; // it's a shallow copy!
|
||||
in_buf = (unsigned char*)incopy.c_str();
|
||||
ret = PK11_CipherOp(ectx,
|
||||
(unsigned char*)out_tmp.c_str(), &written, out_tmp.length(),
|
||||
in_buf, in.length());
|
||||
if (ret != SECSuccess) {
|
||||
PK11_DestroyContext(ectx, PR_TRUE);
|
||||
if (error) {
|
||||
ostringstream oss;
|
||||
oss << "NSS AES failed: " << PR_GetError();
|
||||
*error = oss.str();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int written2;
|
||||
ret = PK11_DigestFinal(ectx,
|
||||
(unsigned char*)out_tmp.c_str()+written, &written2,
|
||||
out_tmp.length()-written);
|
||||
PK11_DestroyContext(ectx, PR_TRUE);
|
||||
if (ret != SECSuccess) {
|
||||
if (error) {
|
||||
ostringstream oss;
|
||||
oss << "NSS AES final round failed: " << PR_GetError();
|
||||
*error = oss.str();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
out_tmp.set_length(written + written2);
|
||||
out.append(out_tmp);
|
||||
return 0;
|
||||
}
|
||||
static constexpr const std::size_t AES_KEY_LEN{16};
|
||||
static constexpr const std::size_t AES_BLOCK_LEN{16};
|
||||
|
||||
class CryptoAESKeyHandler : public CryptoKeyHandler {
|
||||
CK_MECHANISM_TYPE mechanism;
|
||||
PK11SlotInfo *slot;
|
||||
PK11SymKey *key;
|
||||
SECItem *param;
|
||||
AES_KEY enc_key;
|
||||
AES_KEY dec_key;
|
||||
|
||||
public:
|
||||
CryptoAESKeyHandler()
|
||||
: mechanism(CKM_AES_CBC_PAD),
|
||||
slot(NULL),
|
||||
key(NULL),
|
||||
param(NULL) {}
|
||||
~CryptoAESKeyHandler() override {
|
||||
SECITEM_FreeItem(param, PR_TRUE);
|
||||
if (key)
|
||||
PK11_FreeSymKey(key);
|
||||
if (slot)
|
||||
PK11_FreeSlot(slot);
|
||||
: CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_16B()) {
|
||||
}
|
||||
|
||||
int init(const bufferptr& s, ostringstream& err) {
|
||||
secret = s;
|
||||
|
||||
slot = PK11_GetBestSlot(mechanism, NULL);
|
||||
if (!slot) {
|
||||
err << "cannot find NSS slot to use: " << PR_GetError();
|
||||
const int enc_key_ret = \
|
||||
AES_set_encrypt_key((const unsigned char*)secret.c_str(),
|
||||
AES_KEY_LEN * CHAR_BIT, &enc_key);
|
||||
if (enc_key_ret != 0) {
|
||||
err << "cannot set OpenSSL encrypt key for AES: " << enc_key_ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
SECItem keyItem;
|
||||
keyItem.type = siBuffer;
|
||||
keyItem.data = (unsigned char*)secret.c_str();
|
||||
keyItem.len = secret.length();
|
||||
key = PK11_ImportSymKey(slot, mechanism, PK11_OriginUnwrap, CKA_ENCRYPT,
|
||||
&keyItem, NULL);
|
||||
if (!key) {
|
||||
err << "cannot convert AES key for NSS: " << PR_GetError();
|
||||
return -1;
|
||||
}
|
||||
|
||||
SECItem ivItem;
|
||||
ivItem.type = siBuffer;
|
||||
// losing constness due to SECItem.data; IV should never be
|
||||
// modified, regardless
|
||||
ivItem.data = (unsigned char*)CEPH_AES_IV;
|
||||
ivItem.len = sizeof(CEPH_AES_IV);
|
||||
|
||||
param = PK11_ParamFromIV(mechanism, &ivItem);
|
||||
if (!param) {
|
||||
err << "cannot set NSS IV param: " << PR_GetError();
|
||||
const int dec_key_ret = \
|
||||
AES_set_decrypt_key((const unsigned char*)secret.c_str(),
|
||||
AES_KEY_LEN * CHAR_BIT, &dec_key);
|
||||
if (dec_key_ret != 0) {
|
||||
err << "cannot set OpenSSL decrypt key for AES: " << dec_key_ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encrypt(const bufferlist& in,
|
||||
bufferlist& out, std::string *error) const override {
|
||||
return nss_aes_operation(CKA_ENCRYPT, mechanism, key, param, in, out, error);
|
||||
int encrypt(const ceph::bufferlist& in,
|
||||
ceph::bufferlist& out,
|
||||
std::string* /* unused */) const override {
|
||||
// we need to take into account the PKCS#7 padding. There *always* will
|
||||
// be at least one byte of padding. This stays even to input aligned to
|
||||
// AES_BLOCK_LEN. Otherwise we would face ambiguities during decryption.
|
||||
// To exemplify:
|
||||
// 16 + p2align(10, 16) -> 16
|
||||
// 16 + p2align(16, 16) -> 32 including 16 bytes for padding.
|
||||
ceph::bufferptr out_tmp{static_cast<unsigned>(
|
||||
AES_BLOCK_LEN + p2align(in.length(), AES_BLOCK_LEN))};
|
||||
|
||||
// let's pad the data
|
||||
std::uint8_t pad_len = out_tmp.length() - in.length();
|
||||
ceph::bufferptr pad_buf{pad_len};
|
||||
memset(pad_buf.c_str(), pad_len, pad_len);
|
||||
|
||||
// form contiguous buffer for block cipher. The ctor copies shallowly.
|
||||
ceph::bufferlist incopy(in);
|
||||
incopy.append(std::move(pad_buf));
|
||||
const auto in_buf = reinterpret_cast<unsigned char*>(incopy.c_str());
|
||||
|
||||
// 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) == AES_BLOCK_LEN);
|
||||
unsigned char iv[AES_BLOCK_LEN];
|
||||
memcpy(iv, CEPH_AES_IV, AES_BLOCK_LEN);
|
||||
|
||||
// we aren't using EVP because of performance concerns. Profiling
|
||||
// shows the cost is quite high. Endianness might be an issue.
|
||||
// However, as they would affect Cephx, any fallout should pop up
|
||||
// rather early, hopefully.
|
||||
AES_cbc_encrypt(in_buf, reinterpret_cast<unsigned char*>(out_tmp.c_str()),
|
||||
out_tmp.length(), &enc_key, iv, AES_ENCRYPT);
|
||||
|
||||
out.append(out_tmp);
|
||||
return 0;
|
||||
}
|
||||
int decrypt(const bufferlist& in,
|
||||
bufferlist& out, std::string *error) const override {
|
||||
return nss_aes_operation(CKA_DECRYPT, mechanism, key, param, in, out, error);
|
||||
|
||||
int decrypt(const ceph::bufferlist& in,
|
||||
ceph::bufferlist& out,
|
||||
std::string* /* unused */) const override {
|
||||
// PKCS#7 padding enlarges even empty plain-text to take 16 bytes.
|
||||
if (in.length() < AES_BLOCK_LEN || in.length() % AES_BLOCK_LEN) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// needed because of .c_str() on const. It's a shallow copy.
|
||||
ceph::bufferlist incopy(in);
|
||||
const auto in_buf = reinterpret_cast<unsigned char*>(incopy.c_str());
|
||||
|
||||
// make a local, modifiable copy of IV.
|
||||
static_assert(strlen_ct(CEPH_AES_IV) == AES_BLOCK_LEN);
|
||||
unsigned char iv[AES_BLOCK_LEN];
|
||||
memcpy(iv, CEPH_AES_IV, AES_BLOCK_LEN);
|
||||
|
||||
ceph::bufferptr out_tmp{in.length()};
|
||||
AES_cbc_encrypt(in_buf, reinterpret_cast<unsigned char*>(out_tmp.c_str()),
|
||||
in.length(), &dec_key, iv, AES_DECRYPT);
|
||||
|
||||
// BE CAREFUL: we cannot expose any single bit of information about
|
||||
// the cause of failure. Otherwise we'll face padding oracle attack.
|
||||
// See: https://en.wikipedia.org/wiki/Padding_oracle_attack.
|
||||
const auto pad_len = \
|
||||
std::min<std::uint8_t>(out_tmp[in.length() - 1], AES_BLOCK_LEN);
|
||||
out_tmp.set_length(in.length() - pad_len);
|
||||
out.append(std::move(out_tmp));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t encrypt(const in_slice_t& in,
|
||||
const out_slice_t& out) const override {
|
||||
if (out.buf == nullptr) {
|
||||
// 16 + p2align(10, 16) -> 16
|
||||
// 16 + p2align(16, 16) -> 32
|
||||
const std::size_t needed = \
|
||||
AES_BLOCK_LEN + p2align(in.length, AES_BLOCK_LEN);
|
||||
return needed;
|
||||
}
|
||||
|
||||
// how many bytes of in.buf hang outside the alignment boundary and how
|
||||
// much padding we need.
|
||||
// length = 23 -> tail_len = 7, pad_len = 9
|
||||
// length = 32 -> tail_len = 0, pad_len = 16
|
||||
const std::uint8_t tail_len = in.length % AES_BLOCK_LEN;
|
||||
const std::uint8_t pad_len = AES_BLOCK_LEN - tail_len;
|
||||
static_assert(std::numeric_limits<std::uint8_t>::max() > AES_BLOCK_LEN);
|
||||
|
||||
std::array<unsigned char, AES_BLOCK_LEN> last_block;
|
||||
memcpy(last_block.data(), in.buf + in.length - tail_len, tail_len);
|
||||
memset(last_block.data() + tail_len, pad_len, pad_len);
|
||||
|
||||
// need a local copy because AES_cbc_encrypt takes `iv` as non-const.
|
||||
// Useful because it allows us to encrypt in two steps: main + tail.
|
||||
static_assert(strlen_ct(CEPH_AES_IV) == AES_BLOCK_LEN);
|
||||
std::array<unsigned char, AES_BLOCK_LEN> iv;
|
||||
memcpy(iv.data(), CEPH_AES_IV, AES_BLOCK_LEN);
|
||||
|
||||
const std::size_t main_encrypt_size = \
|
||||
std::min(in.length - tail_len, out.max_length);
|
||||
AES_cbc_encrypt(in.buf, out.buf, main_encrypt_size, &enc_key, iv.data(),
|
||||
AES_ENCRYPT);
|
||||
|
||||
const std::size_t tail_encrypt_size = \
|
||||
std::min(AES_BLOCK_LEN, out.max_length - main_encrypt_size);
|
||||
AES_cbc_encrypt(last_block.data(), out.buf + main_encrypt_size,
|
||||
tail_encrypt_size, &enc_key, iv.data(), AES_ENCRYPT);
|
||||
|
||||
return main_encrypt_size + tail_encrypt_size;
|
||||
}
|
||||
|
||||
std::size_t decrypt(const in_slice_t& in,
|
||||
const out_slice_t& out) const override {
|
||||
if (in.length % AES_BLOCK_LEN != 0 || in.length < AES_BLOCK_LEN) {
|
||||
throw std::runtime_error("input not aligned to AES_BLOCK_LEN");
|
||||
} else if (out.buf == nullptr) {
|
||||
// essentially it would be possible to decrypt into a buffer that
|
||||
// doesn't include space for any PKCS#7 padding. We don't do that
|
||||
// for the sake of performance and simplicity.
|
||||
return in.length;
|
||||
} else if (out.max_length < in.length) {
|
||||
throw std::runtime_error("output buffer too small");
|
||||
}
|
||||
|
||||
static_assert(strlen_ct(CEPH_AES_IV) == AES_BLOCK_LEN);
|
||||
std::array<unsigned char, AES_BLOCK_LEN> iv;
|
||||
memcpy(iv.data(), CEPH_AES_IV, AES_BLOCK_LEN);
|
||||
|
||||
AES_cbc_encrypt(in.buf, out.buf, in.length, &dec_key, iv.data(),
|
||||
AES_DECRYPT);
|
||||
|
||||
// NOTE: we aren't handling partial decrypt. PKCS#7 padding must be
|
||||
// at the end. If it's malformed, don't say a word to avoid risk of
|
||||
// having an oracle. All we need to ensure is valid buffer boundary.
|
||||
const auto pad_len = \
|
||||
std::min<std::uint8_t>(out.buf[in.length - 1], AES_BLOCK_LEN);
|
||||
return in.length - pad_len;
|
||||
}
|
||||
};
|
||||
|
||||
@ -268,7 +381,7 @@ int CryptoAES::create(CryptoRandom *random, bufferptr& secret)
|
||||
|
||||
int CryptoAES::validate_secret(const bufferptr& secret)
|
||||
{
|
||||
if (secret.length() < (size_t)AES_KEY_LEN) {
|
||||
if (secret.length() < AES_KEY_LEN) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,29 @@ class CryptoRandom {
|
||||
*/
|
||||
class CryptoKeyHandler {
|
||||
public:
|
||||
bufferptr secret;
|
||||
// The maximum size of a single block for all descendants of the class.
|
||||
static constexpr std::size_t MAX_BLOCK_SIZE {16};
|
||||
|
||||
// A descendant pick-ups one from these and passes it to the ctor template.
|
||||
typedef std::integral_constant<std::size_t, 0> BLOCK_SIZE_0B;
|
||||
typedef std::integral_constant<std::size_t, 16> BLOCK_SIZE_16B;
|
||||
|
||||
struct in_slice_t {
|
||||
const std::size_t length;
|
||||
const unsigned char* const buf;
|
||||
};
|
||||
|
||||
struct out_slice_t {
|
||||
const std::size_t max_length;
|
||||
unsigned char* const buf;
|
||||
};
|
||||
|
||||
ceph::bufferptr secret;
|
||||
|
||||
template <class BlockSizeT>
|
||||
CryptoKeyHandler(BlockSizeT) {
|
||||
static_assert(BlockSizeT::value <= MAX_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
virtual ~CryptoKeyHandler() {}
|
||||
|
||||
@ -52,6 +74,13 @@ public:
|
||||
bufferlist& out, std::string *error) const = 0;
|
||||
virtual int decrypt(const bufferlist& in,
|
||||
bufferlist& out, std::string *error) const = 0;
|
||||
|
||||
// TODO: provide nullptr in the out::buf to get/estimate size requirements?
|
||||
// Or maybe dedicated methods?
|
||||
virtual std::size_t encrypt(const in_slice_t& in,
|
||||
const out_slice_t& out) const;
|
||||
virtual std::size_t decrypt(const in_slice_t& in,
|
||||
const out_slice_t& out) const;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -127,6 +156,24 @@ public:
|
||||
return ckh->decrypt(in, out, error);
|
||||
}
|
||||
|
||||
using in_slice_t = CryptoKeyHandler::in_slice_t;
|
||||
using out_slice_t = CryptoKeyHandler::out_slice_t;
|
||||
|
||||
std::size_t encrypt(CephContext*, const in_slice_t& in,
|
||||
const out_slice_t& out) {
|
||||
assert(ckh);
|
||||
return ckh->encrypt(in, out);
|
||||
}
|
||||
std::size_t decrypt(CephContext*, const in_slice_t& in,
|
||||
const out_slice_t& out) {
|
||||
assert(ckh);
|
||||
return ckh->encrypt(in, out);
|
||||
}
|
||||
|
||||
static constexpr std::size_t get_max_outbuf_size(std::size_t want_size) {
|
||||
return want_size + CryptoKeyHandler::MAX_BLOCK_SIZE;
|
||||
}
|
||||
|
||||
void to_str(std::string& s) const;
|
||||
};
|
||||
WRITE_CLASS_ENCODER(CryptoKey)
|
||||
|
@ -45,17 +45,26 @@ int CephxSessionHandler::_calc_signature(Message *m, uint64_t *psig)
|
||||
mswab<uint32_t>(header.crc), mswab<uint32_t>(footer.front_crc),
|
||||
mswab<uint32_t>(footer.middle_crc), mswab<uint32_t>(footer.data_crc)
|
||||
};
|
||||
bufferlist bl_plaintext;
|
||||
bl_plaintext.append(buffer::create_static(sizeof(sigblock), (char*)&sigblock));
|
||||
|
||||
bufferlist bl_ciphertext;
|
||||
if (key.encrypt(cct, bl_plaintext, bl_ciphertext, NULL) < 0) {
|
||||
char exp_buf[CryptoKey::get_max_outbuf_size(sizeof(sigblock))];
|
||||
|
||||
try {
|
||||
const CryptoKey::in_slice_t in {
|
||||
sizeof(sigblock),
|
||||
reinterpret_cast<const unsigned char*>(&sigblock)
|
||||
};
|
||||
const CryptoKey::out_slice_t out {
|
||||
sizeof(exp_buf),
|
||||
reinterpret_cast<unsigned char*>(&exp_buf)
|
||||
};
|
||||
|
||||
key.encrypt(cct, in, out);
|
||||
} catch (std::exception& e) {
|
||||
lderr(cct) << __func__ << " failed to encrypt signature block" << dendl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto ci = bl_ciphertext.cbegin();
|
||||
decode(*psig, ci);
|
||||
*psig = *reinterpret_cast<__le64*>(exp_buf);
|
||||
|
||||
ldout(cct, 10) << __func__ << " seq " << m->get_seq()
|
||||
<< " front_crc_ = " << footer.front_crc
|
||||
|
@ -126,6 +126,9 @@
|
||||
/* Define if using NSS. */
|
||||
#cmakedefine USE_NSS
|
||||
|
||||
/* Define if using OpenSSL. */
|
||||
#cmakedefine USE_OPENSSL
|
||||
|
||||
/* Accelio conditional compilation */
|
||||
#cmakedefine HAVE_XIO
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "include/types.h"
|
||||
#include "auth/Crypto.h"
|
||||
@ -9,6 +11,12 @@
|
||||
#include "common/ceph_context.h"
|
||||
#include "global/global_context.h"
|
||||
|
||||
#ifdef USE_NSS
|
||||
# include <nspr.h>
|
||||
# include <nss.h>
|
||||
# include <pk11pub.h>
|
||||
#endif // USE_NSS
|
||||
|
||||
class CryptoEnvironment: public ::testing::Environment {
|
||||
public:
|
||||
void SetUp() override {
|
||||
@ -16,6 +24,185 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef USE_NSS
|
||||
// when we say AES, we mean AES-128
|
||||
# define AES_KEY_LEN 16
|
||||
# define AES_BLOCK_LEN 16
|
||||
|
||||
static int nss_aes_operation(CK_ATTRIBUTE_TYPE op,
|
||||
CK_MECHANISM_TYPE mechanism,
|
||||
PK11SymKey *key,
|
||||
SECItem *param,
|
||||
const bufferlist& in, bufferlist& out,
|
||||
std::string *error)
|
||||
{
|
||||
// sample source said this has to be at least size of input + 8,
|
||||
// but i see 15 still fail with SEC_ERROR_OUTPUT_LEN
|
||||
bufferptr out_tmp(in.length()+16);
|
||||
bufferlist incopy;
|
||||
|
||||
SECStatus ret;
|
||||
int written;
|
||||
unsigned char *in_buf;
|
||||
|
||||
PK11Context *ectx;
|
||||
ectx = PK11_CreateContextBySymKey(mechanism, op, key, param);
|
||||
assert(ectx);
|
||||
|
||||
incopy = in; // it's a shallow copy!
|
||||
in_buf = (unsigned char*)incopy.c_str();
|
||||
ret = PK11_CipherOp(ectx,
|
||||
(unsigned char*)out_tmp.c_str(), &written, out_tmp.length(),
|
||||
in_buf, in.length());
|
||||
if (ret != SECSuccess) {
|
||||
PK11_DestroyContext(ectx, PR_TRUE);
|
||||
if (error) {
|
||||
ostringstream oss;
|
||||
oss << "NSS AES failed: " << PR_GetError();
|
||||
*error = oss.str();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int written2;
|
||||
ret = PK11_DigestFinal(ectx,
|
||||
(unsigned char*)out_tmp.c_str()+written, &written2,
|
||||
out_tmp.length()-written);
|
||||
PK11_DestroyContext(ectx, PR_TRUE);
|
||||
if (ret != SECSuccess) {
|
||||
if (error) {
|
||||
ostringstream oss;
|
||||
oss << "NSS AES final round failed: " << PR_GetError();
|
||||
*error = oss.str();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
out_tmp.set_length(written + written2);
|
||||
out.append(out_tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
class LegacyCryptoAESKeyHandler : public CryptoKeyHandler {
|
||||
CK_MECHANISM_TYPE mechanism;
|
||||
PK11SlotInfo *slot;
|
||||
PK11SymKey *key;
|
||||
SECItem *param;
|
||||
|
||||
public:
|
||||
LegacyCryptoAESKeyHandler()
|
||||
: CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_16B()),
|
||||
mechanism(CKM_AES_CBC_PAD),
|
||||
slot(NULL),
|
||||
key(NULL),
|
||||
param(NULL) {}
|
||||
~LegacyCryptoAESKeyHandler() override {
|
||||
SECITEM_FreeItem(param, PR_TRUE);
|
||||
if (key)
|
||||
PK11_FreeSymKey(key);
|
||||
if (slot)
|
||||
PK11_FreeSlot(slot);
|
||||
}
|
||||
|
||||
int init(const bufferptr& s, string& err) {
|
||||
ostringstream oss;
|
||||
const int ret = init(s, oss);
|
||||
err = oss.str();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int init(const bufferptr& s, ostringstream& err) {
|
||||
secret = s;
|
||||
|
||||
slot = PK11_GetBestSlot(mechanism, NULL);
|
||||
if (!slot) {
|
||||
err << "cannot find NSS slot to use: " << PR_GetError();
|
||||
return -1;
|
||||
}
|
||||
|
||||
SECItem keyItem;
|
||||
keyItem.type = siBuffer;
|
||||
keyItem.data = (unsigned char*)secret.c_str();
|
||||
keyItem.len = secret.length();
|
||||
key = PK11_ImportSymKey(slot, mechanism, PK11_OriginUnwrap, CKA_ENCRYPT,
|
||||
&keyItem, NULL);
|
||||
if (!key) {
|
||||
err << "cannot convert AES key for NSS: " << PR_GetError();
|
||||
return -1;
|
||||
}
|
||||
|
||||
SECItem ivItem;
|
||||
ivItem.type = siBuffer;
|
||||
// losing constness due to SECItem.data; IV should never be
|
||||
// modified, regardless
|
||||
ivItem.data = (unsigned char*)CEPH_AES_IV;
|
||||
ivItem.len = sizeof(CEPH_AES_IV);
|
||||
|
||||
param = PK11_ParamFromIV(mechanism, &ivItem);
|
||||
if (!param) {
|
||||
err << "cannot set NSS IV param: " << PR_GetError();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
using CryptoKeyHandler::encrypt;
|
||||
using CryptoKeyHandler::decrypt;
|
||||
|
||||
int encrypt(const bufferlist& in,
|
||||
bufferlist& out, std::string *error) const override {
|
||||
return nss_aes_operation(CKA_ENCRYPT, mechanism, key, param, in, out, error);
|
||||
}
|
||||
int decrypt(const bufferlist& in,
|
||||
bufferlist& out, std::string *error) const override {
|
||||
return nss_aes_operation(CKA_DECRYPT, mechanism, key, param, in, out, error);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(AES, ValidateLegacy) {
|
||||
CryptoHandler* const newh = \
|
||||
g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES);
|
||||
|
||||
const char secret_s[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
};
|
||||
ceph::bufferptr secret(secret_s, sizeof(secret_s));
|
||||
|
||||
std::string error;
|
||||
std::unique_ptr<CryptoKeyHandler> newkh(
|
||||
newh->get_key_handler(secret, error));
|
||||
ASSERT_TRUE(error.empty());
|
||||
|
||||
LegacyCryptoAESKeyHandler oldkh;
|
||||
oldkh.init(secret, error);
|
||||
ASSERT_TRUE(error.empty());
|
||||
|
||||
unsigned char plaintext_s[] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
|
||||
};
|
||||
ceph::bufferlist plaintext;
|
||||
plaintext.append((char *)plaintext_s, sizeof(plaintext_s));
|
||||
|
||||
ceph::bufferlist ciphertext;
|
||||
int r = newkh->encrypt(plaintext, ciphertext, &error);
|
||||
ASSERT_EQ(r, 0);
|
||||
ASSERT_EQ(error, "");
|
||||
|
||||
ceph::bufferlist restored_plaintext;
|
||||
r = oldkh.decrypt(ciphertext, restored_plaintext, &error);
|
||||
ASSERT_EQ(r, 0);
|
||||
ASSERT_TRUE(error.empty());
|
||||
|
||||
ASSERT_EQ(plaintext, restored_plaintext);
|
||||
}
|
||||
|
||||
#else
|
||||
# warning "NSS is not available. Skipping the AES.ValidateLegacy testcase!"
|
||||
#endif // USE_NSS
|
||||
|
||||
TEST(AES, ValidateSecret) {
|
||||
CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES);
|
||||
int l;
|
||||
@ -75,6 +262,50 @@ TEST(AES, Encrypt) {
|
||||
delete kh;
|
||||
}
|
||||
|
||||
TEST(AES, EncryptNoBl) {
|
||||
CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES);
|
||||
char secret_s[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
};
|
||||
bufferptr secret(secret_s, sizeof(secret_s));
|
||||
|
||||
const unsigned char plaintext[] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
|
||||
};
|
||||
|
||||
std::string error;
|
||||
std::unique_ptr<CryptoKeyHandler> kh(h->get_key_handler(secret, error));
|
||||
|
||||
const CryptoKey::in_slice_t plain_slice { sizeof(plaintext), 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);
|
||||
ASSERT_GE(needed, plain_slice.length);
|
||||
|
||||
boost::container::small_vector<
|
||||
// FIXME?
|
||||
//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);
|
||||
ASSERT_EQ(cipher_size, needed);
|
||||
|
||||
const unsigned char want_cipher[] = {
|
||||
0xb3, 0x8f, 0x5b, 0xc9, 0x35, 0x4c, 0xf8, 0xc6,
|
||||
0x13, 0x15, 0x66, 0x6f, 0x37, 0xd7, 0x79, 0x3a,
|
||||
0x11, 0x90, 0x7b, 0xe9, 0xd8, 0x3c, 0x35, 0x70,
|
||||
0x58, 0x7b, 0x97, 0x9b, 0x03, 0xd2, 0xa5, 0x01,
|
||||
};
|
||||
|
||||
ASSERT_EQ(sizeof(want_cipher), cipher_size);
|
||||
|
||||
const int err = memcmp(buf.data(), want_cipher, sizeof(want_cipher));
|
||||
ASSERT_EQ(0, err);
|
||||
}
|
||||
|
||||
TEST(AES, Decrypt) {
|
||||
CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES);
|
||||
char secret_s[] = {
|
||||
@ -115,6 +346,40 @@ TEST(AES, Decrypt) {
|
||||
delete kh;
|
||||
}
|
||||
|
||||
TEST(AES, DecryptNoBl) {
|
||||
CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES);
|
||||
const char secret_s[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
};
|
||||
bufferptr secret(secret_s, sizeof(secret_s));
|
||||
|
||||
const unsigned char ciphertext[] = {
|
||||
0xb3, 0x8f, 0x5b, 0xc9, 0x35, 0x4c, 0xf8, 0xc6,
|
||||
0x13, 0x15, 0x66, 0x6f, 0x37, 0xd7, 0x79, 0x3a,
|
||||
0x11, 0x90, 0x7b, 0xe9, 0xd8, 0x3c, 0x35, 0x70,
|
||||
0x58, 0x7b, 0x97, 0x9b, 0x03, 0xd2, 0xa5, 0x01,
|
||||
};
|
||||
|
||||
const unsigned char want_plaintext[] = {
|
||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
|
||||
};
|
||||
unsigned char plaintext[sizeof(want_plaintext)];
|
||||
|
||||
std::string error;
|
||||
std::unique_ptr<CryptoKeyHandler> kh(h->get_key_handler(secret, error));
|
||||
|
||||
CryptoKey::in_slice_t cipher_slice { sizeof(ciphertext), ciphertext };
|
||||
CryptoKey::out_slice_t plain_slice { sizeof(ciphertext), plaintext };
|
||||
const auto plain_size = kh->decrypt(cipher_slice, plain_slice);
|
||||
|
||||
ASSERT_EQ(plain_size, sizeof(want_plaintext));
|
||||
|
||||
const int err = memcmp(plaintext, want_plaintext, sizeof(plain_size));
|
||||
ASSERT_EQ(0, err);
|
||||
}
|
||||
|
||||
TEST(AES, Loop) {
|
||||
CryptoRandom random;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user