X-Git-Url: https://jfr.im/git/solanum.git/blobdiff_plain/3fe59c99c718be5ce4020eb7a4cbd818de542a6f..36588d4eac744fe679450d6283a385b6bd021ae9:/libratbox/src/gnutls.c diff --git a/libratbox/src/gnutls.c b/libratbox/src/gnutls.c index 9728bb32..b5096084 100644 --- a/libratbox/src/gnutls.c +++ b/libratbox/src/gnutls.c @@ -14,7 +14,7 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 @@ -31,12 +31,29 @@ #include #include -#include - -static gnutls_certificate_credentials x509; -static gnutls_dh_params dh_params; - +#if (GNUTLS_VERSION_MAJOR < 3) +# include +#else +# include +#endif + +static gnutls_certificate_credentials_t x509; +static gnutls_dh_params_t dh_params; +static gnutls_priority_t default_priority; + +/* These are all used for getting GnuTLS to supply a client cert. */ +#define MAX_CERTS 6 +static unsigned int x509_cert_count; +static gnutls_x509_crt_t x509_cert[MAX_CERTS]; +static gnutls_x509_privkey_t x509_key; +#if GNUTLS_VERSION_MAJOR < 3 +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_len, gnutls_retr_st *st); +#else +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_len, gnutls_retr2_st *st); +#endif #define SSL_P(x) *((gnutls_session_t *)F->ssl) @@ -76,7 +93,7 @@ rb_ssl_timeout(rb_fde_t *F, void *notused) static int -do_ssl_handshake(rb_fde_t *F, PF * callback) +do_ssl_handshake(rb_fde_t *F, PF * callback, void *data) { int ret; int flags; @@ -90,7 +107,7 @@ do_ssl_handshake(rb_fde_t *F, PF * callback) flags = RB_SELECT_READ; else flags = RB_SELECT_WRITE; - rb_setselect(F, flags, callback, NULL); + rb_setselect(F, flags, callback, data); return 0; } F->ssl_errno = ret; @@ -107,7 +124,7 @@ rb_ssl_tryaccept(rb_fde_t *F, void *data) lrb_assert(F->accept != NULL); - ret = do_ssl_handshake(F, rb_ssl_tryaccept); + ret = do_ssl_handshake(F, rb_ssl_tryaccept, NULL); /* do_ssl_handshake does the rb_setselect */ if(ret == 0) @@ -117,7 +134,7 @@ rb_ssl_tryaccept(rb_fde_t *F, void *data) F->accept = NULL; rb_settimeout(F, 0, NULL, NULL); rb_setselect(F, RB_SELECT_READ | RB_SELECT_WRITE, NULL, NULL); - + if(ret > 0) ad->callback(F, RB_OK, (struct sockaddr *)&ad->S, ad->addrlen, ad->data); else @@ -146,7 +163,9 @@ rb_ssl_start_accepted(rb_fde_t *new_F, ACCB * cb, void *data, int timeout) gnutls_dh_set_prime_bits(*ssl, 1024); gnutls_transport_set_ptr(*ssl, (gnutls_transport_ptr_t) (long int)new_F->fd); gnutls_certificate_server_set_request(*ssl, GNUTLS_CERT_REQUEST); - if(do_ssl_handshake(new_F, rb_ssl_tryaccept)) + gnutls_priority_set(*ssl, default_priority); + + if(do_ssl_handshake(new_F, rb_ssl_tryaccept, NULL)) { struct acceptdata *ad = new_F->accept; new_F->accept = NULL; @@ -178,7 +197,9 @@ rb_ssl_accept_setup(rb_fde_t *F, rb_fde_t *new_F, struct sockaddr *st, int addrl gnutls_dh_set_prime_bits(SSL_P(new_F), 1024); gnutls_transport_set_ptr(SSL_P(new_F), (gnutls_transport_ptr_t) (long int)rb_get_fd(new_F)); gnutls_certificate_server_set_request(SSL_P(new_F), GNUTLS_CERT_REQUEST); - if(do_ssl_handshake(F, rb_ssl_tryaccept)) + gnutls_priority_set(SSL_P(F), default_priority); + + if(do_ssl_handshake(F, rb_ssl_tryaccept, NULL)) { struct acceptdata *ad = F->accept; F->accept = NULL; @@ -236,11 +257,13 @@ rb_ssl_write(rb_fde_t *F, const void *buf, size_t count) return rb_ssl_read_or_write(1, F, NULL, buf, count); } +#if (GNUTLS_VERSION_MAJOR < 3) static void rb_gcry_random_seed(void *unused) { gcry_fast_random_poll(); } +#endif int rb_init_ssl(void) @@ -252,10 +275,52 @@ rb_init_ssl(void) rb_lib_log("rb_init_ssl: Unable to allocate SSL/TLS certificate credentials"); return 0; } + +#if GNUTLS_VERSION_MAJOR < 3 + gnutls_certificate_client_set_retrieve_function(x509, cert_callback); +#else + gnutls_certificate_set_retrieve_function(x509, cert_callback); +#endif + +#if (GNUTLS_VERSION_MAJOR < 3) rb_event_addish("rb_gcry_random_seed", rb_gcry_random_seed, NULL, 300); +#endif + return 1; } +/* We only have one certificate to authenticate with, as both client and server. Unfortunately, + * GnuTLS tries to be clever, and as client, will attempt to use a certificate that the server + * will trust. We usually use self-signed certs, though, so the result of this search is always + * nothing. Therefore, it uses no certificate to authenticate as a client. This is undesirable + * as it breaks fingerprint auth. Thus, we use this callback to force GnuTLS to always + * authenticate with our certificate at all times. + */ +#if GNUTLS_VERSION_MAJOR < 3 +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_len, gnutls_retr_st *st) +#else +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_len, gnutls_retr2_st *st) +#endif +{ + /* XXX - ugly hack. Tell GnuTLS to use the first (only) certificate we have for auth. */ +#if (GNUTLS_VERSION_MAJOR < 3) + st->type = GNUTLS_CRT_X509; +#else + st->cert_type = GNUTLS_CRT_X509; + st->key_type = GNUTLS_PRIVKEY_X509; +#endif + st->ncerts = x509_cert_count; + st->cert.x509 = x509_cert; + st->key.x509 = x509_key; + st->deinit_all = 0; + + return 0; +} + static void rb_free_datum_t(gnutls_datum_t * d) { @@ -288,9 +353,10 @@ rb_load_file_into_datum_t(const char *file) } int -rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile) +rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile, const char *cipher_list) { int ret; + const char *err; gnutls_datum_t *d_cert, *d_key; if(cert == NULL) { @@ -310,6 +376,24 @@ rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile) return 0; } + /* In addition to creating the certificate set, we also need to store our cert elsewhere + * so we can force GnuTLS to identify with it when acting as a client. + */ + gnutls_x509_privkey_init(&x509_key); + if ((ret = gnutls_x509_privkey_import(x509_key, d_key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) + { + rb_lib_log("rb_setup_ssl_server: Error loading key file: %s", gnutls_strerror(ret)); + return 0; + } + + x509_cert_count = MAX_CERTS; + if ((ret = gnutls_x509_crt_list_import(x509_cert, &x509_cert_count, d_cert, GNUTLS_X509_FMT_PEM, + GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED)) < 0) + { + rb_lib_log("rb_setup_ssl_server: Error loading certificate: %s", gnutls_strerror(ret)); + return 0; + } + x509_cert_count = ret; if((ret = gnutls_certificate_set_x509_key_mem(x509, d_cert, d_key, @@ -319,6 +403,7 @@ rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile) gnutls_strerror(ret)); return 0; } + rb_free_datum_t(d_cert); rb_free_datum_t(d_key); @@ -344,14 +429,27 @@ rb_setup_ssl_server(const char *cert, const char *keyfile, const char *dhfile) else rb_lib_log("rb_setup_ssl_server: Unable to setup DH parameters"); } + + ret = gnutls_priority_init(&default_priority, cipher_list, &err); + if (ret < 0) + { + rb_lib_log("rb_setup_ssl_server: syntax error (using defaults instead) in ssl cipher list at: %s", err); + gnutls_priority_init(&default_priority, NULL, &err); + return 1; + } + return 1; } int -rb_ssl_listen(rb_fde_t *F, int backlog) +rb_ssl_listen(rb_fde_t *F, int backlog, int defer_accept) { + int result; + + result = rb_listen(F, backlog, defer_accept); F->type = RB_FD_SOCKET | RB_FD_LISTEN | RB_FD_SSL; - return listen(F->fd, backlog); + + return result; } struct ssl_connect @@ -382,7 +480,7 @@ rb_ssl_tryconn_cb(rb_fde_t *F, void *data) struct ssl_connect *sconn = data; int ret; - ret = do_ssl_handshake(F, rb_ssl_tryconn_cb); + ret = do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn); switch (ret) { @@ -417,13 +515,12 @@ rb_ssl_tryconn(rb_fde_t *F, int status, void *data) F->ssl = rb_malloc(sizeof(gnutls_session_t)); gnutls_init(F->ssl, GNUTLS_CLIENT); gnutls_set_default_priority(SSL_P(F)); + gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, x509); gnutls_dh_set_prime_bits(SSL_P(F), 1024); gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) (long int)F->fd); + gnutls_priority_set(SSL_P(F), default_priority); - if(do_ssl_handshake(F, rb_ssl_tryconn_cb)) - { - rb_ssl_connect_realcb(F, RB_OK, sconn); - } + do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn); } void @@ -461,35 +558,33 @@ rb_ssl_start_connected(rb_fde_t *F, CNCB * callback, void *data, int timeout) gnutls_init(F->ssl, GNUTLS_CLIENT); gnutls_set_default_priority(SSL_P(F)); + gnutls_credentials_set(SSL_P(F), GNUTLS_CRD_CERTIFICATE, x509); gnutls_dh_set_prime_bits(SSL_P(F), 1024); gnutls_transport_set_ptr(SSL_P(F), (gnutls_transport_ptr_t) (long int)F->fd); + gnutls_priority_set(SSL_P(F), default_priority); rb_settimeout(F, sconn->timeout, rb_ssl_tryconn_timeout_cb, sconn); - if(do_ssl_handshake(F, rb_ssl_tryconn_cb)) - { - rb_ssl_connect_realcb(F, RB_OK, sconn); - } + do_ssl_handshake(F, rb_ssl_tryconn_cb, (void *)sconn); } int rb_init_prng(const char *path, prng_seed_t seed_type) { +#if GNUTLS_VERSION_MAJOR < 3 gcry_fast_random_poll(); +#endif return 1; } int rb_get_random(void *buf, size_t length) { +#if GNUTLS_VERSION_MAJOR < 3 gcry_randomize(buf, length, GCRY_STRONG_RANDOM); - return 1; -} - -int -rb_get_pseudo_random(void *buf, size_t length) -{ - gcry_randomize(buf, length, GCRY_WEAK_RANDOM); +#else + gnutls_rnd(GNUTLS_RND_KEY, buf, length); +#endif return 1; } @@ -500,13 +595,15 @@ rb_get_ssl_strerror(rb_fde_t *F) } int -rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN]) +rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN], int method) { gnutls_x509_crt_t cert; + gnutls_digest_algorithm_t algo; unsigned int cert_list_size; const gnutls_datum_t *cert_list; uint8_t digest[RB_SSL_CERTFP_LEN * 2]; size_t digest_size; + int len; if (gnutls_certificate_type_get(SSL_P(F)) != GNUTLS_CRT_X509) return 0; @@ -528,15 +625,34 @@ rb_get_ssl_certfp(rb_fde_t *F, uint8_t certfp[RB_SSL_CERTFP_LEN]) return 0; } - if (gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, digest, &digest_size) < 0) + switch(method) + { + case RB_SSL_CERTFP_METH_SHA1: + algo = GNUTLS_DIG_SHA1; + len = RB_SSL_CERTFP_LEN_SHA1; + break; + case RB_SSL_CERTFP_METH_SHA256: + algo = GNUTLS_DIG_SHA256; + len = RB_SSL_CERTFP_LEN_SHA256; + break; + case RB_SSL_CERTFP_METH_SHA512: + algo = GNUTLS_DIG_SHA512; + len = RB_SSL_CERTFP_LEN_SHA512; + break; + default: + return 0; + } + + if (gnutls_x509_crt_get_fingerprint(cert, algo, digest, &digest_size) < 0) { gnutls_x509_crt_deinit(cert); return 0; } - memcpy(certfp, digest, RB_SSL_CERTFP_LEN); + memcpy(certfp, digest, len); - return 1; + gnutls_x509_crt_deinit(cert); + return len; } int @@ -548,9 +664,22 @@ rb_supports_ssl(void) void rb_get_ssl_info(char *buf, size_t len) { - rb_snprintf(buf, len, "GNUTLS: compiled (%s), library(%s)", + snprintf(buf, len, "GNUTLS: compiled (%s), library(%s)", LIBGNUTLS_VERSION, gnutls_check_version(NULL)); } - - + +const char * +rb_ssl_get_cipher(rb_fde_t *F) +{ + static char buf[1024]; + + snprintf(buf, sizeof(buf), "%s-%s-%s-%s", + gnutls_protocol_get_name(gnutls_protocol_get_version(SSL_P(F))), + gnutls_kx_get_name(gnutls_kx_get(SSL_P(F))), + gnutls_cipher_get_name(gnutls_cipher_get(SSL_P(F))), + gnutls_mac_get_name(gnutls_mac_get(SSL_P(F)))); + + return buf; +} + #endif /* HAVE_GNUTLS */