From cf430c1a406c849f01394f671938975edf84b483 Mon Sep 17 00:00:00 2001 From: Simon Arlott Date: Sat, 23 Apr 2016 22:51:05 +0100 Subject: [PATCH] ssld: Add new certfp_methods spki_sha256 and spki_sha512 These operate on the SubjectPublicKeyInfo of the certificate, which does change unless the private key is changed. This allows the fingerprint to stay constant even if the certificate is reissued. (The same fingerprint is also used by DANE) --- doc/reference.conf | 8 ++++-- ircd/newconf.c | 12 +++++--- ircd/s_conf.c | 2 +- librb/include/rb_commio.h | 10 +++++-- librb/src/gnutls.c | 59 ++++++++++++++++++++++++++++++++++----- librb/src/mbedtls.c | 44 ++++++++++++++++++++++++----- librb/src/openssl.c | 29 ++++++++++++++++--- ssld/ssld.c | 2 +- 8 files changed, 137 insertions(+), 29 deletions(-) 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