diff --git a/src/jamidht/account_manager.cpp b/src/jamidht/account_manager.cpp
index 14b65e4de72a185fbcd0f6aed32f48dcbe3388ce..fdcac969fcb03725175e59f24683a2e746231765 100644
--- a/src/jamidht/account_manager.cpp
+++ b/src/jamidht/account_manager.cpp
@@ -31,6 +31,7 @@
 #include <exception>
 #include <future>
 #include <fstream>
+#include <gnutls/ocsp.h>
 
 namespace jami {
 
@@ -300,6 +301,12 @@ AccountManager::foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>&
         return false;
     }
 
+    // Check cached OCSP response
+    if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD){
+        JAMI_ERR("Certificate %s is disabled by cached OCSP response", crt->getId().to_c_str());
+        return false;
+    }
+
     account_id = crt->issuer->getId();
     JAMI_WARN("Found peer device: %s account:%s CA:%s",
               crt->getId().toString().c_str(),
diff --git a/src/jamidht/contact_list.cpp b/src/jamidht/contact_list.cpp
index 1ceb92c992ef41be04dd8218dde4aa556add4f5a..bacf4937bcf8412e990d7b35f8d19f122801594c 100644
--- a/src/jamidht/contact_list.cpp
+++ b/src/jamidht/contact_list.cpp
@@ -23,6 +23,7 @@
 #include "account_const.h"
 
 #include <fstream>
+#include <gnutls/ocsp.h>
 
 namespace jami {
 
@@ -418,6 +419,21 @@ ContactList::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>&
                  name.c_str(),
                  crt->getId().toString().c_str());
         tls::CertificateStore::instance().pinCertificate(crt);
+        if (crt->ocspResponse){
+            unsigned int status = crt->ocspResponse->getCertificateStatus();
+            if (status == GNUTLS_OCSP_CERT_GOOD){
+                JAMI_DBG("Certificate %s has good OCSP status", crt->getId().to_c_str());
+                trust_.setCertificateStatus(crt->getId().toString(), tls::TrustStore::PermissionStatus::ALLOWED);
+            }
+            else if (status == GNUTLS_OCSP_CERT_REVOKED){
+                JAMI_ERR("Certificate %s has revoked OCSP status", crt->getId().to_c_str());
+                trust_.setCertificateStatus(crt->getId().toString(), tls::TrustStore::PermissionStatus::BANNED);
+            }
+            else {
+                JAMI_ERR("Certificate %s has unknown OCSP status", crt->getId().to_c_str());
+                trust_.setCertificateStatus(crt->getId().toString(), tls::TrustStore::PermissionStatus::UNDEFINED);
+            }
+        }
         saveKnownDevices();
         callbacks_.devicesChanged(knownDevices_);
     } else {
diff --git a/src/security/certstore.cpp b/src/security/certstore.cpp
index b4ac17b935ce61dc89664556c83aedadd186de07..e3f11bb97e7dbb6173205219207757f86f519e67 100644
--- a/src/security/certstore.cpp
+++ b/src/security/certstore.cpp
@@ -2,6 +2,7 @@
  *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
  *
  *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
+ *  Author: Vsevolod Ivanov <vsevolod.ivanov@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
@@ -26,6 +27,7 @@
 #include "logger.h"
 
 #include <opendht/thread_pool.h>
+#include <gnutls/ocsp.h>
 
 #include <thread>
 #include <sstream>
@@ -47,9 +49,11 @@ CertificateStore::instance()
 CertificateStore::CertificateStore()
     : certPath_(fileutils::get_data_dir() + DIR_SEPARATOR_CH + "certificates")
     , crlPath_(fileutils::get_data_dir() + DIR_SEPARATOR_CH + "crls")
+    , ocspPath_(fileutils::get_data_dir() + DIR_SEPARATOR_CH + "ocsp")
 {
     fileutils::check_dir(certPath_.c_str());
     fileutils::check_dir(crlPath_.c_str());
+    fileutils::check_dir(ocspPath_.c_str());
     loadLocalCertificates();
 }
 
@@ -94,6 +98,32 @@ CertificateStore::loadRevocations(crypto::Certificate& crt) const
             JAMI_WARN("Can't load revocation list: %s", e.what());
         }
     }
+    auto ocsp_dir = ocspPath_+DIR_SEPARATOR_CH+crt.getId().toString();
+    auto ocsp_dir_content = fileutils::readDirectory(ocsp_dir);
+    for (const auto& ocsp /*filename*/ : ocsp_dir_content) {
+        try {
+            std::string ocsp_filepath = ocsp_dir+DIR_SEPARATOR_CH+ocsp;
+            JAMI_DBG("Found %s", ocsp_filepath.c_str());
+            auto serial = crt.getSerialNumber();
+            if (dht::toHex(serial.data(), serial.size()) != ocsp)
+                continue;
+            // Save the response
+            dht::Blob ocspBlob = fileutils::loadFile(ocsp_filepath);
+            crt.ocspResponse = std::make_shared<dht::crypto::OcspResponse>(ocspBlob.data(),ocspBlob.size());
+            unsigned int status = crt.ocspResponse->getCertificateStatus();
+            if (status == GNUTLS_OCSP_CERT_GOOD)
+                JAMI_DBG("Certificate %s has good OCSP status", crt.getId().to_c_str());
+            else if (status == GNUTLS_OCSP_CERT_REVOKED)
+                JAMI_ERR("Certificate %s has revoked OCSP status", crt.getId().to_c_str());
+            else if (status == GNUTLS_OCSP_CERT_UNKNOWN)
+                JAMI_ERR("Certificate %s has unknown OCSP status", crt.getId().to_c_str());
+            else
+                JAMI_ERR("Certificate %s has invalid OCSP status", crt.getId().to_c_str());
+
+        } catch (const std::exception& e) {
+            JAMI_WARN("Can't load OCSP revocation status: %s", e.what());
+        }
+    }
 }
 
 std::vector<std::string>
@@ -367,6 +397,35 @@ CertificateStore::pinRevocationList(const std::string& id, const dht::crypto::Re
                         crl.getPacked());
 }
 
+void
+CertificateStore::pinOcspResponse(const dht::crypto::Certificate& cert)
+{
+    if (not cert.ocspResponse)
+        return;
+    try {
+        cert.ocspResponse->getCertificateStatus();
+    }
+    catch (dht::crypto::CryptoException& e){
+        JAMI_ERR("Failed to read certificate status of OCSP response: %s", e.what());
+        return;
+    }
+    auto id = cert.getId().toString();
+    auto serialhex = dht::toHex(cert.getSerialNumber());
+    auto dir = ocspPath_ + DIR_SEPARATOR_CH + id;
+    dht::ThreadPool::io().run([
+        path = dir + DIR_SEPARATOR_CH + serialhex,
+        dir = std::move(dir),
+        id = std::move(id),
+        serialhex = std::move(serialhex),
+        ocspResponse = cert.ocspResponse
+    ]{
+        JAMI_DBG("Saving OCSP Response of device %s with serial %s", id.c_str(), serialhex.c_str());
+        std::lock_guard<std::mutex> lock(fileutils::getFileLock(path));
+        fileutils::check_dir(dir.c_str());
+        fileutils::saveFile(path, ocspResponse->pack());
+    });
+}
+
 TrustStore::PermissionStatus
 TrustStore::statusFromStr(const char* str)
 {
diff --git a/src/security/certstore.h b/src/security/certstore.h
index 3e34fc6e90cd11960b90b5c6f478a823e34443cc..24642dc9d3d3e6b9e1342a93447af8a7629cc166 100644
--- a/src/security/certstore.h
+++ b/src/security/certstore.h
@@ -83,6 +83,7 @@ public:
                           std::make_shared<dht::crypto::RevocationList>(
                               std::forward<dht::crypto::RevocationList>(crl)));
     }
+    void pinOcspResponse(const dht::crypto::Certificate& cert);
 
     void loadRevocations(crypto::Certificate& crt) const;
 
@@ -94,6 +95,7 @@ private:
 
     const std::string certPath_;
     const std::string crlPath_;
+    const std::string ocspPath_;
 
     mutable std::mutex lock_;
     std::map<std::string, std::shared_ptr<crypto::Certificate>> certs_;
diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp
index 7feb9362e40cb13815ce666a2b5ead7dc6e9df37..9c737eb555ed95dac99d9a80a16d92cd076d68a4 100644
--- a/src/security/tls_session.cpp
+++ b/src/security/tls_session.cpp
@@ -4,6 +4,7 @@
  *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
  *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *  Author: Vsevolod Ivanov <vsevolod.ivanov@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
@@ -38,6 +39,10 @@
 #include <gnutls/dtls.h>
 #include <gnutls/abstract.h>
 
+#include <gnutls/crypto.h>
+#include <gnutls/ocsp.h>
+#include <opendht/http.h>
+
 #include <list>
 #include <mutex>
 #include <condition_variable>
@@ -103,6 +108,8 @@ static constexpr auto RX_OOO_TIMEOUT = std::chrono::milliseconds(1500);
 static constexpr int ASYMETRIC_TRANSPORT_MTU_OFFSET
     = 20; // when client, if your local IP is IPV4 and server is IPV6; you must reduce your MTU to
           // avoid packet too big error on server side. the offset is the difference in size of IP headers
+static constexpr auto OCSP_VERIFICATION_TIMEOUT = std::chrono::seconds(2); // Time to wait for an OCSP verification
+static constexpr auto OCSP_REQUEST_TIMEOUT = std::chrono::seconds(2); // Time to wait for an ocsp-request
 
 // Helper to cast any duration into an integer number of milliseconds
 template<class Rep, class Period>
@@ -203,6 +210,8 @@ class TlsSession::TlsSessionImpl
 public:
     using clock = std::chrono::steady_clock;
     using StateHandler = std::function<TlsSessionState(TlsSessionState state)>;
+    using OcspVerification = std::function<void(const int status)>;
+    using HttpResponse = std::function<void(const dht::http::Response& response)>;
 
     // Constants (ctor init.)
     const bool isServer_;
@@ -284,6 +293,20 @@ public:
     void initCredentials();
     bool commonSessionInit();
 
+    /*
+     * Implicit certificate validations.
+     */
+    int verifyCertificateWrapper(gnutls_session_t session);
+    /*
+     * Verify OCSP (Online Certificate Service Protocol):
+     */
+    void verifyOcsp(const std::string& url, dht::crypto::Certificate& cert, gnutls_x509_crt_t issuer, OcspVerification cb);
+    /*
+     * Send OCSP Request to the specified URI.
+     */
+    void sendOcspRequest(const std::string& uri, std::string body,
+                         std::chrono::seconds timeout, HttpResponse cb = {});
+
     // FSM thread (TLS states)
     ThreadLoop thread_; // ctor init.
     bool setup();
@@ -298,6 +321,9 @@ public:
     int hbPingRecved_ {0};
     bool pmtudOver_ {false};
     void pathMtuHeartbeat();
+
+    std::mutex requestsMtx_;
+    std::set<std::shared_ptr<dht::http::Request>> requests_;
 };
 
 TlsSession::TlsSessionImpl::TlsSessionImpl(std::unique_ptr<SocketType>&& transport,
@@ -441,11 +467,10 @@ TlsSession::TlsSessionImpl::initCredentials()
     // credentials for handshaking and transmission
     xcred_.reset(new TlsCertificateCredendials());
 
-    if (callbacks_.verifyCertificate)
-        gnutls_certificate_set_verify_function(*xcred_, [](gnutls_session_t session) -> int {
-            auto this_ = reinterpret_cast<TlsSessionImpl*>(gnutls_session_get_ptr(session));
-            return this_->callbacks_.verifyCertificate(session);
-        });
+    gnutls_certificate_set_verify_function(*xcred_, [](gnutls_session_t session) -> int {
+        auto this_ = reinterpret_cast<TlsSessionImpl*>(gnutls_session_get_ptr(session));
+        return this_->verifyCertificateWrapper(session);
+    });
 
     // Load user-given CA list
     if (not params_.ca_list.empty()) {
@@ -589,6 +614,200 @@ TlsSession::TlsSessionImpl::commonSessionInit()
     return true;
 }
 
+std::string
+getOcspUrl(gnutls_x509_crt_t cert)
+{
+    int ret;
+    gnutls_datum_t aia;
+    unsigned int seq = 0;
+    do {
+        // Extracts the Authority Information Access (AIA) extension, see RFC 5280 section 4.2.2.1
+        ret = gnutls_x509_crt_get_authority_info_access(cert, seq++, GNUTLS_IA_OCSP_URI, &aia, NULL);
+    }
+    while (ret < 0 && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
+    // could also try the issuer if we include ocsp uri into there
+    if (ret < 0) {
+        return {};
+    }
+    std::string url((const char*)aia.data,(size_t)aia.size);
+    gnutls_free(aia.data);
+    return url;
+}
+
+int
+TlsSession::TlsSessionImpl::verifyCertificateWrapper(gnutls_session_t session)
+{
+    // Perform user-set verification first to avoid flooding with ocsp-requests if peer is denied
+    int verified;
+    if (callbacks_.verifyCertificate)
+    {
+        auto this_ = reinterpret_cast<TlsSessionImpl*>(gnutls_session_get_ptr(session));
+        verified = this_->callbacks_.verifyCertificate(session);
+        if (verified != GNUTLS_E_SUCCESS)
+            return verified;
+    }
+    /*
+     * Support only x509 format
+     */
+    if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509)
+        return GNUTLS_E_CERTIFICATE_ERROR;
+    /*
+     * Get the peer's raw certificate (chain) as sent by the peer.
+     * The first certificate in the list is the peer's certificate, following the issuer's cert. etc.
+     */
+    unsigned int cert_list_size = 0;
+    auto cert_list = gnutls_certificate_get_peers(session, &cert_list_size);
+    if (cert_list == nullptr)
+        return GNUTLS_E_CERTIFICATE_ERROR;
+
+    /*
+     * Extract verification data by deserializing the certificate chain
+     */
+    std::vector<std::pair<uint8_t*, uint8_t*>> crt_data;
+    crt_data.reserve(cert_list_size);
+    for (unsigned i = 0; i < cert_list_size; i++)
+        crt_data.emplace_back(cert_list[i].data, cert_list[i].data + cert_list[i].size);
+    auto cert = dht::crypto::Certificate(crt_data);
+
+    std::string ocspUrl = getOcspUrl(cert.cert);
+    if (ocspUrl.empty()) {
+        JAMI_DBG("Skipping OCSP verification %s: AIA not found", cert.getUID().c_str());
+        return verified;
+    }
+
+    // OCSP (Online Certificate Service Protocol) {
+    bool ocsp_done = false;
+    std::mutex cv_m;
+    std::condition_variable cv;
+    std::unique_lock<std::mutex> cv_lk(cv_m);
+    
+    gnutls_x509_crt_t issuer_crt = cert.issuer ? cert.issuer->cert : nullptr;
+    verifyOcsp(ocspUrl, cert, issuer_crt, [&](const int status) {
+        if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE){
+            // OCSP URI is absent, don't fail the verification by overwritting the user-set one.
+            JAMI_WARN("Skipping OCSP verification %s: request failed", cert.getUID().c_str());
+        } else {
+            if (status != GNUTLS_E_SUCCESS) {
+                JAMI_ERR("OCSP verification failed for %s: %s (%i)",
+                     cert.getUID().c_str(), gnutls_strerror(status), status);
+            }
+            verified = status;
+        }
+        std::lock_guard<std::mutex> cv_lk(cv_m);
+        ocsp_done = true;
+        cv.notify_all();
+    });
+    cv.wait_for(cv_lk, std::chrono::seconds(OCSP_VERIFICATION_TIMEOUT),[&ocsp_done]{return ocsp_done;});
+    // } OCSP
+
+    return verified;
+}
+
+void
+TlsSession::TlsSessionImpl::verifyOcsp(const std::string& aia_uri, dht::crypto::Certificate& cert, gnutls_x509_crt_t issuer, OcspVerification cb)
+{
+    JAMI_DBG("Certificate's AIA URI: %s", aia_uri.c_str());
+
+    // Generate OCSP request
+    std::pair<std::string, dht::Blob> ocsp_req;
+    try {
+        ocsp_req = cert.generateOcspRequest(issuer);
+    }
+    catch (dht::crypto::CryptoException& e){
+        JAMI_ERR("Failed to generate OCSP request: %s", e.what());
+        if (cb)
+            cb(GNUTLS_E_INVALID_REQUEST);
+        return;
+    }
+
+    sendOcspRequest(aia_uri, std::move(ocsp_req.first), OCSP_REQUEST_TIMEOUT,
+        [this, cb = std::move(cb), &cert, nonce=std::move(ocsp_req.second)](const dht::http::Response& r){
+        // Prepare response data
+        // Verify response validity
+        if (r.status_code != 200) {
+            JAMI_WARN("HTTP OCSP Request Failed with code %i", r.status_code);
+            if (cb)
+                cb(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE);
+            return;
+        }
+        JAMI_DBG("HTTP OCSP Request done!");
+        unsigned int verify = 0;
+        try {
+            cert.ocspResponse = std::make_shared<dht::crypto::OcspResponse>((const uint8_t*)r.body.data(), r.body.size());
+            JAMI_DBG("%s", cert.ocspResponse->toString().c_str());
+            verify = cert.ocspResponse->verifyDirect(cert, nonce);
+        }
+        catch (dht::crypto::CryptoException& e) {
+            JAMI_ERR("Failed to verify OCSP response: %s", e.what());
+            if (cb)
+                cb(GNUTLS_E_INVALID_REQUEST);
+            return;
+        }
+        if (verify == 0)
+            JAMI_DBG("OCSP verification success!");
+        else
+            JAMI_ERR("OCSP verification error!");
+        if (verify & GNUTLS_OCSP_VERIFY_SIGNER_NOT_FOUND)
+            JAMI_ERR("Signer cert not found");
+        if (verify & GNUTLS_OCSP_VERIFY_SIGNER_KEYUSAGE_ERROR)
+            JAMI_ERR("Signer cert keyusage error");
+        if (verify & GNUTLS_OCSP_VERIFY_UNTRUSTED_SIGNER)
+            JAMI_ERR("Signer cert is not trusted");
+        if (verify & GNUTLS_OCSP_VERIFY_INSECURE_ALGORITHM)
+            JAMI_ERR("Insecure algorithm");
+        if (verify & GNUTLS_OCSP_VERIFY_SIGNATURE_FAILURE)
+            JAMI_ERR("Signature failure");
+        if (verify & GNUTLS_OCSP_VERIFY_CERT_NOT_ACTIVATED)
+            JAMI_ERR("Signer cert not yet activated");
+        if (verify & GNUTLS_OCSP_VERIFY_CERT_EXPIRED)
+            JAMI_ERR("Signer cert expired");
+        // Save response into the certificate store
+        tls::CertificateStore::instance().pinOcspResponse(cert);
+        if (cb)
+            cb(verify);
+    });
+}
+
+void
+TlsSession::TlsSessionImpl::sendOcspRequest(const std::string& uri, std::string body,
+                                            std::chrono::seconds timeout, HttpResponse cb)
+{
+    using namespace dht;
+    auto request = std::make_shared<http::Request>(*Manager::instance().ioContext(), uri);//, logger);
+    request->set_method(restinio::http_method_post());
+    request->set_header_field(restinio::http_field_t::user_agent, "Jami");
+    request->set_header_field(restinio::http_field_t::accept, "*/*");
+    request->set_header_field(restinio::http_field_t::content_type, "application/ocsp-request");
+    request->set_body(std::move(body));
+    request->set_connection_type(restinio::http_connection_header_t::close);
+    request->add_on_state_change_callback([this, cb = std::move(cb), timeout]
+        (const http::Request::State state, const http::Response response)
+    {
+        JAMI_DBG("HTTP OCSP Request state=%i status_code=%i", (unsigned int) state, response.status_code);
+        if (state == http::Request::State::SENDING){
+            auto request = response.request.lock();
+            request->get_connection()->timeout(timeout, [request](const asio::error_code& ec){
+                if (ec and ec != asio::error::operation_aborted)
+                    JAMI_ERR("HTTP OCSP Request timeout with error: %s", ec.message().c_str());
+                request->cancel();
+            });
+        }
+        if (state != http::Request::State::DONE)
+            return;
+        if (cb)
+            cb(response);
+        if (auto request = response.request.lock()) {
+            std::lock_guard<std::mutex> lock(requestsMtx_);
+            requests_.erase(request);
+        }
+    });
+    {
+        std::lock_guard<std::mutex> lock(requestsMtx_);
+        requests_.emplace(request);
+    }
+    request->send();
+}
+
 std::size_t
 TlsSession::TlsSessionImpl::send(const ValueType* tx_data, std::size_t tx_size, std::error_code& ec)
 {
diff --git a/src/security/tls_session.h b/src/security/tls_session.h
index 823e32aed3a869b94a3d99aeff924e6096ad2034..5951a4008f9382af3f9efa9b96ebb30b1e2dd568 100644
--- a/src/security/tls_session.h
+++ b/src/security/tls_session.h
@@ -4,6 +4,7 @@
  *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
  *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *  Author: Vsevolod Ivanov <vsevolod.ivanov@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