diff --git a/bin/dbus/configurationmanager-introspec.xml b/bin/dbus/configurationmanager-introspec.xml index 4b6cd1b994d9cd8f4e7a51cb3ae7475a1d2ae713..bb498596a98671295b3439b3e9707b4568d2c23a 100644 --- a/bin/dbus/configurationmanager-introspec.xml +++ b/bin/dbus/configurationmanager-introspec.xml @@ -702,6 +702,251 @@ </arg> </method> + <method name="getPinnedCertificates" tp:name-for-bindings="getPinnedCertificates"> + <tp:added version="2.2.0"/> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="certIds" direction="out"> + <tp:docstring> + <p>A list of all known certificate IDs</p> + </tp:docstring> + </arg> + </method> + + <method name="pinCertificate" tp:name-for-bindings="pinCertificate"> + <tp:added version="2.2.0"/> + <arg type="ay" name="certificateRaw" direction="in"> + <tp:docstring> + <p>A raw certificate (PEM or DER encoded) to be pinned.</p> + </tp:docstring> + </arg> + <arg type="b" name="local" direction="in"> + <tp:docstring> + <p>True to save the certificate in the daemon local store.</p> + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="String"/> + <arg type="s" name="certId" direction="out"> + <tp:docstring> + <p>ID of the pinned certificate or empty string on failure.</p> + </tp:docstring> + </arg> + </method> + + <method name="unpinCertificate" tp:name-for-bindings="unpinCertificate"> + <tp:added version="2.2.0"/> + <arg type="s" name="certId" direction="in"> + <tp:docstring> + <p>A certificate ID to unpin.</p> + </tp:docstring> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + <p>True if a certificate was unpinned.</p> + </tp:docstring> + </arg> + </method> + + <method name="pinCertificatePath" tp:name-for-bindings="pinCertificatePath"> + <tp:added version="2.2.0"/> + <arg type="s" name="certPath" direction="in"> + <tp:docstring> + <p>A certificate path to be pinned (assumed non-local).</p> + </tp:docstring> + </arg> + </method> + + <method name="unpinCertificatePath" tp:name-for-bindings="unpinCertificatePath"> + <tp:added version="2.2.0"/> + <arg type="s" name="certPath" direction="in"> + <tp:docstring> + <p>Certificates path.</p> + </tp:docstring> + </arg> + <arg type="u" name="unpinned" direction="out"> + <tp:docstring> + <p>Number of unpinned certificates.</p> + </tp:docstring> + </arg> + </method> + + <method name="pinRemoteCertificate" tp:name-for-bindings="pinRemoteCertificate"> + <tp:added version="2.2.0"/> + <arg type="s" name="accountId" direction="in"> + <tp:docstring> + <p>An account ID</p> + </tp:docstring> + </arg> + <arg type="s" name="certId" direction="in"> + <tp:docstring> + <p>A certificate public key ID</p> + </tp:docstring> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + <p>True if the search started</p> + </tp:docstring> + </arg> + </method> + + <method name="setCertificateStatus" tp:name-for-bindings="setCertificateStatus"> + <tp:added version="2.2.0"/> + <arg type="s" name="accountId" direction="in"> + <tp:docstring> + <p>An account ID</p> + </tp:docstring> + </arg> + <arg type="s" name="certId" direction="in"> + <tp:docstring> + <p>A certificate ID</p> + </tp:docstring> + </arg> + <arg type="s" name="status" direction="in"> + <tp:docstring> + The new status of the certificate for the specified account. + UNDEFINED : forget any previous certificate state for this account. + ALLOWED : consider the certificate as trusted for this account. + BANNED : consider the certificate as banned for this account. + </tp:docstring> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + <p>True if the certificate state was succesfully set.</p> + </tp:docstring> + </arg> + </method> + + <method name="getCertificatesByStatus" tp:name-for-bindings="getCertificatesByStatus"> + <tp:added version="2.2.0"/> + <arg type="s" name="accountId" direction="in"> + <tp:docstring> + <p>An account ID</p> + </tp:docstring> + </arg> + <arg type="s" name="status" direction="in"> + <tp:docstring> + The queried certificate status. + ALLOWED : trusted certificate for this account. + BANNED : banned certificate for this account. + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + <tp:docstring> + A list of certificate ids with the provided status + </tp:docstring> + </arg> + </method> + + <signal name="certificateStateChanged" tp:name-for-bindings="certificateStateChanged"> + <tp:added version="2.2.0"/> + <tp:docstring> + Notify clients that a certificate status have changed. + </tp:docstring> + <arg type="s" name="accountId"> + </arg> + <arg type="s" name="certId"> + </arg> + <arg type="s" name="state"> + </arg> + </signal> + + <signal name="certificatePinned" tp:name-for-bindings="certificatePinned"> + <tp:added version="2.2.0"/> + <tp:docstring> + Notify clients that a certificate have been added to the store. + </tp:docstring> + <arg type="s" name="certId"> + </arg> + </signal> + + <signal name="certificatePathPinned" tp:name-for-bindings="certificatePathPinned"> + <tp:added version="2.2.0"/> + <tp:docstring> + Notify clients that a certificate path have been added to the store. + </tp:docstring> + <arg type="s" name="path"> + <tp:docstring> + Pinned path. + </tp:docstring> + </arg> + <arg type="as" name="certIds"> + <tp:docstring> + A list of certificate ids. + </tp:docstring> + </arg> + </signal> + + <signal name="certificateExpired" tp:name-for-bindings="certificateExpired"> + <tp:added version="2.2.0"/> + <tp:docstring> + Notify clients that a certificate expired. + </tp:docstring> + <arg type="s" name="certId"> + <tp:docstring> + A certificate id. + </tp:docstring> + </arg> + </signal> + + <method name="getTrustRequests" tp:name-for-bindings="getTrustRequests"> + <tp:added version="2.2.0"/> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="a{ss}" name="details" direction="out" tp:type="String_String_Map"> + <tp:docstring> + A list of contact request details. + </tp:docstring> + </arg> + </method> + + <method name="acceptTrustRequest" tp:name-for-bindings="acceptTrustRequest"> + <tp:added version="2.2.0"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="s" name="from" direction="in"> + </arg> + <arg type="b" name="success" direction="out" tp:type="Boolean"> + <tp:docstring> + True if the operation succeeded. + </tp:docstring> + </arg> + </method> + + <method name="discardTrustRequest" tp:name-for-bindings="discardTrustRequest"> + <tp:added version="2.2.0"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="s" name="from" direction="in"> + </arg> + <arg type="b" name="success" direction="out" tp:type="Boolean"> + <tp:docstring> + True if the operation succeeded. + </tp:docstring> + </arg> + </method> + + <signal name="incomingTrustRequest" tp:name-for-bindings="incomingTrustRequest"> + <tp:added version="2.2.0"/> + <tp:docstring> + Notify clients that a new contact request has been received. + </tp:docstring> + <arg type="s" name="accountID"> + </arg> + <arg type="s" name="from"> + </arg> + <arg type="t" name="receiveTime"> + </arg> + </signal> + + <method name="sendTrustRequest" tp:name-for-bindings="sendTrustRequest"> + <tp:added version="2.2.0"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="s" name="to" direction="in"> + </arg> + </method> + <method name="getAddrFromInterfaceName" tp:name-for-bindings="getAddrFromInterfaceName"> <arg type="s" name="interface" direction="in"> </arg> diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp index 7f0fcaa4809afcb7a21a8fe48fb3e826399da8ac..07fd7bf81df43365c8fc41534d757c558a3ad7e9 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -184,6 +184,11 @@ DBusClient::initLibrary(int flags) exportable_callback<ConfigurationSignal::RegistrationStateChanged>(bind(&DBusConfigurationManager::registrationStateChanged, confM, _1, _2, _3, _4)), exportable_callback<ConfigurationSignal::VolatileDetailsChanged>(bind(&DBusConfigurationManager::volatileAccountDetailsChanged, confM, _1, _2)), exportable_callback<ConfigurationSignal::Error>(bind(&DBusConfigurationManager::errorAlert, confM, _1)), + exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&DBusConfigurationManager::incomingTrustRequest, confM, _1, _2, _3 )), + exportable_callback<ConfigurationSignal::CertificatePinned>(bind(&DBusConfigurationManager::certificatePinned, confM, _1 )), + exportable_callback<ConfigurationSignal::CertificatePathPinned>(bind(&DBusConfigurationManager::certificatePathPinned, confM, _1, _2 )), + exportable_callback<ConfigurationSignal::CertificateExpired>(bind(&DBusConfigurationManager::certificateExpired, confM, _1 )), + exportable_callback<ConfigurationSignal::CertificateStateChanged>(bind(&DBusConfigurationManager::certificateStateChanged, confM, _1, _2, _3 )), }; // Presence event handlers diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp index 3af2927c0bd0df43b7c2c9a6a8e0a946b9744c6a..7ee8bb0ea219bc33c2d31f74ed859332e0a7c850 100644 --- a/bin/dbus/dbusconfigurationmanager.cpp +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -388,6 +388,78 @@ DBusConfigurationManager::getCertificateDetailsRaw(const std::vector<uint8_t>& c return DRing::getCertificateDetailsRaw(certificate); } +auto +DBusConfigurationManager::getPinnedCertificates() -> decltype(DRing::getPinnedCertificates()) +{ + return DRing::getPinnedCertificates(); +} + +auto +DBusConfigurationManager::pinCertificate(const std::vector<uint8_t>& certificate, const bool& local) -> decltype(DRing::pinCertificate(certificate, local)) +{ + return DRing::pinCertificate(certificate, local); +} + +void +DBusConfigurationManager::pinCertificatePath(const std::string& certPath) +{ + return DRing::pinCertificatePath(certPath); +} + +auto +DBusConfigurationManager::unpinCertificate(const std::string& certId) -> decltype(DRing::unpinCertificate(certId)) +{ + return DRing::unpinCertificate(certId); +} + +auto +DBusConfigurationManager::unpinCertificatePath(const std::string& p) -> decltype(DRing::unpinCertificatePath(p)) +{ + return DRing::unpinCertificatePath(p); +} + +auto +DBusConfigurationManager::pinRemoteCertificate(const std::string& accountId, const std::string& certId) -> decltype(DRing::pinRemoteCertificate(accountId, certId)) +{ + return DRing::pinRemoteCertificate(accountId, certId); +} + +auto +DBusConfigurationManager::setCertificateStatus(const std::string& accountId, const std::string& certId, const std::string& status) -> decltype(DRing::setCertificateStatus(accountId, certId, status)) +{ + return DRing::setCertificateStatus(accountId, certId, status); +} + +auto +DBusConfigurationManager::getCertificatesByStatus(const std::string& accountId, const std::string& status) -> decltype(DRing::getCertificatesByStatus(accountId, status)) +{ + return DRing::getCertificatesByStatus(accountId, status); +} + +auto +DBusConfigurationManager::getTrustRequests(const std::string& accountId) -> decltype(DRing::getTrustRequests(accountId)) +{ + return DRing::getTrustRequests(accountId); +} + +auto +DBusConfigurationManager::acceptTrustRequest(const std::string& accountId, const std::string& from) -> decltype(DRing::acceptTrustRequest(accountId, from)) +{ + return DRing::acceptTrustRequest(accountId, from); +} + +auto +DBusConfigurationManager::discardTrustRequest(const std::string& accountId, const std::string& from) -> decltype(DRing::discardTrustRequest(accountId, from)) +{ + return DRing::discardTrustRequest(accountId, from); +} + +void +DBusConfigurationManager::sendTrustRequest(const std::string& accountId, const std::string& to) +{ + DRing::sendTrustRequest(accountId, to); +} + auto DBusConfigurationManager::getIp2IpDetails() -> decltype(DRing::getIp2IpDetails()) { diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h index f85351f4fa258e81458d4fa408acd18668edc6e7..c1e0bc3651c5e1e135468e407eb7af649081477b 100644 --- a/bin/dbus/dbusconfigurationmanager.h +++ b/bin/dbus/dbusconfigurationmanager.h @@ -4,6 +4,7 @@ * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * 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 @@ -131,6 +132,18 @@ class DBusConfigurationManager : std::map<std::string, std::string> validateCertificateRaw(const std::string& accountId, const std::vector<uint8_t>& certificate); std::map<std::string, std::string> getCertificateDetails(const std::string& certificate); std::map<std::string, std::string> getCertificateDetailsRaw(const std::vector<uint8_t>& certificate); + std::vector<std::string> getPinnedCertificates(); + std::string pinCertificate(const std::vector<uint8_t>& certificate, const bool& local); + bool unpinCertificate(const std::string& certId); + void pinCertificatePath(const std::string& path); + unsigned unpinCertificatePath(const std::string& path); + bool pinRemoteCertificate(const std::string& accountId, const std::string& certId); + bool setCertificateStatus(const std::string& account, const std::string& certId, const std::string& status); + std::vector<std::string> getCertificatesByStatus(const std::string& account, const std::string& status); + std::map<std::string, std::string> getTrustRequests(const std::string& accountId); + bool acceptTrustRequest(const std::string& accountId, const std::string& from); + bool discardTrustRequest(const std::string& accountId, const std::string& from); + void sendTrustRequest(const std::string& accountId, const std::string& to); }; #endif // __RING_DBUSCONFIGURATIONMANAGER_H__ diff --git a/configure.ac b/configure.ac index 2a58040bd775d4852fa30a694fce7ed4def7e5a4..a062bc9820c119eeb8bfc976e35a2afc8bc55a93 100644 --- a/configure.ac +++ b/configure.ac @@ -618,6 +618,7 @@ AC_CONFIG_FILES([Makefile \ src/media/video/osxvideo/Makefile \ src/media/video/winvideo/Makefile \ src/media/video/test/Makefile \ + src/security/Makefile \ src/upnp/Makefile \ test/Makefile \ ringtones/Makefile \ diff --git a/contrib/src/opendht/rules.mak b/contrib/src/opendht/rules.mak index d761a2d76ae893851e0d6793ab7b03472d3e8d19..3f61120d2a9a05fe38131d9844a94c7401bfe98a 100644 --- a/contrib/src/opendht/rules.mak +++ b/contrib/src/opendht/rules.mak @@ -1,5 +1,5 @@ # OPENDHT -OPENDHT_VERSION := 6c42df6c8adc2958b979299d423f51b4a64fe3c4 +OPENDHT_VERSION := ac492aaa7bd71504c6856d0a7a1f86bf30fdf795 OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz PKGS += opendht diff --git a/src/Makefile.am b/src/Makefile.am index 2ce57ab887bc5e5982aa2a667329cabedc50ad74..06c4d9b3b2909e6bce24cd30f83e4ce3ec76ef6d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,7 +43,7 @@ TLS_LIB = @GNUTLS_LIBS@ TLS_CFLAGS = @GNUTLS_CFLAGS@ endif -SUBDIRS = client media config hooks sip upnp $(IAX_SUBDIR) $(RINGACC_SUBDIR) $(INSTANT_MESSAGING_SUBDIR) $(RING_VIDEO_SUBDIR) +SUBDIRS = client media config hooks sip upnp security $(IAX_SUBDIR) $(RINGACC_SUBDIR) $(INSTANT_MESSAGING_SUBDIR) $(RING_VIDEO_SUBDIR) # libring @@ -55,6 +55,7 @@ libring_la_LIBADD = \ ./client/libclient.la \ ./config/libconfig.la \ ./hooks/libhooks.la \ + ./security/libsecurity.la \ ./upnp/libupnpcontrol.la \ $(RINGACC_LIBA) \ $(IAX_LIBA) \ diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp index c4a5a10d75b3bdfa3b33d92f628c3788fa09ac64..a7d9d8f630dca3c25b8fe51cca99ea7a32b1ffbf 100644 --- a/src/client/configurationmanager.cpp +++ b/src/client/configurationmanager.cpp @@ -39,7 +39,8 @@ #include "account_schema.h" #include "manager.h" #if HAVE_TLS && HAVE_DHT -#include "sip/tlsvalidator.h" +#include "security/tlsvalidator.h" +#include "security/certstore.h" #endif #include "logger.h" #include "fileutils.h" @@ -49,7 +50,6 @@ #if HAVE_IAX #include "iax/iaxaccount.h" #endif -#include "security_const.h" #include "audio/audiolayer.h" #include "system_codec_container.h" #include "account_const.h" @@ -66,7 +66,8 @@ namespace DRing { constexpr unsigned CODECS_NOT_LOADED = 0x1000; /** Codecs not found */ using ring::SIPAccount; -using ring::TlsValidator; +using ring::tls::TlsValidator; +using ring::tls::CertificateStore; using ring::DeviceType; using ring::HookPreference; @@ -170,7 +171,7 @@ getCertificateDetails(const std::string& certificate) { #if HAVE_TLS && HAVE_DHT try { - return TlsValidator{certificate,""}.getSerializedDetails(); + return TlsValidator{CertificateStore::instance().getCertificate(certificate)}.getSerializedDetails(); } catch(const std::runtime_error& e) { RING_WARN("Certificate loading failed"); } @@ -195,6 +196,67 @@ getCertificateDetailsRaw(const std::vector<uint8_t>& certificate_raw) return {{}}; } +std::vector<std::string> +getPinnedCertificates() +{ +#if HAVE_TLS && HAVE_DHT + return ring::tls::CertificateStore::instance().getPinnedCertificates(); +#else + RING_WARN("TLS not supported"); +#endif + return {}; +} + +std::string +pinCertificate(const std::vector<uint8_t>& certificate, bool local) +{ + return ring::tls::CertificateStore::instance().pinCertificate(certificate, local); +} + +void +pinCertificatePath(const std::string& path) +{ + ring::tls::CertificateStore::instance().pinCertificatePath(path); +} + +bool +unpinCertificate(const std::string& certId) +{ + return ring::tls::CertificateStore::instance().unpinCertificate(certId); +} + +unsigned +unpinCertificatePath(const std::string& path) +{ + return ring::tls::CertificateStore::instance().unpinCertificatePath(path); +} + +bool +pinRemoteCertificate(const std::string& accountId, const std::string& certId) +{ + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + return acc->findCertificate(certId); + return false; +} + +bool +setCertificateStatus(const std::string& accountId, const std::string& certId, const std::string& ststr) +{ + auto status = ring::tls::TrustStore::statusFromStr(ststr.c_str()); + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + return acc->setCertificateStatus(certId, status); + return false; +} + +std::vector<std::string> +getCertificatesByStatus(const std::string& accountId, const std::string& ststr) +{ + auto status = ring::tls::TrustStore::statusFromStr(ststr.c_str()); + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + return acc->getCertificatesByStatus(status); + return {}; +} + void setAccountDetails(const std::string& accountID, const std::map<std::string, std::string>& details) { @@ -219,6 +281,38 @@ sendAccountTextMessage(const std::string& accountID, const std::string& to, cons ring::Manager::instance().sendTextMessage(accountID, to, message); } +/* contact requests */ +std::map<std::string, std::string> +getTrustRequests(const std::string& accountId) +{ + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + return acc->getTrustRequests(); + return {{}}; +} + +bool +acceptTrustRequest(const std::string& accountId, const std::string& from) +{ + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + return acc->acceptTrustRequest(from); + return false; +} + +bool +discardTrustRequest(const std::string& accountId, const std::string& from) +{ + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + return acc->discardTrustRequest(from); + return false; +} + +void +sendTrustRequest(const std::string& accountId, const std::string& to) +{ + if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId)) + acc->sendTrustRequest(to); +} + ///This function is used as a base for new accounts for clients that support it std::map<std::string, std::string> getAccountTemplate(const std::string& accountType) diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp index 423b2d42d9c4e525d11267a46b66adff6c7805e3..93104d473ef3e708fac1c1fad88d983dabfb5955 100644 --- a/src/client/ring_signal.cpp +++ b/src/client/ring_signal.cpp @@ -70,6 +70,11 @@ getSignalHandlers() exported_callback<DRing::ConfigurationSignal::StunStatusFailed>(), exported_callback<DRing::ConfigurationSignal::RegistrationStateChanged>(), exported_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(), + exported_callback<DRing::ConfigurationSignal::CertificatePinned>(), + exported_callback<DRing::ConfigurationSignal::CertificatePathPinned>(), + exported_callback<DRing::ConfigurationSignal::CertificateExpired>(), + exported_callback<DRing::ConfigurationSignal::CertificateStateChanged>(), + exported_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(), exported_callback<DRing::ConfigurationSignal::Error>(), /* Presence */ diff --git a/src/dring/call_const.h b/src/dring/call_const.h index 64078d525b56970122cccc4b2fd4fd2796323fee..86d81b41c6351d05388d2e7a11a6d0f7c940c2cd 100644 --- a/src/dring/call_const.h +++ b/src/dring/call_const.h @@ -58,8 +58,6 @@ constexpr static char CONF_ID [] = "CONF_ID" ; constexpr static char TIMESTAMP_START [] = "TIMESTAMP_START" ; constexpr static char ACCOUNTID [] = "ACCOUNTID" ; constexpr static char PEER_HOLDING [] = "PEER_HOLDING" ; -constexpr static char TLS_PEER_CERT [] = "TLS_PEER_CERT" ; -constexpr static char TLS_CIPHER [] = "TLS_CIPHER" ; constexpr static char AUDIO_MUTED [] = "AUDIO_MUTED" ; constexpr static char VIDEO_MUTED [] = "VIDEO_MUTED" ; diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h index 952251a9c2f90ea28a896642f3a67ae5e4496a6e..2259b95d9fc2317756b778f7590b833facebd37e 100644 --- a/src/dring/configurationmanager_interface.h +++ b/src/dring/configurationmanager_interface.h @@ -42,6 +42,7 @@ #include <cstdint> #include "dring.h" +#include "security_const.h" namespace DRing { @@ -137,6 +138,25 @@ std::map<std::string, std::string> validateCertificateRaw(const std::string& acc std::map<std::string, std::string> getCertificateDetails(const std::string& certificate); std::map<std::string, std::string> getCertificateDetailsRaw(const std::vector<uint8_t>& certificate); +std::vector<std::string> getPinnedCertificates(); + +std::string pinCertificate(const std::vector<uint8_t>& certificate, bool local); +bool unpinCertificate(const std::string& certId); + +void pinCertificatePath(const std::string& path); +unsigned unpinCertificatePath(const std::string& path); + +bool pinRemoteCertificate(const std::string& accountId, const std::string& certId); +bool setCertificateStatus(const std::string& account, const std::string& certId, const std::string& status); +std::vector<std::string> getCertificatesByStatus(const std::string& account, const std::string& status); + +/* contact requests */ +std::map<std::string, std::string> getTrustRequests(const std::string& accountId); +bool acceptTrustRequest(const std::string& accountId, const std::string& from); +bool discardTrustRequest(const std::string& accountId, const std::string& from); + +void sendTrustRequest(const std::string& accountId, const std::string& to); + // Configuration signal type definitions struct ConfigurationSignal { struct VolumeChanged { @@ -169,6 +189,26 @@ struct ConfigurationSignal { constexpr static const char* name = "IncomingMessage"; using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*message*/); }; + struct IncomingTrustRequest { + constexpr static const char* name = "IncomingTrustRequest"; + using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, time_t received); + }; + struct CertificatePinned { + constexpr static const char* name = "CertificatePinned"; + using cb_type = void(const std::string& /*certId*/); + }; + struct CertificatePathPinned { + constexpr static const char* name = "CertificatePathPinned"; + using cb_type = void(const std::string& /*path*/, const std::vector<std::string>& /*certId*/); + }; + struct CertificateExpired { + constexpr static const char* name = "CertificateExpired"; + using cb_type = void(const std::string& /*certId*/); + }; + struct CertificateStateChanged { + constexpr static const char* name = "CertificateStateChanged"; + using cb_type = void(const std::string& /*account_id*/, const std::string& /*certId*/, const std::string& /*state*/); + }; }; } // namespace DRing diff --git a/src/dring/security_const.h b/src/dring/security_const.h index 276dab292849766307780735c5627b59b84aa9cc..19196eceb8c7de071dd627acb394a32f9777f5ee 100644 --- a/src/dring/security_const.h +++ b/src/dring/security_const.h @@ -34,6 +34,12 @@ namespace DRing { namespace Certificate { +namespace Status { +constexpr static char UNDEFINED [] = "UNDEFINED"; +constexpr static char ALLOWED [] = "ALLOWED"; +constexpr static char BANNED [] = "BANNED"; +} //namespace Dring::Certificate::Status + /** * Those constantes are used by the ConfigurationManager.validateCertificate method */ @@ -110,6 +116,13 @@ namespace CheckValuesNames { } //namespace DRing::Certificate +namespace TlsTransport { +constexpr static char TLS_PEER_CERT [] = "TLS_PEER_CERT"; +constexpr static char TLS_PEER_CA_NUM [] = "TLS_PEER_CA_NUM"; +constexpr static char TLS_PEER_CA_ [] = "TLS_PEER_CA_"; +constexpr static char TLS_CIPHER [] = "TLS_CIPHER"; +} //namespace DRing::TlsTransport + } //namespace DRing #endif diff --git a/src/manager.cpp b/src/manager.cpp index b1e1e8e32a6ef4fdc098241d141b1895fb0aac2f..335c94d34a5d244087d275a1d77694649b8f11cb 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -81,7 +81,7 @@ #include "dring/call_const.h" #if HAVE_TLS -#include "gnutls_support.h" +#include "security/gnutls_support.h" #endif #include "libav_utils.h" diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index 33b24e8c9adaa4a57aeac6b52c9792bb8dee1bee..f4b7a15cc03517916096567a33dfdcf466acfed7 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -63,9 +63,11 @@ #include "config/yamlparser.h" -#include "gnutls_support.h" +#include "security/certstore.h" +#include "security/gnutls_support.h" #include <opendht/securedht.h> + #include <yaml-cpp/yaml.h> #include <algorithm> @@ -73,6 +75,7 @@ #include <memory> #include <sstream> #include <cctype> +#include <cstdarg> namespace ring { @@ -149,7 +152,7 @@ RingAccount::newOutgoingCall(const std::string& toUrl) if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend()) throw std::invalid_argument("id must be a ring infohash"); - RING_DBG("Calling DHT peer %s", toUrl.c_str()); + RING_DBG("Calling DHT peer %s", toUri.c_str()); auto& manager = Manager::instance(); auto call = manager.callFactory.newCall<SIPCall, RingAccount>(*this, manager.getNewCallID(), @@ -169,6 +172,9 @@ RingAccount::newOutgoingCall(const std::string& toUrl) auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this()); auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT}; + // TODO: for now, we automatically trust all explicitly called peers + setCertificateStatus(toUri, tls::TrustStore::Status::ALLOWED); + manager.addTask([=] { static std::uniform_int_distribution<dht::Value::Id> udist; @@ -197,13 +203,9 @@ RingAccount::newOutgoingCall(const std::string& toUrl) shared_this->dht_.putEncrypted( callkey, toH, dht::Value { - dht::DhtMessage::TYPE, - dht::DhtMessage( - dht::DhtMessage::Service::ICE_CANDIDATES, - ice->getLocalAttributesAndCandidates() - ), - callvid - }, + dht::IceCandidates(ice->getLocalAttributesAndCandidates()), + callvid + }, [=](bool ok) { // Put complete callback if (!ok) { RING_WARN("Can't put ICE descriptor on DHT"); @@ -212,33 +214,24 @@ RingAccount::newOutgoingCall(const std::string& toUrl) Manager::instance().callFailure(*call); // signal client call->removeCall(); } - } + } else + RING_DBG("Succesfully put ICE descriptor on DHT"); shared_this->dht_.cancelPut(callkey, callvid); - }); + } + ); - auto listenKey = shared_this->dht_.listen( + auto listenKey = shared_this->dht_.listen<dht::IceCandidates>( callkey, - [=] (const std::vector<std::shared_ptr<dht::Value>>& vals) { - RING_DBG("Outcall listen callback (%d values)", vals.size()); - for (const auto& v : vals) { - if (v->recipient != shared_this->dht_.getId()) { - RING_DBG("Ignoring non encrypted or bad type value %s", - v->toString().c_str()); - continue; - } - if (v->id != replyvid) - continue; - dht::DhtMessage msg {v->data}; - RING_WARN("ICE request replied from DHT peer %s", - toH.toString().c_str()); - call->setConnectionState(Call::PROGRESSING); - emitSignal<DRing::CallSignal::StateChange>(call->getCallId(), DRing::Call::StateEvent::CONNECTING, 0); - ice->start(msg.getMessage()); - return false; - } - return true; - }, - dht::DhtMessage::ServiceFilter(dht::DhtMessage::Service::ICE_CANDIDATES) + [=] (dht::IceCandidates&& msg) { + RING_DBG("Outcall listen callback"); + if (msg.id != replyvid) + return true; + RING_WARN("ICE request replied from DHT peer %s", toH.toString().c_str()); + call->setConnectionState(Call::PROGRESSING); + emitSignal<DRing::CallSignal::StateChange>(call->getCallId(), DRing::Call::StateEvent::CONNECTING, 0); + ice->start(msg.ice_data); + return false; + } ); shared_this->pendingCalls_.emplace_back(PendingCall{ @@ -392,6 +385,7 @@ 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_PUBLIC_IN_CALLS << YAML::Value << dhtPublicInCalls_; // tls submap out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap; @@ -410,6 +404,9 @@ void RingAccount::unserialize(const YAML::Node &node) if (not dhtPort_) dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE); dhtPortUsed_ = dhtPort_; + + parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_); + checkIdentityPath(); } @@ -534,9 +531,8 @@ RingAccount::handleEvents() } auto ice = c->ice.get(); if (ice->isRunning()) { - regenerateCAList(); auto id = loadIdentity(); - auto remote_h = c->id; + auto remote_h = c->from; tls::TlsParams tlsParams { .ca_list = caListPath_, .id = id.second, @@ -589,7 +585,7 @@ RingAccount::handleEvents() pendingSipCalls_.splice(pendingSipCalls_.begin(), pendingCalls_, in, c); } else { RING_DBG("ICE succeeded : removing pending outgoing call"); - createOutgoingCall(call, c->id.toString(), ice->getRemoteAddress(ICE_COMP_SIP_TRANSPORT)); + createOutgoingCall(call, remote_h.toString(), ice->getRemoteAddress(ICE_COMP_SIP_TRANSPORT)); dht_.cancelListen(c->call_key, c->listen_key.get()); c = pendingCalls_.erase(c); } @@ -693,7 +689,7 @@ void RingAccount::doRegister_() #if 0 // enable if dht_ logging is needed dht_.setLoggers( - [](char const* m, va_list args){ vlogger(LOG_ERR, m, args); } + [](char const* m, va_list args){ vlogger(LOG_ERR, m, args); }, [](char const* m, va_list args){ vlogger(LOG_WARNING, m, args); }, [](char const* m, va_list args){ vlogger(LOG_DEBUG, m, args); } ); @@ -742,87 +738,92 @@ void RingAccount::doRegister_() // Listen for incoming calls auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); - auto listenKey = dht::InfoHash::get("callto:"+dht_.getId().toString()); - RING_WARN("Listening on callto:%s : %s", dht_.getId().toString().c_str(), listenKey.toString().c_str()); - dht_.listen ( - listenKey, - [shared,listenKey] (const std::vector<std::shared_ptr<dht::Value>>& vals) { + callKey_ = dht::InfoHash::get("callto:"+dht_.getId().toString()); + RING_DBG("Listening on callto:%s : %s", dht_.getId().toString().c_str(), callKey_.toString().c_str()); + dht_.listen<dht::IceCandidates>( + callKey_, + [shared] (dht::IceCandidates&& msg) { + // callback for incoming call auto& this_ = *shared.get(); - for (const auto& v : vals) { - std::shared_ptr<SIPCall> call; - try { - if (v->recipient != this_.dht_.getId()) { - RING_DBG("Ignoring non encrypted value %s.", v->toString().c_str()); - continue; - } - auto remote_id = v->owner.getId(); - if (remote_id == this_.dht_.getId()) - continue; - dht::DhtMessage msg {v->data}; - auto res = this_.treatedCalls_.insert(v->id); - this_.saveTreatedCalls(); - if (!res.second) - continue; - auto from = remote_id.toString(); - auto from_vid = v->id; - auto reply_vid = from_vid+1; - RING_WARN("Received incoming DHT call request from %s", from.c_str()); - call = Manager::instance().callFactory.newCall<SIPCall, RingAccount>(this_, Manager::instance().getNewCallID(), Call::INCOMING); - auto& iceTransportFactory = Manager::instance().getIceTransportFactory(); - auto ice = iceTransportFactory.createTransport( - ("sip:"+call->getCallId()).c_str(), - ICE_COMPONENTS, - false, - this_.getUPnPActive() - ); - if (ice->waitForInitialization(ICE_INIT_TIMEOUT) <= 0) - throw std::runtime_error("Can't initialize ICE.."); - - std::weak_ptr<SIPCall> weak_call = call; - - this_.dht_.putEncrypted( - listenKey, - remote_id, - dht::Value { - dht::DhtMessage::TYPE, - dht::DhtMessage( - dht::DhtMessage::Service::ICE_CANDIDATES, - ice->getLocalAttributesAndCandidates() - ), - reply_vid - }, - [weak_call,shared,listenKey,reply_vid](bool ok) { - auto& this_ = *shared.get(); - if (!ok) { - RING_WARN("Can't put ICE descriptor on DHT"); - if (auto call = weak_call.lock()) { - call->setConnectionState(Call::DISCONNECTED); - Manager::instance().callFailure(*call); - call->removeCall(); - } + if (msg.from == this_.dht_.getId()) + return true; + // quick check in case we already explicilty banned this public key + auto trustStatus = this_.trust_.getCertificateStatus(msg.from.toString()); + if (trustStatus == tls::TrustStore::Status::BANNED) { + RING_WARN("Discarding incoming DHT call request from banned peer %s", msg.from.toString().c_str()); + return true; + } + auto res = this_.treatedCalls_.insert(msg.id); + this_.saveTreatedCalls(); + if (!res.second) + return true; + + auto from_h = msg.from; + if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::Status::ALLOWED) { + auto from_vid = msg.id; + { + std::lock_guard<std::mutex> lock(this_.callsMutex_); + this_.pendingUntrustedCalls_.emplace_back(std::move(msg)); + } + this_.findCertificate( + from_h, + [shared,from_h,from_vid](const std::shared_ptr<dht::crypto::Certificate> cert) { + auto& this_ = *shared.get(); + auto pending = std::find_if(this_.pendingUntrustedCalls_.begin(), this_.pendingUntrustedCalls_.end(), [&](dht::IceCandidates& p){ + return p.from == from_h && p.id == from_vid; + }); + if (cert and pending != this_.pendingUntrustedCalls_.end()) { + tls::CertificateStore::instance().pinCertificate(cert); + if (this_.trust_.isTrusted(*cert) and cert->getId() == from_h) { + this_.incomingCall(std::move(*pending)); + } else { + RING_WARN("Discarding incoming DHT call from untrusted peer %s.", from_h.toString().c_str()); } - this_.dht_.cancelPut(listenKey, reply_vid); + } else { + RING_WARN("Can't find certificate of %s for incoming call.", from_h.toString().c_str()); } - ); - ice->start(msg.getMessage()); - call->setPeerNumber(from); - call->initRecFilename(from); - { - std::lock_guard<std::mutex> lock(this_.callsMutex_); - this_.pendingCalls_.emplace_back(PendingCall{std::chrono::steady_clock::now(), ice, weak_call, {}, {}, remote_id}); - } - return true; - } catch (const std::exception& e) { - RING_ERR("ICE/DHT error: %s", e.what()); - if (call) { - call->setConnectionState(Call::DISCONNECTED); - Manager::instance().callFailure(*call); + this_.pendingUntrustedCalls_.erase(pending); } + ); + return true; + } + else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::Status::BANNED) { + this_.findCertificate(from_h.toString().c_str()); + } + // public incoming calls allowed or we explicitly authorised this public key + this_.incomingCall(std::move(msg)); + return true; + } + ); + + auto inboxKey = dht::InfoHash::get("inbox:"+dht_.getId().toString()); + dht_.listen<dht::TrustRequest>( + inboxKey, + [shared](dht::TrustRequest&& v) { + auto& this_ = *shared.get(); + if (v.service != DHT_TYPE_NS) + return true; + // if the invite exists, update it + auto req = this_.trustRequests_.begin(); + for (;req != this_.trustRequests_.end(); ++req) + if (req->from == v.from) { + req->received = std::chrono::system_clock::now(); + break; } + if (req == this_.trustRequests_.end()) { + this_.trustRequests_.emplace_back(TrustRequest{ + .from = v.from, + .received = std::chrono::system_clock::now() + }); + req = std::prev(this_.trustRequests_.end()); } + emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>( + this_.getAccountID(), + req->from.toString(), + std::chrono::system_clock::to_time_t(req->received) + ); return true; - }, - dht::DhtMessage::ServiceFilter(dht::DhtMessage::Service::ICE_CANDIDATES) + } ); } catch (const std::exception& e) { @@ -831,6 +832,51 @@ void RingAccount::doRegister_() } } +void RingAccount::incomingCall(dht::IceCandidates&& msg) +{ + auto from = msg.from.toString(); + auto reply_vid = msg.id+1; + RING_WARN("Received incoming DHT call request from %s", from.c_str()); + auto call = Manager::instance().callFactory.newCall<SIPCall, RingAccount>(*this, Manager::instance().getNewCallID(), Call::INCOMING); + auto ice = Manager::instance().getIceTransportFactory().createTransport( + ("sip:"+call->getCallId()).c_str(), + ICE_COMPONENTS, + false, + getUPnPActive() + ); + if (ice->waitForInitialization(ICE_INIT_TIMEOUT) <= 0) + throw std::runtime_error("Can't initialize ICE.."); + + std::weak_ptr<SIPCall> weak_call = call; + auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); + dht_.putEncrypted( + callKey_, + msg.from, + dht::Value { + dht::IceCandidates(ice->getLocalAttributesAndCandidates()), + reply_vid + }, + [weak_call,shared,reply_vid](bool ok) { + auto& this_ = *shared.get(); + if (!ok) { + RING_WARN("Can't put ICE descriptor on DHT"); + if (auto call = weak_call.lock()) { + call->setConnectionState(Call::DISCONNECTED); + Manager::instance().callFailure(*call); + call->removeCall(); + } + } + this_.dht_.cancelPut(this_.callKey_, reply_vid); + } + ); + ice->start(msg.ice_data); + call->setPeerNumber(from); + call->initRecFilename(from); + { + std::lock_guard<std::mutex> lock(callsMutex_); + pendingCalls_.emplace_back(PendingCall{std::chrono::steady_clock::now(), ice, weak_call, {}, {}, msg.from}); + } +} void RingAccount::doUnregister(std::function<void(bool)> released_cb) { @@ -852,25 +898,44 @@ void RingAccount::doUnregister(std::function<void(bool)> released_cb) released_cb(false); } -void -RingAccount::registerCA(const dht::crypto::Certificate& crt) +bool +RingAccount::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb) { - fileutils::saveFile(caPath_ + DIR_SEPARATOR_STR + crt.getPublicKey().getId().toString(), crt.getPacked()); + if (auto cert = tls::CertificateStore::instance().getCertificate(h.toString())) { + if (cb) + cb(cert); + } else { + dht_.findCertificate(h, [=](const std::shared_ptr<dht::crypto::Certificate> crt) { + if (crt) + tls::CertificateStore::instance().pinCertificate(std::move(crt)); + if (cb) + cb(crt); + }); + } + return true; } bool -RingAccount::unregisterCA(const dht::InfoHash& crt_id) +RingAccount::findCertificate(const std::string& crt_id) { - auto cas = getRegistredCAs(); - bool deleted = false; - for (const auto& ca_path : cas) { - try { - dht::crypto::Certificate tmp_crt(fileutils::loadFile(ca_path)); - if (tmp_crt.getPublicKey().getId() == crt_id) - deleted &= remove(ca_path.c_str()) == 0; - } catch (const std::exception&) {} - } - return deleted; + findCertificate(dht::InfoHash(crt_id)); + return true; +} + +bool +RingAccount::setCertificateStatus(const std::string& cert_id, tls::TrustStore::Status status) +{ + findCertificate(cert_id); + bool done = trust_.setCertificateStatus(cert_id, status); + if (done) + emitSignal<DRing::ConfigurationSignal::CertificateStateChanged>(getAccountID(), cert_id, tls::TrustStore::statusToStr(status)); + return done; +} + +std::vector<std::string> +RingAccount::getCertificatesByStatus(tls::TrustStore::Status status) +{ + return trust_.getCertificatesByStatus(status); } void @@ -909,33 +974,6 @@ RingAccount::saveTreatedCalls() const } } -std::vector<std::string> -RingAccount::getRegistredCAs() -{ - return fileutils::readDirectory(caPath_); -} - -void -RingAccount::regenerateCAList() -{ - std::ofstream list(caListPath_, std::ios::trunc | std::ios::binary); - if (!list.is_open()) { - RING_ERR("Could write CA list"); - return; - } - auto cas = getRegistredCAs(); - { - std::ifstream file(tlsCaListFile_, std::ios::binary); - list << file.rdbuf(); - } - for (const auto& ca : cas) { - std::ifstream file(ca, std::ios::binary); - if (!file) - continue; - list << file.rdbuf(); - } -} - void RingAccount::saveNodes(const std::vector<dht::Dht::NodeExport>& nodes) const { if (nodes.empty()) @@ -1011,8 +1049,6 @@ RingAccount::loadValues() const void RingAccount::initTlsConfiguration() { - regenerateCAList(); - } static std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&> @@ -1117,12 +1153,51 @@ RingAccount::supportPresence(int /* function */, bool /* enabled*/) { } -/* -void RingAccount::updateDialogViaSentBy(pjsip_dialog *dlg) +/* trust requests */ +std::map<std::string, std::string> +RingAccount::getTrustRequests() const { - if (allowViaRewrite_ && via_addr_.host.slen > 0) - pjsip_dlg_set_via_sent_by(dlg, &via_addr_, via_tp_); + std::map<std::string, std::string> ret; + for (const auto& r : trustRequests_) + ret.emplace(r.from.toString(), ring::to_string(std::chrono::system_clock::to_time_t(r.received))); + return ret; +} + + +bool +RingAccount::acceptTrustRequest(const std::string& from) +{ + dht::InfoHash f(from); + for (auto i = std::begin(trustRequests_); i != std::end(trustRequests_); ++i) { + if (i->from == f) { + trust_.setCertificateStatus(from, tls::TrustStore::Status::ALLOWED); + trustRequests_.erase(i); + return true; + } + } + return false; +} + +bool +RingAccount::discardTrustRequest(const std::string& from) +{ + dht::InfoHash f(from); + for (auto i = std::begin(trustRequests_); i != std::end(trustRequests_); ++i) { + if (i->from == f) { + trustRequests_.erase(i); + return true; + } + } + return false; +} + +void +RingAccount::sendTrustRequest(const std::string& to) +{ + setCertificateStatus(to, tls::TrustStore::Status::ALLOWED); + dht_.putEncrypted(dht::InfoHash::get("inbox:"+to), + dht::InfoHash(to), + dht::TrustRequest(DHT_TYPE_NS)); } -*/ } // namespace ring diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h index 4988a04ed5f2c9ec9e2a453901c582f25fb46b20..a5dfcaa12deca1dd2ae9a0f55a90b924a0688c7d 100644 --- a/src/ringdht/ringaccount.h +++ b/src/ringdht/ringaccount.h @@ -43,6 +43,7 @@ #include "ring_types.h" // enable_if_base_of #include <opendht/dhtrunner.h> +#include <opendht/default_types.h> #include <pjsip/sip_types.h> @@ -65,8 +66,11 @@ class Emitter; namespace ring { namespace Conf { - const char *const DHT_PORT_KEY = "dhtPort"; - const char *const DHT_VALUES_PATH_KEY = "dhtValuesPath"; +const char *const DHT_PORT_KEY = "dhtPort"; +const char *const DHT_VALUES_PATH_KEY = "dhtValuesPath"; +const char *const DHT_CONTACTS = "dhtContacts"; +const char *const DHT_PUBLIC_PROFILE = "dhtPublicProfile"; +const char *const DHT_PUBLIC_IN_CALLS = "dhtPublicInCalls"; } namespace tls { @@ -80,6 +84,8 @@ class RingAccount : public SIPAccountBase { constexpr static const char * const ACCOUNT_TYPE = "RING"; constexpr static const in_port_t DHT_DEFAULT_PORT = 4222; constexpr static const char * const DHT_DEFAULT_BOOTSTRAP = "bootstrap.ring.cx"; + constexpr static const char* const DHT_TYPE_NS = "cx.ring"; + /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE; const char* getAccountType() const { @@ -239,13 +245,23 @@ class RingAccount : public SIPAccountBase { return false; } - void registerCA(const dht::crypto::Certificate&); - bool unregisterCA(const dht::InfoHash&); - std::vector<std::string> getRegistredCAs(); + bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::Status status); + std::vector<std::string> getCertificatesByStatus(tls::TrustStore::Status status); + + bool findCertificate(const std::string& id); + bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb = {}); + + /* contact requests */ + std::map<std::string, std::string> getTrustRequests() const; + bool acceptTrustRequest(const std::string& from); + bool discardTrustRequest(const std::string& from); + + void sendTrustRequest(const std::string& to); private: void doRegister_(); + void incomingCall(dht::IceCandidates&& msg); const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)}; //const dht::ValueType ICE_ANNOUCEMENT_TYPE = {10, "ICE descriptors", std::chrono::minutes(3)}; @@ -269,7 +285,6 @@ class RingAccount : public SIPAccountBase { */ bool SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target); - void regenerateCAList(); /** * Maps require port via UPnP @@ -278,19 +293,27 @@ class RingAccount : public SIPAccountBase { dht::DhtRunner dht_ {}; + dht::InfoHash callKey_; + struct PendingCall { std::chrono::steady_clock::time_point start; std::shared_ptr<IceTransport> ice; std::weak_ptr<SIPCall> call; std::future<size_t> listen_key; dht::InfoHash call_key; - dht::InfoHash id; + dht::InfoHash from; }; + /** + * DHT calls waiting for authorization + */ + std::list<dht::IceCandidates> pendingUntrustedCalls_ {}; + /** * DHT calls waiting for ICE negotiation */ std::list<PendingCall> pendingCalls_ {}; + /** * Incoming DHT calls that are not yet actual SIP calls. */ @@ -304,6 +327,15 @@ class RingAccount : public SIPAccountBase { std::string caPath_ {}; std::string caListPath_ {}; + struct TrustRequest { + dht::InfoHash from; + std::chrono::system_clock::time_point received; + }; + + std::vector<TrustRequest> trustRequests_; + + tls::TrustStore trust_; + /** * Validate the values for privkeyPath_ and certPath_. * If one of these fields is empty, reset them to the default values. @@ -331,6 +363,8 @@ class RingAccount : public SIPAccountBase { */ void initTlsConfiguration(); + bool dhtPublicInCalls_ {true}; + /** * DHT port preference */ diff --git a/src/ringdht/sips_transport_ice.cpp b/src/ringdht/sips_transport_ice.cpp index 0f8e546a3c4ec9c17b0b9586450981b3ccfe8eed..bb74d07ed77f0fecfca1050c9be111d0c7a61d2a 100644 --- a/src/ringdht/sips_transport_ice.cpp +++ b/src/ringdht/sips_transport_ice.cpp @@ -32,7 +32,7 @@ #include "ice_transport.h" #include "manager.h" #include "logger.h" -#include "gnutls_support.h" +#include "security/gnutls_support.h" #include <gnutls/dtls.h> #include <gnutls/abstract.h> @@ -623,8 +623,6 @@ SipsIceTransport::onHandshakeComplete(pj_status_t status) int SipsIceTransport::verifyCertificate() { - RING_DBG("SipsIceTransport::verifyCertificate"); - /* Support only x509 format */ if (gnutls_certificate_type_get(session_) != GNUTLS_CRT_X509) { verifyStatus_ = PJ_SSL_CERT_EINVALID_FORMAT; @@ -736,8 +734,7 @@ SipsIceTransport::loop() &trData_.base.key.rem_addr, trData_.base.addr_len, &prestate_, this, [](gnutls_transport_ptr_t t, - const void* d , - size_t s) -> ssize_t { + const void* d, size_t s) -> ssize_t { auto this_ = reinterpret_cast<SipsIceTransport*>(t); return this_->tlsSend(d, s); }); diff --git a/src/security/Makefile.am b/src/security/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..44f1b26bae7613135e6ba9c5a4b96e3e5506fad7 --- /dev/null +++ b/src/security/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libsecurity.la +libsecurity_la_CXXFLAGS = @CXXFLAGS@ -I$(top_srcdir)/src + +libsecurity_la_SOURCES = \ + tlsvalidator.cpp \ + tlsvalidator.h \ + certstore.cpp \ + certstore.h diff --git a/src/security/certstore.cpp b/src/security/certstore.cpp new file mode 100644 index 0000000000000000000000000000000000000000..378e1380fb304c8669a9c174ba3f86aba98f915c --- /dev/null +++ b/src/security/certstore.cpp @@ -0,0 +1,385 @@ +/* + * 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. + */ + +#include "certstore.h" + +#include "client/ring_signal.h" + +#include "fileutils.h" +#include "logger.h" + +#include <thread> +#include <sstream> + +namespace ring { namespace tls { + +CertificateStore& +CertificateStore::instance() +{ + // Meyers singleton + static CertificateStore instance_; + return instance_; +} + +CertificateStore::CertificateStore() + : certPath_(fileutils::get_data_dir()+DIR_SEPARATOR_CH+"certificates") +{ + fileutils::check_dir(certPath_.c_str()); + loadLocalCertificates(certPath_); +} + +unsigned +CertificateStore::loadLocalCertificates(const std::string& path) +{ + std::lock_guard<std::mutex> l(lock_); + + auto dir_content = fileutils::readDirectory(path); + unsigned n = 0; + for (const auto& f : dir_content) { + try { + auto crt = crypto::Certificate(fileutils::loadFile(path+DIR_SEPARATOR_CH+f)); + auto id = crt.getId().toString(); + if (id != f) + throw std::logic_error({}); + certs_.emplace(crt.getId().toString(), std::make_shared<crypto::Certificate>(std::move(crt))); + ++n; + } catch (const std::exception& e) { + remove((path+DIR_SEPARATOR_CH+f).c_str()); + } + } + RING_DBG("CertificateStore: loaded %u local certificates.", n); + return n; +} + +std::vector<std::string> +CertificateStore::getPinnedCertificates() const +{ + std::lock_guard<std::mutex> l(lock_); + + std::vector<std::string> certIds; + certIds.reserve(certs_.size()); + for (const auto& crt : certs_) + certIds.emplace_back(crt.first); + return certIds; +} + +std::shared_ptr<crypto::Certificate> +CertificateStore::getCertificate(const std::string& k) const +{ + std::unique_lock<std::mutex> l(lock_); + + auto cit = certs_.find(k); + if (cit == certs_.cend()) { + l.unlock(); + try { + return std::make_shared<crypto::Certificate>(fileutils::loadFile(k)); + } catch (const std::exception& e) { + return {}; + } + } + return cit->second; +} + +static std::vector<crypto::Certificate> +readCertificates(const std::string& path) +{ + std::vector<crypto::Certificate> ret; + if (fileutils::isDirectory(path)) { + auto files = fileutils::readDirectory(path); + for (const auto& file : files) { + auto certs = readCertificates(path+"/"+file); + ret.insert(std::end(ret), + std::make_move_iterator(std::begin(certs)), + std::make_move_iterator(std::end(certs))); + } + } else { + try { + auto data = fileutils::loadFile(path); + const gnutls_datum_t dt {data.data(), (unsigned)data.size()}; + gnutls_x509_crt_t* certs {nullptr}; + unsigned cert_num {0}; + gnutls_x509_crt_list_import2(&certs, &cert_num, &dt, GNUTLS_X509_FMT_PEM, 0); + for (unsigned i=0; i<cert_num; i++) + ret.emplace_back(certs[i]); + } catch (const std::exception& e) {}; + } + return ret; +} + +void +CertificateStore::pinCertificatePath(const std::string& path) +{ + std::thread([&, path]() { + auto certs = readCertificates(path); + std::vector<std::string> ids; + std::vector<std::weak_ptr<crypto::Certificate>> scerts; + ids.reserve(certs.size()); + scerts.reserve(certs.size()); + { + std::lock_guard<std::mutex> l(lock_); + + for (auto& cert : certs) { + auto shared = std::make_shared<crypto::Certificate>(std::move(cert)); + scerts.emplace_back(shared); + auto e = certs_.emplace(shared->getId().toString(), shared); + ids.emplace_back(e.first->first); + } + paths_.emplace(path, std::move(scerts)); + } + RING_DBG("CertificateStore: loaded %lu certificates from %s.", + certs.size(), path.c_str()); + emitSignal<DRing::ConfigurationSignal::CertificatePathPinned>(path, ids); + }).detach(); +} + +unsigned +CertificateStore::unpinCertificatePath(const std::string& path) +{ + std::lock_guard<std::mutex> l(lock_); + + auto certs = paths_.find(path); + if (certs == std::end(paths_)) + return 0; + unsigned n = 0; + for (const auto& wcert : certs->second) { + if (auto cert = wcert.lock()) { + certs_.erase(cert->getId().toString()); + ++n; + } + } + paths_.erase(certs); + return n; +} + +std::string +CertificateStore::pinCertificate(const std::vector<uint8_t>& cert, + bool local) noexcept +{ + try { + return pinCertificate(cert, local); + } catch (const std::exception& e) {} + return {}; +} + +std::string +CertificateStore::pinCertificate(crypto::Certificate&& cert, bool local) +{ + return pinCertificate(std::make_shared<crypto::Certificate>(std::move(cert)), local); +} + +std::string +CertificateStore::pinCertificate(std::shared_ptr<crypto::Certificate> cert, + bool local) +{ + auto id = cert->getId().toString(); + bool sig {false}; + { + std::lock_guard<std::mutex> l(lock_); + + decltype(certs_)::iterator it; + std::tie(it, sig) = certs_.emplace(id, std::move(cert)); + if (sig and local) + fileutils::saveFile(certPath_+DIR_SEPARATOR_CH+id, it->second->getPacked()); + } + if (sig) + emitSignal<DRing::ConfigurationSignal::CertificatePinned>(id); + return id; +} + +bool +CertificateStore::unpinCertificate(const std::string& id) +{ + std::lock_guard<std::mutex> l(lock_); + + certs_.erase(id); + return remove((certPath_+DIR_SEPARATOR_CH+id).c_str()) == 0; +} + +TrustStore::Status +TrustStore::statusFromStr(const char* str) +{ + if (!std::strcmp(str, DRing::Certificate::Status::ALLOWED)) + return Status::ALLOWED; + if (!std::strcmp(str, DRing::Certificate::Status::BANNED)) + return Status::BANNED; + return Status::UNDEFINED; +} + +const char* +TrustStore::statusToStr(TrustStore::Status s) +{ + switch (s) { + case Status::ALLOWED: + return DRing::Certificate::Status::ALLOWED; + case Status::BANNED: + return DRing::Certificate::Status::BANNED; + default: + return DRing::Certificate::Status::UNDEFINED; + } +} + +TrustStore::TrustStore() +{ + gnutls_x509_trust_list_init(&trust_, 0); +} + +TrustStore::~TrustStore() +{ + gnutls_x509_trust_list_deinit(trust_, false); +} + +bool +TrustStore::setCertificateStatus(const std::string& cert_id, + const TrustStore::Status status) +{ + updateKnownCerts(); + auto s = certStatus_.find(cert_id); + if (s == std::end(certStatus_)) { + if (auto cert = CertificateStore::instance().getCertificate(cert_id)) { + certStatus_[cert->getId().toString()] = {cert, status}; + setStoreCertStatus(*cert, status); + } else + unknownCertStatus_[cert_id] = status; + } else { + s->second.second = status; + setStoreCertStatus(*s->second.first, status); + } + return true; +} + +bool +TrustStore::setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert, + const TrustStore::Status status, bool local) +{ + CertificateStore::instance().pinCertificate(cert, local); + certStatus_[cert->getId().toString()] = {cert, status}; + setStoreCertStatus(*cert, status); + return true; +} + +TrustStore::Status +TrustStore::getCertificateStatus(const std::string& cert_id) const +{ + auto s = certStatus_.find(cert_id); + if (s == std::end(certStatus_)) { + auto us = unknownCertStatus_.find(cert_id); + if (us == std::end(unknownCertStatus_)) + return Status::UNDEFINED; + return us->second; + } + return s->second.second; +} + +std::vector<std::string> +TrustStore::getCertificatesByStatus(TrustStore::Status status) +{ + std::vector<std::string> ret; + for (const auto& i : certStatus_) + if (i.second.second == status) + ret.emplace_back(i.first); + for (const auto& i : unknownCertStatus_) + if (i.second == status) + ret.emplace_back(i.first); + return ret; +} + +bool +TrustStore::isTrusted(const crypto::Certificate& crt) +{ + updateKnownCerts(); + auto crts = getChain(crt); + unsigned result = 0; + +#if GNUTLS_VERSION_NUMBER > 0x030308 + auto ret = gnutls_x509_trust_list_verify_crt2( + trust_, + crts.data(), crts.size(), + nullptr, 0, + GNUTLS_PROFILE_TO_VFLAGS(GNUTLS_PROFILE_MEDIUM), + &result, nullptr); +#else + auto ret = gnutls_x509_trust_list_verify_crt( + trust_, + crts.data(), crts.size(), + 0, + &result, nullptr); +#endif + + if (ret < 0) { + RING_ERR("Error verifying certificate: %s", gnutls_strerror(ret)); + return false; + } + + return !(result & GNUTLS_CERT_INVALID); +} + +std::vector<gnutls_x509_crt_t> +TrustStore::getChain(const crypto::Certificate& crt) +{ + std::vector<gnutls_x509_crt_t> crts; + auto c = &crt; + do { + crts.emplace_back(c->cert); + c = c->issuer.get(); + } while (c); + return crts; +} + +void +TrustStore::updateKnownCerts() +{ + auto i = std::begin(unknownCertStatus_); + while (i != std::end(unknownCertStatus_)) { + if (auto crt = CertificateStore::instance().getCertificate(i->first)) { + certStatus_.emplace(i->first, std::make_pair(crt, i->second)); + setStoreCertStatus(*crt, i->second); + i = unknownCertStatus_.erase(i); + } else + ++i; + } +} + +void +TrustStore::setStoreCertStatus(const crypto::Certificate& crt, + TrustStore::Status status) +{ + if (status == Status::ALLOWED) + gnutls_x509_trust_list_add_cas(trust_, &crt.cert, 1, 0); + else if (status == Status::BANNED) + gnutls_x509_trust_list_remove_cas(trust_, &crt.cert, 1); + + RING_DBG("TrustStore: setting %s status to %s.", + crt.getId().toString().c_str(), + status == TrustStore::Status::ALLOWED ? "ALLOWED" : "BANNED"); +} + +/* +void +TrustStore::generateCAList(const std::string& out_path) +{ + updateKnownCerts(); + std::ostringstream ss; + for (const auto& cert : certStatus_) + ss << cert.second.first->toString(); + auto res = ss.str(); + fileutils::saveFile(out_path, {std::begin(res), std::end(res)}); +}*/ + +}} // namespace ring::tls diff --git a/src/security/certstore.h b/src/security/certstore.h new file mode 100644 index 0000000000000000000000000000000000000000..0affd55922f8eceb52387dc8983ec778bc1091e0 --- /dev/null +++ b/src/security/certstore.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#pragma once + +#include "dring/security_const.h" +#include "noncopyable.h" + +#include <opendht/crypto.h> + +#include <string> +#include <vector> +#include <map> +#include <set> +#include <future> +#include <mutex> + +struct gnutls_x509_trust_list_st; + +namespace ring { namespace tls { + +namespace crypto = dht::crypto; + +class CertificateStore { +public: + static CertificateStore& instance(); + + CertificateStore(); + + std::vector<std::string> getPinnedCertificates() const; + std::shared_ptr<crypto::Certificate> getCertificate(const std::string& cert_id) const; + std::shared_ptr<crypto::Certificate> getCertificateByPublicKey(const std::string& pk_id) const; + + std::string pinCertificate(const std::vector<uint8_t>& crt, bool local = true) noexcept; + std::string pinCertificate(crypto::Certificate&& crt, bool local = true); + std::string pinCertificate(std::shared_ptr<crypto::Certificate> crt, bool local = true); + bool unpinCertificate(const std::string&); + + void pinCertificatePath(const std::string& path); + unsigned unpinCertificatePath(const std::string&); + +private: + NON_COPYABLE(CertificateStore); + + unsigned loadLocalCertificates(const std::string& path); + + const std::string certPath_; + + mutable std::mutex lock_; + std::map<std::string, std::shared_ptr<crypto::Certificate>> certs_; + std::map<std::string, std::vector<std::weak_ptr<crypto::Certificate>>> paths_; +}; + + +class TrustStore { +public: + TrustStore(); + virtual ~TrustStore(); + + enum class Status { + UNDEFINED = 0, + ALLOWED, + BANNED + }; + + static Status statusFromStr(const char* str); + static const char* statusToStr(Status s); + + bool setCertificateStatus(const std::string& cert_id, const Status status); + bool setCertificateStatus(std::shared_ptr<crypto::Certificate>& cert, const Status status, bool local = true); + Status getCertificateStatus(const std::string& cert_id) const; + std::vector<std::string> getCertificatesByStatus(Status status); + + bool isTrusted(const crypto::Certificate& crt); + +private: + NON_COPYABLE(TrustStore); + + static std::vector<gnutls_x509_crt_t> getChain(const crypto::Certificate& crt); + + void updateKnownCerts(); + void setStoreCertStatus(const crypto::Certificate& crt, Status status); + + // unknown certificates with known status + std::map<std::string, Status> unknownCertStatus_; + std::map<std::string, std::pair<std::shared_ptr<crypto::Certificate>, Status>> certStatus_; + gnutls_x509_trust_list_st* trust_; +}; + +}} // namespace ring::tls diff --git a/src/gnutls_support.h b/src/security/gnutls_support.h similarity index 100% rename from src/gnutls_support.h rename to src/security/gnutls_support.h diff --git a/src/sip/tlsvalidator.cpp b/src/security/tlsvalidator.cpp similarity index 95% rename from src/sip/tlsvalidator.cpp rename to src/security/tlsvalidator.cpp index d915f9cd3d20edcf7dc7adb4fccae1a61da1eb0e..c9dc64d467dc30b66628d7ea6273e7d0bc00e88b 100644 --- a/src/sip/tlsvalidator.cpp +++ b/src/security/tlsvalidator.cpp @@ -63,7 +63,7 @@ #include <unistd.h> #include <fcntl.h> -namespace ring { +namespace ring { namespace tls { //Map the internal ring Enum class of the exported names @@ -219,8 +219,8 @@ TlsValidator::TlsValidator(const std::string& certificate, const std::string& pr , certificateFound_(false) { try { - x509crt_ = {fileutils::loadFile(certificatePath_)}; - certificateContent_ = x509crt_.getPacked(); + x509crt_ = std::make_shared<dht::crypto::Certificate>(fileutils::loadFile(certificatePath_)); + certificateContent_ = x509crt_->getPacked(); certificateFound_ = true; } catch (const std::exception& e) { throw TlsValidatorException("Can't load certificate"); @@ -240,9 +240,22 @@ TlsValidator::TlsValidator(const std::vector<uint8_t>& certificate_raw) , certificateFound_(true) { try { - x509crt_ = {certificate_raw}; - certificateContent_ = x509crt_.getPacked(); - certificateFound_ = true; + x509crt_ = std::make_shared<dht::crypto::Certificate>(certificate_raw); + certificateContent_ = x509crt_->getPacked(); + } catch (const std::exception& e) { + throw TlsValidatorException("Can't load certificate"); + } +} + +TlsValidator::TlsValidator(const std::shared_ptr<dht::crypto::Certificate>& crt) + : gtlsGIG_ {tls::GnuTlsGlobalInit::make_guard()} + , certificateFound_(true) +{ + try { + if (not crt) + throw std::invalid_argument("Certificate must be set"); + x509crt_ = crt; + certificateContent_ = x509crt_->getPacked(); } catch (const std::exception& e) { throw TlsValidatorException("Can't load certificate"); } @@ -425,7 +438,7 @@ unsigned int TlsValidator::compareToCa() return caValidationOutput_; const int err = gnutls_x509_crt_verify( - x509crt_.cert, &caCert_->x509crt_.cert, 1, 0, &caValidationOutput_); + x509crt_->cert, &caCert_->x509crt_->cert, 1, 0, &caValidationOutput_); if (err) return GNUTLS_CERT_SIGNER_NOT_FOUND; @@ -954,7 +967,7 @@ bool TlsValidator::hasCa() const TlsValidator::CheckResult TlsValidator::getPublicSignature() { size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_signature(x509crt_.cert, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_signature(x509crt_->cert, copy_buffer, &resultSize); return checkBinaryError(err, copy_buffer, resultSize); } @@ -963,7 +976,7 @@ TlsValidator::CheckResult TlsValidator::getPublicSignature() */ TlsValidator::CheckResult TlsValidator::getVersionNumber() { - int version = gnutls_x509_crt_get_version(x509crt_.cert); + int version = gnutls_x509_crt_get_version(x509crt_->cert); if (version < 0) return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); @@ -981,7 +994,7 @@ TlsValidator::CheckResult TlsValidator::getSerialNumber() // gnutls_x509_crl_iter_crt_serial // gnutls_x509_crt_get_authority_key_gn_serial size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_serial(x509crt_.cert, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_serial(x509crt_->cert, copy_buffer, &resultSize); return checkBinaryError(err, copy_buffer, resultSize); } @@ -991,7 +1004,7 @@ TlsValidator::CheckResult TlsValidator::getSerialNumber() TlsValidator::CheckResult TlsValidator::getIssuer() { size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_issuer_unique_id(x509crt_.cert, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_issuer_unique_id(x509crt_->cert, copy_buffer, &resultSize); return checkError(err, copy_buffer, resultSize); } @@ -1001,7 +1014,7 @@ TlsValidator::CheckResult TlsValidator::getIssuer() TlsValidator::CheckResult TlsValidator::getSubjectKeyAlgorithm() { gnutls_pk_algorithm_t algo = (gnutls_pk_algorithm_t) gnutls_x509_crt_get_pk_algorithm( - x509crt_.cert, nullptr); + x509crt_->cert, nullptr); if (algo < 0) return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); @@ -1021,7 +1034,7 @@ TlsValidator::CheckResult TlsValidator::getCN() { // TODO split, cache size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_dn_by_oid(x509crt_.cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, copy_buffer, &resultSize); return checkError(err, copy_buffer, resultSize); } @@ -1032,7 +1045,7 @@ TlsValidator::CheckResult TlsValidator::getN() { // TODO split, cache size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_dn_by_oid(x509crt_.cert, GNUTLS_OID_X520_NAME, 0, 0, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert, GNUTLS_OID_X520_NAME, 0, 0, copy_buffer, &resultSize); return checkError(err, copy_buffer, resultSize); } @@ -1043,7 +1056,7 @@ TlsValidator::CheckResult TlsValidator::getO() { // TODO split, cache size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_dn_by_oid(x509crt_.cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert, GNUTLS_OID_X520_ORGANIZATION_NAME, 0, 0, copy_buffer, &resultSize); return checkError(err, copy_buffer, resultSize); } @@ -1054,7 +1067,7 @@ TlsValidator::CheckResult TlsValidator::getO() */ TlsValidator::CheckResult TlsValidator::getSignatureAlgorithm() { - gnutls_sign_algorithm_t algo = (gnutls_sign_algorithm_t) gnutls_x509_crt_get_signature_algorithm(x509crt_.cert); + gnutls_sign_algorithm_t algo = (gnutls_sign_algorithm_t) gnutls_x509_crt_get_signature_algorithm(x509crt_->cert); if (algo < 0) return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); @@ -1071,7 +1084,7 @@ TlsValidator::CheckResult TlsValidator::getSignatureAlgorithm() TlsValidator::CheckResult TlsValidator::getMd5Fingerprint() { size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_fingerprint(x509crt_.cert, GNUTLS_DIG_MD5, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_fingerprint(x509crt_->cert, GNUTLS_DIG_MD5, copy_buffer, &resultSize); return checkBinaryError(err, copy_buffer, resultSize); } @@ -1083,7 +1096,7 @@ TlsValidator::CheckResult TlsValidator::getMd5Fingerprint() TlsValidator::CheckResult TlsValidator::getSha1Fingerprint() { size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_fingerprint(x509crt_.cert, GNUTLS_DIG_SHA1, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_fingerprint(x509crt_->cert, GNUTLS_DIG_SHA1, copy_buffer, &resultSize); return checkBinaryError(err, copy_buffer, resultSize); } @@ -1094,7 +1107,7 @@ TlsValidator::CheckResult TlsValidator::getPublicKeyId() { static unsigned char unsigned_copy_buffer[4096]; size_t resultSize = sizeof(unsigned_copy_buffer); - int err = gnutls_x509_crt_get_key_id(x509crt_.cert,0,unsigned_copy_buffer,&resultSize); + int err = gnutls_x509_crt_get_key_id(x509crt_->cert,0,unsigned_copy_buffer,&resultSize); // TODO check for GNUTLS_E_SHORT_MEMORY_BUFFER and increase the buffer size // TODO get rid of the cast, display a HEX or something, need research @@ -1109,7 +1122,7 @@ TlsValidator::CheckResult TlsValidator::getPublicKeyId() TlsValidator::CheckResult TlsValidator::getIssuerDN() { size_t resultSize = sizeof(copy_buffer); - int err = gnutls_x509_crt_get_issuer_dn(x509crt_.cert, copy_buffer, &resultSize); + int err = gnutls_x509_crt_get_issuer_dn(x509crt_->cert, copy_buffer, &resultSize); return checkError(err, copy_buffer, resultSize); } @@ -1123,7 +1136,7 @@ TlsValidator::CheckResult TlsValidator::getExpirationDate() if (not certificateFound_) return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); - time_t expiration = gnutls_x509_crt_get_expiration_time(x509crt_.cert); + time_t expiration = gnutls_x509_crt_get_expiration_time(x509crt_->cert); return formatDate(expiration); } @@ -1138,7 +1151,7 @@ TlsValidator::CheckResult TlsValidator::getActivationDate() if (not certificateFound_) return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); - time_t expiration = gnutls_x509_crt_get_activation_time(x509crt_.cert); + time_t expiration = gnutls_x509_crt_get_activation_time(x509crt_->cert); return formatDate(expiration); } @@ -1156,4 +1169,4 @@ TlsValidator::CheckResult TlsValidator::outgoingServer() } -} //namespace ring +}} //namespace ring diff --git a/src/sip/tlsvalidator.h b/src/security/tlsvalidator.h similarity index 98% rename from src/sip/tlsvalidator.h rename to src/security/tlsvalidator.h index e68319d1a69fe225ab805072421d7df76d90b205..59a9a6f1e4307ceb5a60f805e29376bec8cba930 100644 --- a/src/sip/tlsvalidator.h +++ b/src/security/tlsvalidator.h @@ -30,10 +30,10 @@ #include <string> #include <vector> +#include <memory> -namespace ring { namespace tls { +namespace ring {namespace tls { class GnuTlsGlobalInit; -}} // namespace ring::tls #if !defined (S_IRWXG) #define S_IRWXG 00070 @@ -60,9 +60,6 @@ class GnuTlsGlobalInit; #define S_IXOTH 00001 #endif /* S_IXOTH */ - -namespace ring { - class TlsValidatorException : public std::runtime_error { public: TlsValidatorException(const std::string& str) : std::runtime_error(str) {}; @@ -175,6 +172,8 @@ public: TlsValidator(const std::vector<uint8_t>& certificate_raw); + TlsValidator(const std::shared_ptr<dht::crypto::Certificate>&); + ~TlsValidator(); bool hasCa() const; @@ -263,7 +262,7 @@ private: std::vector<uint8_t> certificateContent_; std::vector<uint8_t> privateKeyContent_; - dht::crypto::Certificate x509crt_; + std::shared_ptr<dht::crypto::Certificate> x509crt_; bool certificateFound_; bool privateKeyFound_ {false}; @@ -297,6 +296,6 @@ public: }; // TlsValidator -} // namespace ring +}} // namespace ring::tls #endif diff --git a/src/sip/Makefile.am b/src/sip/Makefile.am index f69ed0b8a1cc5e1ea83619a23a1c54ca5e4cf812..6b791bae6d71710f8d4721c5fad3c299bde1e2f6 100644 --- a/src/sip/Makefile.am +++ b/src/sip/Makefile.am @@ -21,14 +21,6 @@ libsiplink_la_SOURCES = \ base64.h \ base64.c -if BUILD_TLS -# These files depend on opendht -if USE_DHT -libsiplink_la_SOURCES += tlsvalidator.cpp \ - tlsvalidator.h -endif -endif - libsiplink_la_SOURCES+=sippresence.cpp \ sippresence.h \ pres_sub_server.cpp\ @@ -42,6 +34,5 @@ libsiplink_la_SOURCES+= sdes_negotiator.cpp \ pattern.cpp \ pattern.h - libsiplink_la_CXXFLAGS += @PCRE_CFLAGS@ endif diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp index cb21ea95af628428e85d4e209fe043fef4f72db3..1d5370fab1e23447a92b228221623391150aee87 100644 --- a/src/sip/sipaccount.cpp +++ b/src/sip/sipaccount.cpp @@ -697,7 +697,23 @@ SIPAccount::getVolatileAccountDetails() const } #if HAVE_TLS - //TODO + if (transport_ and transport_->isSecure() and transport_->isConnected()) { + const auto& tlsInfos = transport_->getTlsInfos(); + auto cipher = pj_ssl_cipher_name(tlsInfos.cipher); + if (tlsInfos.cipher and not cipher) + RING_WARN("Unknown cipher: %d", tlsInfos.cipher); + a.emplace(DRing::TlsTransport::TLS_CIPHER, cipher ? cipher : ""); + a.emplace(DRing::TlsTransport::TLS_PEER_CERT, tlsInfos.peerCert->toString()); + auto ca = tlsInfos.peerCert->issuer; + unsigned n = 0; + while (ca) { + std::ostringstream name_str; + name_str << DRing::TlsTransport::TLS_PEER_CA_ << n++; + a.emplace(name_str.str(), ca->toString()); + ca = ca->issuer; + } + a.emplace(DRing::TlsTransport::TLS_PEER_CA_NUM, std::to_string(n)); + } #endif return a; diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h index d1c764722b00c176cccf67f1d8be7a58479039a5..751d86fb34929dbad36445bc96e1b22cd2783155 100644 --- a/src/sip/sipaccountbase.h +++ b/src/sip/sipaccountbase.h @@ -41,6 +41,7 @@ #include "sip_utils.h" #include "ip_utils.h" #include "noncopyable.h" +#include "security/certstore.h" #include <pjsip/sip_types.h> #include <opendht/value.h> diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index b95c77b5dbeaace7e785d897f9dede4b711451a1..ff5859ae27864b34049a3929f67bfe0d64355183 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -964,14 +964,23 @@ std::map<std::string, std::string> SIPCall::getDetails() const { auto details = Call::getDetails(); - details.emplace(DRing::Call::Details::PEER_HOLDING, peerHolding_ ? TRUE_STR : FALSE_STR); + details.emplace(DRing::Call::Details::PEER_HOLDING, peerHolding_ ? TRUE_STR : FALSE_STR); if (transport_ and transport_->isSecure()) { const auto& tlsInfos = transport_->getTlsInfos(); auto cipher = pj_ssl_cipher_name(tlsInfos.cipher); if (tlsInfos.cipher and not cipher) RING_WARN("Unknown cipher: %d", tlsInfos.cipher); - details.emplace(DRing::Call::Details::TLS_CIPHER, cipher ? cipher : ""); - details.emplace(DRing::Call::Details::TLS_PEER_CERT, tlsInfos.peerCert.toString()); + details.emplace(DRing::TlsTransport::TLS_CIPHER, cipher ? cipher : ""); + details.emplace(DRing::TlsTransport::TLS_PEER_CERT, tlsInfos.peerCert->toString()); + auto ca = tlsInfos.peerCert->issuer; + unsigned n = 0; + while (ca) { + std::ostringstream name_str; + name_str << DRing::TlsTransport::TLS_PEER_CA_ << n++; + details.emplace(name_str.str(), ca->toString()); + ca = ca->issuer; + } + details.emplace(DRing::TlsTransport::TLS_PEER_CA_NUM, std::to_string(n)); } return details; } diff --git a/src/sip/siptransport.cpp b/src/sip/siptransport.cpp index 82c61e47687e7ec2d08e900516d8e24c8586861a..cdaa1680d10065655bde6e648bd1669b21a117f1 100644 --- a/src/sip/siptransport.cpp +++ b/src/sip/siptransport.cpp @@ -132,6 +132,8 @@ void SipTransport::stateCallback(pjsip_transport_state state, const pjsip_transport_state_info *info) { + connected = state == PJSIP_TP_STATE_CONNECTED; + #if HAVE_TLS auto extInfo = static_cast<const pjsip_tls_state_info*>(info->ext_info); if (isSecure() && extInfo && extInfo->ssl_sock_info && extInfo->ssl_sock_info->established) { @@ -140,15 +142,16 @@ SipTransport::stateCallback(pjsip_transport_state state, tlsInfos_.cipher = tlsInfo->cipher; tlsInfos_.verifyStatus = (pj_ssl_cert_verify_flag_t)tlsInfo->verify_status; const auto& peers = tlsInfo->remote_cert_info->raw_chain; - const auto& peer_crt = peers.cert_raw[0]; - if (peer_crt.ptr && peer_crt.slen) - tlsInfos_.peerCert = {std::vector<uint8_t>(peer_crt.ptr, peer_crt.ptr + peer_crt.slen)}; - else - tlsInfos_.peerCert = {}; + std::vector<std::pair<const uint8_t*, const uint8_t*>> bits; + bits.resize(peers.cnt); + std::transform(peers.cert_raw, peers.cert_raw+peers.cnt, std::begin(bits), + [](const pj_str_t& crt){ + return std::make_pair((uint8_t*)crt.ptr, + (uint8_t*)(crt.ptr+crt.slen)); + }); + tlsInfos_.peerCert = std::make_shared<dht::crypto::Certificate>(bits); } else { - tlsInfos_.proto = PJ_SSL_SOCK_PROTO_DEFAULT; - tlsInfos_.cipher = PJ_TLS_UNKNOWN_CIPHER; - tlsInfos_.peerCert = {}; + tlsInfos_ = {}; } #endif diff --git a/src/sip/siptransport.h b/src/sip/siptransport.h index 07e099948919f4df0f83c312abbe42200c603222..ffecabc7dc67b136d3f704dae178309d7c35c7bb 100644 --- a/src/sip/siptransport.h +++ b/src/sip/siptransport.h @@ -106,10 +106,10 @@ private: }; struct TlsInfos { - pj_ssl_cipher cipher; - pj_ssl_sock_proto proto; - pj_ssl_cert_verify_flag_t verifyStatus; - dht::crypto::Certificate peerCert; + pj_ssl_cipher cipher {PJ_TLS_UNKNOWN_CIPHER}; + pj_ssl_sock_proto proto {PJ_SSL_SOCK_PROTO_DEFAULT}; + pj_ssl_cert_verify_flag_t verifyStatus {}; + std::shared_ptr<dht::crypto::Certificate> peerCert {}; }; using SipTransportStateCallback = std::function<void(pjsip_transport_state, const pjsip_transport_state_info*)>; @@ -146,6 +146,9 @@ class SipTransport static bool isAlive(const std::shared_ptr<SipTransport>&, pjsip_transport_state state); + /** Only makes sense for connection-oriented transports */ + bool isConnected() const { return connected; } ; + private: NON_COPYABLE(SipTransport); @@ -156,6 +159,7 @@ class SipTransport std::map<uintptr_t, SipTransportStateCallback> stateListeners_; std::mutex stateListenersMutex_; + bool connected {false}; TlsInfos tlsInfos_; };