Commit c8f451e4 authored by Guillaume Roguez's avatar Guillaume Roguez

security: extract TLS session from SipsIceTransport

TLS session (class handling gnutls session client/server) has
to be extracted from SipsIceTransport class.
This last is PJSIP transport related, but we need TLS session
to securise other kind of network socket.

This extract has moslty re-written all previous classes
has the global working model has change.

This also introduces:
- flood protection in server SYN cookie state
- better FSM model in TLS session
- network statistics recording
- simplify SipsIceTransport design
- ICE fixes to not block in waitForData() if ICE is stopped
- integrate Diffie-Hellman params generation from RingAccount

Change-Id: I32cf1f0c82dee548912d9efdaca5a4447ab05ec7
Tuleap: #106
parent 8ac6e93f
......@@ -853,9 +853,11 @@ IceTransport::waitForData(int comp_id, unsigned int timeout)
auto& io = compIO_[comp_id];
std::unique_lock<std::mutex> lk(io.mutex);
if (!, std::chrono::milliseconds(timeout),
[&io]{ return !io.queue.empty(); })) {
[this, &io]{ return !io.queue.empty() or !isRunning(); })) {
return 0;
if (!isRunning())
return -1; // acknowledged as an error
return io.queue.front().datalen;
......@@ -665,9 +665,12 @@ RingAccount::handlePendingCall(PendingCall& pc, bool incoming)
// Securize a SIP transport with TLS (on top of ICE tranport) and assign the call with it
auto remote_h = pc.from;
auto id(loadIdentity());
tls::TlsParams tlsParams {
.ca_list = "",
.id = loadIdentity(),
.cert = id.second,
.cert_key = id.first,
.dh_params = dhParams_,
.timeout = std::chrono::duration_cast<decltype(tls::TlsParams::timeout)>(TLS_TIMEOUT),
.cert_check = [remote_h](unsigned status, const gnutls_datum_t* cert_list,
......@@ -1209,36 +1212,10 @@ RingAccount::loadValues() const
return values;
static std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>
using namespace std::chrono;
auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_NORMAL);
RING_DBG("Generating DH params with %u bits", bits);
auto t1 = high_resolution_clock::now();
gnutls_dh_params_t new_params_;
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};
ret = gnutls_dh_params_generate2(new_params_, bits);
if (ret != GNUTLS_E_SUCCESS) {
RING_ERR("Error generating DH params: %s", gnutls_strerror(ret));
return {nullptr, gnutls_dh_params_deinit};
auto time_span = duration_cast<duration<double>>(high_resolution_clock::now() - t1);
RING_DBG("Generated DH params with %u bits in %lfs", bits, time_span.count());
return {new_params_, gnutls_dh_params_deinit};
std::packaged_task<decltype(getNewDhParams())()> task(&getNewDhParams);
std::packaged_task<decltype(tls::newDhParams)> task(tls::newDhParams);
dhParams_ = task.get_future();
std::thread task_td(std::move(task));
......@@ -24,6 +24,7 @@
#include "config.h"
#include "security/tls_session.h"
#include "sip/sipaccountbase.h"
#include "noncopyable.h"
......@@ -374,7 +375,7 @@ class RingAccount : public SIPAccountBase {
void generateDhParams();
std::shared_future<std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>> dhParams_;
std::shared_future<tls::TlsParams::DhParams> dhParams_;
std::mutex dhParamsMtx_;
std::condition_variable dhParamsCv_;
bool allowPeersFromHistory_;
This diff is collapsed.
* Copyright (C) 2004-2015 Savoir-faire Linux Inc.
* Copyright (C) 2004-2016 Savoir-faire Linux Inc.
* Author: Adrien Béraud <>
* Author: Guillaume Roguez <>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -20,10 +21,9 @@
#pragma once
#include "security/tls_session.h"
#include "ip_utils.h"
#include "threadloop.h"
#include <opendht/crypto.h>
#include "noncopyable.h"
#include <pjsip.h>
#include <pj/pool.h>
......@@ -36,11 +36,10 @@
#include <functional>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <future>
#include <queue>
#include <utility>
#include <vector>
namespace ring {
class IceTransport;
......@@ -48,23 +47,11 @@ class IceTransport;
namespace ring { namespace tls {
enum class TlsConnectionState {
struct TlsParams {
std::string ca_list;
dht::crypto::Identity id;
std::shared_future<std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>> dh_params;
std::chrono::steady_clock::duration timeout;
std::function<pj_status_t(unsigned status,
const gnutls_datum_t* cert_list,
unsigned cert_list_size)> cert_check;
* SipsIceTransport
* Implements TLS transport as an pjsip_transport
struct SipsIceTransport
using clock = std::chrono::steady_clock;
......@@ -81,57 +68,36 @@ struct SipsIceTransport
void shutdown();
IpAddr getLocalAddress() const;
IpAddr getRemoteAddress() const;
std::shared_ptr<IceTransport> getIceTransport() const {
return ice_;
std::shared_ptr<IceTransport> getIceTransport() const { return ice_; }
pjsip_transport* getTransportBase() { return &trData_.base; }
pjsip_transport* getTransportBase() {
return &trData_.base;
IpAddr getLocalAddress() const { return local_; }
IpAddr getRemoteAddress() const { return remote_; }
std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_;
std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> rxPool_;
using DelayedTxData = struct {
pjsip_tx_data_op_key *tdata_op_key;
clock::time_point timeout;
TransportData trData_; // uplink to this (used in C callbacks)
const std::shared_ptr<IceTransport> ice_;
const int comp_id_;
TlsParams param_;
bool is_server_ {false};
//bool has_pending_connect_ {false};
const std::function<int(unsigned, const gnutls_datum_t*, unsigned)> certCheck_;
IpAddr local_ {};
IpAddr remote_ {};
ThreadLoop tlsThread_;
std::condition_variable_any cv_;
std::atomic<bool> canRead_ {false};
std::atomic<bool> canWrite_ {false};
// PJSIP transport backend
TransportData trData_; // uplink to "this" (used by PJSIP called C-callbacks)
pj_status_t verifyStatus_ {};
int last_err_;
std::unique_ptr<pj_pool_t, decltype(pj_pool_release)*> pool_;
std::unique_ptr<pj_pool_t, decltype(pj_pool_release)*> rxPool_;
pjsip_rx_data rdata_;
pj_ssl_cert_info localCertInfo_;
pj_ssl_cert_info remoteCertInfo_;
std::atomic<TlsConnectionState> state_ {TlsConnectionState::DISCONNECTED};
clock::time_point handshakeStart_;
pj_status_t verifyStatus_ {PJ_EUNKNOWN};
gnutls_session_t session_ {nullptr};
std::unique_ptr<gnutls_certificate_credentials_st, decltype(gnutls_certificate_free_credentials)&> xcred_;
gnutls_priority_t priority_cache_ {nullptr};
gnutls_datum_t cookie_key_ {nullptr, 0};
gnutls_dtls_prestate_st prestate_;
// TlsSession backend
struct ChangeStateEventData {
pj_ssl_sock_info ssl_info;
......@@ -140,60 +106,29 @@ private:
std::mutex stateChangeEventsMutex_;
std::queue<ChangeStateEventData> stateChangeEvents_;
std::unique_ptr<TlsSession> tls_;
std::atomic_bool tlsConnected_ {false}; // set by updateTransportState
* To be called on a regular basis to receive packets
void handleEvents();
// ThreadLoop
bool setup();
void loop();
void clean();
// SIP transport <-> GnuTLS
pj_status_t send(pjsip_tx_data* tdata, const pj_sockaddr_t* rem_addr,
int addr_len, void* token,
pjsip_transport_callback callback);
ssize_t trySend(pjsip_tx_data_op_key* tdata);
pj_status_t flushOutputBuff();
std::list<DelayedTxData> outputBuff_;
std::list<std::pair<DelayedTxData, ssize_t>> outputAckBuff_; // acknowledged outputBuff_
std::list<std::vector<uint8_t>> txBuff_;
std::mutex outputBuffMtx_;
std::mutex stateChangeEventsMutex_ {};
std::list<ChangeStateEventData> stateChangeEvents_ {};
std::mutex rxMtx_;
std::condition_variable_any rxCv_;
std::list<std::vector<uint8_t>> rxPending_;
pjsip_rx_data rdata_;
// data buffer pool
std::list<std::vector<uint8_t>> buffPool_;
std::mutex buffPoolMtx_;
void getBuff(decltype(buffPool_)& l, const uint8_t* b, const uint8_t* e);
void getBuff(decltype(buffPool_)& l, const size_t s);
void putBuff(decltype(buffPool_)&& l);
// GnuTLS <-> ICE
ssize_t tlsSend(const void*, size_t);
ssize_t tlsRecv(void* d , size_t s);
int waitForTlsData(unsigned ms);
std::mutex inputBuffMtx_;
std::list<std::vector<uint8_t>> tlsInputBuff_;
pj_status_t startTlsSession();
void closeTlsSession();
pj_status_t tryHandshake();
void certGetCn(const pj_str_t* gen_name, pj_str_t* cn);
void certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci, const gnutls_datum_t* cert, size_t crt_raw_num);
void certUpdate();
bool onHandshakeComplete(pj_status_t status);
int verifyCertificate();
void getInfo(pj_ssl_sock_info* info);
static pj_status_t tls_status_from_err(int err);
std::mutex txAckQueueMutex_;
std::list<std::pair<pjsip_tx_data*, ssize_t>> txAckQueue_;
pj_status_t send(pjsip_tx_data*, const pj_sockaddr_t*, int, void*, pjsip_transport_callback);
void handleEvents();
void pushChangeStateEvent(ChangeStateEventData&&);
void updateTransportState(pjsip_transport_state);
void certGetInfo(pj_pool_t*, pj_ssl_cert_info*, const gnutls_datum_t*, size_t);
void certGetCn(const pj_str_t*, pj_str_t*);
void getInfo(pj_ssl_sock_info*, bool);
void onTlsStateChange(TlsSessionState);
void onRxData(std::vector<uint8_t>&&);
void onCertificatesUpdate(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int);
int verifyCertificate(gnutls_session_t);
}} // namespace ring::tls
......@@ -4,7 +4,9 @@ noinst_LTLIBRARIES =
libsecurity_la_CXXFLAGS = @CXXFLAGS@ -I$(top_srcdir)/src
libsecurity_la_SOURCES = \
tlsvalidator.cpp \
tlsvalidator.h \
certstore.cpp \
tls_session.cpp \
tls_session.h \
tlsvalidator.cpp \
tlsvalidator.h \
certstore.cpp \
This diff is collapsed.
* Copyright (C) 2016 Savoir-faire Linux Inc.
* Author: Adrien Béraud <>
* Author: Guillaume Roguez <>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#pragma once
#include "threadloop.h"
#include <gnutls/gnutls.h>
#include <gnutls/dtls.h>
#include <gnutls/abstract.h>
#include <string>
#include <list>
#include <functional>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <future>
#include <utility>
#include <vector>
#include <map>
namespace ring {
class IceTransport;
class IceSocket;
} // namespace ring
namespace dht { namespace crypto {
class Certificate;
class PrivateKey;
}} // namespace dht::crypto
namespace ring { namespace tls {
enum class TlsSessionState {
COOKIE, // server only
struct TlsParams {
using DhParams = std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>;
// User CA list for session credentials
std::string ca_list;
// User identity for credential
std::shared_ptr<dht::crypto::Certificate> cert;
std::shared_ptr<dht::crypto::PrivateKey> cert_key;
// Diffie-Hellman computed by gnutls_dh_params_init/gnutls_dh_params_generateX
std::shared_future<DhParams> dh_params;
// DTLS timeout
std::chrono::steady_clock::duration timeout;
// Callback for certificate checkings
std::function<int(unsigned status,
const gnutls_datum_t* cert_list,
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
* Manages a DTLS connection over an ICE transport.
* This implementation uses a Threadloop to manage IO from ICE and TLS states,
* so IO are asynchronous.
class TlsSession {
using OnStateChangeFunc = std::function<void(TlsSessionState)>;
using OnRxDataFunc = std::function<void(std::vector<uint8_t>&&)>;
using OnCertificatesUpdate = std::function<void(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int)>;
using VerifyCertificate = std::function<int(gnutls_session_t)>;
using TxDataCompleteFunc = std::function<void(std::size_t bytes_sent)>;
// ===> WARNINGS <===
// Following callbacks are called into the FSM thread context
// Do not call blocking routines inside them.
using TlsSessionCallbacks = struct {
OnStateChangeFunc onStateChange;
OnRxDataFunc onRxData;
OnCertificatesUpdate onCertificatesUpdate;
VerifyCertificate verifyCertificate;
TlsSession(std::shared_ptr<IceTransport> ice, int ice_comp_id, const TlsParams& params,
const TlsSessionCallbacks& cbs);
// Returns the TLS session type ('server' or 'client')
const char* typeName() const;
// Request TLS thread to stop and quit. IO are not possible after that.
void shutdown();
// Can be called by onStateChange callback when state == ESTABLISHED
// to obtain the used cypher suite id.
// Return the name of current cipher.
const char* getCurrentCipherSuiteId(std::array<uint8_t, 2>& cs_id) const;
// Asynchronous sending operation. on_send_complete will be called with a positive number
// for number of bytes sent, or negative for errors, or 0 in case of shutdown (end of session).
ssize_t async_send(void* data, std::size_t size, TxDataCompleteFunc on_send_complete);
using clock = std::chrono::steady_clock;
using StateHandler = std::function<TlsSessionState(TlsSessionState state)>;
// Constants (ctor init.)
const std::unique_ptr<IceSocket> socket_;
const bool isServer_;
const TlsParams params_;
const TlsSessionCallbacks callbacks_;
// State machine
TlsSessionState handleStateSetup(TlsSessionState state);
TlsSessionState handleStateCookie(TlsSessionState state);
TlsSessionState handleStateHandshake(TlsSessionState state);
TlsSessionState handleStateEstablished(TlsSessionState state);
TlsSessionState handleStateShutdown(TlsSessionState state);
std::map<TlsSessionState, StateHandler> fsmHandlers_ {};
std::atomic<TlsSessionState> state_ {TlsSessionState::SETUP};
// IO GnuTLS <-> ICE
struct TxData {
void* const ptr;
std::size_t size;
TxDataCompleteFunc onComplete;
std::mutex ioMutex_ {};
std::condition_variable ioCv_ {};
std::list<TxData> txQueue_ {};
std::list<std::vector<uint8_t>> rxQueue_ {};
ssize_t send(const TxData&);
ssize_t sendRaw(const void*, size_t);
ssize_t sendRawVec(const giovec_t*, int);
ssize_t recvRaw(void*, size_t);
int waitForRawData(unsigned);
// Statistics (also protected by mutex ioMutex_)
std::size_t stRxRawPacketCnt_ {0};
std::size_t stRxRawBytesCnt_ {0};
std::size_t stRxRawPacketDropCnt_ {0};
std::size_t stTxRawPacketCnt_ {0};
std::size_t stTxRawBytesCnt_ {0};
void dump_io_stats() const;
// GnuTLS backend and connection state
class TlsCertificateCredendials;
std::unique_ptr<TlsCertificateCredendials> xcred_; // ctor init.
gnutls_session_t session_ {nullptr};
gnutls_datum_t cookie_key_ {nullptr, 0};
gnutls_priority_t priority_cache_ {nullptr};
gnutls_dtls_prestate_st prestate_ {};
ssize_t cookie_count_ {0};
TlsSessionState setupClient();
TlsSessionState setupServer();
void initCredentials();
bool commonSessionInit();
// FSM thread (TLS states)
ThreadLoop thread_; // ctor init.
bool setup();
void process();
void cleanup();
}} // namespace ring::tls
......@@ -27,6 +27,8 @@
#include <pjsip/sip_msg.h>
#include <pjlib.h>
#include <pj/pool.h>
#include <pjsip/sip_endpoint.h>
#include <utility>
#include <string>
......@@ -95,6 +97,16 @@ private:
pjsip_dialog* dialog_;
// Helper on PJSIP memory pool allocation from endpoint
// This encapsulate the allocated memory pool inside a unique_ptr
static inline std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&>
smart_alloc_pool(pjsip_endpoint* endpt, const char* const name, pj_size_t initial, pj_size_t inc) {
auto pool = pjsip_endpt_create_pool(endpt, name, initial, inc);
if (not pool)
throw std::bad_alloc();
return std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&>(pool, pj_pool_release);
}} // namespace ring::sip_utils
#endif // SIP_UTILS_H_
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