X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Ftls-gnu.c;h=6db7e4a8cf25a014e59c24752f4e0dffcd169e7f;hb=4af0d74a886c7fbd4e3eec1743f4b1d2d8a4d457;hp=593319393f1690afb3a736a5fdc2996291eed9d7;hpb=74954688e4f61475b2451b2cb22045ab8e7f0150;p=exim.git diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 593319393..6db7e4a8c 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -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_TPDA) -# warning "GnuTLS library version too old; TPDA tls:cert event unsupported" -# undef EXPERIMENTAL_TPDA +#if GNUTLS_VERSION_NUMBER < 0x020a00 && defined(EXPERIMENTAL_EVENT) +# warning "GnuTLS library version too old; tls:cert event unsupported" +# undef EXPERIMENTAL_EVENT #endif #if GNUTLS_VERSION_NUMBER >= 0x030306 # define SUPPORT_CA_DIR #else # undef SUPPORT_CA_DIR #endif +#if GNUTLS_VERSION_NUMBER >= 0x030314 +# 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_TPDA + const uschar *exp_tls_verify_cert_hostnames; +#ifdef EXPERIMENTAL_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_TPDA + NULL, +#ifdef EXPERIMENTAL_EVENT NULL, #endif NULL, @@ -862,6 +857,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 +881,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); @@ -1389,11 +1395,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 +1407,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,23 +1550,24 @@ return 0; #endif -#ifdef EXPERIMENTAL_TPDA +#ifdef EXPERIMENTAL_EVENT /* We use this callback to get observability and detail-level control -for an exim client TLS connection, raising a TPDA tls:cert event -for each cert in the chain presented by the server. Any event +for an exim TLS connection (either direction), raising a tls:cert event +for each cert in the chain presented by the peer. Any event can deny verification. Return 0 for the handshake to continue or non-zero to terminate. */ static int -client_verify_cb(gnutls_session_t session) +verify_cb(gnutls_session_t session) { 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 +1583,12 @@ if (cert_list) } state->tlsp->peercert = crt; - if (tpda_raise_event(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,6 +1674,15 @@ else gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } +#ifdef EXPERIMENTAL_EVENT +if (event_action) + { + state->event_action = event_action; + gnutls_session_set_ptr(state->session, state); + gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); + } +#endif + /* Register SNI handling; always, even if not in tls_certificate, so that the expansion variable $tls_sni is always available. */ @@ -1760,6 +1779,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 EXPERIMENTAL_INTERNATIONAL + 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 * *************************************************/ @@ -1791,11 +1829,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); @@ -1826,39 +1863,22 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, 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; @@ -1885,12 +1905,12 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_TPDA -if (tb->tpda_event_action) +#ifdef EXPERIMENTAL_EVENT +if (tb->event_action) { - state->event_action = tb->tpda_event_action; + state->event_action = tb->event_action; gnutls_session_set_ptr(state->session, state); - gnutls_certificate_set_verify_function(state->x509_cred, client_verify_cb); + gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); } #endif