Skip to content
Snippets Groups Projects
Commit 65e35da8 authored by Guillaume Roguez's avatar Guillaume Roguez
Browse files

security: use anon+certificate authentification

The TLS handshaking using certificate authentification leaks
them: they are exchanged before cryptographic parameters,
so the are in plaintext. This is an issue in TLS protocol itself.

So this patch implements a new method to make a crypted channel
first, then uses it to exchange certificates and permit trusted
authentification based on certificates.

This implementation is backware compatible.
This is implies that old daemon can continue to work with patched one,
but in such case certificates are leaked!

Change-Id: Id5906df37b29bb938abdcdf25b875052527437e8
Tuleap: #494
parent 700ebf57
Branches
Tags
No related merge requests found
...@@ -38,7 +38,8 @@ ...@@ -38,7 +38,8 @@
namespace ring { namespace tls { namespace ring { namespace tls {
static constexpr const char* TLS_PRIORITY_STRING {"SECURE192:-RSA:-VERS-TLS-ALL:+VERS-DTLS-ALL:%SERVER_PRECEDENCE"}; static constexpr const char* TLS_CERT_PRIORITY_STRING {"SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"};
static constexpr const char* TLS_FULL_PRIORITY_STRING {"SECURE192:-KX-ALL:+ANON-ECDH:+ANON-DH:+SECURE192:-VERS-TLS-ALL:+VERS-DTLS-ALL:-RSA:%SERVER_PRECEDENCE:%SAFE_RENEGOTIATION"};
static constexpr int DTLS_MTU {1400}; // limit for networks like ADSL static constexpr int DTLS_MTU {1400}; // limit for networks like ADSL
static constexpr std::size_t INPUT_MAX_SIZE {1000}; // Maximum packet to store before dropping (pkt size = DTLS_MTU) static constexpr std::size_t INPUT_MAX_SIZE {1000}; // Maximum packet to store before dropping (pkt size = DTLS_MTU)
static constexpr ssize_t FLOOD_THRESHOLD {4*1024}; static constexpr ssize_t FLOOD_THRESHOLD {4*1024};
...@@ -101,24 +102,65 @@ private: ...@@ -101,24 +102,65 @@ private:
T creds_; T creds_;
}; };
class TlsSession::TlsAnonymousClientCredendials
{
using T = gnutls_anon_client_credentials_t;
public:
TlsAnonymousClientCredendials() {
int ret = gnutls_anon_allocate_client_credentials(&creds_);
if (ret < 0) {
RING_ERR("gnutls_anon_allocate_client_credentials() failed with ret=%d", ret);
throw std::bad_alloc();
}
}
~TlsAnonymousClientCredendials() {
gnutls_anon_free_client_credentials(creds_);
}
operator T() { return creds_; }
private:
NON_COPYABLE(TlsAnonymousClientCredendials);
T creds_;
};
class TlsSession::TlsAnonymousServerCredendials
{
using T = gnutls_anon_server_credentials_t;
public:
TlsAnonymousServerCredendials() {
int ret = gnutls_anon_allocate_server_credentials(&creds_);
if (ret < 0) {
RING_ERR("gnutls_anon_allocate_server_credentials() failed with ret=%d", ret);
throw std::bad_alloc();
}
}
~TlsAnonymousServerCredendials() {
gnutls_anon_free_server_credentials(creds_);
}
operator T() { return creds_; }
private:
NON_COPYABLE(TlsAnonymousServerCredendials);
T creds_;
};
TlsSession::TlsSession(std::shared_ptr<IceTransport> ice, int ice_comp_id, TlsSession::TlsSession(std::shared_ptr<IceTransport> ice, int ice_comp_id,
const TlsParams& params, const TlsSessionCallbacks& cbs) const TlsParams& params, const TlsSessionCallbacks& cbs)
: socket_(new IceSocket(ice, ice_comp_id)) : socket_(new IceSocket(ice, ice_comp_id))
, isServer_(not ice->isInitiator()) , isServer_(not ice->isInitiator())
, params_(params) , params_(params)
, callbacks_(cbs) , callbacks_(cbs)
, cacred_(nullptr)
, sacred_(nullptr)
, xcred_(nullptr) , xcred_(nullptr)
, thread_([this] { return setup(); }, , thread_([this] { return setup(); },
[this] { process(); }, [this] { process(); },
[this] { cleanup(); }) [this] { cleanup(); })
{ {
// Setup TLS algorithms priority list
auto ret = gnutls_priority_init(&priority_cache_, TLS_PRIORITY_STRING, nullptr);
if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("[TLS] priority setup failed: %s", gnutls_strerror(ret));
throw std::runtime_error("TlsSession");
}
socket_->setOnRecv([this](uint8_t* buf, size_t len) { socket_->setOnRecv([this](uint8_t* buf, size_t len) {
std::lock_guard<std::mutex> lk {ioMutex_}; std::lock_guard<std::mutex> lk {ioMutex_};
if (rxQueue_.size() == INPUT_MAX_SIZE) { if (rxQueue_.size() == INPUT_MAX_SIZE) {
...@@ -142,9 +184,6 @@ TlsSession::~TlsSession() ...@@ -142,9 +184,6 @@ TlsSession::~TlsSession()
thread_.join(); thread_.join();
socket_->setOnRecv(nullptr); socket_->setOnRecv(nullptr);
if (priority_cache_)
gnutls_priority_deinit(priority_cache_);
} }
const char* const char*
...@@ -182,6 +221,24 @@ TlsSession::setupServer() ...@@ -182,6 +221,24 @@ TlsSession::setupServer()
return TlsSessionState::COOKIE; return TlsSessionState::COOKIE;
} }
void
TlsSession::initAnonymous()
{
// credentials for handshaking and transmission
if (isServer_)
sacred_.reset(new TlsAnonymousServerCredendials());
else
cacred_.reset(new TlsAnonymousClientCredendials());
// Setup DH-params for anonymous authentification
if (isServer_) {
if (const auto& dh_params = params_.dh_params.get().get())
gnutls_anon_set_server_dh_params(*sacred_, dh_params);
else
RING_WARN("[TLS] DH params unavailable"); // YOMGUI: need to stop?
}
}
void void
TlsSession::initCredentials() TlsSession::initCredentials()
{ {
...@@ -238,15 +295,28 @@ TlsSession::commonSessionInit() ...@@ -238,15 +295,28 @@ TlsSession::commonSessionInit()
{ {
int ret; int ret;
ret = gnutls_priority_set(session_, priority_cache_); // Force anonymous connection, see handleStateHandshake how we handle failures
ret = gnutls_priority_set_direct(session_, TLS_FULL_PRIORITY_STRING, nullptr);
if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("[TLS] TLS priority set failed: %s", gnutls_strerror(ret));
return false;
}
// Add anonymous credentials
if (isServer_)
ret = gnutls_credentials_set(session_, GNUTLS_CRD_ANON, *sacred_);
else
ret = gnutls_credentials_set(session_, GNUTLS_CRD_ANON, *cacred_);
if (ret != GNUTLS_E_SUCCESS) { if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("[TLS] session TLS priority set failed: %s", gnutls_strerror(ret)); RING_ERR("[TLS] anonymous credential set failed: %s", gnutls_strerror(ret));
return false; return false;
} }
// Add certificate credentials
ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, *xcred_); ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, *xcred_);
if (ret != GNUTLS_E_SUCCESS) { if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("[TLS] session credential set failed: %s", gnutls_strerror(ret)); RING_ERR("[TLS] certificate credential set failed: %s", gnutls_strerror(ret));
return false; return false;
} }
...@@ -462,9 +532,10 @@ TlsSession::handleStateSetup(UNUSED TlsSessionState state) ...@@ -462,9 +532,10 @@ TlsSession::handleStateSetup(UNUSED TlsSessionState state)
RING_DBG("[TLS] Start %s DTLS session", typeName()); RING_DBG("[TLS] Start %s DTLS session", typeName());
try { try {
initAnonymous();
initCredentials(); initCredentials();
} catch (const std::exception& e) { } catch (const std::exception& e) {
RING_ERR("[TLS] credential init failed: %s", e.what()); RING_ERR("[TLS] authentifications init failed: %s", e.what());
return TlsSessionState::SHUTDOWN; return TlsSessionState::SHUTDOWN;
} }
...@@ -560,30 +631,65 @@ TlsSession::handleStateHandshake(TlsSessionState state) ...@@ -560,30 +631,65 @@ TlsSession::handleStateHandshake(TlsSessionState state)
auto ret = gnutls_handshake(session_); auto ret = gnutls_handshake(session_);
if (ret == GNUTLS_E_SUCCESS) { // Stop on fatal error
auto desc = gnutls_session_get_desc(session_); if (gnutls_error_is_fatal(ret)) {
RING_WARN("[TLS] Session established: %s", desc); RING_ERR("[TLS] handshake failed: %s", gnutls_strerror(ret));
gnutls_free(desc); return TlsSessionState::SHUTDOWN;
}
// Aware about certificates updates
if (callbacks_.onCertificatesUpdate) {
unsigned int remote_count;
auto local = gnutls_certificate_get_ours(session_);
auto remote = gnutls_certificate_get_peers(session_, &remote_count);
callbacks_.onCertificatesUpdate(local, remote, remote_count);
}
return TlsSessionState::ESTABLISHED; // Continue handshaking on non-fatal error
if (ret != GNUTLS_E_SUCCESS) {
// TODO: handle GNUTLS_E_LARGE_PACKET (MTU must be lowered)
RING_DBG("[TLS] non-fatal handshake error: %s", gnutls_strerror(ret));
return state;
} }
if (gnutls_error_is_fatal(ret)) { // Safe-Renegotiation status shall always be true to prevent MiM attack
RING_ERR("[TLS] handshake failed: %s", gnutls_strerror(ret)); if (!gnutls_safe_renegotiation_status(session_)) {
dump_io_stats(); RING_ERR("[TLS] server identity changed! MiM attack?");
return TlsSessionState::SHUTDOWN; return TlsSessionState::SHUTDOWN;
} }
// TODO: handle GNUTLS_E_LARGE_PACKET (MTU must be lowered) auto desc = gnutls_session_get_desc(session_);
return state; RING_WARN("[TLS] session established: %s", desc);
gnutls_free(desc);
// Anonymous connection? rehandshake immediatly with certificate authentification forced
auto cred = gnutls_auth_get_type(session_);
if (cred == GNUTLS_CRD_ANON) {
RING_DBG("[TLS] renogotiate with certificate authentification");
// Re-setup TLS algorithms priority list with only certificate based cipher suites
ret = gnutls_priority_set_direct(session_, TLS_CERT_PRIORITY_STRING, nullptr);
if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("[TLS] session TLS cert-only priority set failed: %s", gnutls_strerror(ret));
return TlsSessionState::SHUTDOWN;
}
// remove anon credentials and re-enable certificate ones
gnutls_credentials_clear(session_);
ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, *xcred_);
if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("[TLS] session credential set failed: %s", gnutls_strerror(ret));
return TlsSessionState::SHUTDOWN;
}
return state; // handshake
} else if (cred != GNUTLS_CRD_CERTIFICATE) {
RING_ERR("[TLS] spurious session credential (%u)", cred);
return TlsSessionState::SHUTDOWN;
}
// Aware about certificates updates
if (callbacks_.onCertificatesUpdate) {
unsigned int remote_count;
auto local = gnutls_certificate_get_ours(session_);
auto remote = gnutls_certificate_get_peers(session_, &remote_count);
callbacks_.onCertificatesUpdate(local, remote, remote_count);
}
return TlsSessionState::ESTABLISHED;
} }
TlsSessionState TlsSessionState
...@@ -678,7 +784,6 @@ TlsSession::process() ...@@ -678,7 +784,6 @@ TlsSession::process()
callbacks_.onStateChange(new_state); callbacks_.onStateChange(new_state);
} }
DhParams DhParams
DhParams::generate() DhParams::generate()
{ {
......
...@@ -197,15 +197,19 @@ private: ...@@ -197,15 +197,19 @@ private:
// GnuTLS backend and connection state // GnuTLS backend and connection state
class TlsCertificateCredendials; class TlsCertificateCredendials;
class TlsAnonymousClientCredendials;
class TlsAnonymousServerCredendials;
std::unique_ptr<TlsAnonymousClientCredendials> cacred_; // ctor init.
std::unique_ptr<TlsAnonymousServerCredendials> sacred_; // ctor init.
std::unique_ptr<TlsCertificateCredendials> xcred_; // ctor init. std::unique_ptr<TlsCertificateCredendials> xcred_; // ctor init.
gnutls_session_t session_ {nullptr}; gnutls_session_t session_ {nullptr};
gnutls_datum_t cookie_key_ {nullptr, 0}; gnutls_datum_t cookie_key_ {nullptr, 0};
gnutls_priority_t priority_cache_ {nullptr};
gnutls_dtls_prestate_st prestate_ {}; gnutls_dtls_prestate_st prestate_ {};
ssize_t cookie_count_ {0}; ssize_t cookie_count_ {0};
TlsSessionState setupClient(); TlsSessionState setupClient();
TlsSessionState setupServer(); TlsSessionState setupServer();
void initAnonymous();
void initCredentials(); void initCredentials();
bool commonSessionInit(); bool commonSessionInit();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment