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_;
 };