diff --git a/doc/reference.conf b/doc/reference.conf index b9d1e644..aff7787c 100644 --- a/doc/reference.conf +++ b/doc/reference.conf @@ -1405,8 +1405,12 @@ general { away_interval = 30; /* certfp_method: the method that should be used for computing certificate fingerprints. - * Acceptable options are sha1, sha256 and sha512. Networks running versions of charybdis - * prior to charybdis 3.5 MUST use sha1 for certfp_method. + * Acceptable options are sha1, sha256, spki_sha256, sha512 and spki_sha512. Networks + * running versions of charybdis prior to charybdis 3.5 MUST use sha1 for certfp_method. + * + * The spki_* variants operate on the SubjectPublicKeyInfo of the certificate, which does + * not change unless the private key is changed. This allows the fingerprint to stay + * constant even if the certificate is reissued. */ certfp_method = sha1; diff --git a/ircd/newconf.c b/ircd/newconf.c index 01af2eb7..253d02b6 100644 --- a/ircd/newconf.c +++ b/ircd/newconf.c @@ -1668,14 +1668,18 @@ conf_set_general_certfp_method(void *data) char *method = data; if (!rb_strcasecmp(method, "sha1")) - ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SHA1; + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_CERT_SHA1; else if (!rb_strcasecmp(method, "sha256")) - ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SHA256; + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_CERT_SHA256; else if (!rb_strcasecmp(method, "sha512")) - ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SHA512; + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_CERT_SHA512; + else if (!rb_strcasecmp(method, "spki_sha256")) + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SPKI_SHA256; + else if (!rb_strcasecmp(method, "spki_sha512")) + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SPKI_SHA512; else { - ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SHA1; + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_CERT_SHA1; conf_report_error("Ignoring general::certfp_method -- bogus certfp method %s", method); } } diff --git a/ircd/s_conf.c b/ircd/s_conf.c index 36f336a4..dcb4911f 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -813,7 +813,7 @@ set_default_conf(void) ServerInfo.default_max_clients = MAXCONNECTIONS; ConfigFileEntry.nicklen = NICKLEN; - ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_SHA1; + ConfigFileEntry.certfp_method = RB_SSL_CERTFP_METH_CERT_SHA1; ConfigFileEntry.hide_opers_in_whois = 0; if (!alias_dict) diff --git a/librb/include/rb_commio.h b/librb/include/rb_commio.h index 7123a981..5c15b0a2 100644 --- a/librb/include/rb_commio.h +++ b/librb/include/rb_commio.h @@ -102,9 +102,13 @@ void rb_note(rb_fde_t *, const char *); #define RB_SSL_CERTFP_LEN 64 /* Methods for certfp */ -#define RB_SSL_CERTFP_METH_SHA1 0 -#define RB_SSL_CERTFP_METH_SHA256 1 -#define RB_SSL_CERTFP_METH_SHA512 2 +/* Digest of full X.509 certificate */ +#define RB_SSL_CERTFP_METH_CERT_SHA1 0x0000 +#define RB_SSL_CERTFP_METH_CERT_SHA256 0x0001 +#define RB_SSL_CERTFP_METH_CERT_SHA512 0x0002 +/* Digest of SubjectPublicKeyInfo (RFC 5280), used by DANE (RFC 6698) */ +#define RB_SSL_CERTFP_METH_SPKI_SHA256 0x1001 +#define RB_SSL_CERTFP_METH_SPKI_SHA512 0x1002 #define RB_SSL_CERTFP_LEN_SHA1 20 #define RB_SSL_CERTFP_LEN_SHA256 32 diff --git a/librb/src/gnutls.c b/librb/src/gnutls.c index 4038dc3d..efff9757 100644 --- a/librb/src/gnutls.c +++ b/librb/src/gnutls.c @@ -26,10 +26,12 @@ #include #include #include +#include #ifdef HAVE_GNUTLS #include #include +#include #if (GNUTLS_VERSION_MAJOR < 3) # include @@ -603,6 +605,7 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) uint8_t digest[RB_SSL_CERTFP_LEN * 2]; size_t digest_size; int len; + bool spki = false; if (gnutls_certificate_type_get(SSL_P(F)) != GNUTLS_CRT_X509) return 0; @@ -626,15 +629,19 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) switch(method) { - case RB_SSL_CERTFP_METH_SHA1: + case RB_SSL_CERTFP_METH_CERT_SHA1: algo = GNUTLS_DIG_SHA1; len = RB_SSL_CERTFP_LEN_SHA1; break; - case RB_SSL_CERTFP_METH_SHA256: + case RB_SSL_CERTFP_METH_SPKI_SHA256: + spki = true; + case RB_SSL_CERTFP_METH_CERT_SHA256: algo = GNUTLS_DIG_SHA256; len = RB_SSL_CERTFP_LEN_SHA256; break; - case RB_SSL_CERTFP_METH_SHA512: + case RB_SSL_CERTFP_METH_SPKI_SHA512: + spki = true; + case RB_SSL_CERTFP_METH_CERT_SHA512: algo = GNUTLS_DIG_SHA512; len = RB_SSL_CERTFP_LEN_SHA512; break; @@ -642,13 +649,51 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) return 0; } - if (gnutls_x509_crt_get_fingerprint(cert, algo, digest, &digest_size) < 0) + if (!spki) { - gnutls_x509_crt_deinit(cert); - return 0; + if (gnutls_x509_crt_get_fingerprint(cert, algo, digest, &digest_size) < 0) + len = 0; + } + else + { + gnutls_pubkey_t pubkey; + unsigned char *der_pubkey = NULL; + size_t der_pubkey_len = 0; + + if (gnutls_pubkey_init(&pubkey) == GNUTLS_E_SUCCESS) + { + if (gnutls_pubkey_import_x509(pubkey, cert, 0) == GNUTLS_E_SUCCESS) + { + if (gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, der_pubkey, &der_pubkey_len) == GNUTLS_E_SHORT_MEMORY_BUFFER) + { + der_pubkey = rb_malloc(der_pubkey_len); + + if (gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_DER, der_pubkey, &der_pubkey_len) != GNUTLS_E_SUCCESS) + { + rb_free(der_pubkey); + der_pubkey = NULL; + } + } + } + + gnutls_pubkey_deinit(pubkey); + } + + if (der_pubkey) + { + if (gnutls_hash_fast(algo, der_pubkey, der_pubkey_len, digest) != 0) + len = 0; + + rb_free(der_pubkey); + } + else + { + len = 0; + } } - memcpy(certfp, digest, len); + if (len) + memcpy(certfp, digest, len); gnutls_x509_crt_deinit(cert); return len; diff --git a/librb/src/mbedtls.c b/librb/src/mbedtls.c index 9d9e3f7d..4fd38f17 100644 --- a/librb/src/mbedtls.c +++ b/librb/src/mbedtls.c @@ -27,6 +27,7 @@ #include #include #include +#include #ifdef HAVE_MBEDTLS @@ -545,18 +546,23 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) const mbedtls_md_info_t *md_info; mbedtls_md_type_t md_type; int ret; + bool spki = false; switch (method) { - case RB_SSL_CERTFP_METH_SHA1: + case RB_SSL_CERTFP_METH_CERT_SHA1: md_type = MBEDTLS_MD_SHA1; hashlen = RB_SSL_CERTFP_LEN_SHA1; break; - case RB_SSL_CERTFP_METH_SHA256: + case RB_SSL_CERTFP_METH_SPKI_SHA256: + spki = true; + case RB_SSL_CERTFP_METH_CERT_SHA256: md_type = MBEDTLS_MD_SHA256; hashlen = RB_SSL_CERTFP_LEN_SHA256; break; - case RB_SSL_CERTFP_METH_SHA512: + case RB_SSL_CERTFP_METH_SPKI_SHA512: + spki = true; + case RB_SSL_CERTFP_METH_CERT_SHA512: md_type = MBEDTLS_MD_SHA512; hashlen = RB_SSL_CERTFP_LEN_SHA512; break; @@ -572,13 +578,37 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) if (md_info == NULL) return 0; - if ((ret = mbedtls_md(md_info, peer_cert->raw.p, peer_cert->raw.len, hash)) != 0) + if (!spki) { - rb_lib_log("rb_get_ssl_certfp: unable to get certfp for F: %p, -0x%x", -ret); - return 0; + if ((ret = mbedtls_md(md_info, peer_cert->raw.p, peer_cert->raw.len, hash)) != 0) + { + rb_lib_log("rb_get_ssl_certfp: unable to get certfp for F: %p, -0x%x", -ret); + hashlen = 0; + } + } + else + { + const size_t der_pubkey_bufsz = 4096; + void *der_pubkey = rb_malloc(der_pubkey_bufsz); + int der_pubkey_len; + + der_pubkey_len = mbedtls_pk_write_pubkey_der((mbedtls_pk_context *)&peer_cert->pk, der_pubkey, der_pubkey_bufsz); + if (der_pubkey_len < 0) + { + rb_lib_log("rb_get_ssl_certfp: unable to get pubkey for F: %p, -0x%x", -der_pubkey_len); + hashlen = 0; + } + else if ((ret = mbedtls_md(md_info, der_pubkey+(der_pubkey_bufsz-der_pubkey_len), der_pubkey_len, hash)) != 0) + { + rb_lib_log("rb_get_ssl_certfp: unable to get certfp for F: %p, -0x%x", -ret); + hashlen = 0; + } + + rb_free(der_pubkey); } - memcpy(certfp, hash, hashlen); + if (hashlen) + memcpy(certfp, hash, hashlen); return hashlen; } diff --git a/librb/src/openssl.c b/librb/src/openssl.c index a8d998c4..85f07c5e 100644 --- a/librb/src/openssl.c +++ b/librb/src/openssl.c @@ -706,28 +706,49 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) res == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || res == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) { + const ASN1_ITEM *it; const EVP_MD *evp; + void *data; unsigned int len; switch(method) { - case RB_SSL_CERTFP_METH_SHA1: + case RB_SSL_CERTFP_METH_CERT_SHA1: + it = ASN1_ITEM_rptr(X509); evp = EVP_sha1(); + data = cert; len = RB_SSL_CERTFP_LEN_SHA1; break; - case RB_SSL_CERTFP_METH_SHA256: + case RB_SSL_CERTFP_METH_CERT_SHA256: + it = ASN1_ITEM_rptr(X509); evp = EVP_sha256(); + data = cert; len = RB_SSL_CERTFP_LEN_SHA256; break; - case RB_SSL_CERTFP_METH_SHA512: + case RB_SSL_CERTFP_METH_CERT_SHA512: + it = ASN1_ITEM_rptr(X509); evp = EVP_sha512(); + data = cert; + len = RB_SSL_CERTFP_LEN_SHA512; + break; + case RB_SSL_CERTFP_METH_SPKI_SHA256: + it = ASN1_ITEM_rptr(X509_PUBKEY); + evp = EVP_sha256(); + data = X509_get_X509_PUBKEY(cert); + len = RB_SSL_CERTFP_LEN_SHA256; + break; + case RB_SSL_CERTFP_METH_SPKI_SHA512: + it = ASN1_ITEM_rptr(X509_PUBKEY); + evp = EVP_sha512(); + data = X509_get_X509_PUBKEY(cert); len = RB_SSL_CERTFP_LEN_SHA512; break; default: return 0; } - X509_digest(cert, evp, certfp, &len); + if (ASN1_item_digest(it, evp, data, certfp, &len) != 1) + len = 0; X509_free(cert); return len; } diff --git a/ssld/ssld.c b/ssld/ssld.c index e8adcb7c..68f0cda3 100644 --- a/ssld/ssld.c +++ b/ssld/ssld.c @@ -152,7 +152,7 @@ static void conn_plain_read_shutdown_cb(rb_fde_t *fd, void *data); static void mod_cmd_write_queue(mod_ctl_t * ctl, const void *data, size_t len); static const char *remote_closed = "Remote host closed the connection"; static bool ssld_ssl_ok; -static int certfp_method = RB_SSL_CERTFP_METH_SHA1; +static int certfp_method = RB_SSL_CERTFP_METH_CERT_SHA1; #ifdef HAVE_LIBZ static bool zlib_ok = true; #else