From fbdc7b2476d330897ff2014e9fc17fd128eb4c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Thu, 3 Mar 2016 00:50:42 -0500 Subject: [PATCH] crypto: save dh params Diffie-Hellman parameters can take a few seconds to compute, during this time a CPU core will be fully used. This leads to high power consumption on startup, which can be problematic for mobile devices. This patch introduces saving DH params to the persistent cache to avoid regenerating them too often. The file modification time is used to know the age of the parameters. DH params can be reused for up to 3 days. * Intoduce writeTime to know a file modification time * Introduce to_wstring to convert filenames to the Windows format * Introduce a DhParams structure to handle serialization Tuleap: #452 Change-Id: Iaea9cf24d922fc6cfc542f8fa7b0c208ebc141d2 --- src/fileutils.cpp | 35 ++++++++++++++++++++++++++++ src/fileutils.h | 3 +++ src/ringdht/ringaccount.cpp | 27 ++++++++++++++++++++-- src/ringdht/ringaccount.h | 4 +++- src/security/tls_session.cpp | 44 ++++++++++++++++++++++++++++-------- src/security/tls_session.h | 33 ++++++++++++++++++++++----- src/string_utils.cpp | 19 ++++++++++++++++ src/string_utils.h | 6 +++++ 8 files changed, 153 insertions(+), 18 deletions(-) diff --git a/src/fileutils.cpp b/src/fileutils.cpp index 9b7cecc819..4fae5dc529 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -31,6 +31,9 @@ #ifdef __ANDROID__ #include "client/ring_signal.h" #endif +#ifdef _WIN32 +#include "string_utils.h" +#endif #include <sys/types.h> #include <sys/stat.h> @@ -204,6 +207,38 @@ bool isDirectoryWritable(const std::string &directory) return access(directory.c_str(), W_OK) == 0; } +std::chrono::system_clock::time_point +writeTime(const std::string& path) +{ +#ifndef _WIN32 + struct stat s; + auto ret = stat(path.c_str(), &s); + if (ret) + throw std::runtime_error("Can't check write time for: " + path); + return std::chrono::system_clock::from_time_t(s.st_mtime); +#else + HANDLE h = CreateFile(ring::to_wstring(path).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (h == INVALID_HANDLE_VALUE) + throw std::runtime_error("Can't open: " + path); + FILETIME lastWriteTime; + if (!GetFileTime(h, nullptr, nullptr, &lastWriteTime)) + throw std::runtime_error("Can't check write time for: " + path); + CloseHandle(h); + SYSTEMTIME sTime; + if (!FileTimeToSystemTime(&lastWriteTime, &sTime)) + throw std::runtime_error("Can't check write time for: " + path); + struct tm tm {}; + tm.tm_year = sTime.wYear - 1900; + tm.tm_mon = sTime.wMonth - 1; + tm.tm_mday = sTime.wDay; + tm.tm_hour = sTime.wHour; + tm.tm_min = sTime.wMinute; + tm.tm_sec = sTime.wSecond; + tm.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(mktime(&tm)); +#endif +} + std::vector<uint8_t> loadFile(const std::string& path) { diff --git a/src/fileutils.h b/src/fileutils.h index ff0d79bc53..ef7ea8bbf9 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -23,6 +23,7 @@ #include <string> #include <vector> +#include <chrono> #define PROTECTED_GETENV(str) ({char *envvar_ = getenv((str)); \ envvar_ ? envvar_ : "";}) @@ -66,6 +67,8 @@ namespace ring { namespace fileutils { bool isDirectory(const std::string& path); + std::chrono::system_clock::time_point writeTime(const std::string& path); + /** * Read content of the directory. * The result is a list of full paths of files in the directory, diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index 34a8cd6a9c..acda9c942d 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -1220,12 +1220,35 @@ RingAccount::loadValues() const return values; } +tls::DhParams +RingAccount::loadDhParams(const std::string path) +{ + try { + auto modified = fileutils::writeTime(path); + RING_WARN("modification date: %ld", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - modified).count()); + if (modified > std::chrono::system_clock::now() - std::chrono::hours(24 * 3)) + return {fileutils::loadFile(path)}; + else { + RING_WARN("file is too old"); + throw std::runtime_error("file is too old"); + } + } catch (...) { + auto params = tls::DhParams::generate(); + try { + fileutils::saveFile(path, params.serialize(), 0600); + } catch (...) { + RING_WARN("error saving dh params"); + } + return params; + } +} + void RingAccount::generateDhParams() { - std::packaged_task<decltype(tls::newDhParams)> task(tls::newDhParams); + std::packaged_task<decltype(loadDhParams)> task(loadDhParams); dhParams_ = task.get_future(); - std::thread task_td(std::move(task)); + std::thread task_td(std::move(task), cachePath_ + DIR_SEPARATOR_STR "dhParams"); task_td.detach(); } diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h index 3be9463437..0d899cf153 100644 --- a/src/ringdht/ringaccount.h +++ b/src/ringdht/ringaccount.h @@ -346,6 +346,8 @@ class RingAccount : public SIPAccountBase { void loadTreatedMessages(); void saveTreatedMessages() const; + static tls::DhParams loadDhParams(const std::string path); + /** * If privkeyPath_ is a valid private key file (PEM or DER), * and certPath_ a valid certificate file, load and returns them. @@ -375,7 +377,7 @@ class RingAccount : public SIPAccountBase { */ void generateDhParams(); - std::shared_future<tls::TlsParams::DhParams> dhParams_; + std::shared_future<tls::DhParams> dhParams_; std::mutex dhParamsMtx_; std::condition_variable dhParamsCv_; bool allowPeersFromHistory_; diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp index 0c53b45cbc..c5818096fc 100644 --- a/src/security/tls_session.cpp +++ b/src/security/tls_session.cpp @@ -54,6 +54,30 @@ duration2ms(std::chrono::duration<Rep, Period> d) return std::chrono::duration_cast<std::chrono::milliseconds>(d).count(); } +DhParams::DhParams(const std::vector<uint8_t>& data) +{ + gnutls_dh_params_t new_params_; + int ret = gnutls_dh_params_init(&new_params_); + if (ret) + throw std::runtime_error(std::string("Error initializing DH params: ") + gnutls_strerror(ret)); + params_.reset(new_params_); + const gnutls_datum_t dat {(uint8_t*)data.data(), (unsigned)data.size()}; + if (int ret_pem = gnutls_dh_params_import_pkcs3(params_.get(), &dat, GNUTLS_X509_FMT_PEM)) + if (int ret_der = gnutls_dh_params_import_pkcs3(params_.get(), &dat, GNUTLS_X509_FMT_DER)) + throw std::runtime_error(std::string("Error importing DH params: ") + gnutls_strerror(ret_pem) + " " + gnutls_strerror(ret_der)); +} + +std::vector<uint8_t> +DhParams::serialize() const +{ + gnutls_datum_t out; + if (gnutls_dh_params_export2_pkcs3(params_.get(), GNUTLS_X509_FMT_PEM, &out)) + return {}; + std::vector<uint8_t> ret {out.data, out.data+out.size}; + gnutls_free(out.data); + return ret; +} + class TlsSession::TlsCertificateCredendials { using T = gnutls_certificate_credentials_t; @@ -202,8 +226,8 @@ TlsSession::initCredentials() // Setup DH-params (server only, may block on dh_params.get()) if (isServer_) { - if (auto& dh_params = params_.dh_params.get()) - gnutls_certificate_set_dh_params(*xcred_, dh_params.get()); + if (const auto& dh_params = params_.dh_params.get().get()) + gnutls_certificate_set_dh_params(*xcred_, dh_params); else RING_WARN("[TLS] DH params unavailable"); // YOMGUI: need to stop? } @@ -654,12 +678,13 @@ TlsSession::process() callbacks_.onStateChange(new_state); } -TlsParams::DhParams -newDhParams() + +DhParams +DhParams::generate() { using clock = std::chrono::high_resolution_clock; - auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_NORMAL); + auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_HIGH); RING_DBG("Generating DH params with %u bits", bits); auto start = clock::now(); @@ -667,18 +692,19 @@ newDhParams() int ret = gnutls_dh_params_init(&new_params_); if (ret != GNUTLS_E_SUCCESS) { RING_ERR("Error initializing DH params: %s", gnutls_strerror(ret)); - return {nullptr, gnutls_dh_params_deinit}; + return {}; } + DhParams params {new_params_}; - ret = gnutls_dh_params_generate2(new_params_, bits); + ret = gnutls_dh_params_generate2(params.get(), bits); if (ret != GNUTLS_E_SUCCESS) { RING_ERR("Error generating DH params: %s", gnutls_strerror(ret)); - return {nullptr, gnutls_dh_params_deinit}; + return {}; } std::chrono::duration<double> time_span = clock::now() - start; RING_DBG("Generated DH params with %u bits in %lfs", bits, time_span.count()); - return {new_params_, gnutls_dh_params_deinit}; + return params; } }} // namespace ring::tls diff --git a/src/security/tls_session.h b/src/security/tls_session.h index 5bd2b5b717..450634ea6a 100644 --- a/src/security/tls_session.h +++ b/src/security/tls_session.h @@ -59,9 +59,34 @@ enum class TlsSessionState { SHUTDOWN }; -struct TlsParams { - using DhParams = std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>; +class DhParams { +public: + DhParams() = default; + DhParams(DhParams&&) = default; + + /** Take ownership of gnutls_dh_params */ + explicit DhParams(gnutls_dh_params_t p) : params_(p, gnutls_dh_params_deinit) {}; + + /** Deserialize DER or PEM encoded DH-params */ + DhParams(const std::vector<uint8_t>& data); + + gnutls_dh_params_t get() { + return params_.get(); + } + const gnutls_dh_params_t get() const { + return params_.get(); + } + + /** Serialize data in PEM format */ + std::vector<uint8_t> serialize() const; + static DhParams generate(); + +private: + std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&> params_ {nullptr, gnutls_dh_params_deinit}; +}; + +struct TlsParams { // User CA list for session credentials std::string ca_list; @@ -81,10 +106,6 @@ struct TlsParams { unsigned cert_list_size)> cert_check; }; -// Compute Diffie-Hellman parameters suitable for TlsParams::dh_params. -// Synchonous call: turn it asynchronous with std::async(std::launch::async, newDhParams) -TlsParams::DhParams newDhParams(); - /** * TlsSession * diff --git a/src/string_utils.cpp b/src/string_utils.cpp index fbe8058fcd..181e88818c 100644 --- a/src/string_utils.cpp +++ b/src/string_utils.cpp @@ -25,9 +25,28 @@ #include <algorithm> #include <ostream> #include <stdexcept> +#ifdef _WIN32 +#include <windows.h> +#endif namespace ring { +#ifdef _WIN32 + +std::wstring to_wstring(const std::string& s) +{ + int slength = (int)s.length() + 1; + int len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, nullptr, 0); + if (not len) + throw std::runtime_error("Can't convert string to wchar"); + std::wstring r((size_t)len, 0); + if (!MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, &(*r.begin()), len)) + throw std::runtime_error("Can't convert string to wchar"); + return r; +} + +#endif + std::string to_string(double value) { diff --git a/src/string_utils.h b/src/string_utils.h index 6ba88f2bb2..707c88f33a 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -42,6 +42,12 @@ bool_to_str(bool b) noexcept std::string to_string(double value); +#ifdef _WIN32 + +std::wstring to_wstring(const std::string& s); + +#endif + #ifdef __ANDROID__ // Rationale: -- GitLab