X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/98716abe2b636d275e866f3ad6374cb70bf6e504..9960d1e59f08254a951283981d4a32c73ca7d3ad:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index e2ac17c88..c9dc4d9be 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -42,21 +42,22 @@ require current GnuTLS, then we'll drop support for the ancient libraries). /* needed to disable PKCS11 autoload unless requested */ #if GNUTLS_VERSION_NUMBER >= 0x020c00 # include +# define SUPPORT_PARAM_TO_PK_BITS #endif #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(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 >= 0x030314 +#if GNUTLS_VERSION_NUMBER >= 0x030014 # define SUPPORT_SYSDEFAULT_CABUNDLE #endif @@ -121,7 +122,7 @@ typedef struct exim_gnutls_state { uschar *exp_tls_require_ciphers; uschar *exp_tls_ocsp_file; const uschar *exp_tls_verify_cert_hostnames; -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT uschar *event_action; #endif @@ -140,7 +141,7 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT NULL, #endif NULL, @@ -176,6 +177,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 */ @@ -335,7 +340,7 @@ tls_error(when, msg, state->host); } while (0) static int -import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp) +import_cert(const gnutls_datum_t * cert, gnutls_x509_crt_t * crtp) { int rc; @@ -409,7 +414,7 @@ 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"); } @@ -421,7 +426,7 @@ tlsp->sni = state->received_sni; /* record our certificate */ { - const gnutls_datum * cert = gnutls_certificate_get_ours(state->session); + 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; @@ -453,7 +458,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; @@ -705,6 +710,74 @@ return OK; +/* Create and install a selfsigned certificate, for use in server mode */ + +static int +tls_install_selfsign(exim_gnutls_state_st * state) +{ +gnutls_x509_crt_t cert = NULL; +time_t now; +gnutls_x509_privkey_t pkey = NULL; +const uschar * where; +int rc; + +where = US"initialising pkey"; +if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; + +where = US"initialising cert"; +if ((rc = gnutls_x509_crt_init(&cert))) goto err; + +where = US"generating pkey"; +if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA, +#ifdef SUPPORT_PARAM_TO_PK_BITS + gnutls_sec_param_to_pk_bits(GNUTLS_PK_RSA, GNUTLS_SEC_PARAM_LOW), +#else + 1024, +#endif + 0))) + goto err; + +where = US"configuring cert"; +now = 0; +if ( (rc = gnutls_x509_crt_set_version(cert, 3)) + || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now))) + || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL))) + || (rc = gnutls_x509_crt_set_expiration_time(cert, now + 60 * 60)) /* 1 hr */ + || (rc = gnutls_x509_crt_set_key(cert, pkey)) + + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_COUNTRY_NAME, 0, "UK", 2)) + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_ORGANIZATION_NAME, 0, "Exim Developers", 15)) + || (rc = gnutls_x509_crt_set_dn_by_oid(cert, + GNUTLS_OID_X520_COMMON_NAME, 0, + smtp_active_hostname, Ustrlen(smtp_active_hostname))) + ) + goto err; + +where = US"signing cert"; +if ((rc = gnutls_x509_crt_sign(cert, cert, pkey))) goto err; + +where = US"installing selfsign cert"; + /* Since: 2.4.0 */ +if ((rc = gnutls_certificate_set_x509_key(state->x509_cred, &cert, 1, pkey))) + goto err; + +rc = OK; + +out: + if (cert) gnutls_x509_crt_deinit(cert); + if (pkey) gnutls_x509_privkey_deinit(pkey); + return rc; + +err: + rc = tls_error(where, gnutls_strerror(rc), NULL); + goto out; +} + + + + /************************************************* * Variables re-expanded post-SNI * *************************************************/ @@ -737,7 +810,6 @@ int cert_count; /* We check for tls_sni *before* expansion. */ if (!host) /* server */ - { if (!state->received_sni) { if (state->tls_certificate && @@ -758,7 +830,6 @@ if (!host) /* server */ saved_tls_verify_certificates = state->exp_tls_verify_certificates; saved_tls_crl = state->exp_tls_crl; } - } rc = gnutls_certificate_allocate_credentials(&state->x509_cred); exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials"); @@ -775,14 +846,13 @@ if (!expand_check_tlsvar(tls_certificate)) /* certificate is mandatory in server, optional in client */ -if ((state->exp_tls_certificate == NULL) || - (*state->exp_tls_certificate == '\0')) - { +if ( !state->exp_tls_certificate + || !*state->exp_tls_certificate + ) if (!host) - return tls_error(US"no TLS server certificate is specified", NULL, NULL); + return tls_install_selfsign(state); else DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); - } if (state->tls_privatekey && !expand_check_tlsvar(tls_privatekey)) return DEFER; @@ -802,9 +872,9 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) state->exp_tls_certificate, state->exp_tls_privatekey); if (state->received_sni) - { - if ((Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0) && - (Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0)) + if ( Ustrcmp(state->exp_tls_certificate, saved_tls_certificate) == 0 + && Ustrcmp(state->exp_tls_privatekey, saved_tls_privatekey) == 0 + ) { DEBUG(D_tls) debug_printf("TLS SNI: cert and key unchanged\n"); } @@ -812,7 +882,6 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) { DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n"); } - } rc = gnutls_certificate_set_x509_key_file(state->x509_cred, CS state->exp_tls_certificate, CS state->exp_tls_privatekey, @@ -831,18 +900,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("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 @@ -900,7 +976,7 @@ else 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 ... */ + So s/!S_ISREG/S_ISDIR/ and change some messaging ... */ if (S_ISDIR(statbuf.st_mode)) { DEBUG(D_tls) @@ -1011,6 +1087,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. @@ -1074,6 +1182,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; } @@ -1229,7 +1342,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; @@ -1400,7 +1513,7 @@ else int sep = 0; const uschar * list = state->exp_tls_verify_cert_hostnames; uschar * name; - while (name = string_nextinlist(&list, &sep, NULL, 0)) + while ((name = string_nextinlist(&list, &sep, NULL, 0))) if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name)) break; if (!name) @@ -1550,7 +1663,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 @@ -1563,7 +1676,7 @@ Return 0 for the handshake to continue or non-zero to terminate. static int verify_cb(gnutls_session_t session) { -const gnutls_datum * cert_list; +const gnutls_datum_t * cert_list; unsigned int cert_list_size = 0; gnutls_x509_crt_t crt; int rc; @@ -1674,7 +1787,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; @@ -1705,18 +1818,16 @@ if (!state->tlsp->on_connect) that the GnuTLS library doesn't. */ gnutls_transport_set_ptr2(state->session, - (gnutls_transport_ptr)(long) fileno(smtp_in), - (gnutls_transport_ptr)(long) 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); sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); do - { rc = gnutls_handshake(state->session); - } while ((rc == GNUTLS_E_AGAIN) || - (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen)); +while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); alarm(0); if (rc != GNUTLS_E_SUCCESS) @@ -1768,6 +1879,7 @@ and initialize appropriately. */ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size); receive_getc = tls_getc; +receive_get_cache = tls_get_cache; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; @@ -1786,7 +1898,7 @@ tls_client_setup_hostname_checks(host_item * host, exim_gnutls_state_st * state, if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK) { state->exp_tls_verify_cert_hostnames = -#ifdef EXPERIMENTAL_INTERNATIONAL +#ifdef SUPPORT_I18N string_domain_utf8_to_alabel(host->name, NULL); #else host->name; @@ -1819,7 +1931,7 @@ tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, transport_instance *tb #ifdef EXPERIMENTAL_DANE - , dne_answer * unused_tlsa_dnsa + , dns_answer * unused_tlsa_dnsa #endif ) { @@ -1859,7 +1971,7 @@ 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 */ @@ -1905,7 +2017,7 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT if (tb->event_action) { state->event_action = tb->event_action; @@ -1914,7 +2026,7 @@ if (tb->event_action) } #endif -gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd); +gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd); state->fd_in = fd; state->fd_out = fd; @@ -2068,6 +2180,7 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) DEBUG(D_tls) debug_printf("Got TLS_EOF\n"); receive_getc = smtp_getc; + receive_get_cache = smtp_get_cache; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; @@ -2106,6 +2219,17 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) return state->xfer_buffer[state->xfer_buffer_lwm++]; } +void +tls_get_cache() +{ +#ifndef DISABLE_DKIM +exim_gnutls_state_st * state = &state_server; +int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm; +if (n > 0) + dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n); +#endif +} +