Commit fbdc7b24 authored by Adrien Béraud's avatar Adrien Béraud Committed by Guillaume Roguez

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
parent b3c8e1b3
......@@ -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)
{
......
......@@ -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,
......
......@@ -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();
}
......
......@@ -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_;
......
......@@ -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
......@@ -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
*
......
......@@ -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)
{
......
......@@ -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:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment