X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Ftls-gnu.c;h=601197475c71ca424f372f7e236ba3a29529779b;hb=dfe7d917154afce0e4016a60f142bd540677930f;hp=ace59633a565f36e7b15a30e7877e6abadd060df;hpb=2b4a568dfa3d79a9a968984cf5b23829c084a951;p=exim.git diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index ace59633a..601197475 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 */ @@ -43,7 +43,24 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x020c00 # include #endif -#ifdef EXPERIMENTAL_OCSP +#if GNUTLS_VERSION_NUMBER < 0x030103 && !defined(DISABLE_OCSP) +# warning "GnuTLS library version too old; define DISABLE_OCSP in Makefile" +# define DISABLE_OCSP +#endif +#if GNUTLS_VERSION_NUMBER < 0x020a00 && !defined(DISABLE_EVENT) +# warning "GnuTLS library version too old; tls:cert event unsupported" +# 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 #endif @@ -60,7 +77,8 @@ Changes: /* Values for verify_requirement */ -enum peer_verify_requirement { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED }; +enum peer_verify_requirement + { 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 @@ -74,19 +92,20 @@ Not handled here: global tls_channelbinding_b64. */ typedef struct exim_gnutls_state { - gnutls_session_t session; + gnutls_session_t session; gnutls_certificate_credentials_t x509_cred; - gnutls_priority_t priority_cache; + gnutls_priority_t priority_cache; enum peer_verify_requirement verify_requirement; - int fd_in; - int fd_out; - BOOL peer_cert_verified; - BOOL trigger_sni_changes; - BOOL have_set_peerdn; + int fd_in; + int fd_out; + BOOL peer_cert_verified; + BOOL trigger_sni_changes; + BOOL have_set_peerdn; const struct host_item *host; - uschar *peerdn; - uschar *ciphersuite; - uschar *received_sni; + gnutls_x509_crt_t peercert; + uschar *peerdn; + uschar *ciphersuite; + uschar *received_sni; const uschar *tls_certificate; const uschar *tls_privatekey; @@ -94,12 +113,17 @@ typedef struct exim_gnutls_state { const uschar *tls_verify_certificates; const uschar *tls_crl; const uschar *tls_require_ciphers; + uschar *exp_tls_certificate; uschar *exp_tls_privatekey; - uschar *exp_tls_sni; uschar *exp_tls_verify_certificates; uschar *exp_tls_crl; uschar *exp_tls_require_ciphers; + uschar *exp_tls_ocsp_file; + const uschar *exp_tls_verify_cert_hostnames; +#ifndef DISABLE_EVENT + uschar *event_action; +#endif tls_support *tlsp; /* set in tls_init() */ @@ -114,7 +138,11 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, +#ifndef DISABLE_EVENT + NULL, +#endif NULL, NULL, 0, 0, 0, 0, }; @@ -126,7 +154,9 @@ context we're currently dealing with" pointer and rely upon being single-threaded to keep from processing data on an inbound TLS connection while talking to another TLS connection for an outbound check. This does mean that there's no way for heart-beats to be responded to, for the duration of the -second connection. */ +second connection. +XXX But see gnutls_session_get_ptr() +*/ static exim_gnutls_state_st state_server, state_client; @@ -146,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 */ @@ -156,18 +190,18 @@ static BOOL exim_gnutls_base_init_done = FALSE; the library logging; a value less than 0 disables the calls to set up logging callbacks. */ #ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL -#define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1 +# define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1 #endif #ifndef EXIM_CLIENT_DH_MIN_BITS -#define EXIM_CLIENT_DH_MIN_BITS 1024 +# define EXIM_CLIENT_DH_MIN_BITS 1024 #endif /* With GnuTLS 2.12.x+ we have gnutls_sec_param_to_pk_bits() with which we can ask for a bit-strength. Without that, we stick to the constant we had before, for now. */ #ifndef EXIM_SERVER_DH_BITS_PRE2_12 -#define EXIM_SERVER_DH_BITS_PRE2_12 1024 +# define EXIM_SERVER_DH_BITS_PRE2_12 1024 #endif #define exim_gnutls_err_check(Label) do { \ @@ -176,18 +210,18 @@ before, for now. */ #define expand_check_tlsvar(Varname) expand_check(state->Varname, US #Varname, &state->exp_##Varname) #if GNUTLS_VERSION_NUMBER >= 0x020c00 -#define HAVE_GNUTLS_SESSION_CHANNEL_BINDING -#define HAVE_GNUTLS_SEC_PARAM_CONSTANTS -#define HAVE_GNUTLS_RND +# define HAVE_GNUTLS_SESSION_CHANNEL_BINDING +# define HAVE_GNUTLS_SEC_PARAM_CONSTANTS +# define HAVE_GNUTLS_RND /* The security fix we provide with the gnutls_allow_auto_pkcs11 option * (4.82 PP/09) introduces a compatibility regression. The symbol simply * isn't available sometimes, so this needs to become a conditional * compilation; the sanest way to deal with this being a problem on * older OSes is to block it in the Local/Makefile with this compiler * definition */ -#ifndef AVOID_GNUTLS_PKCS11 -#define HAVE_GNUTLS_PKCS11 -#endif /* AVOID_GNUTLS_PKCS11 */ +# ifndef AVOID_GNUTLS_PKCS11 +# define HAVE_GNUTLS_PKCS11 +# endif /* AVOID_GNUTLS_PKCS11 */ #endif @@ -202,6 +236,10 @@ static void exim_gnutls_logger_cb(int level, const char *message); static int exim_sni_handling_cb(gnutls_session_t session); +#ifndef DISABLE_OCSP +static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, + gnutls_datum_t * ocsp_response); +#endif @@ -234,7 +272,7 @@ tls_error(const uschar *prefix, const char *msg, const host_item *host) { if (host) { - log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s", + log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s)%s%s", host->name, host->address, prefix, msg ? ": " : "", msg ? msg : ""); return FAIL; } @@ -243,6 +281,7 @@ else uschar *conn_info = smtp_get_connection_info(); if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; + /* I'd like to get separated H= here, but too hard for now */ log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s", conn_info, prefix, msg ? ": " : "", msg ? msg : ""); return DEFER; @@ -288,12 +327,40 @@ tls_error(when, msg, state->host); * Set various Exim expansion vars * *************************************************/ +#define exim_gnutls_cert_err(Label) \ + do \ + { \ + if (rc != GNUTLS_E_SUCCESS) \ + { \ + DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", \ + (Label), gnutls_strerror(rc)); \ + return rc; \ + } \ + } while (0) + +static int +import_cert(const gnutls_datum_t * cert, gnutls_x509_crt_t * crtp) +{ +int rc; + +rc = gnutls_x509_crt_init(crtp); +exim_gnutls_cert_err(US"gnutls_x509_crt_init (crt)"); + +rc = gnutls_x509_crt_import(*crtp, cert, GNUTLS_X509_FMT_DER); +exim_gnutls_cert_err(US"failed to import certificate [gnutls_x509_crt_import(cert)]"); + +return rc; +} + +#undef exim_gnutls_cert_err + + /* We set various Exim global variables from the state, once a session has been established. With TLS callouts, may need to change this to stack variables, or just re-call it with the server state after client callout has finished. -Make sure anything set here is inset in tls_getc(). +Make sure anything set here is unset in tls_getc(). Sets: tls_active fd @@ -301,15 +368,17 @@ Sets: tls_certificate_verified bool indicator tls_channelbinding_b64 for some SASL mechanisms tls_cipher a string + tls_peercert pointer to library internal tls_peerdn a string tls_sni a (UTF-8) string + tls_ourcert pointer to library internal Argument: state the relevant exim_gnutls_state_st * */ static void -extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server) +extract_exim_vars_from_tls_state(exim_gnutls_state_st * state) { gnutls_cipher_algorithm_t cipher; #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING @@ -317,18 +386,19 @@ int old_pool; int rc; gnutls_datum_t channel; #endif +tls_support * tlsp = state->tlsp; -state->tlsp->active = state->fd_out; +tlsp->active = state->fd_out; cipher = gnutls_cipher_get(state->session); /* returns size in "bytes" */ -state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8; +tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8; -state->tlsp->cipher = state->ciphersuite; +tlsp->cipher = state->ciphersuite; DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite); -state->tlsp->certificate_verified = state->peer_cert_verified; +tlsp->certificate_verified = state->peer_cert_verified; /* note that tls_channelbinding_b64 is not saved to the spool file, since it's only available for use for authenticators while this TLS session is running. */ @@ -343,14 +413,23 @@ if (rc) { } else { old_pool = store_pool; store_pool = POOL_PERM; - tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size); + tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size); store_pool = old_pool; DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n"); } #endif -state->tlsp->peerdn = state->peerdn; -state->tlsp->sni = state->received_sni; +/* peercert is set in peer_status() */ +tlsp->peerdn = state->peerdn; +tlsp->sni = state->received_sni; + +/* record our certificate */ + { + const gnutls_datum_t * cert = gnutls_certificate_get_ours(state->session); + gnutls_x509_crt_t crt; + + tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL; + } } @@ -378,7 +457,7 @@ init_server_dh(void) { int fd, rc; unsigned int dh_bits; -gnutls_datum m; +gnutls_datum_t m; uschar filename_buf[PATH_MAX]; uschar *filename = NULL; size_t sz; @@ -751,23 +830,30 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) /* Set the OCSP stapling server info */ -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if ( !host /* server */ && tls_ocsp_file ) { - uschar * expanded; - int rc; + 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; - if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &expanded)) - 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. */ - /* Lazy way; would like callback to emit debug on actual response */ + gnutls_certificate_set_ocsp_status_request_function(state->x509_cred, + server_ocsp_stapling_cb, state->exp_tls_ocsp_file); - rc = gnutls_certificate_set_ocsp_status_request_file(state->x509_cred, - expanded, 0); - exim_gnutls_err_check(US"gnutls_certificate_set_ocsp_status_request_file"); - DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", expanded); + DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file); + } } #endif @@ -782,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; @@ -802,46 +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; + } -/* 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; - } +#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; + } +#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 = + +#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) + : +#endif + gnutls_certificate_set_x509_trust_file(state->x509_cred, + CS state->exp_tls_verify_certificates, GNUTLS_X509_FMT_PEM); } -cert_count = 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); @@ -913,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. @@ -976,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; } @@ -1023,15 +1169,15 @@ if (rc != OK) return rc; /* set SNI in client, only */ if (host) { - if (!expand_check(state->tlsp->sni, US"tls_out_sni", &state->exp_tls_sni)) + if (!expand_check(sni, US"tls_out_sni", &state->tlsp->sni)) return DEFER; - if (state->exp_tls_sni && *state->exp_tls_sni) + if (state->tlsp->sni && *state->tlsp->sni) { DEBUG(D_tls) - debug_printf("Setting TLS client SNI to \"%s\"\n", state->exp_tls_sni); - sz = Ustrlen(state->exp_tls_sni); + debug_printf("Setting TLS client SNI to \"%s\"\n", state->tlsp->sni); + sz = Ustrlen(state->tlsp->sni); rc = gnutls_server_name_set(state->session, - GNUTLS_NAME_DNS, state->exp_tls_sni, sz); + GNUTLS_NAME_DNS, state->tlsp->sni, sz); exim_gnutls_err_check(US"gnutls_server_name_set"); } } @@ -1099,7 +1245,6 @@ return OK; - /************************************************* * Extract peer information * *************************************************/ @@ -1132,7 +1277,7 @@ static int peer_status(exim_gnutls_state_st *state) { uschar cipherbuf[256]; -const gnutls_datum *cert_list; +const gnutls_datum_t *cert_list; int old_pool, rc; unsigned int cert_list_size = 0; gnutls_protocol_t protocol; @@ -1181,7 +1326,7 @@ if (cert_list == NULL || cert_list_size == 0) { DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n", cert_list, cert_list_size); - if (state->verify_requirement == VERIFY_REQUIRED) + if (state->verify_requirement >= VERIFY_REQUIRED) return tls_error(US"certificate verification failed", "no certificate received from peer", state->host); return OK; @@ -1193,23 +1338,29 @@ if (ct != GNUTLS_CRT_X509) const char *ctn = gnutls_certificate_type_get_name(ct); DEBUG(D_tls) debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn); - if (state->verify_requirement == VERIFY_REQUIRED) + if (state->verify_requirement >= VERIFY_REQUIRED) return tls_error(US"certificate verification not possible, unhandled type", ctn, state->host); return OK; } -#define exim_gnutls_peer_err(Label) do { \ - if (rc != GNUTLS_E_SUCCESS) { \ - DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \ - if (state->verify_requirement == VERIFY_REQUIRED) { return tls_error((Label), gnutls_strerror(rc), state->host); } \ - return OK; } } while (0) +#define exim_gnutls_peer_err(Label) \ + do { \ + if (rc != GNUTLS_E_SUCCESS) \ + { \ + DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \ + (Label), gnutls_strerror(rc)); \ + if (state->verify_requirement >= VERIFY_REQUIRED) \ + return tls_error((Label), gnutls_strerror(rc), state->host); \ + return OK; \ + } \ + } while (0) + +rc = import_cert(&cert_list[0], &crt); +exim_gnutls_peer_err(US"cert 0"); -rc = gnutls_x509_crt_init(&crt); -exim_gnutls_peer_err(US"gnutls_x509_crt_init (crt)"); +state->tlsp->peercert = state->peercert = crt; -rc = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER); -exim_gnutls_peer_err(US"failed to import certificate [gnutls_x509_crt_import(cert 0)]"); sz = 0; rc = gnutls_x509_crt_get_dn(crt, NULL, &sz); if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) @@ -1220,6 +1371,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) dn_buf = store_get_perm(sz); rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz); exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]"); + state->peerdn = dn_buf; return OK; @@ -1266,7 +1418,9 @@ else /* Handle the result of verification. INVALID seems to be set as well as REVOKED, but leave the test for both. */ -if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) +if (rc < 0 || + verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED) + ) { state->peer_cert_verified = FALSE; if (!*error) @@ -1274,21 +1428,44 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) ? "certificate revoked" : "certificate invalid"; DEBUG(D_tls) - debug_printf("TLS certificate verification failed (%s): peerdn=%s\n", + debug_printf("TLS certificate verification failed (%s): peerdn=\"%s\"\n", *error, state->peerdn ? state->peerdn : US""); - if (state->verify_requirement == VERIFY_REQUIRED) + if (state->verify_requirement >= VERIFY_REQUIRED) { - gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); return FALSE; } DEBUG(D_tls) debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n"); } + else { + if (state->exp_tls_verify_cert_hostnames) + { + int sep = 0; + 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)) + break; + if (!name) + { + DEBUG(D_tls) + debug_printf("TLS certificate verification failed: cert name mismatch\n"); + if (state->verify_requirement >= VERIFY_REQUIRED) + { + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; + } + return TRUE; + } + } state->peer_cert_verified = TRUE; - DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=%s\n", + DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n", state->peerdn ? state->peerdn : US""); } @@ -1398,6 +1575,79 @@ return 0; +#ifndef DISABLE_OCSP + +static int +server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, + gnutls_datum_t * ocsp_response) +{ +int ret; + +if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0) + { + DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n", + (char *)ptr); + tls_in.ocsp = OCSP_NOT_RESP; + return GNUTLS_E_NO_CERTIFICATE_STATUS; + } + +tls_in.ocsp = OCSP_VFY_NOT_TRIED; +return 0; +} + +#endif + + +#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 +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 +verify_cb(gnutls_session_t session) +{ +const gnutls_datum_t * 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); +if (cert_list) + while (cert_list_size--) + { + rc = import_cert(&cert_list[cert_list_size], &crt); + if (rc != GNUTLS_E_SUCCESS) + { + DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n", + cert_list_size, gnutls_strerror(rc)); + break; + } + + state->tlsp->peercert = crt; + 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: %s", + cert_list_size, yield); + return 1; /* reject */ + } + state->tlsp->peercert = NULL; + } + +return 0; +} + +#endif + + /* ------------------------------------------------------------------------ */ /* Exported functions */ @@ -1452,23 +1702,35 @@ optional, set up appropriately. */ if (verify_check_host(&tls_verify_hosts) == OK) { - DEBUG(D_tls) debug_printf("TLS: a client certificate will be required.\n"); + DEBUG(D_tls) + debug_printf("TLS: a client certificate will be required.\n"); state->verify_requirement = VERIFY_REQUIRED; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } else if (verify_check_host(&tls_try_verify_hosts) == OK) { - DEBUG(D_tls) debug_printf("TLS: a client certificate will be requested but not required.\n"); + DEBUG(D_tls) + debug_printf("TLS: a client certificate will be requested but not required.\n"); state->verify_requirement = VERIFY_OPTIONAL; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); } else { - DEBUG(D_tls) debug_printf("TLS: a client certificate will not be requested.\n"); + DEBUG(D_tls) + debug_printf("TLS: a client certificate will not be requested.\n"); state->verify_requirement = VERIFY_NONE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } +#ifndef DISABLE_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. */ @@ -1484,15 +1746,15 @@ mode, the fflush() happens when smtp_getc() is called. */ if (!state->tlsp->on_connect) { smtp_printf("220 TLS go ahead\r\n"); - fflush(smtp_out); /*XXX JGH */ + fflush(smtp_out); } /* Now negotiate the TLS session. We put our own timer on it, since it seems that the GnuTLS library doesn't. */ gnutls_transport_set_ptr2(state->session, - (gnutls_transport_ptr)fileno(smtp_in), - (gnutls_transport_ptr)fileno(smtp_out)); + (gnutls_transport_ptr_t)(long) fileno(smtp_in), + (gnutls_transport_ptr_t)(long) fileno(smtp_out)); state->fd_in = fileno(smtp_in); state->fd_out = fileno(smtp_out); @@ -1526,22 +1788,17 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); /* Verify after the fact */ -if (state->verify_requirement != VERIFY_NONE) +if ( state->verify_requirement != VERIFY_NONE + && !verify_certificate(state, &error)) { - if (!verify_certificate(state, &error)) + if (state->verify_requirement != VERIFY_OPTIONAL) { - if (state->verify_requirement == VERIFY_OPTIONAL) - { - DEBUG(D_tls) - debug_printf("TLS: continuing on only because verification was optional, after: %s\n", - error); - } - else - { - tls_error(US"certificate verification failed", error, NULL); - return FAIL; - } + tls_error(US"certificate verification failed", error, NULL); + return FAIL; } + DEBUG(D_tls) + debug_printf("TLS: continuing on only because verification was optional, after: %s\n", + error); } /* Figure out peer DN, and if authenticated, etc. */ @@ -1551,7 +1808,7 @@ if (rc != OK) return rc; /* Sets various Exim expansion variables; always safe within server */ -extract_exim_vars_from_tls_state(state, TRUE); +extract_exim_vars_from_tls_state(state); /* TLS has been set up. Adjust the input functions to read via TLS, and initialize appropriately. */ @@ -1570,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 * *************************************************/ @@ -1580,17 +1856,7 @@ Arguments: fd the fd of the connection host connected host (for messages) addr the first address (not used) - certificate certificate file - privatekey private key file - sni TLS SNI to send to remote host - verify_certs file for certificate verify - verify_crl CRL for verify - require_ciphers list of allowed ciphers or NULL - hosts_require_ocsp hosts for which to request certificate-status (OCSP) - dh_min_bits minimum number of bits acceptable in server's DH prime - timeout startup timeout - verify_hosts mandatory client verification - try_verify_hosts optional client verification + tb transport (always smtp) Returns: OK/DEFER/FAIL (because using common functions), but for a client, DEFER and FAIL have the same meaning @@ -1599,85 +1865,112 @@ Returns: OK/DEFER/FAIL (because using common functions), int tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, - uschar *certificate, uschar *privatekey, uschar *sni, - uschar *verify_certs, uschar *verify_crl, - uschar *require_ciphers, -#ifdef EXPERIMENTAL_OCSP - uschar *hosts_require_ocsp, + transport_instance *tb +#ifdef EXPERIMENTAL_DANE + , dne_answer * unused_tlsa_dnsa #endif - int dh_min_bits, int timeout, - uschar *verify_hosts, uschar *try_verify_hosts) + ) { +smtp_transport_options_block *ob = + (smtp_transport_options_block *)tb->options_block; int rc; const char *error; exim_gnutls_state_st *state = NULL; -#ifdef EXPERIMENTAL_OCSP -BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp, - NULL, host->name, host->address, NULL) == OK; +#ifndef DISABLE_OCSP +BOOL require_ocsp = + verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; +BOOL request_ocsp = require_ocsp ? TRUE + : 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); -if ((rc = tls_init(host, certificate, privatekey, - sni, verify_certs, verify_crl, require_ciphers, &state)) != OK) +if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, + ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, + ob->tls_require_ciphers, &state)) != OK) return rc; -if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) { - DEBUG(D_tls) - debug_printf("WARNING: tls_dh_min_bits far too low, clamping %d up to %d\n", - dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS); - dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS; - } + int dh_min_bits = ob->tls_dh_min_bits; + if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) + { + DEBUG(D_tls) + debug_printf("WARNING: tls_dh_min_bits far too low," + " clamping %d up to %d\n", + dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS); + dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS; + } -DEBUG(D_tls) debug_printf("Setting D-H prime minimum acceptable bits to %d\n", - dh_min_bits); -gnutls_dh_set_prime_bits(state->session, dh_min_bits); + DEBUG(D_tls) debug_printf("Setting D-H prime minimum" + " acceptable bits to %d\n", + dh_min_bits); + 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 - && !verify_hosts - && !try_verify_hosts - ) - || - verify_check_host(&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 ) { - DEBUG(D_tls) debug_printf("TLS: server certificate verification required.\n"); + 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(&try_verify_hosts) == OK) +else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK) { - DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n"); + tls_client_setup_hostname_checks(host, state, ob); + DEBUG(D_tls) + debug_printf("TLS: server certificate verification optional.\n"); state->verify_requirement = VERIFY_OPTIONAL; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUEST); } else { - DEBUG(D_tls) debug_printf("TLS: server certificate verification not required.\n"); + DEBUG(D_tls) + debug_printf("TLS: server certificate verification not required.\n"); state->verify_requirement = VERIFY_NONE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_IGNORE); } -#ifdef EXPERIMENTAL_OCSP /* since GnuTLS 3.1.3 */ -if (require_ocsp && - (rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL)) - != OK) - return tls_error(US"cert-status-req", gnutls_strerror(rc), state->host); +#ifndef DISABLE_OCSP + /* supported since GnuTLS 3.1.3 */ +if (request_ocsp) + { + DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); + if ((rc = gnutls_ocsp_status_request_enable_client(state->session, + NULL, 0, NULL)) != OK) + return tls_error(US"cert-status-req", + gnutls_strerror(rc), state->host); + tls_out.ocsp = OCSP_NOT_RESP; + } +#endif + +#ifndef DISABLE_EVENT +if (tb->event_action) + { + state->event_action = tb->event_action; + gnutls_session_set_ptr(state->session, state); + gnutls_certificate_set_verify_function(state->x509_cred, verify_cb); + } #endif -gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd); +gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd); state->fd_in = fd; state->fd_out = fd; +DEBUG(D_tls) debug_printf("about to gnutls_handshake\n"); /* There doesn't seem to be a built-in timeout on connection. */ sigalrm_seen = FALSE; -alarm(timeout); +alarm(ob->command_timeout); do { rc = gnutls_handshake(state->session); @@ -1697,7 +1990,7 @@ if (state->verify_requirement != VERIFY_NONE && !verify_certificate(state, &error)) return tls_error(US"certificate verification failed", error, state->host); -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (require_ocsp) { DEBUG(D_tls) @@ -1711,17 +2004,20 @@ if (require_ocsp) && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0 ) { - fprintf(stderr, "%.4096s", printed.data); + debug_printf("%.4096s", printed.data); gnutls_free(printed.data); } else (void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host); } - fprintf(stderr, "%s: checking ocsp\n", __FUNCTION__); if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) + { + tls_out.ocsp = OCSP_FAILED; return tls_error(US"certificate status check failed", NULL, state->host); + } DEBUG(D_tls) debug_printf("Passed OCSP checking\n"); + tls_out.ocsp = OCSP_VFIED; } #endif @@ -1732,7 +2028,7 @@ if ((rc = peer_status(state)) != OK) /* Sets various Exim expansion variables; may need to adjust for ACL callouts */ -extract_exim_vars_from_tls_state(state, FALSE); +extract_exim_vars_from_tls_state(state); return OK; } @@ -1830,8 +2126,9 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) state->tlsp->active = -1; state->tlsp->bits = 0; state->tlsp->certificate_verified = FALSE; - tls_channelbinding_b64 = NULL; /*XXX JGH */ + tls_channelbinding_b64 = NULL; state->tlsp->cipher = NULL; + state->tlsp->peercert = NULL; state->tlsp->peerdn = NULL; return smtp_getc();