// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2010-2011 Dreamhost * * 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 "common/ceph_context.h" #include "common/ceph_mutex.h" #include "common/config.h" #include "ceph_crypto.h" #include #if OPENSSL_VERSION_NUMBER < 0x10100000L # include # include # include #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ namespace TOPNSPC::crypto::ssl { #if OPENSSL_VERSION_NUMBER < 0x10100000L static std::atomic_uint32_t crypto_refs; static auto ssl_mutexes = ceph::make_lock_container( static_cast(std::max(CRYPTO_num_locks(), 0)), [](const size_t i) { return ceph::make_shared_mutex( std::string("ssl-mutex-") + std::to_string(i)); }); 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 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 (mutex_num < 0 || static_cast(mutex_num) >= ssl_mutexes.size()) { ceph_assert_always("openssl passed wrong mutex index" == nullptr); } 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(); } } } 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 */ 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(); // 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); OPENSSL_config(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)); } #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ } static void shutdown() { #if OPENSSL_VERSION_NUMBER < 0x10100000L if (--crypto_refs != 0) { return; } // drop error queue for each thread that called the init() function to // satisfy valgrind. { 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); } } // 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 */ } void zeroize_for_security(void* const s, const size_t n) { OPENSSL_cleanse(s, n); } } // namespace TOPNSPC::crypto::openssl namespace TOPNSPC::crypto { void init() { ssl::init(); } void shutdown([[maybe_unused]] const bool shared) { ssl::shutdown(); } void zeroize_for_security(void* const s, const size_t n) { ssl::zeroize_for_security(s, n); } ssl::OpenSSLDigest::OpenSSLDigest(const EVP_MD * _type) : mpContext(EVP_MD_CTX_create()) , mpType(_type) { this->Restart(); } 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(reinterpret_cast(input)), length); } } void ssl::OpenSSLDigest::Final(unsigned char *digest) { unsigned int s; EVP_DigestFinal_ex(mpContext, digest, &s); } }