// vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2004-2009 Sage Weil * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #include #include "Crypto.h" #ifdef USE_CRYPTOPP # include # include # include #elif defined(USE_NSS) # include # include # include #endif #include "include/assert.h" #include "common/Clock.h" #include "common/armor.h" #include "common/ceph_crypto.h" #include "common/hex.h" #include "common/safe_io.h" #include "include/ceph_fs.h" #include "include/compat.h" #include "common/Formatter.h" #include "common/debug.h" #include int get_random_bytes(char *buf, int len) { int fd = TEMP_FAILURE_RETRY(::open("/dev/urandom", O_RDONLY)); if (fd < 0) return -errno; int ret = safe_read_exact(fd, buf, len); VOID_TEMP_FAILURE_RETRY(::close(fd)); return ret; } static int get_random_bytes(int len, bufferlist& bl) { char buf[len]; get_random_bytes(buf, len); bl.append(buf, len); return 0; } uint64_t get_random(uint64_t min_val, uint64_t max_val) { uint64_t r; get_random_bytes((char *)&r, sizeof(r)); r = min_val + r % (max_val - min_val + 1); return r; } // --------------------------------------------------- class CryptoNoneKeyHandler : public CryptoKeyHandler { public: int encrypt(const bufferlist& in, bufferlist& out, std::string *error) const override { out = in; return 0; } int decrypt(const bufferlist& in, bufferlist& out, std::string *error) const override { out = in; return 0; } }; class CryptoNone : public CryptoHandler { public: CryptoNone() { } ~CryptoNone() override {} int get_type() const override { return CEPH_CRYPTO_NONE; } int create(bufferptr& secret) override { return 0; } int validate_secret(const bufferptr& secret) override { return 0; } CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override { return new CryptoNoneKeyHandler; } }; // --------------------------------------------------- class CryptoAES : public CryptoHandler { public: CryptoAES() { } ~CryptoAES() override {} int get_type() const override { return CEPH_CRYPTO_AES; } int create(bufferptr& secret) override; int validate_secret(const bufferptr& secret) override; CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override; }; #ifdef USE_CRYPTOPP # define AES_KEY_LEN ((size_t)CryptoPP::AES::DEFAULT_KEYLENGTH) # define AES_BLOCK_LEN ((size_t)CryptoPP::AES::BLOCKSIZE) class CryptoAESKeyHandler : public CryptoKeyHandler { public: CryptoPP::AES::Encryption *enc_key; CryptoPP::AES::Decryption *dec_key; CryptoAESKeyHandler() : enc_key(NULL), dec_key(NULL) {} ~CryptoAESKeyHandler() { delete enc_key; delete dec_key; } int init(const bufferptr& s, ostringstream& err) { secret = s; enc_key = new CryptoPP::AES::Encryption( (byte*)secret.c_str(), CryptoPP::AES::DEFAULT_KEYLENGTH); dec_key = new CryptoPP::AES::Decryption( (byte*)secret.c_str(), CryptoPP::AES::DEFAULT_KEYLENGTH); return 0; } int encrypt(const bufferlist& in, bufferlist& out, std::string *error) const { string ciphertext; CryptoPP::StringSink *sink = new CryptoPP::StringSink(ciphertext); CryptoPP::CBC_Mode_ExternalCipher::Encryption cbc( *enc_key, (const byte*)CEPH_AES_IV); CryptoPP::StreamTransformationFilter stfEncryptor(cbc, sink); for (std::list::const_iterator it = in.buffers().begin(); it != in.buffers().end(); ++it) { const unsigned char *in_buf = (const unsigned char *)it->c_str(); stfEncryptor.Put(in_buf, it->length()); } try { stfEncryptor.MessageEnd(); } catch (CryptoPP::Exception& e) { if (error) { ostringstream oss; oss << "encryptor.MessageEnd::Exception: " << e.GetWhat(); *error = oss.str(); } return -1; } out.append((const char *)ciphertext.c_str(), ciphertext.length()); return 0; } int decrypt(const bufferlist& in, bufferlist& out, std::string *error) const { string decryptedtext; CryptoPP::StringSink *sink = new CryptoPP::StringSink(decryptedtext); CryptoPP::CBC_Mode_ExternalCipher::Decryption cbc( *dec_key, (const byte*)CEPH_AES_IV ); CryptoPP::StreamTransformationFilter stfDecryptor(cbc, sink); for (std::list::const_iterator it = in.buffers().begin(); it != in.buffers().end(); ++it) { const unsigned char *in_buf = (const unsigned char *)it->c_str(); stfDecryptor.Put(in_buf, it->length()); } try { stfDecryptor.MessageEnd(); } catch (CryptoPP::Exception& e) { if (error) { ostringstream oss; oss << "decryptor.MessageEnd::Exception: " << e.GetWhat(); *error = oss.str(); } return -1; } out.append((const char *)decryptedtext.c_str(), decryptedtext.length()); return 0; } }; #elif defined(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 CryptoAESKeyHandler : public CryptoKeyHandler { CK_MECHANISM_TYPE mechanism; PK11SlotInfo *slot; PK11SymKey *key; SECItem *param; 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); } 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; } 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); } }; #else # error "No supported crypto implementation found." #endif // ------------------------------------------------------------ int CryptoAES::create(bufferptr& secret) { bufferlist bl; int r = get_random_bytes(AES_KEY_LEN, bl); if (r < 0) return r; secret = buffer::ptr(bl.c_str(), bl.length()); return 0; } int CryptoAES::validate_secret(const bufferptr& secret) { if (secret.length() < (size_t)AES_KEY_LEN) { return -EINVAL; } return 0; } CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret, string& error) { CryptoAESKeyHandler *ckh = new CryptoAESKeyHandler; ostringstream oss; if (ckh->init(secret, oss) < 0) { error = oss.str(); delete ckh; return NULL; } return ckh; } // -- // --------------------------------------------------- void CryptoKey::encode(bufferlist& bl) const { ::encode(type, bl); ::encode(created, bl); __u16 len = secret.length(); ::encode(len, bl); bl.append(secret); } void CryptoKey::decode(bufferlist::iterator& bl) { ::decode(type, bl); ::decode(created, bl); __u16 len; ::decode(len, bl); bufferptr tmp; bl.copy_deep(len, tmp); if (_set_secret(type, tmp) < 0) throw buffer::malformed_input("malformed secret"); } int CryptoKey::set_secret(int type, const bufferptr& s, utime_t c) { int r = _set_secret(type, s); if (r < 0) return r; this->created = c; return 0; } int CryptoKey::_set_secret(int t, const bufferptr& s) { if (s.length() == 0) { secret = s; ckh.reset(); return 0; } CryptoHandler *ch = CryptoHandler::create(t); if (ch) { int ret = ch->validate_secret(s); if (ret < 0) { delete ch; return ret; } string error; ckh.reset(ch->get_key_handler(s, error)); delete ch; if (error.length()) { return -EIO; } } else { return -EOPNOTSUPP; } type = t; secret = s; return 0; } int CryptoKey::create(CephContext *cct, int t) { CryptoHandler *ch = CryptoHandler::create(t); if (!ch) { if (cct) lderr(cct) << "ERROR: cct->get_crypto_handler(type=" << t << ") returned NULL" << dendl; return -EOPNOTSUPP; } bufferptr s; int r = ch->create(s); delete ch; if (r < 0) return r; r = _set_secret(t, s); if (r < 0) return r; created = ceph_clock_now(); return r; } void CryptoKey::print(std::ostream &out) const { out << encode_base64(); } void CryptoKey::to_str(std::string& s) const { int len = secret.length() * 4; char buf[len]; hex2str(secret.c_str(), secret.length(), buf, len); s = buf; } void CryptoKey::encode_formatted(string label, Formatter *f, bufferlist &bl) { f->open_object_section(label.c_str()); f->dump_string("key", encode_base64()); f->close_section(); f->flush(bl); } void CryptoKey::encode_plaintext(bufferlist &bl) { bl.append(encode_base64()); } // ------------------ CryptoHandler *CryptoHandler::create(int type) { switch (type) { case CEPH_CRYPTO_NONE: return new CryptoNone; case CEPH_CRYPTO_AES: return new CryptoAES; default: return NULL; } }