+ public:
+ // Nothing to deallocate, constructor may throw freely
+ Hash(const std::string& hashname)
+ {
+ // As older versions of gnutls can't do this, let's disable it where needed.
+#ifdef GNUTLS_HAS_MAC_GET_ID
+ // As gnutls_digest_algorithm_t and gnutls_mac_algorithm_t are mapped 1:1, we can do this
+ // There is no gnutls_dig_get_id() at the moment, but it may come later
+ hash = (gnutls_digest_algorithm_t)gnutls_mac_get_id(hashname.c_str());
+ if (hash == GNUTLS_DIG_UNKNOWN)
+ throw Exception("Unknown hash type " + hashname);
+
+ // Check if the user is giving us something that is a valid MAC but not digest
+ gnutls_hash_hd_t is_digest;
+ if (gnutls_hash_init(&is_digest, hash) < 0)
+ throw Exception("Unknown hash type " + hashname);
+ gnutls_hash_deinit(is_digest, NULL);
+#else
+ if (hashname == "md5")
+ hash = GNUTLS_DIG_MD5;
+ else if (hashname == "sha1")
+ hash = GNUTLS_DIG_SHA1;
+#ifdef INSPIRCD_GNUTLS_ENABLE_SHA256_FINGERPRINT
+ else if (hashname == "sha256")
+ hash = GNUTLS_DIG_SHA256;
+#endif
+ else
+ throw Exception("Unknown hash type " + hashname);
+#endif
+ }
+
+ gnutls_digest_algorithm_t get() const { return hash; }
+ };
+
+ class DHParams
+ {
+ gnutls_dh_params_t dh_params;
+
+ DHParams()
+ {
+ ThrowOnError(gnutls_dh_params_init(&dh_params), "gnutls_dh_params_init() failed");
+ }
+
+ public:
+ /** Import */
+ static std::auto_ptr<DHParams> Import(const std::string& dhstr)
+ {
+ std::auto_ptr<DHParams> dh(new DHParams);
+ int ret = gnutls_dh_params_import_pkcs3(dh->dh_params, Datum(dhstr).get(), GNUTLS_X509_FMT_PEM);
+ ThrowOnError(ret, "Unable to import DH params");
+ return dh;
+ }
+
+ /** Generate */
+ static std::auto_ptr<DHParams> Generate(unsigned int bits)
+ {
+ std::auto_ptr<DHParams> dh(new DHParams);
+ ThrowOnError(gnutls_dh_params_generate2(dh->dh_params, bits), "Unable to generate DH params");
+ return dh;
+ }
+
+ ~DHParams()
+ {
+ gnutls_dh_params_deinit(dh_params);
+ }
+
+ const gnutls_dh_params_t& get() const { return dh_params; }
+ };
+
+ class X509Key
+ {
+ /** Ensure that the key is deinited in case the constructor of X509Key throws
+ */
+ class RAIIKey
+ {
+ public:
+ gnutls_x509_privkey_t key;
+
+ RAIIKey()
+ {
+ ThrowOnError(gnutls_x509_privkey_init(&key), "gnutls_x509_privkey_init() failed");
+ }
+
+ ~RAIIKey()
+ {
+ gnutls_x509_privkey_deinit(key);
+ }
+ } key;
+
+ public:
+ /** Import */
+ X509Key(const std::string& keystr)
+ {
+ int ret = gnutls_x509_privkey_import(key.key, Datum(keystr).get(), GNUTLS_X509_FMT_PEM);
+ ThrowOnError(ret, "Unable to import private key");
+ }
+
+ gnutls_x509_privkey_t& get() { return key.key; }
+ };
+
+ class X509CertList
+ {
+ std::vector<gnutls_x509_crt_t> certs;
+
+ public:
+ /** Import */
+ X509CertList(const std::string& certstr)
+ {
+ unsigned int certcount = 3;
+ certs.resize(certcount);
+ Datum datum(certstr);
+
+ int ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
+ if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
+ {
+ // the buffer wasn't big enough to hold all certs but gnutls changed certcount to the number of available certs,
+ // try again with a bigger buffer
+ certs.resize(certcount);
+ ret = gnutls_x509_crt_list_import(raw(), &certcount, datum.get(), GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
+ }
+
+ ThrowOnError(ret, "Unable to load certificates");
+
+ // Resize the vector to the actual number of certs because we rely on its size being correct
+ // when deallocating the certs
+ certs.resize(certcount);
+ }
+
+ ~X509CertList()
+ {
+ for (std::vector<gnutls_x509_crt_t>::iterator i = certs.begin(); i != certs.end(); ++i)
+ gnutls_x509_crt_deinit(*i);
+ }
+
+ gnutls_x509_crt_t* raw() { return &certs[0]; }
+ unsigned int size() const { return certs.size(); }
+ };
+
+ class X509CRL : public refcountbase
+ {
+ class RAIICRL
+ {
+ public:
+ gnutls_x509_crl_t crl;
+
+ RAIICRL()
+ {
+ ThrowOnError(gnutls_x509_crl_init(&crl), "gnutls_x509_crl_init() failed");
+ }
+
+ ~RAIICRL()
+ {
+ gnutls_x509_crl_deinit(crl);
+ }
+ } crl;
+
+ public:
+ /** Import */
+ X509CRL(const std::string& crlstr)
+ {
+ int ret = gnutls_x509_crl_import(get(), Datum(crlstr).get(), GNUTLS_X509_FMT_PEM);
+ ThrowOnError(ret, "Unable to load certificate revocation list");
+ }
+
+ gnutls_x509_crl_t& get() { return crl.crl; }
+ };
+
+#ifdef GNUTLS_NEW_PRIO_API
+ class Priority
+ {
+ gnutls_priority_t priority;
+
+ public:
+ Priority(const std::string& priorities)
+ {
+ // Try to set the priorities for ciphers, kex methods etc. to the user supplied string
+ // If the user did not supply anything then the string is already set to "NORMAL"
+ const char* priocstr = priorities.c_str();
+ const char* prioerror;
+
+ int ret = gnutls_priority_init(&priority, priocstr, &prioerror);
+ if (ret < 0)
+ {
+ // gnutls did not understand the user supplied string
+ throw Exception("Unable to initialize priorities to \"" + priorities + "\": " + gnutls_strerror(ret) + " Syntax error at position " + ConvToStr((unsigned int) (prioerror - priocstr)));
+ }
+ }
+
+ ~Priority()
+ {
+ gnutls_priority_deinit(priority);
+ }
+
+ void SetupSession(gnutls_session_t sess)
+ {
+ gnutls_priority_set(sess, priority);
+ }
+ };
+#else
+ /** Dummy class, used when gnutls_priority_set() is not available
+ */
+ class Priority
+ {
+ public:
+ Priority(const std::string& priorities)
+ {
+ if (priorities != "NORMAL")
+ throw Exception("You've set a non-default priority string, but GnuTLS lacks support for it");
+ }
+
+ static void SetupSession(gnutls_session_t sess)
+ {
+ // Always set the default priorities
+ gnutls_set_default_priority(sess);
+ }
+ };
+#endif
+
+ class CertCredentials
+ {
+ /** DH parameters associated with these credentials
+ */
+ std::auto_ptr<DHParams> dh;
+
+ protected:
+ gnutls_certificate_credentials_t cred;
+
+ public:
+ CertCredentials()
+ {
+ ThrowOnError(gnutls_certificate_allocate_credentials(&cred), "Cannot allocate certificate credentials");
+ }
+
+ ~CertCredentials()
+ {
+ gnutls_certificate_free_credentials(cred);
+ }
+
+ /** Associates these credentials with the session
+ */
+ void SetupSession(gnutls_session_t sess)
+ {
+ gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, cred);
+ }
+
+ /** Set the given DH parameters to be used with these credentials
+ */
+ void SetDH(std::auto_ptr<DHParams>& DH)
+ {
+ dh = DH;
+ gnutls_certificate_set_dh_params(cred, dh->get());
+ }
+ };
+
+ class X509Credentials : public CertCredentials
+ {
+ /** Private key
+ */
+ X509Key key;
+
+ /** Certificate list, presented to the peer
+ */
+ X509CertList certs;
+
+ /** Trusted CA, may be NULL
+ */
+ std::auto_ptr<X509CertList> trustedca;
+
+ /** Certificate revocation list, may be NULL
+ */
+ std::auto_ptr<X509CRL> crl;
+
+ static int cert_callback(gnutls_session_t session, const gnutls_datum_t* req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t* sign_algos, int sign_algos_length, cert_cb_last_param_type* st);
+
+ public:
+ X509Credentials(const std::string& certstr, const std::string& keystr)
+ : key(keystr)
+ , certs(certstr)
+ {
+ // Throwing is ok here, the destructor of Credentials is called in that case
+ int ret = gnutls_certificate_set_x509_key(cred, certs.raw(), certs.size(), key.get());
+ ThrowOnError(ret, "Unable to set cert/key pair");
+
+#ifdef GNUTLS_NEW_CERT_CALLBACK_API
+ gnutls_certificate_set_retrieve_function(cred, cert_callback);
+#else
+ gnutls_certificate_client_set_retrieve_function(cred, cert_callback);
+#endif
+ }
+
+ /** Sets the trusted CA and the certificate revocation list
+ * to use when verifying certificates
+ */
+ void SetCA(std::auto_ptr<X509CertList>& certlist, std::auto_ptr<X509CRL>& CRL)
+ {
+ // Do nothing if certlist is NULL
+ if (certlist.get())
+ {
+ int ret = gnutls_certificate_set_x509_trust(cred, certlist->raw(), certlist->size());
+ ThrowOnError(ret, "gnutls_certificate_set_x509_trust() failed");
+
+ if (CRL.get())
+ {
+ ret = gnutls_certificate_set_x509_crl(cred, &CRL->get(), 1);
+ ThrowOnError(ret, "gnutls_certificate_set_x509_crl() failed");
+ }
+
+ trustedca = certlist;
+ crl = CRL;
+ }
+ }
+ };