X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/723fe533c452eb258a5a7e0b808d714bbbc7cb01..0cbf2b821bb13da0268556d0e30ea627d5592c60:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 1966c557d..28ae46d9d 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2014 */ +/* Copyright (c) University of Cambridge 1995 - 2015 */ /* See the file NOTICE for conditions of use and distribution. */ /* Copyright (c) Phil Pennock 2012 */ @@ -47,15 +47,18 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile" # define DISABLE_OCSP #endif -#if GNUTLS_VERSION_NUMBER < 0x020a00 && defined(EXPERIMENTAL_EVENT) +#if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT) # warning "GnuTLS library version too old; tls:cert event unsupported" -# undef EXPERIMENTAL_EVENT +# define DISABLE_EVENT #endif #if GNUTLS_VERSION_NUMBER >= 0x030306 # define SUPPORT_CA_DIR #else # undef SUPPORT_CA_DIR #endif +#if GNUTLS_VERSION_NUMBER >= 0x030014 +# define SUPPORT_SYSDEFAULT_CABUNDLE +#endif #ifndef DISABLE_OCSP # include @@ -75,11 +78,7 @@ Changes: /* Values for verify_requirement */ enum peer_verify_requirement - { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED -#ifdef EXPERIMENTAL_CERTNAMES - ,VERIFY_WITHHOST -#endif - }; + { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED }; /* This holds most state for server or client; with this, we can set up an outbound TLS-enabled connection in an ACL callout, while not stomping all @@ -121,10 +120,8 @@ typedef struct exim_gnutls_state { uschar *exp_tls_crl; uschar *exp_tls_require_ciphers; uschar *exp_tls_ocsp_file; -#ifdef EXPERIMENTAL_CERTNAMES - uschar *exp_tls_verify_cert_hostnames; -#endif -#ifdef EXPERIMENTAL_EVENT + const uschar *exp_tls_verify_cert_hostnames; +#ifndef DISABLE_EVENT uschar *event_action; #endif @@ -142,10 +139,8 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -#ifdef EXPERIMENTAL_CERTNAMES - NULL, -#endif -#ifdef EXPERIMENTAL_EVENT + NULL, +#ifndef DISABLE_EVENT NULL, #endif NULL, @@ -181,6 +176,10 @@ static const char * const exim_default_gnutls_priority = "NORMAL"; static BOOL exim_gnutls_base_init_done = FALSE; +#ifndef DISABLE_OCSP +static BOOL gnutls_buggy_ocsp = FALSE; +#endif + /* ------------------------------------------------------------------------ */ /* macros */ @@ -836,18 +835,25 @@ if ( !host /* server */ && tls_ocsp_file ) { - if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", - &state->exp_tls_ocsp_file)) - return DEFER; + if (gnutls_buggy_ocsp) + { + DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); + } + else + { + if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", + &state->exp_tls_ocsp_file)) + return DEFER; - /* Use the full callback method for stapling just to get observability. - More efficient would be to read the file once only, if it never changed - (due to SNI). Would need restart on file update, or watch datestamp. */ + /* Use the full callback method for stapling just to get observability. + More efficient would be to read the file once only, if it never changed + (due to SNI). Would need restart on file update, or watch datestamp. */ - gnutls_certificate_set_ocsp_status_request_function(state->x509_cred, - server_ocsp_stapling_cb, state->exp_tls_ocsp_file); + gnutls_certificate_set_ocsp_status_request_function(state->x509_cred, + server_ocsp_stapling_cb, state->exp_tls_ocsp_file); - DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file); + DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file); + } } #endif @@ -862,6 +868,10 @@ if (state->tls_verify_certificates && *state->tls_verify_certificates) { if (!expand_check_tlsvar(tls_verify_certificates)) return DEFER; +#ifndef SUPPORT_SYSDEFAULT_CABUNDLE + if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) + state->exp_tls_verify_certificates = NULL; +#endif if (state->tls_crl && *state->tls_crl) if (!expand_check_tlsvar(tls_crl)) return DEFER; @@ -882,58 +892,65 @@ else return OK; } -if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0) +#ifdef SUPPORT_SYSDEFAULT_CABUNDLE +if (Ustrcmp(state->exp_tls_verify_certificates, "system") == 0) + cert_count = gnutls_certificate_set_x509_system_trust(state->x509_cred); +else +#endif { - log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s " - "(tls_verify_certificates): %s", state->exp_tls_verify_certificates, - strerror(errno)); - return DEFER; - } + if (Ustat(state->exp_tls_verify_certificates, &statbuf) < 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, "could not stat %s " + "(tls_verify_certificates): %s", state->exp_tls_verify_certificates, + strerror(errno)); + return DEFER; + } #ifndef SUPPORT_CA_DIR -/* The test suite passes in /dev/null; we could check for that path explicitly, -but who knows if someone has some weird FIFO which always dumps some certs, or -other weirdness. The thing we really want to check is that it's not a -directory, since while OpenSSL supports that, GnuTLS does not. -So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */ -if (S_ISDIR(statbuf.st_mode)) - { - DEBUG(D_tls) - debug_printf("verify certificates path is a dir: \"%s\"\n", - state->exp_tls_verify_certificates); - log_write(0, LOG_MAIN|LOG_PANIC, - "tls_verify_certificates \"%s\" is a directory", - state->exp_tls_verify_certificates); - return DEFER; - } + /* The test suite passes in /dev/null; we could check for that path explicitly, + but who knows if someone has some weird FIFO which always dumps some certs, or + other weirdness. The thing we really want to check is that it's not a + directory, since while OpenSSL supports that, GnuTLS does not. + So s/!S_ISREG/S_ISDIR/ and change some messsaging ... */ + if (S_ISDIR(statbuf.st_mode)) + { + DEBUG(D_tls) + debug_printf("verify certificates path is a dir: \"%s\"\n", + state->exp_tls_verify_certificates); + log_write(0, LOG_MAIN|LOG_PANIC, + "tls_verify_certificates \"%s\" is a directory", + state->exp_tls_verify_certificates); + return DEFER; + } #endif -DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n", - state->exp_tls_verify_certificates, statbuf.st_size); + DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n", + state->exp_tls_verify_certificates, statbuf.st_size); -if (statbuf.st_size == 0) - { - DEBUG(D_tls) - debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n"); - return OK; - } + if (statbuf.st_size == 0) + { + DEBUG(D_tls) + debug_printf("cert file empty, no certs, no verification, ignoring any CRL\n"); + return OK; + } -cert_count = + cert_count = #ifdef SUPPORT_CA_DIR - (statbuf.st_mode & S_IFMT) == S_IFDIR - ? - gnutls_certificate_set_x509_trust_dir(state->x509_cred, - CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM) - : + (statbuf.st_mode & S_IFMT) == S_IFDIR + ? + gnutls_certificate_set_x509_trust_dir(state->x509_cred, + CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM) + : #endif - gnutls_certificate_set_x509_trust_file(state->x509_cred, - CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_x509_trust_file(state->x509_cred, + CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM); + } if (cert_count < 0) { rc = cert_count; - exim_gnutls_err_check(US"gnutls_certificate_set_x509_trust_file"); + exim_gnutls_err_check(US"setting certificate trust"); } DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count); @@ -1005,6 +1022,38 @@ return OK; * Initialize for GnuTLS * *************************************************/ + +#ifndef DISABLE_OCSP + +static BOOL +tls_is_buggy_ocsp(void) +{ +const uschar * s; +uschar maj, mid, mic; + +s = CUS gnutls_check_version(NULL); +maj = atoi(CCS s); +if (maj == 3) + { + while (*s && *s != '.') s++; + mid = atoi(CCS ++s); + if (mid <= 2) + return TRUE; + else if (mid >= 5) + return FALSE; + else + { + while (*s && *s != '.') s++; + mic = atoi(CCS ++s); + return mic <= (mid == 3 ? 16 : 3); + } + } +return FALSE; +} + +#endif + + /* Called from both server and client code. In the case of a server, errors before actual TLS negotiation return DEFER. @@ -1068,6 +1117,11 @@ if (!exim_gnutls_base_init_done) } #endif +#ifndef DISABLE_OCSP + if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) + log_write(0, LOG_MAIN, "OCSP unusable with this GnuTLS library version"); +#endif + exim_gnutls_base_init_done = TRUE; } @@ -1389,11 +1443,10 @@ if (rc < 0 || else { -#ifdef EXPERIMENTAL_CERTNAMES - if (state->verify_requirement == VERIFY_WITHHOST) + if (state->exp_tls_verify_cert_hostnames) { int sep = 0; - uschar * list = state->exp_tls_verify_cert_hostnames; + const uschar * list = state->exp_tls_verify_cert_hostnames; uschar * name; while (name = string_nextinlist(&list, &sep, NULL, 0)) if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name)) @@ -1402,12 +1455,15 @@ else { DEBUG(D_tls) debug_printf("TLS certificate verification failed: cert name mismatch\n"); - gnutls_alert_send(state->session, - GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); - return FALSE; + if (state->verify_requirement >= VERIFY_REQUIRED) + { + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; + } + return TRUE; } } -#endif state->peer_cert_verified = TRUE; DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n", state->peerdn ? state->peerdn : US""); @@ -1542,7 +1598,7 @@ return 0; #endif -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT /* We use this callback to get observability and detail-level control for an exim TLS connection (either direction), raising a tls:cert event @@ -1559,6 +1615,7 @@ const gnutls_datum * cert_list; unsigned int cert_list_size = 0; gnutls_x509_crt_t crt; int rc; +uschar * yield; exim_gnutls_state_st * state = gnutls_session_get_ptr(session); cert_list = gnutls_certificate_get_peers(session, &cert_list_size); @@ -1574,11 +1631,12 @@ if (cert_list) } state->tlsp->peercert = crt; - if (event_raise(state->event_action, - US"tls:cert", string_sprintf("%d", cert_list_size)) == DEFER) + if ((yield = event_raise(state->event_action, + US"tls:cert", string_sprintf("%d", cert_list_size)))) { log_write(0, LOG_MAIN, - "SSL verify denied by event-action: depth=%d", cert_list_size); + "SSL verify denied by event-action: depth=%d: %s", + cert_list_size, yield); return 1; /* reject */ } state->tlsp->peercert = NULL; @@ -1664,7 +1722,7 @@ else gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT if (event_action) { state->event_action = event_action; @@ -1769,6 +1827,25 @@ return OK; +static void +tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state, + smtp_transport_options_block * ob) +{ +if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK) + { + state->exp_tls_verify_cert_hostnames = +#ifdef SUPPORT_I18N + string_domain_utf8_to_alabel(host->name, NULL); +#else + host->name; +#endif + DEBUG(D_tls) + debug_printf("TLS: server cert verification includes hostname: \"%s\".\n", + state->exp_tls_verify_cert_hostnames); + } +} + + /************************************************* * Start a TLS session in a client * *************************************************/ @@ -1800,11 +1877,10 @@ int rc; const char *error; exim_gnutls_state_st *state = NULL; #ifndef DISABLE_OCSP -BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp, - NULL, host->name, host->address, NULL) == OK; +BOOL require_ocsp = + verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; BOOL request_ocsp = require_ocsp ? TRUE - : verify_check_this_host(&ob->hosts_request_ocsp, - NULL, host->name, host->address, NULL) == OK; + : verify_check_given_host(&ob->hosts_request_ocsp, host) == OK; #endif DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd); @@ -1831,43 +1907,26 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, gnutls_dh_set_prime_bits(state->session, dh_min_bits); } -/* Stick to the old behaviour for compatibility if tls_verify_certificates is +/* Stick to the old behaviour for compatibility if tls_verify_certificates is set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only the specified host patterns if one of them is defined */ -if (( state->exp_tls_verify_certificates - && !ob->tls_verify_hosts - && !ob->tls_try_verify_hosts - ) - || - verify_check_host(&ob->tls_verify_hosts) == OK +if ( ( state->exp_tls_verify_certificates + && !ob->tls_verify_hosts + && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts) + ) + || verify_check_given_host(&ob->tls_verify_hosts, host) == OK ) { -#ifdef EXPERIMENTAL_CERTNAMES - if (ob->tls_verify_cert_hostnames) - { - DEBUG(D_tls) - debug_printf("TLS: server cert incl. hostname verification required.\n"); - state->verify_requirement = VERIFY_WITHHOST; - if (!expand_check(ob->tls_verify_cert_hostnames, - US"tls_verify_cert_hostnames", - &state->exp_tls_verify_cert_hostnames)) - return FAIL; - if (state->exp_tls_verify_cert_hostnames) - DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", - state->exp_tls_verify_cert_hostnames); - } - else -#endif - { - DEBUG(D_tls) - debug_printf("TLS: server certificate verification required.\n"); - state->verify_requirement = VERIFY_REQUIRED; - } + tls_client_setup_hostname_checks(host, state, ob); + DEBUG(D_tls) + debug_printf("TLS: server certificate verification required.\n"); + state->verify_requirement = VERIFY_REQUIRED; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } -else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) +else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK) { + tls_client_setup_hostname_checks(host, state, ob); DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n"); state->verify_requirement = VERIFY_OPTIONAL; @@ -1894,7 +1953,7 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT if (tb->event_action) { state->event_action = tb->event_action;