X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/9d1c15ef45fcc8809349378922de20ae9a774c75..e51c7be22dfccad376659a1a46cee93c9979bbf7:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 880aaeb14..af43686e4 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -60,7 +60,12 @@ 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 +#ifdef EXPERIMENTAL_CERTNAMES + ,VERIFY_WITHHOST +#endif + }; /* 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 @@ -95,12 +100,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; +#ifdef EXPERIMENTAL_CERTNAMES + uschar *exp_tls_verify_cert_hostnames; +#endif tls_support *tlsp; /* set in tls_init() */ @@ -115,7 +125,10 @@ 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, +#ifdef EXPERIMENTAL_CERTNAMES + NULL, +#endif NULL, NULL, 0, 0, 0, 0, }; @@ -177,18 +190,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 @@ -203,6 +216,10 @@ static void exim_gnutls_logger_cb(int level, const char *message); static int exim_sni_handling_cb(gnutls_session_t session); +#ifdef EXPERIMENTAL_OCSP +static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, + gnutls_datum_t * ocsp_response); +#endif @@ -289,10 +306,16 @@ 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) +#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 * cert, gnutls_x509_crt_t * crtp) @@ -791,18 +814,18 @@ if ( !host /* server */ && tls_ocsp_file ) { - uschar * expanded; - int rc; - - if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &expanded)) + if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", + &state->exp_tls_ocsp_file)) return DEFER; - /* Lazy way; would like callback to emit debug on actual response */ + /* 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); - 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("Set OCSP response file %s\n", &state->exp_tls_ocsp_file); } #endif @@ -1215,7 +1238,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; @@ -1227,17 +1250,23 @@ 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"); @@ -1301,7 +1330,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) @@ -1309,21 +1340,42 @@ 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 { +#ifdef EXPERIMENTAL_CERTNAMES + if (state->verify_requirement == VERIFY_WITHHOST) + { + int sep = 0; + 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"); + gnutls_alert_send(state->session, + GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; + } + } +#endif 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""); } @@ -1433,6 +1485,31 @@ return 0; +#ifdef EXPERIMENTAL_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 + + + + /* ------------------------------------------------------------------------ */ /* Exported functions */ @@ -1487,19 +1564,22 @@ 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); } @@ -1526,8 +1606,8 @@ if (!state->tlsp->on_connect) 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)(long) fileno(smtp_in), + (gnutls_transport_ptr)(long) fileno(smtp_out)); state->fd_in = fileno(smtp_in); state->fd_out = fileno(smtp_out); @@ -1610,17 +1690,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 + ob smtp transport options Returns: OK/DEFER/FAIL (because using common functions), but for a client, DEFER and FAIL have the same meaning @@ -1629,83 +1699,107 @@ 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, -#endif - int dh_min_bits, int timeout, - uschar *verify_hosts, uschar *try_verify_hosts) + void *v_ob) { +smtp_transport_options_block *ob = v_ob; int rc; const char *error; exim_gnutls_state_st *state = NULL; #ifdef EXPERIMENTAL_OCSP -BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp, +BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp, NULL, host->name, host->address, NULL) == OK; +BOOL request_ocsp = require_ocsp ? TRUE + : verify_check_this_host(&ob->hosts_request_ocsp, + NULL, host->name, host->address, NULL) == 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 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 + && !ob->tls_verify_hosts + && !ob->tls_try_verify_hosts ) || - verify_check_host(&verify_hosts) == OK + verify_check_host(&ob->tls_verify_hosts) == OK ) { - DEBUG(D_tls) debug_printf("TLS: server certificate verification required.\n"); - state->verify_requirement = VERIFY_REQUIRED; +#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; + } gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); } -else if (verify_check_host(&try_verify_hosts) == OK) +else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) { - DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n"); + 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) +if (request_ocsp) { DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n"); - rc = gnutls_ocsp_status_request_enable_client(state->session, - NULL, 0, NULL); - if (rc != OK) + 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 -gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd); +gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd); state->fd_in = fd; state->fd_out = fd; @@ -1713,7 +1807,7 @@ 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); @@ -1747,17 +1841,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