diff --git a/daemon/contrib/src/opendht/rules.mak b/daemon/contrib/src/opendht/rules.mak index c8633b24810b5dc359fa7c45a657683c73907d71..df031dc4b2a57c996d9fcd0f10b6454fd550fc78 100644 --- a/daemon/contrib/src/opendht/rules.mak +++ b/daemon/contrib/src/opendht/rules.mak @@ -1,5 +1,5 @@ # OPENDHT -OPENDHT_VERSION := 29f5d8c68ec363899373469c0af2ba86ceb75dac +OPENDHT_VERSION := b5d93471747ad08254806843253e27e5ae9b9372 OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz PKGS += opendht @@ -8,7 +8,7 @@ PKGS_FOUND += opendht endif # Avoid building distro-provided dependencies in case opendht was built manually -ifneq ($(call need_pkg,"gnutls >= 3.0.20"),) +ifneq ($(call need_pkg,"gnutls >= 3.1"),) DEPS_opendht = gnutls $(DEPS_gnutls) endif diff --git a/daemon/src/dring/account_const.h b/daemon/src/dring/account_const.h index 59cd573e706588dcca5704c1a9393d8090fd05f8..e64435d430a6a69d82cd6f38ea977955a6d85e9a 100644 --- a/daemon/src/dring/account_const.h +++ b/daemon/src/dring/account_const.h @@ -189,8 +189,6 @@ constexpr static const char NEGOTIATION_TIMEOUT_SEC [] = "TLS.negotiationTime namespace DHT { constexpr static const char PORT [] = "DHT.port" ; -constexpr static const char PRIVATE_PATH [] = "DHT.privkeyPath" ; -constexpr static const char CERT_PATH [] = "DHT.certificatePath" ; } //namespace DRing::Account::DHT diff --git a/daemon/src/ice_transport.cpp b/daemon/src/ice_transport.cpp index 6a3671c376065010cf6ad26959731871122ec97c..4e173cdafbbf89e92ce34fbae64a5385b499bb65 100644 --- a/daemon/src/ice_transport.cpp +++ b/daemon/src/ice_transport.cpp @@ -241,6 +241,14 @@ IceTransport::setSlaveSession() createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED); } +bool +IceTransport::isInitiator() const +{ + if (isInitialized()) + return pj_ice_strans_get_role(icest_.get()) == PJ_ICE_SESS_ROLE_CONTROLLING; + return initiator_session_; +} + bool IceTransport::start(const Attribute& rem_attrs, const std::vector<IceCandidate>& rem_candidates) diff --git a/daemon/src/ice_transport.h b/daemon/src/ice_transport.h index 98a7eba494106b16cae87ea734851b2476c22fd1..8f5dda1dbe058dbd458826a7f94f016faef1b3f9 100644 --- a/daemon/src/ice_transport.h +++ b/daemon/src/ice_transport.h @@ -91,6 +91,11 @@ class IceTransport { */ bool setSlaveSession(); + /** + * Get current state + */ + bool isInitiator() const; + /** * Start tranport negociation between local candidates and given remote * to find the right candidate pair. diff --git a/daemon/src/ringdht/Makefile.am b/daemon/src/ringdht/Makefile.am index 21554a38cd2c0103841c8b9407aea82ac0d01c23..1ee66dc804589e072cc16a1071a9edae70f8729c 100644 --- a/daemon/src/ringdht/Makefile.am +++ b/daemon/src/ringdht/Makefile.am @@ -11,6 +11,8 @@ libringacc_la_SOURCES = \ ringaccount.cpp \ ringaccount.h \ sip_transport_ice.cpp \ - sip_transport_ice.h + sip_transport_ice.h \ + sips_transport_ice.cpp \ + sips_transport_ice.h endif diff --git a/daemon/src/ringdht/ringaccount.cpp b/daemon/src/ringdht/ringaccount.cpp index 267c6207b00a77c784fb9c252e4eaf5b4e58b599..7581e55c24d150af28898ba8d27697838995659c 100644 --- a/daemon/src/ringdht/ringaccount.cpp +++ b/daemon/src/ringdht/ringaccount.cpp @@ -40,15 +40,14 @@ #include "sip/sipcall.h" #include "sip/siptransport.h" -#include "sip_transport_ice.h" +#include "sips_transport_ice.h" #include "ice_transport.h" -#include <opendht/securedht.h> - -#include "array_size.h" - #include "client/signal.h" +#include "upnp/upnp_control.h" +#include "system_codec_container.h" + #include "account_schema.h" #include "logger.h" #include "manager.h" @@ -57,12 +56,13 @@ #include "libav_utils.h" #endif #include "fileutils.h" +#include "string_utils.h" +#include "array_size.h" #include "config/yamlparser.h" -#include <yaml-cpp/yaml.h> -#include "upnp/upnp_control.h" -#include "system_codec_container.h" +#include <opendht/securedht.h> +#include <yaml-cpp/yaml.h> #include <algorithm> #include <array> @@ -80,7 +80,7 @@ static constexpr int ICE_NEGOTIATION_TIMEOUT {60}; constexpr const char * const RingAccount::ACCOUNT_TYPE; RingAccount::RingAccount(const std::string& accountID, bool /* presenceEnabled */) - : SIPAccountBase(accountID), tlsSetting_(), via_addr_() + : SIPAccountBase(accountID), via_addr_() { fileutils::check_dir(fileutils::get_cache_dir().c_str()); cachePath_ = fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID(); @@ -233,7 +233,8 @@ RingAccount::newOutgoingCall(const std::string& id, const std::string& toUrl) void RingAccount::createOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to_id, IpAddr target) { - RING_WARN("RingAccount::createOutgoingCall to: %s target: %s tlsListener: %d", to_id.c_str(), target.toString(true).c_str(), tlsListener_?1:0); + RING_WARN("RingAccount::createOutgoingCall to: %s target: %s", + to_id.c_str(), target.toString(true).c_str()); call->initIceTransport(true); call->setIPToIP(true); call->setPeerNumber(getToUri(to_id+"@"+target.toString(true).c_str())); @@ -368,9 +369,6 @@ void RingAccount::serialize(YAML::Emitter &out) out << YAML::BeginMap; SIPAccountBase::serialize(out); out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort_; - out << YAML::Key << Conf::DHT_PRIVKEY_PATH_KEY << YAML::Value << privkeyPath_; - out << YAML::Key << Conf::DHT_CERT_PATH_KEY << YAML::Value << certPath_; - out << YAML::Key << Conf::DHT_CA_CERT_PATH_KEY << YAML::Value << cacertPath_; // tls submap out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap; @@ -389,22 +387,19 @@ void RingAccount::unserialize(const YAML::Node &node) parseValue(node, Conf::DHT_PORT_KEY, port); dhtPort_ = port ? port : DHT_DEFAULT_PORT; dhtPortUsed_ = dhtPort_; - parseValue(node, Conf::DHT_PRIVKEY_PATH_KEY, privkeyPath_); - parseValue(node, Conf::DHT_CERT_PATH_KEY, certPath_); - parseValue(node, Conf::DHT_CA_CERT_PATH_KEY, cacertPath_); checkIdentityPath(); } void RingAccount::checkIdentityPath() { - if (not privkeyPath_.empty() and not dataPath_.empty()) + if (not tlsPrivateKeyFile_.empty() and not tlsCertificateFile_.empty()) return; const auto idPath = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID(); - privkeyPath_ = idPath + DIR_SEPARATOR_STR "dht.key"; - certPath_ = idPath + DIR_SEPARATOR_STR "dht.crt"; - cacertPath_ = idPath + DIR_SEPARATOR_STR "ca.crt"; + tlsPrivateKeyFile_ = idPath + DIR_SEPARATOR_STR "dht.key"; + tlsCertificateFile_ = idPath + DIR_SEPARATOR_STR "dht.crt"; + tlsCaListFile_ = idPath + DIR_SEPARATOR_STR "ca.crt"; } std::vector<uint8_t> @@ -445,9 +440,9 @@ RingAccount::loadIdentity() dht::crypto::PrivateKey dht_key; try { - ca_cert = dht::crypto::Certificate(fileutils::loadFile(cacertPath_)); - dht_cert = dht::crypto::Certificate(fileutils::loadFile(certPath_)); - dht_key = dht::crypto::PrivateKey(fileutils::loadFile(privkeyPath_)); + ca_cert = dht::crypto::Certificate(fileutils::loadFile(tlsCaListFile_)); + dht_cert = dht::crypto::Certificate(fileutils::loadFile(tlsCertificateFile_)); + dht_key = dht::crypto::PrivateKey(fileutils::loadFile(tlsPrivateKeyFile_)); } catch (const std::exception& e) { RING_ERR("Error loading identity: %s", e.what()); @@ -463,11 +458,11 @@ RingAccount::loadIdentity() fileutils::check_dir(idPath_.c_str()); saveIdentity(ca, idPath_ + DIR_SEPARATOR_STR "ca"); - cacertPath_ = idPath_ + DIR_SEPARATOR_STR "ca.crt"; + tlsCaListFile_ = idPath_ + DIR_SEPARATOR_STR "ca.crt"; saveIdentity(id, idPath_ + DIR_SEPARATOR_STR "dht"); - certPath_ = idPath_ + DIR_SEPARATOR_STR "dht.crt"; - privkeyPath_ = idPath_ + DIR_SEPARATOR_STR "dht.key"; + tlsCertificateFile_ = idPath_ + DIR_SEPARATOR_STR "dht.crt"; + tlsPrivateKeyFile_ = idPath_ + DIR_SEPARATOR_STR "dht.key"; return {ca.second, id}; } @@ -511,20 +506,13 @@ void RingAccount::setAccountDetails(const std::map<std::string, std::string> &de if (dhtPort_ == 0) dhtPort_ = DHT_DEFAULT_PORT; dhtPortUsed_ = dhtPort_; - parseString(details, Conf::CONFIG_DHT_PRIVKEY_PATH, privkeyPath_); - parseString(details, Conf::CONFIG_DHT_CERT_PATH, certPath_); checkIdentityPath(); } std::map<std::string, std::string> RingAccount::getAccountDetails() const { std::map<std::string, std::string> a = SIPAccountBase::getAccountDetails(); - - std::stringstream dhtport; - dhtport << dhtPort_; - a[Conf::CONFIG_DHT_PORT] = dhtport.str(); - a[Conf::CONFIG_DHT_PRIVKEY_PATH] = privkeyPath_; - a[Conf::CONFIG_DHT_CERT_PATH] = certPath_; + a[Conf::CONFIG_DHT_PORT] = ring::to_string(dhtPort_); return a; } @@ -540,15 +528,70 @@ RingAccount::handleEvents() auto call = c->call.lock(); if (not call) { RING_WARN("Removing deleted call from pending calls"); - dht_.cancelListen(c->call_key, c->listen_key.get()); + if (c->call_key != dht::InfoHash()) + dht_.cancelListen(c->call_key, c->listen_key.get()); c = pendingCalls_.erase(c); continue; } auto ice = c->ice.get(); if (ice->isRunning()) { - call->setTransport(link_->sipTransportBroker->getIceTransport(c->ice, ICE_COMP_SIP_TRANSPORT)); + regenerateCAList(); + auto id = loadIdentity(); + auto remote_h = c->id; + std::shared_ptr<gnutls_dh_params_int> dh; + { + std::unique_lock<std::mutex> l(dhParamsMtx_); + dhParamsCv_.wait(l, [&]() { + return static_cast<bool>(dhParams_); + }); + dh = dhParams_; + } + tls::TlsParams tlsParams { + .ca_list = caListPath_, + .id = id.second, + .dh_params = dh, + .timeout = std::chrono::seconds(30), + .cert_check = [remote_h](unsigned status, + const gnutls_datum_t* cert_list, + unsigned cert_num) -> pj_status_t { + RING_WARN("TLS certificate check for %s", + remote_h.toString().c_str()); + + if (status & GNUTLS_CERT_EXPIRED || + status & GNUTLS_CERT_NOT_ACTIVATED) + return PJ_SSL_CERT_EVALIDITY_PERIOD; + else if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + return PJ_SSL_CERT_EUNTRUSTED; + + if (cert_num == 0) + return PJ_SSL_CERT_EUNKNOWN; + + try { + std::vector<uint8_t> crt_blob(cert_list[0].data, + cert_list[0].data + cert_list[0].size); + dht::crypto::Certificate crt(crt_blob); + const auto tls_id = crt.getId(); + if (crt.getUID() != tls_id.toString()) { + RING_WARN("Certificate UID must be the public key ID"); + return PJ_SSL_CERT_EUNTRUSTED; + } + + if (tls_id != remote_h) { + RING_WARN("Certificate public key (ID %s) doesn't match expectation (%s)", + tls_id.toString().c_str(), + remote_h.toString().c_str()); + return PJ_SSL_CERT_EUNTRUSTED; + } + } catch (const std::exception& e) { + return PJ_SSL_CERT_EUNKNOWN; + } + return PJ_SUCCESS; + } + }; + auto tr = link_->sipTransportBroker->getTlsIceTransport(c->ice, ICE_COMP_SIP_TRANSPORT, tlsParams); + call->setTransport(tr); call->setConnectionState(Call::PROGRESSING); - if (c->id == dht::InfoHash()) { + if (c->call_key == dht::InfoHash()) { RING_WARN("ICE succeeded : moving incomming call to pending sip call"); auto in = c; ++c; @@ -561,7 +604,7 @@ RingAccount::handleEvents() } } else if (ice->isFailed() || now - c->start > std::chrono::seconds(ICE_NEGOTIATION_TIMEOUT)) { RING_WARN("ICE timeout : removing pending outgoing call"); - if (c->id != dht::InfoHash()) + if (c->call_key != dht::InfoHash()) dht_.cancelListen(c->call_key, c->listen_key.get()); call->setConnectionState(Call::DISCONNECTED); Manager::instance().callFailure(*call); @@ -606,6 +649,10 @@ void RingAccount::doRegister() return; } + if (not dhParams_) { + generateDhParams(); + } + /* if UPnP is enabled, then wait for IGD to complete registration */ if ( upnpEnabled_ ) { auto shared = shared_from_this(); @@ -637,22 +684,10 @@ void RingAccount::doRegister_() case dht::Dht::Status::Connecting: case dht::Dht::Status::Connected: setRegistrationState(status == dht::Dht::Status::Connected ? RegistrationState::REGISTERED : RegistrationState::TRYING); - /*if (!tlsListener_) { - initTlsConfiguration(); - tlsListener_ = link_->sipTransport->getTlsListener( - SipTransportDescr {getTransportType(), getTlsListenerPort(), getLocalInterface()}, - getTlsSetting()); - if (!tlsListener_) { - setRegistrationState(RegistrationState::ERROR_GENERIC); - RING_ERR("Error creating TLS listener."); - return; - } - }*/ break; case dht::Dht::Status::Disconnected: default: setRegistrationState(status == dht::Dht::Status::Disconnected ? RegistrationState::UNREGISTERED : RegistrationState::ERROR_GENERIC); - tlsListener_.reset(); break; } }); @@ -723,13 +758,14 @@ void RingAccount::doRegister_() RING_DBG("Ignoring non encrypted or bad type value %s.", v->toString().c_str()); continue; } - if (v->owner.getId() == this_.dht_.getId()) + auto remote_id = v->owner.getId(); + if (remote_id == this_.dht_.getId()) continue; auto res = this_.treatedCalls_.insert(v->id); this_.saveTreatedCalls(); if (!res.second) continue; - auto from = v->owner.getId().toString(); + auto from = remote_id.toString(); auto from_vid = v->id; auto reply_vid = from_vid+1; RING_WARN("Received incomming DHT call request from %s (vid %llx) !!", from.c_str(), from_vid); @@ -748,7 +784,7 @@ void RingAccount::doRegister_() this_.dht_.putEncrypted( listenKey, - v->owner.getId(), + remote_id, dht::Value { this_.ICE_ANNOUCEMENT_TYPE.id, ice->getLocalAttributesAndCandidates(), @@ -772,7 +808,7 @@ void RingAccount::doRegister_() call->initRecFilename(from); { std::lock_guard<std::mutex> lock(this_.callsMutex_); - this_.pendingCalls_.emplace_back(PendingCall{std::chrono::steady_clock::now(), ice, weak_call, {}, {}, {}}); + this_.pendingCalls_.emplace_back(PendingCall{std::chrono::steady_clock::now(), ice, weak_call, {}, {}, remote_id}); } return true; } catch (const std::exception& e) { @@ -809,7 +845,6 @@ void RingAccount::doUnregister(std::function<void(bool)> released_cb) saveNodes(dht_.exportNodes()); saveValues(dht_.exportValues()); dht_.join(); - tlsListener_.reset(); setRegistrationState(RegistrationState::UNREGISTERED); if (released_cb) released_cb(false); @@ -888,7 +923,7 @@ RingAccount::regenerateCAList() } auto cas = getRegistredCAs(); { - std::ifstream file(cacertPath_, std::ios::binary); + std::ifstream file(tlsCaListFile_, std::ios::binary); list << file.rdbuf(); } for (const auto& ca : cas) { @@ -969,25 +1004,39 @@ RingAccount::loadValues() const return values; } -void RingAccount::initTlsConfiguration() +void +RingAccount::initTlsConfiguration() { - // TLS listener is unique and should be only modified through IP2IP_PROFILE - pjsip_tls_setting_default(&tlsSetting_); regenerateCAList(); - pj_cstr(&tlsSetting_.ca_list_file, caListPath_.c_str()); - pj_cstr(&tlsSetting_.cert_file, certPath_.c_str()); - pj_cstr(&tlsSetting_.privkey_file, privkeyPath_.c_str()); - pj_cstr(&tlsSetting_.password, ""); - tlsSetting_.method = PJSIP_TLSV1_METHOD; - tlsSetting_.ciphers_num = 0; - tlsSetting_.ciphers = nullptr; - tlsSetting_.verify_server = false; - tlsSetting_.verify_client = false; - tlsSetting_.require_client_cert = false; - tlsSetting_.timeout.sec = 2; - tlsSetting_.qos_type = PJ_QOS_TYPE_BEST_EFFORT; - tlsSetting_.qos_ignore_error = PJ_TRUE; +} + +void +RingAccount::generateDhParams() +{ + auto shared = shared_from_this(); + std::weak_ptr<RingAccount> shared_w = std::static_pointer_cast<RingAccount>(shared); + auto t = std::thread([shared_w](){ + using namespace std::chrono; + auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_NORMAL */ GNUTLS_SEC_PARAM_HIGH); + RING_DBG("Generating DH params with %u bits", bits); + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + gnutls_dh_params_t new_params_; + gnutls_dh_params_init(&new_params_); + gnutls_dh_params_generate2(new_params_, bits); + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration<double> time_span = duration_cast<duration<double>>(t2 - t1); + RING_WARN("Generated DH params with %u bits in %lfs", bits, time_span.count()); + std::shared_ptr<gnutls_dh_params_int> sp(new_params_, gnutls_dh_params_deinit); + if (auto s = shared_w.lock()) { + { + std::lock_guard<std::mutex> l(s->dhParamsMtx_); + s->dhParams_ = std::move(sp); + } + s->dhParamsCv_.notify_all(); + } + }); + t.detach(); } void RingAccount::loadConfig() @@ -1018,8 +1067,8 @@ std::string RingAccount::getFromUri() const std::string RingAccount::getToUri(const std::string& to) const { const std::string transport {pjsip_transport_get_type_name(transportType_)}; - return "<sip:" + to + ">"; - //return "<sips:" + to + ";transport=" + transport + ">"; + //return "<sip:" + to + ">"; + return "<sips:" + to + ";transport=" + transport + ">"; } pj_str_t @@ -1029,53 +1078,20 @@ RingAccount::getContactHeader(pjsip_transport* t) t = transport_->get(); if (!t) { RING_ERR("Transport not created yet"); - pj_cstr(&contact_, "<sip:>"); + pj_cstr(&contact_, "<sips:>"); return contact_; } // FIXME: be sure that given transport is from SipIceTransport - auto ice = reinterpret_cast<SipIceTransport::TransportData*>(t)->self; - - // The transport type must be specified, in our case START_OTHER refers to stun transport - /*pjsip_transport_type_e transportType = transportType_; - - if (transportType == PJSIP_TRANSPORT_START_OTHER) - transportType = PJSIP_TRANSPORT_UDP;*/ - - // Else we determine this infor based on transport information - //std::string address = "ring.dht"; - //pj_uint16_t port = getTlsListenerPort(); - - //link_->sipTransport->findLocalAddressFromTransport(t, transportType, hostname_, address, port); - auto address = ice->getLocalAddress(); - /*if (addr) { - address = addr; - port = - }*/ - /*auto ports = ice->getLocalPorts(); - if (not ports.empty()) - port = ports[0];*/ -/* -#if HAVE_IPV6 - // Enclose IPv6 address in square brackets - if (IpAddr::isIpv6(address)) { - address = IpAddr(address);//.toString(false, true); - } -#endif -*/ + auto tlsTr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(t)->self; + auto address = tlsTr->getLocalAddress(); RING_WARN("getContactHeader %s@%s", username_.c_str(), address.toString(true).c_str()); contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, - "<sip:%s%s%s>", - username_.c_str(), - (username_.empty() ? "" : "@"), - address.toString(true).c_str()); /* - contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, - "<sips:%s%s%s:%d;transport=%s>", + "<sips:%s%s%s;transport=%s>", username_.c_str(), (username_.empty() ? "" : "@"), - address.c_str(), - port, - pjsip_transport_get_type_name(transportType));*/ + address.toString(true).c_str(), + pjsip_transport_get_type_name(transportType_)); return contact_; } diff --git a/daemon/src/ringdht/ringaccount.h b/daemon/src/ringdht/ringaccount.h index 5faa843acdb058113a5d8ab066f9b2b36b426272..4c48d47be99093a6ebf13519d901409f037bf4f5 100644 --- a/daemon/src/ringdht/ringaccount.h +++ b/daemon/src/ringdht/ringaccount.h @@ -48,6 +48,7 @@ #include <vector> #include <map> #include <chrono> +#include <list> /** * @file sipaccount.h @@ -63,10 +64,6 @@ namespace ring { namespace Conf { const char *const DHT_PORT_KEY = "dhtPort"; - const char *const DHT_PRIVKEY_PATH_KEY = "dhtPrivkeyPath"; - const char *const DHT_PRIVKEY_PASSWORD_KEY = "dhtPrivkeyPassword"; - const char *const DHT_CERT_PATH_KEY = "dhtCertificatePath"; - const char *const DHT_CA_CERT_PATH_KEY = "dhtCACertificatePath"; const char *const DHT_VALUES_PATH_KEY = "dhtValuesPath"; } @@ -235,11 +232,11 @@ class RingAccount : public SIPAccountBase { newIncomingCall(const std::string& from = {}); virtual bool isTlsEnabled() const { - return false; + return true; } virtual bool getSrtpEnabled() const { - return false; + return true; } virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const { @@ -287,15 +284,6 @@ class RingAccount : public SIPAccountBase { */ bool mapPortUPnP(); - /** - * @return pjsip_tls_setting structure, filled from the configuration - * file, that can be used directly by PJSIP to initialize - * TLS transport. - */ - pjsip_tls_setting * getTlsSetting() { - return &tlsSetting_; - } - dht::DhtRunner dht_ {}; struct PendingCall { @@ -318,11 +306,7 @@ class RingAccount : public SIPAccountBase { std::set<dht::Value::Id> treatedCalls_ {}; mutable std::mutex callsMutex_ {}; - std::string cacertPath_ {}; - std::string privkeyPath_ {}; - std::string certPath_ {}; std::string idPath_ {}; - std::string cachePath_ {}; std::string dataPath_ {}; std::string caPath_ {}; @@ -371,7 +355,10 @@ class RingAccount : public SIPAccountBase { /** * The TLS settings, used only if tls is chosen as a sip transport. */ - pjsip_tls_setting tlsSetting_; + void generateDhParams(); + std::shared_ptr<gnutls_dh_params_int> dhParams_; + std::mutex dhParamsMtx_; + std::condition_variable dhParamsCv_; /** * Optional: "received" parameter from VIA header diff --git a/daemon/src/ringdht/sips_transport_ice.cpp b/daemon/src/ringdht/sips_transport_ice.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ac1fe466c06468505376073c5cc3d60d92b90b0a --- /dev/null +++ b/daemon/src/ringdht/sips_transport_ice.cpp @@ -0,0 +1,1160 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sips_transport_ice.h" +#include "ice_transport.h" +#include "logger.h" + +#include <pjsip/sip_transport.h> +#include <pjsip/sip_endpoint.h> +#include <pj/compat/socket.h> +#include <pj/lock.h> + +#include <algorithm> + +namespace ring { namespace tls { + +static constexpr int POOL_TP_INIT {512}; +static constexpr int POOL_TP_INC {512}; +static constexpr int TRANSPORT_INFO_LENGTH {64}; +static constexpr int GNUTLS_LOG_LEVEL {8}; + +static void +sockaddr_to_host_port(pj_pool_t* pool, + pjsip_host_port* host_port, + const pj_sockaddr* addr) +{ + host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4); + pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 0); + host_port->host.slen = pj_ansi_strlen(host_port->host.ptr); + host_port->port = pj_sockaddr_get_port(addr); +} + +static void tls_print_logs(int level, const char* msg) +{ + if (level < 3) + return; + RING_DBG("GnuTLS [%d]: %s", level, msg); +} + + +static pj_status_t tls_status_from_err(int err) +{ + pj_status_t status; + + switch (err) { + case GNUTLS_E_SUCCESS: + status = PJ_SUCCESS; + break; + case GNUTLS_E_MEMORY_ERROR: + status = PJ_ENOMEM; + break; + case GNUTLS_E_LARGE_PACKET: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_NO_CERTIFICATE_FOUND: + status = PJ_ENOTFOUND; + break; + case GNUTLS_E_SESSION_EOF: + status = PJ_EEOF; + break; + case GNUTLS_E_HANDSHAKE_TOO_LARGE: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_EXPIRED: + status = PJ_EGONE; + break; + case GNUTLS_E_TIMEDOUT: + status = PJ_ETIMEDOUT; + break; + case GNUTLS_E_PREMATURE_TERMINATION: + status = PJ_ECANCELLED; + break; + case GNUTLS_E_INTERNAL_ERROR: + case GNUTLS_E_UNIMPLEMENTED_FEATURE: + status = PJ_EBUG; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_REHANDSHAKE: + status = PJ_EPENDING; + break; + case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: + case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: + case GNUTLS_E_RECORD_LIMIT_REACHED: + status = PJ_ETOOMANY; + break; + case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: + case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: + case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: + case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: + case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: + case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: + status = PJ_ENOTSUP; + break; + case GNUTLS_E_INVALID_SESSION: + case GNUTLS_E_INVALID_REQUEST: + case GNUTLS_E_INVALID_PASSWORD: + case GNUTLS_E_ILLEGAL_PARAMETER: + case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: + case GNUTLS_E_UNEXPECTED_PACKET: + case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: + case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: + case GNUTLS_E_UNWANTED_ALGORITHM: + case GNUTLS_E_USER_ERROR: + status = PJ_EINVAL; + break; + default: + status = PJ_EUNKNOWN; + break; + } + + /* Not thread safe */ + /*tls_last_error = err; + if (ssock) + ssock->last_err = err;*/ + return status; +} + +pj_status_t +SipsIceTransport::tryHandshake() +{ + RING_DBG("SipsIceTransport::tryHandshake as %s", + (is_server_ ? "server" : "client")); + pj_status_t status; + int ret = gnutls_handshake(session_); + if (ret == GNUTLS_E_SUCCESS) { + /* System are GO */ + RING_DBG("SipsIceTransport::tryHandshake : ESTABLISHED"); + state_ = TlsConnectionState::ESTABLISHED; + status = PJ_SUCCESS; + } else if (!gnutls_error_is_fatal(ret)) { + /* Non fatal error, retry later (busy or again) */ + RING_DBG("SipsIceTransport::tryHandshake : EPENDING"); + status = PJ_EPENDING; + } else { + /* Fatal error invalidates session, no fallback */ + RING_DBG("SipsIceTransport::tryHandshake : EINVAL"); + status = PJ_EINVAL; + } + last_err_ = ret; + return status; +} + +int +SipsIceTransport::verifyCertificate() +{ + RING_DBG("SipsIceTransport::verifyCertificate"); + + unsigned int status; + int ret; + + /* Support only x509 format */ + ret = gnutls_certificate_type_get(session_) != GNUTLS_CRT_X509; + if (ret < 0) + return GNUTLS_E_CERTIFICATE_ERROR; + + /* Store verification status */ + ret = gnutls_certificate_verify_peers2(session_, &status); + if (ret < 0 || status & GNUTLS_CERT_SIGNATURE_FAILURE) + return GNUTLS_E_CERTIFICATE_ERROR; + + unsigned int cert_list_size; + const gnutls_datum_t *cert_list; + + cert_list = gnutls_certificate_get_peers(session_, &cert_list_size); + if (cert_list == NULL) { + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (param_.cert_check) { + pj_status_t check_ret = param_.cert_check(status, cert_list, + cert_list_size); + if (check_ret != PJ_SUCCESS) { + return GNUTLS_E_CERTIFICATE_ERROR; + } + } + + /* notify GnuTLS to continue handshake normally */ + return GNUTLS_E_SUCCESS; +} + +SipsIceTransport::SipsIceTransport(pjsip_endpoint* endpt, + const TlsParams& param, + const std::shared_ptr<ring::IceTransport>& ice, + int comp_id) + : pool_(nullptr, pj_pool_release) + , rxPool_(nullptr, pj_pool_release) + , trData_() + , ice_(ice) + , comp_id_(comp_id) + , param_(param) + , tlsThread_( + std::bind(&SipsIceTransport::setup, this), + std::bind(&SipsIceTransport::loop, this), + std::bind(&SipsIceTransport::clean, this)) +{ + trData_.self = this; + + if (not ice or not ice->isRunning()) + throw std::logic_error("ICE transport must exist and negotiation completed"); + + RING_DBG("SipIceTransport@%p {tr=%p}", this, &trData_.base); + auto& base = trData_.base; + + pool_.reset(pjsip_endpt_create_pool(endpt, "SipsIceTransport.pool", + POOL_TP_INIT, POOL_TP_INC)); + if (not pool_) { + RING_ERR("Can't create PJSIP pool"); + throw std::bad_alloc(); + } + auto pool = pool_.get(); + + pj_ansi_snprintf(base.obj_name, PJ_MAX_OBJ_NAME, "SipsIceTransport"); + base.endpt = endpt; + base.tpmgr = pjsip_endpt_get_tpmgr(endpt); + base.pool = pool; + + if (pj_atomic_create(pool, 0, &base.ref_cnt) != PJ_SUCCESS) + throw std::runtime_error("Can't create PJSIP atomic."); + + if (pj_lock_create_recursive_mutex(pool, "SipsIceTransport.mutex", + &base.lock) != PJ_SUCCESS) + throw std::runtime_error("Can't create PJSIP mutex."); + + is_server_ = not ice->isInitiator(); + local_ = ice->getLocalAddress(comp_id); + remote_ = ice->getRemoteAddress(comp_id); + pj_sockaddr_cp(&base.key.rem_addr, remote_.pjPtr()); + base.key.type = PJSIP_TRANSPORT_TLS; + base.type_name = (char*)pjsip_transport_get_type_name((pjsip_transport_type_e)base.key.type); + base.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)base.key.type); + base.info = (char*) pj_pool_alloc(pool, TRANSPORT_INFO_LENGTH); + + char print_addr[PJ_INET6_ADDRSTRLEN+10]; + pj_ansi_snprintf(base.info, TRANSPORT_INFO_LENGTH, "%s to %s", + base.type_name, + pj_sockaddr_print(remote_.pjPtr(), print_addr, + sizeof(print_addr), 3)); + base.addr_len = remote_.getLength(); + base.dir = PJSIP_TP_DIR_NONE; ///is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING; + base.data = nullptr; + + /* Set initial local address */ + auto local = ice->getDefaultLocalAddress(); + pj_sockaddr_cp(&base.local_addr, local.pjPtr()); + + sockaddr_to_host_port(pool, &base.local_name, &base.local_addr); + sockaddr_to_host_port(pool, &base.remote_name, remote_.pjPtr()); + + base.send_msg = [](pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, int addr_len, + void *token, pjsip_transport_callback callback) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + return this_->send(tdata, rem_addr, addr_len, token, callback); + }; + base.do_shutdown = [](pjsip_transport *transport) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + RING_WARN("SipsIceTransport@%p: shutdown", this_); + this_->reset(); + return PJ_SUCCESS; + }; + base.destroy = [](pjsip_transport *transport) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + RING_WARN("SipsIceTransport@%p: destroy", this_); + delete this_; + return PJ_SUCCESS; + }; + + /* Init rdata_ */ + rxPool_.reset(pjsip_endpt_create_pool(base.endpt, + "SipsIceTransport.rtd%p", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_LEN)); + if (not rxPool_) { + RING_ERR("Can't create PJSIP rx pool"); + throw std::bad_alloc(); + } + pj_bzero(&rdata_, sizeof(pjsip_rx_data)); + rdata_.tp_info.pool = rxPool_.get(); + rdata_.tp_info.transport = &base; + rdata_.tp_info.tp_data = this; + rdata_.tp_info.op_key.rdata = &rdata_; + pj_ioqueue_op_key_init(&rdata_.tp_info.op_key.op_key, + sizeof(pj_ioqueue_op_key_t)); + rdata_.pkt_info.src_addr = base.key.rem_addr; + rdata_.pkt_info.src_addr_len = sizeof(rdata_.pkt_info.src_addr); + auto rem_addr = &base.key.rem_addr; + pj_sockaddr_print(rem_addr, rdata_.pkt_info.src_name, + sizeof(rdata_.pkt_info.src_name), 0); + rdata_.pkt_info.src_port = pj_sockaddr_get_port(rem_addr); + rdata_.pkt_info.len = 0; + rdata_.pkt_info.zero = 0; + + pj_bzero(&local_cert_info_, sizeof(pj_ssl_cert_info)); + pj_bzero(&remote_cert_info_, sizeof(pj_ssl_cert_info)); + + /* Register error subsystem */ + /*pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER + + PJ_ERRNO_SPACE_SIZE * 6, + PJ_ERRNO_SPACE_SIZE, + &tls_strerror); + pj_assert(status == PJ_SUCCESS);*/ + + /* Init GnuTLS library */ + int ret = gnutls_global_init(); + if (ret < 0) + throw std::runtime_error("Can't initialise GNUTLS : " + + std::string(gnutls_strerror(ret))); + + gnutls_global_set_log_level(GNUTLS_LOG_LEVEL); + gnutls_global_set_log_function(tls_print_logs); + + gnutls_priority_init(&priority_cache, + "SECURE192:-VERS-TLS-ALL:+VERS-DTLS1.0:%SERVER_PRECEDENCE", + nullptr); + + if (pjsip_transport_register(base.tpmgr, &base) != PJ_SUCCESS) + throw std::runtime_error("Can't register PJSIP transport."); + is_registered_ = true; + + ice_->setOnRecv(comp_id_, [this](uint8_t* buf, size_t len) { + { + std::lock_guard<std::mutex> l(inputBuffMtx_); + tlsInputBuff_.emplace_back(buf, buf+len); + canRead_ = true; + RING_DBG("Ice: got data at %lu", + clock::now().time_since_epoch().count()); + } + cv_.notify_all(); + return len; + }); + + tlsThread_.start(); +} + +SipsIceTransport::~SipsIceTransport() +{ + RING_DBG("~SipsIceTransport"); + reset(); + ice_->setOnRecv(comp_id_, nullptr); + tlsThread_.join(); + + /* Free GnuTLS library */ + gnutls_global_deinit(); + + pj_lock_destroy(trData_.base.lock); + pj_atomic_destroy(trData_.base.ref_cnt); +} + +pj_status_t +SipsIceTransport::startTlsSession() +{ + RING_DBG("SipsIceTransport::startTlsSession as %s", + (is_server_ ? "server" : "client")); + int ret; + ret = gnutls_init(&session_, (is_server_ ? GNUTLS_SERVER : GNUTLS_CLIENT) | GNUTLS_DATAGRAM); + if (ret != GNUTLS_E_SUCCESS) { + reset(); + return tls_status_from_err(ret); + } + + gnutls_session_set_ptr(session_, this); + gnutls_transport_set_ptr(session_, this); + + gnutls_priority_set(session_, priority_cache); + + /* Allocate credentials for handshaking and transmission */ + ret = gnutls_certificate_allocate_credentials(&xcred_); + if (ret < 0) { + RING_ERR("Can't allocate credentials"); + reset(); + return PJ_ENOMEM; + } + + if (is_server_) + gnutls_certificate_set_dh_params(xcred_, param_.dh_params.get()); + + gnutls_certificate_set_verify_function(xcred_, [](gnutls_session_t session) { + auto this_ = reinterpret_cast<SipsIceTransport*>(gnutls_session_get_ptr(session)); + return this_->verifyCertificate(); + }); + + if (not param_.ca_list.empty()) { + /* Load CA if one is specified. */ + ret = gnutls_certificate_set_x509_trust_file(xcred_, + param_.ca_list.c_str(), + GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_file(xcred_, + param_.ca_list.c_str(), + GNUTLS_X509_FMT_DER); + if (ret < 0) + throw std::runtime_error("Can't load CA."); + RING_WARN("Loaded %s", param_.ca_list.c_str()); + + if (param_.id.first) { + /* Load certificate, key and pass */ + ret = gnutls_certificate_set_x509_key(xcred_, + ¶m_.id.second->cert, 1, + param_.id.first->x509_key); + if (ret < 0) + throw std::runtime_error("Can't load certificate : " + + std::string(gnutls_strerror(ret))); + } + } + + /* Finally set credentials for this session */ + ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, xcred_); + if (ret != GNUTLS_E_SUCCESS) { + reset(); + return tls_status_from_err(ret); + } + + if (is_server_) { + /* Require client certificate and valid cookie */ + gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE); + gnutls_dtls_prestate_set(session_, &prestate_); + } + int mtu = 3200; + gnutls_dtls_set_mtu(session_, mtu); + + gnutls_transport_set_push_function(session_, [](gnutls_transport_ptr_t t, + const void* d , + size_t s) -> ssize_t { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->tlsSend(d, s); + }); + gnutls_transport_set_pull_function(session_, [](gnutls_transport_ptr_t t, + void* d, + size_t s) -> ssize_t { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->tlsRecv(d, s); + }); + gnutls_transport_set_pull_timeout_function(session_, [](gnutls_transport_ptr_t t, + unsigned ms) -> int { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->waitForTlsData(ms); + }); + + // start handshake + handshakeStart_ = clock::now(); + state_ = TlsConnectionState::HANDSHAKING; +} + +void +SipsIceTransport::certGetCn(const pj_str_t *gen_name, pj_str_t *cn) +{ + pj_str_t CN_sign = {(char*)"CN=", 3}; + char *p, *q; + + pj_bzero(cn, sizeof(cn)); + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 3; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, ','); + if (q) + cn->slen = q - p; +} + +/* Get certificate info; in case the certificate info is already populated, + * this function will check if the contents need updating by inspecting the + * issuer and the serial number. */ +void +SipsIceTransport::certGetInfo(pj_pool_t *pool, pj_ssl_cert_info *ci, + gnutls_x509_crt_t cert) +{ + RING_DBG("SipsIceTransport::certGetInfo"); + char buf[512] = { 0 }; + size_t bufsize = sizeof(buf); + std::array<uint8_t, sizeof(ci->serial_no)> serial_no; /* should be >= sizeof(ci->serial_no) */ + size_t serialsize = serial_no.size(); + size_t len = sizeof(buf); + int i, ret, seq = 0; + pj_ssl_cert_name_type type; + + pj_assert(pool && ci && cert); + + /* Get issuer */ + gnutls_x509_crt_get_issuer_dn(cert, buf, &bufsize); + + /* Get serial no */ + gnutls_x509_crt_get_serial(cert, serial_no.data(), &serialsize); + + /* Check if the contents need to be updated */ + if (not pj_strcmp2(&ci->issuer.info, buf) and + not std::memcmp(ci->serial_no, serial_no.data(), serialsize)) + return; + + /* Update cert info */ + pj_bzero(ci, sizeof(pj_ssl_cert_info)); + + /* Version */ + ci->version = gnutls_x509_crt_get_version(cert); + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + certGetCn(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + //pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); + std::copy(serial_no.cbegin(), serial_no.cend(), (uint8_t*)ci->serial_no); + + /* Subject */ + bufsize = sizeof(buf); + gnutls_x509_crt_get_dn(cert, buf, &bufsize); + pj_strdup2(pool, &ci->subject.info, buf); + certGetCn(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(cert); + ci->validity.start.sec = gnutls_x509_crt_get_activation_time(cert); + ci->validity.gmt = 0; + + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + char out[256] = { 0 }; + /* Get the number of all alternate names so that we can allocate + * the correct number of bytes in subj_alt_name */ + while (gnutls_x509_crt_get_subject_alt_name(cert, seq, out, &len, NULL) != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + seq++; + + ci->subj_alt_name.entry = \ + (decltype(ci->subj_alt_name.entry))pj_pool_calloc(pool, seq, sizeof(*ci->subj_alt_name.entry)); + if (!ci->subj_alt_name.entry) { + last_err_ = GNUTLS_E_MEMORY_ERROR; + return; + } + + /* Now populate the alternative names */ + for (i = 0; i < seq; i++) { + len = sizeof(out) - 1; + ret = gnutls_x509_crt_get_subject_alt_name(cert, i, out, &len, NULL); + switch (ret) { + case GNUTLS_SAN_IPADDRESS: + type = PJ_SSL_CERT_NAME_IP; + pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6() : pj_AF_INET(), + out, buf, sizeof(buf)); + break; + case GNUTLS_SAN_URI: + type = PJ_SSL_CERT_NAME_URI; + break; + case GNUTLS_SAN_RFC822NAME: + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GNUTLS_SAN_DNSNAME: + type = PJ_SSL_CERT_NAME_DNS; + break; + default: + type = PJ_SSL_CERT_NAME_UNKNOWN; + break; + } + + if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + type == PJ_SSL_CERT_NAME_IP ? buf : out); + ci->subj_alt_name.cnt++; + } + } + /* TODO: if no DNS alt. names were found, we could check against + * the commonName as per RFC3280. */ + } +} + + +/* Update local & remote certificates info. This function should be + * called after handshake or renegotiation successfully completed. */ +void +SipsIceTransport::certUpdate() +{ + RING_DBG("SipsIceTransport::certUpdate"); + gnutls_x509_crt_t cert = NULL; + const gnutls_datum_t *us; + const gnutls_datum_t *certs; + unsigned int certslen = 0; + int ret = GNUTLS_CERT_INVALID; + + + //pj_assert(ssock->connection_state == TLS_STATE_ESTABLISHED); + + /* Get active local certificate */ + us = gnutls_certificate_get_ours(session_); + if (!us) + goto us_out; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto us_out; + ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_DER); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_PEM); + if (ret < 0) + goto us_out; + + certGetInfo(pool_.get(), &local_cert_info_, cert); + +us_out: + last_err_ = ret; + if (cert) + gnutls_x509_crt_deinit(cert); + else + pj_bzero(&local_cert_info_, sizeof(pj_ssl_cert_info)); + + cert = NULL; + + /* Get active remote certificate */ + certs = gnutls_certificate_get_peers(session_, &certslen); + if (certs == NULL || certslen == 0) + goto peer_out; + + ret = gnutls_x509_crt_init(&cert); + if (ret < 0) + goto peer_out; + + ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER); + if (ret < 0) + goto peer_out; + + certGetInfo(pool_.get(), &remote_cert_info_, cert); + +peer_out: + last_err_ = ret; + if (cert) + gnutls_x509_crt_deinit(cert); + else + pj_bzero(&remote_cert_info_, sizeof(pj_ssl_cert_info)); +} + +pj_status_t +SipsIceTransport::getInfo(pj_ssl_sock_info *info) +{ + RING_DBG("SipsIceTransport::getInfo"); + pj_bzero(info, sizeof(*info)); + + /* Established flag */ + info->established = (state_ == TlsConnectionState::ESTABLISHED); + + /* Protocol */ + info->proto = PJ_SSL_SOCK_PROTO_DTLS1; + + /* Local address */ + pj_sockaddr_cp(&info->local_addr, local_.pjPtr()); + + if (info->established) { + int i; + gnutls_cipher_algorithm_t lookup; + gnutls_cipher_algorithm_t cipher; + + /* Current cipher */ + cipher = gnutls_cipher_get(session_); + for (i = 0; ; i++) { + unsigned char id[2]; + const char *suite = gnutls_cipher_suite_info(i, (unsigned char *)id, + NULL, &lookup, + NULL, NULL); + if (suite) { + if (lookup == cipher) { + info->cipher = (pj_ssl_cipher) ((id[0] << 8) | id[1]); + break; + } + } else + break; + } + + /* Remote address */ + pj_sockaddr_cp(&info->remote_addr, remote_.pjPtr()); + + /* Certificates info */ + info->local_cert_info = &local_cert_info_; + info->remote_cert_info = &remote_cert_info_; + + /* Verification status */ + info->verify_status = PJ_SUCCESS;//verify_status_; + } + + /* Last known GnuTLS error code */ + info->last_native_err = last_err_; + + return PJ_SUCCESS; +} + + +/* When handshake completed: + * - notify application + * - if handshake failed, reset SSL state + * - return PJ_FALSE when SSL socket instance is destroyed by application. */ +pj_bool_t +SipsIceTransport::onHandshakeComplete(pj_status_t status) +{ + RING_DBG("SipsIceTransport::onHandshakeComplete %d", status); + pj_bool_t ret = PJ_TRUE; + + /* Cancel handshake timer */ + /*if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) { + pj_timer_heap_cancel(param_.timer_heap, &ssock->timer); + ssock->timer.id = TIMER_NONE; + }*/ + + /* Update certificates info on successful handshake */ + if (status == PJ_SUCCESS) + certUpdate(); + + pj_ssl_sock_info ssl_info; + getInfo(&ssl_info); + + auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr); + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + + /* Init transport state info */ + pj_bzero(&state_info, sizeof(state_info)); + pj_bzero(&tls_info, sizeof(tls_info)); + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + state_info.status = ssl_info.verify_status ? PJSIP_TLS_ECERTVERIF : PJ_SUCCESS; + + /* Accepting */ + if (is_server_) { + if (status != PJ_SUCCESS) { + /* Handshake failed in accepting, destroy our self silently. */ + + char errmsg[PJ_ERR_MSG_SIZE]; + RING_WARN("Handshake failed in accepting %s: %s", + remote_.toString(true).c_str(), + pj_strerror(status, errmsg, sizeof(errmsg)).ptr); + + /* Workaround for ticket #985 */ +/*#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) + if (param_.timer_heap) { + pj_time_val interval = {0, DELAYED_CLOSE_TIMEOUT}; + + reset(); + + timer.id = TIMER_CLOSE; + pj_time_val_normalize(&interval); + if (pj_timer_heap_schedule(param_.timer_heap, &timer, &interval) != 0) { + timer.id = TIMER_NONE; + close(); + } + } else +#endif*/ /* PJ_WIN32 */ + { + reset(); + } + + return PJ_FALSE; + } + /* Notify application the newly accepted SSL socket */ + if (state_cb) + (*state_cb)(getTransportBase(), PJSIP_TP_STATE_CONNECTED, + &state_info); + } else { /* Connecting */ + /* On failure, reset SSL socket state first, as app may try to + * reconnect in the callback. */ + if (status != PJ_SUCCESS) { + /* Server disconnected us, possibly due to negotiation failure */ + reset(); + } + if (state_cb) + (*state_cb)(getTransportBase(), + (status != PJ_SUCCESS) ? PJSIP_TP_STATE_DISCONNECTED : PJSIP_TP_STATE_CONNECTED, + &state_info); + } + + return ret; +} + +bool +SipsIceTransport::setup() +{ + RING_WARN("Starting GnuTLS thread"); + if (is_server_) { + gnutls_key_generate(&cookie_key_, GNUTLS_COOKIE_KEY_SIZE); + state_ = TlsConnectionState::COOKIE; + } else + startTlsSession(); + return true; +} + +void +SipsIceTransport::loop() +{ + if (not ice_->isRunning()) { + reset(); + return; + } + if (state_ == TlsConnectionState::COOKIE) { + { + std::unique_lock<std::mutex> l(inputBuffMtx_); + if (tlsInputBuff_.empty()) { + cv_.wait(l, [&](){ + return state_ != TlsConnectionState::COOKIE or not tlsInputBuff_.empty(); + }); + } + RING_DBG("Cookie: got data at %lu", + clock::now().time_since_epoch().count()); + if (state_ != TlsConnectionState::COOKIE) + return; + const auto& pck = tlsInputBuff_.front(); + memset(&prestate_, 0, sizeof(prestate_)); + int ret = gnutls_dtls_cookie_verify(&cookie_key_, + &trData_.base.key.rem_addr, + trData_.base.addr_len, + (char*)pck.data(), pck.size(), + &prestate_); + if (ret < 0) { + RING_DBG("gnutls_dtls_cookie_send"); + gnutls_dtls_cookie_send(&cookie_key_, + &trData_.base.key.rem_addr, + trData_.base.addr_len, &prestate_, this, + [](gnutls_transport_ptr_t t, + const void* d , + size_t s) -> ssize_t { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->tlsSend(d, s); + }); + tlsInputBuff_.pop_front(); + if (tlsInputBuff_.empty()) + canRead_ = false; + return; + } + } + startTlsSession(); + } + if (state_ == TlsConnectionState::HANDSHAKING) { + if (clock::now() - handshakeStart_ > param_.timeout) { + onHandshakeComplete(PJ_ETIMEDOUT); + return; + } + int status = tryHandshake(); + if (status != PJ_EPENDING) + onHandshakeComplete(status); + } + if (state_ == TlsConnectionState::ESTABLISHED) { + { + std::mutex flagsMtx_ {}; + std::unique_lock<std::mutex> l(flagsMtx_); + cv_.wait(l, [&](){ + return state_ != TlsConnectionState::ESTABLISHED or canRead_ or canWrite_; + }); + } + if (state_ != TlsConnectionState::ESTABLISHED) + return; + while (canRead_) { + if (rdata_.pkt_info.len < 0) + rdata_.pkt_info.len = 0; + ssize_t decrypted_size = gnutls_record_recv(session_, + (uint8_t*)rdata_.pkt_info.packet + rdata_.pkt_info.len, + sizeof(rdata_.pkt_info.packet)-rdata_.pkt_info.len); + rdata_.pkt_info.len += decrypted_size; + RING_WARN("gnutls_record_recv : %d (tot %d)", decrypted_size, + rdata_.pkt_info.len); + if (decrypted_size > 0/* || transport error */) { + rdata_.pkt_info.zero = 0; + pj_gettimeofday(&rdata_.pkt_info.timestamp); + auto eaten = pjsip_tpmgr_receive_packet(trData_.base.tpmgr, + &rdata_); + auto rem = rdata_.pkt_info.len - eaten; + if (rem > 0 && rem != rdata_.pkt_info.len) { + std::move(rdata_.pkt_info.packet + eaten, + rdata_.pkt_info.packet + eaten + rem, + rdata_.pkt_info.packet); + } + rdata_.pkt_info.len = rem; + pj_pool_reset(rdata_.tp_info.pool); + } else if (decrypted_size == 0) { + /* Nothing more to read */ + reset(); + break;// PJ_TRUE; + } else if (decrypted_size == GNUTLS_E_AGAIN or + decrypted_size == GNUTLS_E_INTERRUPTED) { + break;// PJ_TRUE; + } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) { + /* Seems like we are renegotiating */ + pj_status_t try_handshake_status = tryHandshake(); + + /* Not pending is either success or failed */ + if (try_handshake_status != PJ_EPENDING) { + if (!onHandshakeComplete(try_handshake_status)) { + break;// PJ_FALSE; + } + } + + if (try_handshake_status != PJ_SUCCESS and + try_handshake_status != PJ_EPENDING) { + break;// PJ_FALSE; + } + } else if (!gnutls_error_is_fatal(decrypted_size)) { + /* non-fatal error, let's just continue */ + } else { + reset(); + break;// PJ_FALSE; + } + } + flushOutputBuff(); + } +} + +void +SipsIceTransport::clean() +{ + RING_WARN("Ending GnuTLS thread"); + tlsInputBuff_.clear(); + canRead_ = false; + + while (not outputBuff_.empty()) { + auto& f = outputBuff_.front(); + f.tdata_op_key->tdata = nullptr; + if (f.tdata_op_key->callback) + f.tdata_op_key->callback(getTransportBase(), + f.tdata_op_key->token, + -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN)); + outputBuff_.pop_front(); + } + canWrite_ = false; + if (cookie_key_.data) { + gnutls_free(cookie_key_.data); + cookie_key_.data = nullptr; + cookie_key_.size = 0; + } + + pjsip_transport_add_ref(getTransportBase()); + close(); + auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr); + if (state_cb) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + + /* Init transport state info */ + pj_bzero(&state_info, sizeof(state_info)); + pj_bzero(&tls_info, sizeof(tls_info)); + pj_ssl_sock_info ssl_info; + getInfo(&ssl_info); + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + state_info.status = PJ_SUCCESS; + + (*state_cb)(getTransportBase(), PJSIP_TP_STATE_DISCONNECTED, + &state_info); + } + if (trData_.base.is_shutdown or trData_.base.is_destroying) { + pjsip_transport_dec_ref(getTransportBase()); + return; + } + pjsip_transport_shutdown(getTransportBase()); + /* Now, it is ok to destroy the transport. */ + pjsip_transport_dec_ref(getTransportBase()); +} + +IpAddr +SipsIceTransport::getLocalAddress() const +{ + return ice_->getLocalAddress(comp_id_); +} + +ssize_t +SipsIceTransport::tlsSend(const void* d , size_t s) +{ + RING_DBG("SipsIceTransport::tlsSend %lu", s); + return ice_->send(comp_id_, (const uint8_t*)d, s); +} + +ssize_t +SipsIceTransport::tlsRecv(void* d , size_t s) +{ + std::lock_guard<std::mutex> l(inputBuffMtx_); + if (tlsInputBuff_.empty()) { + errno = EAGAIN; + return -1; + } + RING_DBG("SipsIceTransport::tlsRecv %lu at %lu", + s, clock::now().time_since_epoch().count()); + const auto& front = tlsInputBuff_.front(); + const auto n = std::min(front.size(), s); + std::copy_n(front.begin(), n, (uint8_t*)d); + tlsInputBuff_.pop_front(); + if (tlsInputBuff_.empty()) + canRead_ = false; + return n; +} + +int +SipsIceTransport::waitForTlsData(unsigned ms) +{ + RING_DBG("SipsIceTransport::waitForTlsData %u", ms); + + std::unique_lock<std::mutex> l(inputBuffMtx_); + if (tlsInputBuff_.empty()) { + cv_.wait_for(l, std::chrono::milliseconds(ms), [&]() { + return state_ == TlsConnectionState::DISCONNECTED or not tlsInputBuff_.empty(); + }); + } + return tlsInputBuff_.empty() ? 0 : tlsInputBuff_.front().size(); +} + +pj_status_t +SipsIceTransport::send(pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr, + int addr_len, void *token, + pjsip_transport_callback callback) +{ + // Sanity check + PJ_ASSERT_RETURN(tdata, PJ_EINVAL); + + // Check that there's no pending operation associated with the tdata + PJ_ASSERT_RETURN(tdata->op_key.tdata == nullptr, PJSIP_EPENDINGTX); + + // Check the address is supported + PJ_ASSERT_RETURN(rem_addr and + (addr_len==sizeof(pj_sockaddr_in) or + addr_len==sizeof(pj_sockaddr_in6)), + PJ_EINVAL); + + tdata->op_key.tdata = tdata; + tdata->op_key.token = token; + tdata->op_key.callback = callback; + { + std::lock_guard<std::mutex> l(outputBuffMtx_); + outputBuff_.emplace_back(DelayedTxData{&tdata->op_key, {}}); + if (tdata->msg && tdata->msg->type == PJSIP_REQUEST_MSG) { + auto& dtd = outputBuff_.back(); + dtd.timeout = clock::now(); + dtd.timeout += std::chrono::milliseconds(pjsip_cfg()->tsx.td); + } + canWrite_ = true; + } + cv_.notify_all(); + return PJ_EPENDING; +} + +pj_status_t +SipsIceTransport::flushOutputBuff() +{ + if (state_ != TlsConnectionState::ESTABLISHED) + return PJ_EPENDING; + + ssize_t status = PJ_SUCCESS; + while (true) { + DelayedTxData f; + { + std::lock_guard<std::mutex> l(outputBuffMtx_); + if (outputBuff_.empty()) { + canWrite_ = false; + break; + } + else { + f = outputBuff_.front(); + outputBuff_.pop_front(); + } + } + if (f.timeout != clock::time_point() && f.timeout < clock::now()) + continue; + status = trySend(f.tdata_op_key); + f.tdata_op_key->tdata = nullptr; + if (f.tdata_op_key->callback) + f.tdata_op_key->callback(getTransportBase(), + f.tdata_op_key->token, status); + if (status < 0) + break; + } + return status > 0 ? PJ_SUCCESS : (pj_status_t)status; +} + +ssize_t +SipsIceTransport::trySend(pjsip_tx_data_op_key *pck) +{ + if (state_ != TlsConnectionState::ESTABLISHED) + return PJ_EPENDING; + + const size_t max_tx_sz = gnutls_dtls_get_data_mtu(session_); + + pjsip_tx_data *tdata = pck->tdata; + size_t size = tdata->buf.cur - tdata->buf.start; + size_t total_written = 0; + while (total_written < size) { + /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push + * callback to actually write the encrypted bytes into our output circular + * buffer. GnuTLS may refuse to "send" everything at once, but since we are + * not really sending now, we will just call it again now until it succeeds + * (or fails in a fatal way). */ + auto tx_size = std::min(max_tx_sz, size - total_written); + int nwritten = gnutls_record_send(session_, + tdata->buf.start + total_written, + tx_size); + if (nwritten > 0) { + /* Good, some data was encrypted and written */ + total_written += nwritten; + } else { + /* Normally we would have to retry record_send but our internal + * state has not changed, so we have to ask for more data first. + * We will just try again later, although this should never happen. + */ + RING_ERR("gnutls_record_send : %s", gnutls_strerror(nwritten)); + return tls_status_from_err(nwritten); + } + } + return total_written; +} + +void +SipsIceTransport::close() +{ + RING_WARN("SipsIceTransport::close"); + state_ = TlsConnectionState::DISCONNECTED; + if (session_) { + gnutls_bye(session_, GNUTLS_SHUT_RDWR); + gnutls_deinit(session_); + session_ = nullptr; + } + + if (xcred_) { + gnutls_certificate_free_credentials(xcred_); + xcred_ = nullptr; + } +} + +void +SipsIceTransport::reset() +{ + RING_WARN("SipsIceTransport::reset"); + state_ = TlsConnectionState::DISCONNECTED; + tlsThread_.stop(); + cv_.notify_all(); +} + +}} // namespace ring::tls diff --git a/daemon/src/ringdht/sips_transport_ice.h b/daemon/src/ringdht/sips_transport_ice.h new file mode 100644 index 0000000000000000000000000000000000000000..2f49028661cc962de248a4251917d111460a01c5 --- /dev/null +++ b/daemon/src/ringdht/sips_transport_ice.h @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#include "ip_utils.h" +#include "threadloop.h" + +#include <opendht/crypto.h> + +#include <pjsip.h> +#include <pj/pool.h> + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gnutls/dtls.h> +#include <gnutls/abstract.h> + +#include <list> +#include <functional> +#include <memory> +#include <mutex> +#include <condition_variable> +#include <chrono> + +namespace ring { +class IceTransport; +} // namespace ring + +namespace ring { namespace tls { + +enum class TlsConnectionState { + DISCONNECTED, + COOKIE, + HANDSHAKING, + ESTABLISHED +}; + +struct TlsParams { + std::string ca_list; + dht::crypto::Identity id; + std::shared_ptr<gnutls_dh_params_int> 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; +}; + +struct SipsIceTransport +{ + using clock = std::chrono::steady_clock; + using TransportData = struct { + pjsip_transport base; // do not move, SHOULD be the fist member + SipsIceTransport* self {nullptr}; + }; + static_assert(std::is_standard_layout<TransportData>::value, + "TranportData requires standard-layout"); + + SipsIceTransport(pjsip_endpoint* endpt, const TlsParams& param, + const std::shared_ptr<IceTransport>& ice, int comp_id); + ~SipsIceTransport(); + + void reset(); + + IpAddr getLocalAddress() const; + + std::shared_ptr<IceTransport> getIceTransport() const { + return ice_; + } + + pjsip_transport* getTransportBase() { + return &trData_.base; + } + +private: + 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_; + bool is_registered_ {false}; + const std::shared_ptr<IceTransport> ice_; + const int comp_id_; + + TlsParams param_; + + bool is_server_ {false}; + //bool has_pending_connect_ {false}; + IpAddr local_ {}; + IpAddr remote_ {}; + + ThreadLoop tlsThread_; + std::condition_variable_any cv_; + std::atomic<bool> canRead_ {false}; + std::atomic<bool> canWrite_ {false}; + + // TODO + pj_status_t verify_status_; + int last_err_; + + pj_ssl_cert_info local_cert_info_; + pj_ssl_cert_info remote_cert_info_; + + std::atomic<TlsConnectionState> state_ {TlsConnectionState::DISCONNECTED}; + clock::time_point handshakeStart_; + + gnutls_session_t session_ {nullptr}; + gnutls_certificate_credentials_t xcred_; + gnutls_priority_t priority_cache; + gnutls_datum_t cookie_key_; + gnutls_dtls_prestate_st prestate_; + + /** + * To be called on a regular basis to receive packets + */ + 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::mutex outputBuffMtx_ {}; + pjsip_rx_data rdata_; + + // 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(); + 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, + gnutls_x509_crt_t cert); + void certUpdate(); + pj_bool_t onHandshakeComplete(pj_status_t status); + int verifyCertificate(); + pj_status_t getInfo (pj_ssl_sock_info *info); + + void close(); +}; + +}} // namespace ring::tls diff --git a/daemon/src/sip/sipaccount.cpp b/daemon/src/sip/sipaccount.cpp index 4d0ca0f85c56e9174f8dc6d3cb4b64bf1d3320d7..17d0a65849e59f8296c163fb60386f312d16243a 100644 --- a/daemon/src/sip/sipaccount.cpp +++ b/daemon/src/sip/sipaccount.cpp @@ -124,10 +124,6 @@ SIPAccount::SIPAccount(const std::string& accountID, bool presenceEnabled) , cred_() , tlsSetting_() , ciphers_(100) - , tlsCaListFile_() - , tlsCertificateFile_() - , tlsPrivateKeyFile_() - , tlsPassword_() , tlsMethod_("TLSv1") , tlsCiphers_() , tlsServerName_(0, 0) @@ -397,12 +393,8 @@ void SIPAccount::serialize(YAML::Emitter &out) out << YAML::Key << Conf::VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer_; out << YAML::Key << Conf::REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate_; out << YAML::Key << Conf::TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeoutSec_; - out << YAML::Key << Conf::CALIST_KEY << YAML::Value << tlsCaListFile_; - out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << tlsCertificateFile_; out << YAML::Key << Conf::CIPHERS_KEY << YAML::Value << tlsCiphers_; out << YAML::Key << Conf::METHOD_KEY << YAML::Value << tlsMethod_; - out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword_; - out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile_; out << YAML::Key << Conf::SERVER_KEY << YAML::Value << tlsServerName_; out << YAML::EndMap; @@ -498,16 +490,12 @@ void SIPAccount::unserialize(const YAML::Node &node) const auto &tlsMap = node[Conf::TLS_KEY]; parseValue(tlsMap, Conf::TLS_ENABLE_KEY, tlsEnable_); - parseValue(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_); - parseValue(tlsMap, Conf::CALIST_KEY, tlsCaListFile_); parseValue(tlsMap, Conf::CIPHERS_KEY, tlsCiphers_); std::string tmpMethod(tlsMethod_); parseValue(tlsMap, Conf::METHOD_KEY, tmpMethod); validate(tlsMethod_, tmpMethod, VALID_TLS_METHODS); - parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_); - parseValue(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_); parseValue(tlsMap, Conf::SERVER_KEY, tlsServerName_); parseValue(tlsMap, Conf::REQUIRE_CERTIF_KEY, tlsRequireClientCertificate_); parseValue(tlsMap, Conf::VERIFY_CLIENT_KEY, tlsVerifyClient_); @@ -567,12 +555,6 @@ void SIPAccount::setAccountDetails(const std::map<std::string, std::string> &det // TLS settings parseBool(details, Conf::CONFIG_TLS_ENABLE, tlsEnable_); - parseInt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort_); - parseString(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_); - parseString(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_); - - parseString(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_); - parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_); auto iter = details.find(Conf::CONFIG_TLS_METHOD); if (iter != details.end()) validate(tlsMethod_, iter->second, VALID_TLS_METHODS); @@ -673,10 +655,6 @@ std::map<std::string, std::string> SIPAccount::getAccountDetails() const // TLS listener is unique and parameters are modified through IP2IP_PROFILE a[Conf::CONFIG_TLS_ENABLE] = tlsEnable_ ? TRUE_STR : FALSE_STR; - a[Conf::CONFIG_TLS_CA_LIST_FILE] = tlsCaListFile_; - a[Conf::CONFIG_TLS_CERTIFICATE_FILE] = tlsCertificateFile_; - a[Conf::CONFIG_TLS_PRIVATE_KEY_FILE] = tlsPrivateKeyFile_; - a[Conf::CONFIG_TLS_PASSWORD] = tlsPassword_; a[Conf::CONFIG_TLS_METHOD] = tlsMethod_; a[Conf::CONFIG_TLS_CIPHERS] = tlsCiphers_; a[Conf::CONFIG_TLS_SERVER_NAME] = tlsServerName_; diff --git a/daemon/src/sip/sipaccount.h b/daemon/src/sip/sipaccount.h index b2e8997cc0be93cc29b6414511269c477d2ab3ec..5eaf76911c2763db57ef17a5c6385fe3a9ec51a6 100644 --- a/daemon/src/sip/sipaccount.h +++ b/daemon/src/sip/sipaccount.h @@ -644,10 +644,6 @@ class SIPAccount : public SIPAccountBase { pj_uint16_t stunPort_ {PJ_STUN_PORT}; bool tlsEnable_ {false}; - std::string tlsCaListFile_; - std::string tlsCertificateFile_; - std::string tlsPrivateKeyFile_; - std::string tlsPassword_; std::string tlsMethod_; std::string tlsCiphers_; std::string tlsServerName_; diff --git a/daemon/src/sip/sipaccountbase.cpp b/daemon/src/sip/sipaccountbase.cpp index d283c70eb2150cf32dfb069d854a65f214d72064..d315f23dcb190b1881ca910a4620f06eba3df083 100644 --- a/daemon/src/sip/sipaccountbase.cpp +++ b/daemon/src/sip/sipaccountbase.cpp @@ -121,6 +121,10 @@ void SIPAccountBase::serialize(YAML::Emitter &out) void SIPAccountBase::serializeTls(YAML::Emitter &out) { out << YAML::Key << Conf::TLS_PORT_KEY << YAML::Value << tlsListenerPort_; + out << YAML::Key << Conf::CALIST_KEY << YAML::Value << tlsCaListFile_; + out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << tlsCertificateFile_; + out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword_; + out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile_; } void SIPAccountBase::unserialize(const YAML::Node &node) @@ -171,6 +175,10 @@ void SIPAccountBase::unserialize(const YAML::Node &node) // get tls submap const auto &tlsMap = node[Conf::TLS_KEY]; parseValue(tlsMap, Conf::TLS_PORT_KEY, tlsListenerPort_); + parseValue(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_); + parseValue(tlsMap, Conf::CALIST_KEY, tlsCaListFile_); + parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_); + parseValue(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_); unserializeRange(node, Conf::AUDIO_PORT_MIN_KEY, Conf::AUDIO_PORT_MAX_KEY, audioPortRange_); unserializeRange(node, Conf::VIDEO_PORT_MIN_KEY, Conf::VIDEO_PORT_MAX_KEY, videoPortRange_); @@ -207,6 +215,10 @@ void SIPAccountBase::setAccountDetails(const std::map<std::string, std::string> // TLS parseInt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort_); + parseString(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_); + parseString(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_); + parseString(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_); + parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_); } std::map<std::string, std::string> @@ -240,6 +252,10 @@ SIPAccountBase::getAccountDetails() const std::stringstream tlslistenerport; tlslistenerport << tlsListenerPort_; a[Conf::CONFIG_TLS_LISTENER_PORT] = tlslistenerport.str(); + a[Conf::CONFIG_TLS_CA_LIST_FILE] = tlsCaListFile_; + a[Conf::CONFIG_TLS_CERTIFICATE_FILE] = tlsCertificateFile_; + a[Conf::CONFIG_TLS_PRIVATE_KEY_FILE] = tlsPrivateKeyFile_; + a[Conf::CONFIG_TLS_PASSWORD] = tlsPassword_; return a; } diff --git a/daemon/src/sip/sipaccountbase.h b/daemon/src/sip/sipaccountbase.h index d2796ba55f7b3e705e046985abccec2892b779e4..89a379e6d871990fcb825981a4a5fc3d125e2a0f 100644 --- a/daemon/src/sip/sipaccountbase.h +++ b/daemon/src/sip/sipaccountbase.h @@ -336,9 +336,13 @@ protected: pj_uint16_t publishedPort_ {DEFAULT_SIP_PORT}; /** - * The global TLS listener port which can be configured through the IP2IP_PROFILE + * The TLS listener port */ pj_uint16_t tlsListenerPort_ {DEFAULT_SIP_TLS_PORT}; + std::string tlsCaListFile_; + std::string tlsCertificateFile_; + std::string tlsPrivateKeyFile_; + std::string tlsPassword_; /** * DTMF type used for this account SIPINFO or RTP diff --git a/daemon/src/sip/siptransport.cpp b/daemon/src/sip/siptransport.cpp index 68c0a959ba6d24d4e9b35d36120efd28db267063..36aa772fe504d2da04a1dc539555c377d4f5b694 100644 --- a/daemon/src/sip/siptransport.cpp +++ b/daemon/src/sip/siptransport.cpp @@ -35,6 +35,7 @@ #include "ip_utils.h" #include "ringdht/sip_transport_ice.h" +#include "ringdht/sips_transport_ice.h" #include "array_size.h" #include "intrin.h" @@ -163,14 +164,14 @@ SipTransport::removeStateListener(uintptr_t lid) } SipTransportBroker::SipTransportBroker(pjsip_endpoint *endpt, - pj_caching_pool& cp, pj_pool_t& pool) - : cp_(cp), pool_(pool), endpt_(endpt) + pj_caching_pool& cp, pj_pool_t& pool) : +cp_(cp), pool_(pool), endpt_(endpt) { -#if HAVE_DHT +/*#if HAVE_DHT pjsip_transport_register_type(PJSIP_TRANSPORT_DATAGRAM, "ICE", pjsip_transport_get_default_port_for_type(PJSIP_TRANSPORT_UDP), &ice_pj_transport_type_); -#endif +#endif*/ RING_DBG("SipTransportBroker@%p", this); } @@ -412,15 +413,34 @@ std::shared_ptr<SipTransport> SipTransportBroker::getIceTransport(const std::shared_ptr<IceTransport> ice, unsigned comp_id) { - auto sip_ice_tr = std::unique_ptr<SipIceTransport>(new SipIceTransport(endpt_, pool_, - ice_pj_transport_type_, - ice, comp_id)); + auto sip_ice_tr = std::unique_ptr<SipIceTransport>( + new SipIceTransport(endpt_, pool_, ice_pj_transport_type_, ice, comp_id)); + auto tr = sip_ice_tr->getTransportBase(); + auto sip_tr = std::make_shared<SipTransport>(tr); + sip_ice_tr.release(); // managed by PJSIP now + + { + std::lock_guard<std::mutex> lock(transportMapMutex_); + // we do not check for key existance as we've just created it + // (member of new SipIceTransport instance) + transports_.emplace(std::make_pair(tr, sip_tr)); + } + return sip_tr; +} + +std::shared_ptr<SipTransport> +SipTransportBroker::getTlsIceTransport(const std::shared_ptr<ring::IceTransport> ice, + unsigned comp_id, + const tls::TlsParams& params) +{ + auto sip_ice_tr = std::unique_ptr<tls::SipsIceTransport>( + new tls::SipsIceTransport(endpt_, params, ice, comp_id)); auto tr = sip_ice_tr->getTransportBase(); auto sip_tr = std::make_shared<SipTransport>(tr); sip_ice_tr.release(); // managed by PJSIP now { - std::unique_lock<std::mutex> lock(transportMapMutex_); + std::lock_guard<std::mutex> lock(transportMapMutex_); // we do not check for key existance as we've just created it // (member of new SipIceTransport instance) transports_.emplace(std::make_pair(tr, sip_tr)); diff --git a/daemon/src/sip/siptransport.h b/daemon/src/sip/siptransport.h index 4cf382e57d4f41f0adbe11035aa8f7f38e7934d8..64232882c1eb26e31ff9e7d90c01b61d4b26249c 100644 --- a/daemon/src/sip/siptransport.h +++ b/daemon/src/sip/siptransport.h @@ -145,8 +145,10 @@ class SipTransport }; class IpAddr; -class SipIceTransport; class IceTransport; +namespace tls { + struct TlsParams; +}; /** * Manages the transports and receive callbacks from PJSIP @@ -160,13 +162,20 @@ public: std::shared_ptr<SipTransport> getUdpTransport(const SipTransportDescr&); #if HAVE_TLS - std::shared_ptr<TlsListener> getTlsListener(const SipTransportDescr&, const pjsip_tls_setting*); + std::shared_ptr<TlsListener> + getTlsListener(const SipTransportDescr&, const pjsip_tls_setting*); - std::shared_ptr<SipTransport> getTlsTransport(const std::shared_ptr<TlsListener>&, const IpAddr& remote); + std::shared_ptr<SipTransport> + getTlsTransport(const std::shared_ptr<TlsListener>&, const IpAddr& remote); #endif #if HAVE_DHT - std::shared_ptr<SipTransport> getIceTransport(const std::shared_ptr<IceTransport>, unsigned comp_id); + std::shared_ptr<SipTransport> + getIceTransport(const std::shared_ptr<IceTransport>, unsigned comp_id); + + std::shared_ptr<SipTransport> + getTlsIceTransport(const std::shared_ptr<IceTransport>, unsigned comp_id, + const tls::TlsParams&); #endif std::shared_ptr<SipTransport> addTransport(pjsip_transport*);