*
*/
+#include <vector>
+
+#include "common/ceph_context.h"
+#include "common/ceph_mutex.h"
#include "common/config.h"
#include "ceph_crypto.h"
-#include "include/scope_guard.h"
-#ifdef USE_CRYPTOPP
-void ceph::crypto::init(CephContext *cct)
-{
-}
+#include <openssl/evp.h>
-void ceph::crypto::shutdown(bool)
-{
-}
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+# include <openssl/conf.h>
+# include <openssl/engine.h>
+# include <openssl/err.h>
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
-// nothing
-ceph::crypto::HMACSHA1::~HMACSHA1()
-{
-}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-ceph::crypto::HMACSHA256::~HMACSHA256()
-{
-}
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+namespace TOPNSPC::crypto::ssl {
-#elif defined(USE_NSS)
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static std::atomic_uint32_t crypto_refs;
-// for SECMOD_RestartModules()
-#include <secmod.h>
-#include <nspr.h>
-static pthread_mutex_t crypto_init_mutex = PTHREAD_MUTEX_INITIALIZER;
-static uint32_t crypto_refs = 0;
-static NSSInitContext *crypto_context = NULL;
-static pid_t crypto_init_pid = 0;
+static auto ssl_mutexes = ceph::make_lock_container<ceph::shared_mutex>(
+ static_cast<size_t>(std::max(CRYPTO_num_locks(), 0)),
+ [](const size_t i) {
+ return ceph::make_shared_mutex(
+ std::string("ssl-mutex-") + std::to_string(i));
+ });
-PK11SymKey *ceph::crypto::PK11_ImportSymKey_FIPS(
- PK11SlotInfo * const slot,
- const CK_MECHANISM_TYPE type,
- const PK11Origin origin,
- const CK_ATTRIBUTE_TYPE operation,
- SECItem * const raw_key,
- void * const wincx)
+static struct {
+ // we could use e.g. unordered_set instead at the price of providing
+ // std::hash<...> specialization. However, we can live with duplicates
+ // quite well while the benefit is not worth the effort.
+ std::vector<CRYPTO_THREADID> tids;
+ ceph::mutex lock = ceph::make_mutex("crypto::ssl::init_records::lock");;
+} init_records;
+
+static void
+ssl_locking_callback(
+ const int mode,
+ const int mutex_num,
+ [[maybe_unused]] const char *file,
+ [[maybe_unused]] const int line)
{
- if (PK11_IsFIPS() == PR_FALSE) {
- // This isn't the FIPS mode, and thus PK11_ImportSymKey is available. Let's
- // make use of it to avoid overhead related to e.g. creating extra PK11Ctx.
- PK11SymKey *ret_key = nullptr;
- ret_key = PK11_ImportSymKey(slot, type, origin, operation, raw_key, wincx);
+ if (mutex_num < 0 || static_cast<size_t>(mutex_num) >= ssl_mutexes.size()) {
+ ceph_assert_always("openssl passed wrong mutex index" == nullptr);
+ }
- return ret_key;
+ if (mode & CRYPTO_READ) {
+ if (mode & CRYPTO_LOCK) {
+ ssl_mutexes[mutex_num].lock_shared();
+ } else if (mode & CRYPTO_UNLOCK) {
+ ssl_mutexes[mutex_num].unlock_shared();
+ }
+ } else if (mode & CRYPTO_WRITE) {
+ if (mode & CRYPTO_LOCK) {
+ ssl_mutexes[mutex_num].lock();
+ } else if (mode & CRYPTO_UNLOCK) {
+ ssl_mutexes[mutex_num].unlock();
+ }
}
+}
- ceph_assert_always(wincx == nullptr);
+static unsigned long
+ssl_get_thread_id(void)
+{
+ static_assert(sizeof(unsigned long) >= sizeof(pthread_t));
+ /* pthread_t may be any data type, so a simple cast to unsigned long
+ * can rise a warning/error, depending on the platform.
+ * Here memcpy is used as an anything-to-anything cast. */
+ unsigned long ret = 0;
+ pthread_t t = pthread_self();
+ memcpy(&ret, &t, sizeof(pthread_t));
+ return ret;
+}
+#endif /* not OPENSSL_VERSION_NUMBER < 0x10100000L */
- std::vector<unsigned char> wrapped_key;
+static void init() {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ if (++crypto_refs == 1) {
+ // according to
+ // https://wiki.openssl.org/index.php/Library_Initialization#libcrypto_Initialization
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
- // getting 306 on my system which is CKM_DES3_ECB.
- const CK_MECHANISM_TYPE wrap_mechanism = PK11_GetBestWrapMechanism(slot);
+ // initialize locking callbacks, needed for thread safety.
+ // http://www.openssl.org/support/faq.html#PROG1
+ CRYPTO_set_locking_callback(&ssl_locking_callback);
+ CRYPTO_set_id_callback(&ssl_get_thread_id);
- // Generate a wrapping key. It will be used exactly twice over the scope:
- // * to encrypt raw_key giving wrapped_key,
- // * to decrypt wrapped_key in the internals of PK11_UnwrapSymKey().
- PK11SymKey * const wrapping_key = PK11_KeyGen(
- slot,
- wrap_mechanism,
- nullptr,
- PK11_GetBestKeyLength(slot, wrap_mechanism),
- nullptr);
- if (wrapping_key == nullptr) {
- return nullptr;
+ OPENSSL_config(nullptr);
}
- auto wk_guard = make_scope_guard([wrapping_key] {
- PK11_FreeSymKey(wrapping_key);
- });
- // Prepare a PK11 context for the raw_key -> wrapped_key encryption.
- SECItem tmp_sec_item;
- ::memset(&tmp_sec_item, 0, sizeof(tmp_sec_item));
- PK11Context * const wrap_key_crypt_context = PK11_CreateContextBySymKey(
- wrap_mechanism,
- CKA_ENCRYPT,
- wrapping_key,
- &tmp_sec_item);
- if (wrap_key_crypt_context == nullptr) {
- return nullptr;
+ // we need to record IDs of all threads calling the initialization in
+ // order to *manually* free per-thread memory OpenSSL *automagically*
+ // allocated in ERR_get_state().
+ // XXX: this solution/nasty hack is IMPERFECT. A leak will appear when
+ // a client init()ializes the crypto subsystem with one thread and then
+ // uses it from another one in a way that results in ERR_get_state().
+ // XXX: for discussion about another approaches please refer to:
+ // https://www.mail-archive.com/openssl-users@openssl.org/msg59070.html
+ {
+ std::lock_guard l(init_records.lock);
+ CRYPTO_THREADID tmp;
+ CRYPTO_THREADID_current(&tmp);
+ init_records.tids.emplace_back(std::move(tmp));
}
- auto wkcc_guard = make_scope_guard([wrap_key_crypt_context] {
- PK11_DestroyContext(wrap_key_crypt_context, PR_TRUE);
- });
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+}
+static void shutdown() {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ if (--crypto_refs != 0) {
+ return;
+ }
- // Finally wrap the key. Important note is that the wrapping mechanism
- // selection (read: just grabbing a cipher) offers, at least in my NSS
- // copy, mostly CKM_*_ECB ciphers (with 3DES as the leading one, see
- // wrapMechanismList[] in pk11mech.c). There is no CKM_*_*_PAD variant
- // which means that plaintext we are providing to PK11_CipherOp() must
- // be aligned to cipher's block size. For 3DES it's 64 bits.
+ // drop error queue for each thread that called the init() function to
+ // satisfy valgrind.
{
- const auto block_size = PK11_GetBlockSize(wrap_mechanism, nullptr);
- SECItem * const raw_key_aligned = PK11_BlockData(raw_key, block_size);
- if (raw_key_aligned == nullptr) {
- return nullptr;
- }
- auto rka_guard = make_scope_guard([raw_key_aligned] {
- SECITEM_FreeItem(raw_key_aligned, PR_TRUE);
- });
-
- // PARANOIA: always add space for one extra cipher's block. This seems
- // unnecessary at the moment as padding is never used (see the comment
- // above) but let's assume it can change in the future. Just in case.
- wrapped_key.resize(raw_key_aligned->len + block_size, 0x0);
- int out_len = 0;
-
- int ret = PK11_CipherOp(
- wrap_key_crypt_context,
- wrapped_key.data(),
- &out_len,
- wrapped_key.size(), // max space
- raw_key_aligned->data,
- raw_key_aligned->len);
- if (ret != SECSuccess) {
- return nullptr;
+ std::lock_guard l(init_records.lock);
+
+ // NOTE: in OpenSSL 1.0.2g the signature is:
+ // void ERR_remove_thread_state(const CRYPTO_THREADID *tid);
+ // but in 1.1.0j it has been changed to
+ // void ERR_remove_thread_state(void *);
+ // We're basing on the OPENSSL_VERSION_NUMBER check to preserve
+ // const-correctness without failing builds on modern envs.
+ for (const auto& tid : init_records.tids) {
+ ERR_remove_thread_state(&tid);
}
+ }
- ret = PK11_Finalize(wrap_key_crypt_context);
- if (ret != SECSuccess) {
- return nullptr;
- }
+ // Shutdown according to
+ // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup
+ // http://stackoverflow.com/questions/29845527/how-to-properly-uninitialize-openssl
+ //
+ // The call to CONF_modules_free() has been introduced after a valgring run.
+ CRYPTO_set_locking_callback(nullptr);
+ CRYPTO_set_id_callback(nullptr);
+ ENGINE_cleanup();
+ CONF_modules_free();
+ CONF_modules_unload(1);
+ ERR_free_strings();
+ EVP_cleanup();
+ CRYPTO_cleanup_all_ex_data();
+
+ // NOTE: don't clear ssl_mutexes as we should be ready for init-deinit-init
+ // sequence.
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+}
- ceph_assert(out_len <= static_cast<int>(wrapped_key.size()));
- wrapped_key.resize(out_len);
- }
+void zeroize_for_security(void* const s, const size_t n) {
+ OPENSSL_cleanse(s, n);
+}
- // Key is wrapped now so we can acquire the ultimate PK11SymKey through
- // unwrapping it. Of course these two opposite operations form NOP with
- // a side effect: FIPS level 1 compatibility.
- ::memset(&tmp_sec_item, 0, sizeof(tmp_sec_item));
-
- SECItem wrapped_key_item;
- ::memset(&wrapped_key_item, 0, sizeof(wrapped_key_item));
- wrapped_key_item.data = wrapped_key.data();
- wrapped_key_item.len = wrapped_key.size();
-
- return PK11_UnwrapSymKey(
- wrapping_key,
- wrap_mechanism,
- &tmp_sec_item,
- &wrapped_key_item,
- type,
- operation,
- raw_key->len);
+} // namespace TOPNSPC::crypto::openssl
+
+
+namespace TOPNSPC::crypto {
+void init() {
+ ssl::init();
}
-void ceph::crypto::init(CephContext *cct)
-{
- pid_t pid = getpid();
- pthread_mutex_lock(&crypto_init_mutex);
- if (crypto_init_pid != pid) {
- if (crypto_init_pid > 0) {
- SECMOD_RestartModules(PR_FALSE);
- }
- crypto_init_pid = pid;
- }
+void shutdown([[maybe_unused]] const bool shared) {
+ ssl::shutdown();
+}
- if (++crypto_refs == 1) {
- NSSInitParameters init_params;
- memset(&init_params, 0, sizeof(init_params));
- init_params.length = sizeof(init_params);
+void zeroize_for_security(void* const s, const size_t n) {
+ ssl::zeroize_for_security(s, n);
+}
- uint32_t flags = (NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
- if (cct->_conf->nss_db_path.empty()) {
- flags |= (NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB);
- }
- crypto_context = NSS_InitContext(cct->_conf->nss_db_path.c_str(), "", "",
- SECMOD_DB, &init_params, flags);
- }
- pthread_mutex_unlock(&crypto_init_mutex);
- assert(crypto_context != NULL);
+ssl::OpenSSLDigest::OpenSSLDigest(const EVP_MD * _type)
+ : mpContext(EVP_MD_CTX_create())
+ , mpType(_type) {
+ this->Restart();
}
-void ceph::crypto::shutdown(bool shared)
-{
- pthread_mutex_lock(&crypto_init_mutex);
- assert(crypto_refs > 0);
- if (--crypto_refs == 0) {
- NSS_ShutdownContext(crypto_context);
- if (!shared) {
- PR_Cleanup();
- }
- crypto_context = NULL;
- crypto_init_pid = 0;
+ssl::OpenSSLDigest::~OpenSSLDigest() {
+ EVP_MD_CTX_destroy(mpContext);
+}
+
+void ssl::OpenSSLDigest::Restart() {
+ EVP_DigestInit_ex(mpContext, mpType, NULL);
+}
+
+void ssl::OpenSSLDigest::SetFlags(int flags) {
+ EVP_MD_CTX_set_flags(mpContext, flags);
+ this->Restart();
+}
+
+void ssl::OpenSSLDigest::Update(const unsigned char *input, size_t length) {
+ if (length) {
+ EVP_DigestUpdate(mpContext, const_cast<void *>(reinterpret_cast<const void *>(input)), length);
}
- pthread_mutex_unlock(&crypto_init_mutex);
}
-ceph::crypto::HMAC::~HMAC()
-{
- PK11_DestroyContext(ctx, PR_TRUE);
- PK11_FreeSymKey(symkey);
- PK11_FreeSlot(slot);
+void ssl::OpenSSLDigest::Final(unsigned char *digest) {
+ unsigned int s;
+ EVP_DigestFinal_ex(mpContext, digest, &s);
+}
+
}
-#else
-# error "No supported crypto implementation found."
-#endif
+#pragma clang diagnostic pop
+#pragma GCC diagnostic pop