diff --git a/src/fileutils.cpp b/src/fileutils.cpp index 9b7cecc81917b8c4fdd2dbdf3c915499ea3dce12..4fae5dc5290ef72f44ca17701dde01552c47c6d9 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 ff0d79bc533c3832dd5f7349a32270176f4d665b..ef7ea8bbf94dcf1c937f28a5b42e6431596faa26 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 34a8cd6a9c8f8840b4e3645c53c0c65f0fa3f6a3..acda9c942d35ebfb5770f0accc27929832dee6ff 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 3be94634375e043ff43787cf92c7fcb860231aea..0d899cf15386628df5e1d625fce64f2412a9b63b 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 0c53b45cbcce7ccd571c35c3bed8063601880e2f..c5818096fcccdf5dcd29a43580166ffdea5f6c24 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 5bd2b5b7171bef4e785434455f7f102b71755ea5..450634ea6ab2271475cd01250feaca07ce034959 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 fbe8058fcd6609fb2d953c61324a6619f500e9b5..181e88818c327b040efcb2c56cf73e5111a97090 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 6ba88f2bb2fe15307fe3a554c9ba6ca17c43beac..707c88f33a196e856ee152a30bfeafc91c01b399 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: