diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp index c5818096fcccdf5dcd29a43580166ffdea5f6c24..ce91c6ae08ae90312ccabf49ed0885bdc092ef06 100644 --- a/src/security/tls_session.cpp +++ b/src/security/tls_session.cpp @@ -38,7 +38,8 @@ 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 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}; @@ -101,24 +102,65 @@ private: 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, const TlsParams& params, const TlsSessionCallbacks& cbs) : socket_(new IceSocket(ice, ice_comp_id)) , isServer_(not ice->isInitiator()) , params_(params) , callbacks_(cbs) + , cacred_(nullptr) + , sacred_(nullptr) , xcred_(nullptr) , thread_([this] { return setup(); }, [this] { process(); }, [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) { std::lock_guard<std::mutex> lk {ioMutex_}; if (rxQueue_.size() == INPUT_MAX_SIZE) { @@ -142,9 +184,6 @@ TlsSession::~TlsSession() thread_.join(); socket_->setOnRecv(nullptr); - - if (priority_cache_) - gnutls_priority_deinit(priority_cache_); } const char* @@ -182,6 +221,24 @@ TlsSession::setupServer() 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 TlsSession::initCredentials() { @@ -238,15 +295,28 @@ TlsSession::commonSessionInit() { 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) { - 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; } + // Add certificate credentials 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)); + RING_ERR("[TLS] certificate credential set failed: %s", gnutls_strerror(ret)); return false; } @@ -462,9 +532,10 @@ TlsSession::handleStateSetup(UNUSED TlsSessionState state) RING_DBG("[TLS] Start %s DTLS session", typeName()); try { + initAnonymous(); initCredentials(); } 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; } @@ -560,30 +631,65 @@ TlsSession::handleStateHandshake(TlsSessionState state) auto ret = gnutls_handshake(session_); - if (ret == GNUTLS_E_SUCCESS) { - auto desc = gnutls_session_get_desc(session_); - RING_WARN("[TLS] Session established: %s", desc); - gnutls_free(desc); - - // 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); - } + // Stop on fatal error + if (gnutls_error_is_fatal(ret)) { + RING_ERR("[TLS] handshake failed: %s", gnutls_strerror(ret)); + return TlsSessionState::SHUTDOWN; + } - 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)) { - RING_ERR("[TLS] handshake failed: %s", gnutls_strerror(ret)); - dump_io_stats(); + // Safe-Renegotiation status shall always be true to prevent MiM attack + if (!gnutls_safe_renegotiation_status(session_)) { + RING_ERR("[TLS] server identity changed! MiM attack?"); return TlsSessionState::SHUTDOWN; } - // TODO: handle GNUTLS_E_LARGE_PACKET (MTU must be lowered) - return state; + auto desc = gnutls_session_get_desc(session_); + 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 @@ -678,7 +784,6 @@ TlsSession::process() callbacks_.onStateChange(new_state); } - DhParams DhParams::generate() { diff --git a/src/security/tls_session.h b/src/security/tls_session.h index c51e697225a0c1add6b482c0bef3d3073a42242b..0a09ba2eb82d1b0e802d124b2b152067ec597b6e 100644 --- a/src/security/tls_session.h +++ b/src/security/tls_session.h @@ -197,15 +197,19 @@ private: // GnuTLS backend and connection state 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. 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 initAnonymous(); void initCredentials(); bool commonSessionInit();