diff --git a/include/opendht/crypto.h b/include/opendht/crypto.h
index badb0c757144336247094b4534a1863cdcc20474..6782ac7b1e8632c1a8e7ba2dffcb15feeb387937 100644
--- a/include/opendht/crypto.h
+++ b/include/opendht/crypto.h
@@ -66,7 +66,7 @@ using Identity = std::pair<std::shared_ptr<PrivateKey>, std::shared_ptr<Certific
  */
 struct OPENDHT_PUBLIC PublicKey
 {
-    PublicKey() {}
+    PublicKey();
 
     /**
      * Takes ownership of an existing gnutls_pubkey.
@@ -115,7 +115,9 @@ struct OPENDHT_PUBLIC PublicKey
 
     void msgpack_unpack(msgpack::object o);
 
-    gnutls_pubkey_t pk {};
+    gnutls_digest_algorithm_t getPreferredDigest() const;
+
+    gnutls_pubkey_t pk {nullptr};
 private:
     PublicKey(const PublicKey&) = delete;
     PublicKey& operator=(const PublicKey&) = delete;
@@ -254,14 +256,48 @@ private:
     RevocationList& operator=(const RevocationList&) = delete;
 };
 
+enum class NameType { UNKNOWN = 0, RFC822, DNS, URI, IP };
+
+class OPENDHT_PUBLIC CertificateRequest {
+public:
+    CertificateRequest();
+    CertificateRequest(const uint8_t* data, size_t size);
+    CertificateRequest(const Blob& data) : CertificateRequest(data.data(), data.size()) {}
+
+    CertificateRequest(CertificateRequest&& o) noexcept : request(std::move(o.request)) {
+        o.request = nullptr;
+    }
+    CertificateRequest& operator=(CertificateRequest&& o) noexcept;
+
+    ~CertificateRequest();
+
+    void setName(const std::string& name);
+    void setUID(const std::string& name);
+    void setAltName(NameType type, const std::string& name);
+
+    std::string getName() const;
+    std::string getUID() const;
+
+    void sign(const PrivateKey& key, const std::string& password = {});
+
+    bool verify() const;
+
+    Blob pack() const;
+
+    gnutls_x509_crq_t get() const { return request; }
+private:
+    CertificateRequest(const CertificateRequest& o) = delete;
+    CertificateRequest& operator=(const CertificateRequest& o) = delete;
+    gnutls_x509_crq_t request {nullptr};
+};
 
 struct OPENDHT_PUBLIC Certificate {
-    Certificate() {}
+    Certificate() noexcept {}
 
     /**
      * Take ownership of existing gnutls structure
      */
-    Certificate(gnutls_x509_crt_t crt) : cert(crt) {}
+    Certificate(gnutls_x509_crt_t crt) noexcept : cert(crt) {}
 
     Certificate(Certificate&& o) noexcept : cert(o.cert), issuer(std::move(o.issuer)) { o.cert = nullptr; };
 
@@ -392,8 +428,6 @@ struct OPENDHT_PUBLIC Certificate {
     /** Read certificate issuer User ID (UID) */
     std::string getIssuerUID() const;
 
-    enum class NameType { UNKNOWN = 0, RFC822, DNS, URI, IP };
-
     /** Read certificate alternative names */
     std::vector<std::pair<NameType, std::string>> getAltNames() const;
 
@@ -432,6 +466,7 @@ struct OPENDHT_PUBLIC Certificate {
     void addRevocationList(std::shared_ptr<RevocationList>);
 
     static Certificate generate(const PrivateKey& key, const std::string& name = "dhtnode", const Identity& ca = {}, bool is_ca = false);
+    static Certificate generate(const CertificateRequest& request, const Identity& ca);
 
     gnutls_x509_crt_t getCopy() const {
         if (not cert)
@@ -472,7 +507,9 @@ struct OPENDHT_PUBLIC Certificate {
         return {crts, crls};
     }
 
-    gnutls_x509_crt_t cert {};
+    gnutls_digest_algorithm_t getPreferredDigest() const;
+
+    gnutls_x509_crt_t cert {nullptr};
     std::shared_ptr<Certificate> issuer {};
 private:
     Certificate(const Certificate&) = delete;
@@ -513,7 +550,7 @@ struct OPENDHT_PUBLIC TrustList
 private:
     TrustList(const TrustList& o) = delete;
     TrustList& operator=(const TrustList& o) = delete;
-    gnutls_x509_trust_list_t trust;
+    gnutls_x509_trust_list_t trust {nullptr};
 };
 
 template <class T>
diff --git a/python/tools/pingpong.py b/python/tools/pingpong.py
index 158337ee28e9911af342eb4c522cad3604c01a74..f8dca3c3c9d0929bfe3c8a56408ee3f0c9fcb5d4 100755
--- a/python/tools/pingpong.py
+++ b/python/tools/pingpong.py
@@ -27,7 +27,7 @@ ping_node.run()
 pong_node = dht.DhtRunner()
 pong_node.run()
 #pong_node.enableLogging()
-pong_node.ping(ping_node.getBound());
+pong_node.ping(ping_node.getBound())
 
 loc_ping = dht.InfoHash.get("toto99")
 loc_pong = dht.InfoHash.get(str(loc_ping))
diff --git a/src/crypto.cpp b/src/crypto.cpp
index 7f82889cda76f15584aa3cda589791820f728b59..62ef4f5042ad129ed2afdf5fd1d6a3d9f06a0a40 100644
--- a/src/crypto.cpp
+++ b/src/crypto.cpp
@@ -40,31 +40,6 @@ static std::uniform_int_distribution<int> rand_byte{ 0, std::numeric_limits<uint
 static std::uniform_int_distribution<uint8_t> rand_byte;
 #endif
 
-static gnutls_digest_algorithm_t get_dig_for_pub(gnutls_pubkey_t pubkey)
-{
-    gnutls_digest_algorithm_t dig;
-    int result = gnutls_pubkey_get_preferred_hash_algorithm(pubkey, &dig, nullptr);
-    if (result < 0)
-        return GNUTLS_DIG_UNKNOWN;
-    return dig;
-}
-
-static gnutls_digest_algorithm_t get_dig(gnutls_x509_crt_t crt)
-{
-    gnutls_pubkey_t pubkey;
-    gnutls_pubkey_init(&pubkey);
-
-    int result = gnutls_pubkey_import_x509(pubkey, crt, 0);
-    if (result < 0) {
-        gnutls_pubkey_deinit(pubkey);
-        return GNUTLS_DIG_UNKNOWN;
-    }
-
-    gnutls_digest_algorithm_t dig = get_dig_for_pub(pubkey);
-    gnutls_pubkey_deinit(pubkey);
-    return dig;
-}
-
 // support for GnuTLS < 3.4.
 #if GNUTLS_VERSION_NUMBER < 0x030400
 #define GNUTLS_PKCS_PKCS12_3DES GNUTLS_PKCS_USE_PKCS12_3DES
@@ -377,15 +352,19 @@ PrivateKey::serialize(const std::string& password) const
 PublicKey
 PrivateKey::getPublicKey() const
 {
-    gnutls_pubkey_t pk;
-    gnutls_pubkey_init(&pk);
-    PublicKey pk_ret {pk};
-    if (gnutls_pubkey_import_privkey(pk, key, GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN, 0) != GNUTLS_E_SUCCESS)
-        return {};
-    return pk_ret;
+    PublicKey pk;
+    if (auto err = gnutls_pubkey_import_privkey(pk.pk, key, GNUTLS_KEY_KEY_CERT_SIGN | GNUTLS_KEY_CRL_SIGN, 0))
+        throw CryptoException(std::string("Can't retreive public key: ") + gnutls_strerror(err));
+    return pk;
+}
+
+PublicKey::PublicKey()
+{
+    if (auto err = gnutls_pubkey_init(&pk))
+        throw CryptoException(std::string("Can't initialize public key: ") + gnutls_strerror(err));
 }
 
-PublicKey::PublicKey(const Blob& dat)
+PublicKey::PublicKey(const Blob& dat) : PublicKey()
 {
     unpack(dat.data(), dat.size());
 }
@@ -415,8 +394,7 @@ PublicKey::pack(Blob& b) const
         throw CryptoException(std::string("Could not export public key: null key"));
     std::vector<uint8_t> tmp(2048);
     size_t sz = tmp.size();
-    int err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_DER, tmp.data(), &sz);
-    if (err != GNUTLS_E_SUCCESS)
+    if (int err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_DER, tmp.data(), &sz))
         throw CryptoException(std::string("Could not export public key: ") + gnutls_strerror(err));
     tmp.resize(sz);
     b.insert(b.end(), tmp.begin(), tmp.end());
@@ -425,9 +403,6 @@ PublicKey::pack(Blob& b) const
 void
 PublicKey::unpack(const uint8_t* data, size_t data_size)
 {
-    if (pk)
-        gnutls_pubkey_deinit(pk);
-    gnutls_pubkey_init(&pk);
     const gnutls_datum_t dat {(uint8_t*)data, (unsigned)data_size};
     int err = gnutls_pubkey_import(pk, &dat, GNUTLS_X509_FMT_PEM);
     if (err != GNUTLS_E_SUCCESS)
@@ -446,10 +421,9 @@ PublicKey::toString() const
     int err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_PEM, (void*)ret.data(), &sz);
     if (err ==  GNUTLS_E_SHORT_MEMORY_BUFFER) {
         ret.resize(sz);
-        int err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_PEM, (void*)ret.data(), &sz);
-        if (err != GNUTLS_E_SUCCESS)
-            throw CryptoException(std::string("Could not print public key: ") + gnutls_strerror(err));
-    } else if (err != GNUTLS_E_SUCCESS)
+        err = gnutls_pubkey_export(pk, GNUTLS_X509_FMT_PEM, (void*)ret.data(), &sz);
+    }
+    if (err != GNUTLS_E_SUCCESS)
         throw CryptoException(std::string("Could not print public key: ") + gnutls_strerror(err));
     return ret;
 }
@@ -572,6 +546,179 @@ PublicKey::getLongId() const
 #endif
 }
 
+gnutls_digest_algorithm_t
+PublicKey::getPreferredDigest() const
+{
+    gnutls_digest_algorithm_t dig;
+    int result = gnutls_pubkey_get_preferred_hash_algorithm(pk, &dig, nullptr);
+    if (result < 0)
+        return GNUTLS_DIG_UNKNOWN;
+    return dig;
+}
+
+// Certificate Request
+
+static NameType
+typeFromGnuTLS(gnutls_x509_subject_alt_name_t type)
+{
+    switch(type) {
+    case GNUTLS_SAN_DNSNAME:
+        return NameType::DNS;
+    case GNUTLS_SAN_RFC822NAME:
+        return NameType::RFC822;
+    case GNUTLS_SAN_URI:
+        return NameType::URI;
+    case GNUTLS_SAN_IPADDRESS:
+        return NameType::IP;
+    default:
+        return NameType::UNKNOWN;
+    }
+}
+
+static gnutls_x509_subject_alt_name_t
+GnuTLSFromType(NameType type)
+{
+    switch(type) {
+    case NameType::DNS:
+        return GNUTLS_SAN_DNSNAME;
+    case NameType::RFC822:
+        return GNUTLS_SAN_RFC822NAME;
+    case NameType::URI:
+        return GNUTLS_SAN_URI;
+    case NameType::IP:
+        return GNUTLS_SAN_IPADDRESS;
+    default:
+        return (gnutls_x509_subject_alt_name_t)0;
+    }
+}
+
+static std::string
+getDN(gnutls_x509_crq_t request, const char* oid)
+{
+    std::string dn;
+    dn.resize(512);
+    size_t dn_sz = dn.size();
+    int ret = gnutls_x509_crq_get_dn_by_oid(request, oid, 0, 0, &(*dn.begin()), &dn_sz);
+    if (ret != GNUTLS_E_SUCCESS)
+        return {};
+    dn.resize(dn_sz);
+    return dn;
+}
+
+CertificateRequest::CertificateRequest()
+{
+    if (auto err = gnutls_x509_crq_init(&request))
+        throw CryptoException(std::string("Can't initialize certificate request: ") + gnutls_strerror(err));
+}
+
+CertificateRequest::CertificateRequest(const uint8_t* data, size_t size) : CertificateRequest()
+{
+    const gnutls_datum_t dat {(uint8_t*)data, (unsigned)size};
+    if (auto err = gnutls_x509_crq_import(request, &dat, GNUTLS_X509_FMT_PEM))
+        throw CryptoException(std::string("Can't import certificate request: ") + gnutls_strerror(err));
+}
+
+CertificateRequest::~CertificateRequest()
+{
+    if (request) {
+        gnutls_x509_crq_deinit(request);
+        request = nullptr;
+    }
+}
+
+CertificateRequest&
+CertificateRequest::operator=(CertificateRequest&& o) noexcept
+{
+    if (request)
+        gnutls_x509_crq_deinit(request);
+    request = o.request;
+    o.request = nullptr;
+    return *this;
+}
+
+void
+CertificateRequest::setAltName(NameType type, const std::string& name)
+{
+    gnutls_x509_crq_set_subject_alt_name(request, GnuTLSFromType(type), name.data(), name.size(), 0);
+}
+
+void
+CertificateRequest::setName(const std::string& name)
+{
+    gnutls_x509_crq_set_dn_by_oid(request, GNUTLS_OID_X520_COMMON_NAME, 0, name.data(), name.length());
+}
+
+std::string
+CertificateRequest::getName() const
+{
+    return getDN(request, GNUTLS_OID_X520_COMMON_NAME);
+}
+
+std::string
+CertificateRequest::getUID() const
+{
+    return getDN(request, GNUTLS_OID_LDAP_UID);
+}
+
+void
+CertificateRequest::setUID(const std::string& uid)
+{
+    gnutls_x509_crq_set_dn_by_oid(request, GNUTLS_OID_LDAP_UID, 0, uid.data(), uid.length());
+}
+
+void
+CertificateRequest::sign(const PrivateKey& key, const std::string& password)
+{
+    gnutls_x509_crq_set_version(request, 1);
+    if (not password.empty())
+        gnutls_x509_crq_set_challenge_password(request, password.c_str());
+
+    if (auto err = gnutls_x509_crq_set_key(request,  key.x509_key))
+        throw CryptoException(std::string("Can't set certificate request key: ") + gnutls_strerror(err));
+
+#if GNUTLS_VERSION_NUMBER < 0x030601
+    if (auto err = gnutls_x509_crq_privkey_sign(request, key.key, key.getPublicKey().getPreferredDigest(), 0))
+        throw CryptoException(std::string("Can't sign certificate request: ") + gnutls_strerror(err));
+#else
+    if (auto err = gnutls_x509_crq_privkey_sign(request, key.key, GNUTLS_DIG_UNKNOWN, 0))
+        throw CryptoException(std::string("Can't sign certificate request: ") + gnutls_strerror(err));
+#endif
+}
+
+bool
+CertificateRequest::verify() const 
+{
+    return gnutls_x509_crq_verify(request, 0) >= 0;
+}
+
+Blob 
+CertificateRequest::pack() const 
+{
+    gnutls_datum_t dat {nullptr, 0};
+    if (auto err = gnutls_x509_crq_export2(request, GNUTLS_X509_FMT_PEM, &dat))
+        throw CryptoException(std::string("Can't export certificate request: ") + gnutls_strerror(err));
+    Blob ret(dat.data, dat.data + dat.size);
+    gnutls_free(dat.data);
+    return ret;
+}
+
+// Certificate
+
+static std::string
+getDN(gnutls_x509_crt_t cert, const char* oid, bool issuer = false)
+{
+    std::string dn;
+    dn.resize(512);
+    size_t dn_sz = dn.size();
+    int ret = issuer
+            ? gnutls_x509_crt_get_issuer_dn_by_oid(cert, oid, 0, 0, &(*dn.begin()), &dn_sz)
+            : gnutls_x509_crt_get_dn_by_oid(       cert, oid, 0, 0, &(*dn.begin()), &dn_sz);
+    if (ret != GNUTLS_E_SUCCESS)
+        return {};
+    dn.resize(dn_sz);
+    return dn;
+}
+
 Certificate::Certificate(const Blob& certData)
 {
     unpack(certData.data(), certData.size());
@@ -656,11 +803,9 @@ Certificate::~Certificate()
 PublicKey
 Certificate::getPublicKey() const
 {
-    gnutls_pubkey_t pk;
-    gnutls_pubkey_init(&pk);
-    PublicKey pk_ret(pk);
-    if (gnutls_pubkey_import_x509(pk, cert, 0) != GNUTLS_E_SUCCESS)
-        return {};
+    PublicKey pk_ret;
+    if (auto err = gnutls_pubkey_import_x509(pk_ret.pk, cert, 0))
+        throw CryptoException(std::string("Can't get certificate public key: ") + gnutls_strerror(err));
     return pk_ret;
 }
 
@@ -696,21 +841,6 @@ Certificate::getLongId() const
 #endif
 }
 
-static std::string
-getDN(gnutls_x509_crt_t cert, const char* oid, bool issuer = false)
-{
-    std::string dn;
-    dn.resize(512);
-    size_t dn_sz = dn.size();
-    int ret = issuer
-            ? gnutls_x509_crt_get_issuer_dn_by_oid(cert, oid, 0, 0, &(*dn.begin()), &dn_sz)
-            : gnutls_x509_crt_get_dn_by_oid(       cert, oid, 0, 0, &(*dn.begin()), &dn_sz);
-    if (ret != GNUTLS_E_SUCCESS)
-        return {};
-    dn.resize(dn_sz);
-    return dn;
-}
-
 std::string
 Certificate::getName() const
 {
@@ -735,24 +865,7 @@ Certificate::getIssuerUID() const
     return getDN(cert, GNUTLS_OID_LDAP_UID, true);
 }
 
-static Certificate::NameType
-typeFromGnuTLS(gnutls_x509_subject_alt_name_t type)
-{
-    switch(type) {
-    case GNUTLS_SAN_DNSNAME:
-        return Certificate::NameType::DNS;
-    case GNUTLS_SAN_RFC822NAME:
-        return Certificate::NameType::RFC822;
-    case GNUTLS_SAN_URI:
-        return Certificate::NameType::URI;
-    case GNUTLS_SAN_IPADDRESS:
-        return Certificate::NameType::IP;
-    default:
-        return Certificate::NameType::UNKNOWN;
-    }
-}
-
-std::vector<std::pair<Certificate::NameType, std::string>>
+std::vector<std::pair<NameType, std::string>>
 Certificate::getAltNames() const
 {
     std::vector<std::pair<NameType, std::string>> names;
@@ -867,6 +980,14 @@ Certificate::getExpiration() const
     return std::chrono::system_clock::from_time_t(t);
 }
 
+gnutls_digest_algorithm_t
+Certificate::getPreferredDigest() const
+{
+    return getPublicKey().getPreferredDigest();
+}
+
+// PrivateKey
+
 PrivateKey
 PrivateKey::generate(unsigned key_length)
 {
@@ -922,6 +1043,27 @@ generateEcIdentity(const std::string& name, const Identity& ca) {
     return generateEcIdentity(name, ca, !ca.first || !ca.second);
 }
 
+void
+setValidityPeriod(gnutls_x509_crt_t cert, int64_t validity)
+{
+    int64_t now = time(nullptr);
+    /* 2038 bug: don't allow time wrap */
+    auto boundTime = [](int64_t t) -> time_t {
+        return std::min<int64_t>(t, std::numeric_limits<time_t>::max());
+    };
+    gnutls_x509_crt_set_activation_time(cert, boundTime(now));
+    gnutls_x509_crt_set_expiration_time(cert, boundTime(now + validity));
+}
+
+void
+setRandomSerial(gnutls_x509_crt_t cert)
+{
+    random_device rdev;
+    std::uniform_int_distribution<uint64_t> dist{};
+    uint64_t cert_serial = dist(rdev);
+    gnutls_x509_crt_set_serial(cert, &cert_serial, sizeof(cert_serial));
+}
+
 Certificate
 Certificate::generate(const PrivateKey& key, const std::string& name, const Identity& ca, bool is_ca)
 {
@@ -930,13 +1072,7 @@ Certificate::generate(const PrivateKey& key, const std::string& name, const Iden
         return {};
     Certificate ret {cert};
 
-    int64_t now = time(nullptr);
-    /* 2038 bug: don't allow time wrap */
-    auto boundTime = [](int64_t t) -> time_t {
-        return std::min<int64_t>(t, std::numeric_limits<time_t>::max());
-    };
-    gnutls_x509_crt_set_activation_time(cert, boundTime(now));
-    gnutls_x509_crt_set_expiration_time(cert, boundTime(now + (10 * 365 * 24 * 60 * 60)));
+    setValidityPeriod(cert, 10 * 365 * 24 * 60 * 60);
     if (gnutls_x509_crt_set_key(cert, key.x509_key) != GNUTLS_E_SUCCESS) {
         std::cerr << "Error when setting certificate key" << std::endl;
         return {};
@@ -947,19 +1083,15 @@ Certificate::generate(const PrivateKey& key, const std::string& name, const Iden
     }
 
     // TODO: compute the subject key using the recommended RFC method
-    auto pk_id = key.getPublicKey().getId();
+    auto pk = key.getPublicKey();
+    auto pk_id = pk.getId();
     const std::string uid_str = pk_id.toString();
 
     gnutls_x509_crt_set_subject_key_id(cert, &pk_id, sizeof(pk_id));
     gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, name.data(), name.length());
     gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_LDAP_UID, 0, uid_str.data(), uid_str.length());
 
-    {
-        random_device rdev;
-        std::uniform_int_distribution<uint64_t> dist{};
-        uint64_t cert_serial = dist(rdev);
-        gnutls_x509_crt_set_serial(cert, &cert_serial, sizeof(cert_serial));
-    }
+    setRandomSerial(cert);
 
     unsigned key_usage = 0;
     if (is_ca) {
@@ -975,15 +1107,13 @@ Certificate::generate(const PrivateKey& key, const std::string& name, const Iden
             // Signing certificate must be CA.
             return {};
         }
-        //if (gnutls_x509_crt_sign2(cert, ca.second->cert, ca.first->x509_key, get_dig(cert), 0) != GNUTLS_E_SUCCESS) {
-        if (gnutls_x509_crt_privkey_sign(cert, ca.second->cert, ca.first->key, get_dig(cert), 0) != GNUTLS_E_SUCCESS) {
+        if (gnutls_x509_crt_privkey_sign(cert, ca.second->cert, ca.first->key, pk.getPreferredDigest(), 0) != GNUTLS_E_SUCCESS) {
             std::cerr << "Error when signing certificate" << std::endl;
             return {};
         }
         ret.issuer = ca.second;
     } else {
-        //if (gnutls_x509_crt_sign2(cert, cert, key, get_dig(cert), 0) != GNUTLS_E_SUCCESS) {
-        if (gnutls_x509_crt_privkey_sign(cert, cert, key.key, get_dig(cert), 0) != GNUTLS_E_SUCCESS) {
+        if (gnutls_x509_crt_privkey_sign(cert, cert, key.key, pk.getPreferredDigest(), 0) != GNUTLS_E_SUCCESS) {
             std::cerr << "Error when signing certificate" << std::endl;
             return {};
         }
@@ -992,6 +1122,31 @@ Certificate::generate(const PrivateKey& key, const std::string& name, const Iden
     return ret.getPacked();
 }
 
+Certificate
+Certificate::generate(const CertificateRequest& request, const Identity& ca)
+{
+    gnutls_x509_crt_t cert;
+    if (auto err = gnutls_x509_crt_init(&cert))
+        throw CryptoException(std::string("Can't initialize certificate: ") + gnutls_strerror(err));
+    Certificate ret {cert};
+    if (auto err = gnutls_x509_crt_set_crq(cert, request.get()))
+        throw CryptoException(std::string("Can't initialize certificate: ") + gnutls_strerror(err));
+
+    if (auto err = gnutls_x509_crt_set_version(cert, 3)) {
+        throw CryptoException(std::string("Can't set certificate version: ") + gnutls_strerror(err));
+    }
+
+    setValidityPeriod(cert, 10 * 365 * 24 * 60 * 60);
+    setRandomSerial(cert);
+
+    if (auto err = gnutls_x509_crt_privkey_sign(cert, ca.second->cert, ca.first->key, ca.second->getPreferredDigest(), 0)) {
+        throw CryptoException(std::string("Can't sign certificate: ") + gnutls_strerror(err));
+    }
+    ret.issuer = ca.second;
+
+    return ret.getPacked();
+}
+
 std::vector<std::shared_ptr<RevocationList>>
 Certificate::getRevocationLists() const
 {
@@ -1002,6 +1157,8 @@ Certificate::getRevocationLists() const
     return ret;
 }
 
+// RevocationList
+
 RevocationList::RevocationList()
 {
     gnutls_x509_crl_init(&crl);
@@ -1219,6 +1376,8 @@ RevocationList::toString() const
     return ret;
 }
 
+// TrustList
+
 TrustList::TrustList() {
     gnutls_x509_trust_list_init(&trust, 0);
 }
diff --git a/tests/cryptotester.cpp b/tests/cryptotester.cpp
index dbcb49967df6e605358991bdad40249bad083d26..a69ad26406a27198afbfd1f8687884e6938ed2fe 100644
--- a/tests/cryptotester.cpp
+++ b/tests/cryptotester.cpp
@@ -54,7 +54,6 @@ CryptoTester::testCertificateRevocation()
     auto device11 = dht::crypto::generateIdentity("dev11", account1);
     auto device12 = dht::crypto::generateIdentity("dev12", account1);
 
-
     dht::crypto::TrustList list;
     list.add(*ca1.second);
     auto v = list.verify(*account1.second);
@@ -83,6 +82,34 @@ CryptoTester::testCertificateRevocation()
     CPPUNIT_ASSERT_MESSAGE(v.toString(), v);
 }
 
+void
+CryptoTester::testCertificateRequest()
+{
+    // Generate CA
+    auto ca = dht::crypto::generateIdentity("Test CA");
+
+    // Generate signed request
+    auto deviceKey = dht::crypto::PrivateKey::generate();
+    auto request = dht::crypto::CertificateRequest();
+    request.setName("Test Device");
+    request.sign(deviceKey);
+
+    // Export/import request
+    auto importedRequest = dht::crypto::CertificateRequest(request.pack());
+    CPPUNIT_ASSERT(importedRequest.verify());
+
+    // Generate/sign certificate from request
+    auto signedCert = dht::crypto::Certificate::generate(request, ca);
+    CPPUNIT_ASSERT_EQUAL(ca.second->getName(), signedCert.getIssuerName());
+    CPPUNIT_ASSERT_EQUAL(request.getName(), signedCert.getName());
+
+    // Check generated certificate
+    dht::crypto::TrustList list;
+    list.add(*ca.second);
+    auto v = list.verify(signedCert);
+    CPPUNIT_ASSERT_MESSAGE(v.toString(), v);
+}
+
 void
 CryptoTester::tearDown() {
 
diff --git a/tests/cryptotester.h b/tests/cryptotester.h
index 20f6cc344a22c086fa961102e2d6638271458228..79f6b664f5b7f9d5fcb90ac72d81d706cb2d52b1 100644
--- a/tests/cryptotester.h
+++ b/tests/cryptotester.h
@@ -29,6 +29,7 @@ class CryptoTester : public CppUnit::TestFixture {
     CPPUNIT_TEST_SUITE(CryptoTester);
     CPPUNIT_TEST(testSignatureEncryption);
     CPPUNIT_TEST(testCertificateRevocation);
+    CPPUNIT_TEST(testCertificateRequest);
     CPPUNIT_TEST_SUITE_END();
 
  public:
@@ -48,6 +49,10 @@ class CryptoTester : public CppUnit::TestFixture {
      * Test certificate generation, validation and revocation
      */
     void testCertificateRevocation();
+    /**
+     * Test certificate requests
+     */
+    void testCertificateRequest();
 };
 
 }  // namespace test