X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/421aff8575b352ecb3711f12f86b9b4739684e80..fa41615da7020d4d951ed3a0b98464bed66ff58b:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 3000b8fcb..73ac80734 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.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. */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -22,17 +22,61 @@ functions from the OpenSSL library. */ #include #include #include -#ifdef EXPERIMENTAL_OCSP -#include +#ifndef OPENSSL_NO_ECDH +# include +#endif +#ifndef DISABLE_OCSP +# include +#endif +#ifdef EXPERIMENTAL_DANE +# include #endif -#ifdef EXPERIMENTAL_OCSP -#define EXIM_OCSP_SKEW_SECONDS (300L) -#define EXIM_OCSP_MAX_AGE (-1L) + +#ifndef DISABLE_OCSP +# define EXIM_OCSP_SKEW_SECONDS (300L) +# define EXIM_OCSP_MAX_AGE (-1L) #endif #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) -#define EXIM_HAVE_OPENSSL_TLSEXT +# define EXIM_HAVE_OPENSSL_TLSEXT +#endif + +/* + * X509_check_host provides sane certificate hostname checking, but was added + * to OpenSSL late, after other projects forked off the code-base. So in + * addition to guarding against the base version number, beware that LibreSSL + * does not (at this time) support this function. + * + * If LibreSSL gains a different API, perhaps via libtls, then we'll probably + * opt to disentangle and ask a LibreSSL user to provide glue for a third + * crypto provider for libtls instead of continuing to tie the OpenSSL glue + * into even twistier knots. If LibreSSL gains the same API, we can just + * change this guard and punt the issue for a while longer. + */ +#ifndef LIBRESSL_VERSION_NUMBER +# if OPENSSL_VERSION_NUMBER >= 0x010100000L +# define EXIM_HAVE_OPENSSL_CHECKHOST +# endif +# if OPENSSL_VERSION_NUMBER >= 0x010000000L \ + && (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L +# define EXIM_HAVE_OPENSSL_CHECKHOST +# endif + +# if !defined(OPENSSL_NO_ECDH) +# if OPENSSL_VERSION_NUMBER >= 0x0090800fL +# define EXIM_HAVE_ECDH +# endif +# if OPENSSL_VERSION_NUMBER >= 0x10002000L +# define EXIM_HAVE_OPENSSL_ECDH_AUTO +# define EXIM_HAVE_OPENSSL_EC_NIST2NID +# endif +# endif +#endif + +#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP) +# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile" +# define DISABLE_OCSP #endif /* Structure for collecting random data for seeding. */ @@ -88,7 +132,7 @@ static BOOL reexpand_tls_files_for_sni = FALSE; typedef struct tls_ext_ctx_cb { uschar *certificate; uschar *privatekey; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP BOOL is_server; union { struct { @@ -107,6 +151,10 @@ typedef struct tls_ext_ctx_cb { uschar *server_cipher_list; /* only passed down to tls_error: */ host_item *host; + const uschar * verify_cert_hostnames; +#ifdef EXPERIMENTAL_EVENT + uschar * event_action; +#endif } tls_ext_ctx_cb; /* should figure out a cleanup of API to handle state preserved per @@ -123,7 +171,7 @@ setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL opt #ifdef EXIM_HAVE_OPENSSL_TLSEXT static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); #endif -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP static int tls_server_stapling_cb(SSL *s, void *arg); #endif @@ -149,29 +197,30 @@ Returns: OK/DEFER/FAIL */ static int -tls_error(uschar *prefix, host_item *host, uschar *msg) +tls_error(uschar * prefix, const host_item * host, uschar * msg) { -if (msg == NULL) +if (!msg) { ERR_error_string(ERR_get_error(), ssl_errstring); msg = (uschar *)ssl_errstring; } -if (host == NULL) +if (host) + { + log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s", + host->name, host->address, prefix, msg); + return FAIL; + } +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", conn_info, prefix, msg); return DEFER; } -else - { - log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s): %s", - host->name, host->address, prefix, msg); - return FAIL; - } } @@ -209,7 +258,7 @@ return rsa_key; /* Extreme debug -#if defined(EXPERIMENTAL_OCSP) +#ifndef DISABLE_OCSP void x509_store_dump_cert_s_names(X509_STORE * store) { @@ -224,6 +273,7 @@ for(i= 0; idata.x509; X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name)); + name[sizeof(name)-1] = '\0'; debug_printf(" %s\n", name); } } @@ -232,27 +282,67 @@ for(i= 0; ievent_action : event_action; +if (ev) + { + old_cert = tlsp->peercert; + tlsp->peercert = X509_dup(cert); + /* NB we do not bother setting peerdn */ + if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth)))) + { + log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: " + "depth=%d cert=%s: %s", + tlsp == &tls_out ? deliver_host_address : sender_host_address, + what, depth, dn, yield); + *calledp = TRUE; + if (!*optionalp) + { + if (old_cert) tlsp->peercert = old_cert; /* restore 1st failing cert */ + return 1; /* reject (leaving peercert set) */ + } + DEBUG(D_tls) debug_printf("Event-action verify failure overridden " + "(host in tls_try_verify_hosts)\n"); + } + X509_free(tlsp->peercert); + tlsp->peercert = old_cert; + } +return 0; +} +#endif + /************************************************* * Callback for verification * *************************************************/ /* The SSL library does certificate verification if set up to do so. This callback has the current yes/no state is in "state". If verification succeeded, -we set up the tls_peerdn string. If verification failed, what happens depends -on whether the client is required to present a verifiable certificate or not. +we set the certificate-verified flag. If verification failed, what happens +depends on whether the client is required to present a verifiable certificate +or not. If verification is optional, we change the state to yes, but still log the verification error. For some reason (it really would help to have proper documentation of OpenSSL), this callback function then gets called again, this -time with state = 1. In fact, that's useful, because we can set up the peerdn -value, but we must take care not to set the private verified flag on the second -time through. +time with state = 1. We must take care not to set the private verified flag on +the second time through. Note: this function is not called if the client fails to present a certificate when asked. We get here only if a certificate has been received. Handling of optional verification for this case is done when requesting SSL to verify, by setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case. +May be called multiple times for different issues with a certificate, even +for a given "depth" in the certificate chain. + Arguments: state current yes/no state as 1/0 x509ctx certificate information. @@ -266,33 +356,34 @@ verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp) { X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx); -static uschar txt[256]; +int depth = X509_STORE_CTX_get_error_depth(x509ctx); +uschar dn[256]; -X509_NAME_oneline(X509_get_subject_name(cert), - CS txt, sizeof(txt)); +X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)); +dn[sizeof(dn)-1] = '\0'; if (state == 0) { - log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s", - X509_STORE_CTX_get_error_depth(x509ctx), + log_write(0, LOG_MAIN, "[%s] SSL verify error: depth=%d error=%s cert=%s", + tlsp == &tls_out ? deliver_host_address : sender_host_address, + depth, X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), - txt); - tlsp->certificate_verified = FALSE; + dn); *calledp = TRUE; if (!*optionalp) { - tlsp->peercert = X509_dup(cert); - return 0; /* reject */ + if (!tlsp->peercert) + tlsp->peercert = X509_dup(cert); /* record failing cert */ + return 0; /* reject */ } DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in " "tls_try_verify_hosts)\n"); } -else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0) +else if (depth != 0) { - DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", - X509_STORE_CTX_get_error_depth(x509ctx), txt); -#ifdef EXPERIMENTAL_OCSP + DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn); +#ifndef DISABLE_OCSP if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store) { /* client, wanting stapling */ /* Add the server cert's signing chain as the one @@ -302,19 +393,79 @@ else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0) cert)) ERR_clear_error(); } +#endif +#ifdef EXPERIMENTAL_EVENT + if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL")) + return 0; /* reject, with peercert set */ #endif } else { - tlsp->peerdn = txt; - tlsp->peercert = X509_dup(cert); + const uschar * verify_cert_hostnames; + + if ( tlsp == &tls_out + && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames))) + /* client, wanting hostname check */ + { + +#ifdef EXIM_HAVE_OPENSSL_CHECKHOST +# ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS +# define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0 +# endif +# ifndef X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS +# define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0 +# endif + int sep = 0; + const uschar * list = verify_cert_hostnames; + uschar * name; + int rc; + while ((name = string_nextinlist(&list, &sep, NULL, 0))) + if ((rc = X509_check_host(cert, name, 0, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS + | X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS, + NULL))) + { + if (rc < 0) + { + log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error", + tlsp == &tls_out ? deliver_host_address : sender_host_address); + name = NULL; + } + break; + } + if (!name) +#else + if (!tls_is_name_for_cert(verify_cert_hostnames, cert)) +#endif + { + log_write(0, LOG_MAIN, + "[%s] SSL verify error: certificate name mismatch: \"%s\"", + tlsp == &tls_out ? deliver_host_address : sender_host_address, + dn); + *calledp = TRUE; + if (!*optionalp) + { + if (!tlsp->peercert) + tlsp->peercert = X509_dup(cert); /* record failing cert */ + return 0; /* reject */ + } + DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in " + "tls_try_verify_hosts)\n"); + } + } + +#ifdef EXPERIMENTAL_EVENT + if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL")) + return 0; /* reject, with peercert set */ +#endif + DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n", - *calledp ? "" : " authenticated", txt); + *calledp ? "" : " authenticated", dn); if (!*calledp) tlsp->certificate_verified = TRUE; *calledp = TRUE; } -return 1; /* accept */ +return 1; /* accept, at least for this level */ } static int @@ -330,6 +481,40 @@ return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called, } +#ifdef EXPERIMENTAL_DANE + +/* This gets called *by* the dane library verify callback, which interposes +itself. +*/ +static int +verify_callback_client_dane(int state, X509_STORE_CTX * x509ctx) +{ +X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx); +uschar dn[256]; +#ifdef EXPERIMENTAL_EVENT +int depth = X509_STORE_CTX_get_error_depth(x509ctx); +BOOL dummy_called, optional = FALSE; +#endif + +X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)); +dn[sizeof(dn)-1] = '\0'; + +DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", dn); + +#ifdef EXPERIMENTAL_EVENT + if (verify_event(&tls_out, cert, depth, dn, + &dummy_called, &optional, US"DANE")) + return 0; /* reject, with peercert set */ +#endif + +if (state == 1) + tls_out.dane_verified = + tls_out.certificate_verified = TRUE; +return 1; +} + +#endif /*EXPERIMENTAL_DANE*/ + /************************************************* * Information callback * @@ -364,6 +549,7 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s)); /* If dhparam is set, expand it, and load up the parameters for DH encryption. Arguments: + sctx The current SSL CTX (inbound or outbound) dhparam DH parameter file or fixed parameter identity string host connected host, if client; NULL if server @@ -371,7 +557,7 @@ Returns: TRUE if OK (nothing to set up, or setup worked) */ static BOOL -init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host) +init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host) { BIO *bio; DH *dh; @@ -381,14 +567,11 @@ const char *pem; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded)) return FALSE; -if (dhexpanded == NULL || *dhexpanded == '\0') - { +if (!dhexpanded || !*dhexpanded) bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); - } else if (dhexpanded[0] == '/') { - bio = BIO_new_file(CS dhexpanded, "r"); - if (bio == NULL) + if (!(bio = BIO_new_file(CS dhexpanded, "r"))) { tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), host, US strerror(errno)); @@ -403,8 +586,7 @@ else return TRUE; } - pem = std_dh_prime_named(dhexpanded); - if (!pem) + if (!(pem = std_dh_prime_named(dhexpanded))) { tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), host, US strerror(errno)); @@ -413,8 +595,7 @@ else bio = BIO_new_mem_buf(CS pem, -1); } -dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); -if (dh == NULL) +if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) { BIO_free(bio); tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), @@ -448,7 +629,108 @@ return TRUE; -#ifdef EXPERIMENTAL_OCSP +/************************************************* +* Initialize for ECDH * +*************************************************/ + +/* Load parameters for ECDH encryption. + +For now, we stick to NIST P-256 because: it's simple and easy to configure; +it avoids any patent issues that might bite redistributors; despite events in +the news and concerns over curve choices, we're not cryptographers, we're not +pretending to be, and this is "good enough" to be better than no support, +protecting against most adversaries. Given another year or two, there might +be sufficient clarity about a "right" way forward to let us make an informed +decision, instead of a knee-jerk reaction. + +Longer-term, we should look at supporting both various named curves and +external files generated with "openssl ecparam", much as we do for init_dh(). +We should also support "none" as a value, to explicitly avoid initialisation. + +Patches welcome. + +Arguments: + sctx The current SSL CTX (inbound or outbound) + host connected host, if client; NULL if server + +Returns: TRUE if OK (nothing to set up, or setup worked) +*/ + +static BOOL +init_ecdh(SSL_CTX * sctx, host_item * host) +{ +#ifdef OPENSSL_NO_ECDH +return TRUE; +#else + +EC_KEY * ecdh; +uschar * exp_curve; +int nid; +BOOL rv; + +if (host) /* No ECDH setup for clients, only for servers */ + return TRUE; + +# ifndef EXIM_HAVE_ECDH +DEBUG(D_tls) + debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); +return TRUE; +# else + +if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve)) + return FALSE; +if (!exp_curve || !*exp_curve) + return TRUE; + +# ifdef EXIM_HAVE_OPENSSL_ECDH_AUTO +/* check if new enough library to support auto ECDH temp key parameter selection */ +if (Ustrcmp(exp_curve, "auto") == 0) + { + DEBUG(D_tls) debug_printf( + "ECDH temp key parameter settings: OpenSSL 1.2+ autoselection\n"); + SSL_CTX_set_ecdh_auto(sctx, 1); + return TRUE; + } +# endif + +DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve); +if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef +# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID + && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef +# endif + ) + { + tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", + exp_curve), + host, NULL); + return FALSE; + } + +if (!(ecdh = EC_KEY_new_by_curve_name(nid))) + { + tls_error(US"Unable to create ec curve", host, NULL); + return FALSE; + } + +/* The "tmp" in the name here refers to setting a temporary key +not to the stability of the interface. */ + +if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) + tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL); +else + DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); + +EC_KEY_free(ecdh); +return !rv; + +# endif /*EXIM_HAVE_ECDH*/ +#endif /*OPENSSL_NO_ECDH*/ +} + + + + +#ifndef DISABLE_OCSP /************************************************* * Load OCSP information into state * *************************************************/ @@ -582,7 +864,7 @@ bad: } return; } -#endif /*EXPERIMENTAL_OCSP*/ +#endif /*!DISABLE_OCSP*/ @@ -644,7 +926,7 @@ if (expanded != NULL && *expanded != 0) "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL); } -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (cbinfo->is_server && cbinfo->u_ocsp.server.file != NULL) { if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded)) @@ -715,8 +997,7 @@ if (!reexpand_tls_files_for_sni) not confident that memcpy wouldn't break some internal reference counting. Especially since there's a references struct member, which would be off. */ -server_sni = SSL_CTX_new(SSLv23_server_method()); -if (!server_sni) +if (!(server_sni = SSL_CTX_new(SSLv23_server_method()))) { ERR_error_string(ERR_get_error(), ssl_errstring); DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); @@ -732,9 +1013,15 @@ SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx)); SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx)); SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); + +if ( !init_dh(server_sni, cbinfo->dhparam, NULL) + || !init_ecdh(server_sni, NULL) + ) + return SSL_TLSEXT_ERR_NOACK; + if (cbinfo->server_cipher_list) SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list); -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (cbinfo->u_ocsp.server.file) { SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb); @@ -747,11 +1034,8 @@ if (rc != OK) return SSL_TLSEXT_ERR_NOACK; /* do this after setup_certs, because this can require the certs for verifying OCSP information. */ -rc = tls_expand_session_files(server_sni, cbinfo); -if (rc != OK) return SSL_TLSEXT_ERR_NOACK; - -rc = init_dh(server_sni, cbinfo->dhparam, NULL); -if (rc != OK) return SSL_TLSEXT_ERR_NOACK; +if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK) + return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); SSL_set_SSL_CTX(s, server_sni); @@ -763,7 +1047,7 @@ return SSL_TLSEXT_ERR_OK; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /************************************************* * Callback to handle OCSP Stapling * @@ -785,7 +1069,7 @@ uschar *response_der; int response_der_len; DEBUG(D_tls) - debug_printf("Received TLS status request (OCSP stapling); %s response.", + debug_printf("Received TLS status request (OCSP stapling); %s response\n", cbinfo->u_ocsp.server.response ? "have" : "lack"); tls_in.ocsp = OCSP_NOT_RESP; @@ -827,8 +1111,7 @@ len = SSL_get_tlsext_status_ocsp_resp(s, &p); if(!p) { /* Expect this when we requested ocsp but got none */ - if ( cbinfo->u_ocsp.client.verify_required - && log_extra_selector & LX_tls_cipher) + if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher)) log_write(0, LOG_MAIN, "Received TLS status callback, null content"); else DEBUG(D_tls) debug_printf(" null\n"); @@ -838,8 +1121,8 @@ if(!p) if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len))) { tls_out.ocsp = OCSP_FAILED; - if (log_extra_selector & LX_tls_cipher) - log_write(0, LOG_MAIN, "Received TLS status response, parse error"); + if (LOGGING(tls_cipher)) + log_write(0, LOG_MAIN, "Received TLS cert status response, parse error"); else DEBUG(D_tls) debug_printf(" parse error\n"); return 0; @@ -848,8 +1131,8 @@ if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len))) if(!(bs = OCSP_response_get1_basic(rsp))) { tls_out.ocsp = OCSP_FAILED; - if (log_extra_selector & LX_tls_cipher) - log_write(0, LOG_MAIN, "Received TLS status response, error parsing response"); + if (LOGGING(tls_cipher)) + log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response"); else DEBUG(D_tls) debug_printf(" error parsing response\n"); OCSP_RESPONSE_free(rsp); @@ -879,6 +1162,8 @@ if(!(bs = OCSP_response_get1_basic(rsp))) cbinfo->u_ocsp.client.verify_store, 0)) <= 0) { tls_out.ocsp = OCSP_FAILED; + if (LOGGING(tls_cipher)) + log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable"); BIO_printf(bp, "OCSP response verify failure\n"); ERR_print_errors(bp); i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; @@ -947,24 +1232,25 @@ if(!(bs = OCSP_response_get1_basic(rsp))) OCSP_RESPONSE_free(rsp); return i; } -#endif /*EXPERIMENTAL_OCSP*/ - +#endif /*!DISABLE_OCSP*/ /************************************************* * Initialize for TLS * *************************************************/ -/* Called from both server and client code, to do preliminary initialization of -the library. +/* Called from both server and client code, to do preliminary initialization +of the library. We allocate and return a context structure. Arguments: + ctxp returned SSL context host connected host, if client; NULL if server dhparam DH parameter file certificate certificate file privatekey private key ocsp_file file of stapling info (server); flag for require ocsp (client) addr address if client; NULL if server (for some randomness) + cbp place to put allocated callback context Returns: OK/DEFER/FAIL */ @@ -972,7 +1258,7 @@ Returns: OK/DEFER/FAIL static int tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP uschar *ocsp_file, #endif address_item *addr, tls_ext_ctx_cb ** cbp) @@ -980,12 +1266,12 @@ tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, long init_options; int rc; BOOL okay; -tls_ext_ctx_cb *cbinfo; +tls_ext_ctx_cb * cbinfo; cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); cbinfo->certificate = certificate; cbinfo->privatekey = privatekey; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if ((cbinfo->is_server = host==NULL)) { cbinfo->u_ocsp.server.file = ocsp_file; @@ -996,7 +1282,11 @@ else cbinfo->u_ocsp.client.verify_store = NULL; #endif cbinfo->dhparam = dhparam; +cbinfo->server_cipher_list = NULL; cbinfo->host = host; +#ifdef EXPERIMENTAL_EVENT +cbinfo->event_action = NULL; +#endif SSL_load_error_strings(); /* basic set up */ OpenSSL_add_ssl_algorithms(); @@ -1046,7 +1336,7 @@ if (!RAND_status()) /* Set up the information callback, which outputs if debugging is at a suitable level. */ -SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback); +DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback); /* Automatically re-try reads/writes after renegotiation. */ (void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY); @@ -1075,8 +1365,12 @@ else DEBUG(D_tls) debug_printf("no SSL CTX options to set\n"); /* Initialize with DH parameters if supplied */ +/* Initialize ECDH temp key parameter selection */ -if (!init_dh(*ctxp, dhparam, host)) return DEFER; +if ( !init_dh(*ctxp, dhparam, host) + || !init_ecdh(*ctxp, host) + ) + return DEFER; /* Set up certificate and key (and perhaps OCSP info) */ @@ -1087,7 +1381,7 @@ if (rc != OK) return rc; #ifdef EXIM_HAVE_OPENSSL_TLSEXT if (host == NULL) /* server */ { -# ifdef EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP /* We check u_ocsp.server.file, not server.response, because we care about if the option exists, not what the current expansion might be, as SNI might change the certificate and OCSP file in use between now and the time the @@ -1103,7 +1397,7 @@ if (host == NULL) /* server */ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo); } -# ifdef EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP else /* client */ if(ocsp_file) /* wanting stapling */ { @@ -1118,6 +1412,8 @@ else /* client */ # endif #endif +cbinfo->verify_cert_hostnames = NULL; + /* Set up the RSA callback */ SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback); @@ -1168,6 +1464,29 @@ DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf); } +static void +peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned bsize) +{ +/*XXX we might consider a list-of-certs variable for the cert chain. +SSL_get_peer_cert_chain(SSL*). We'd need a new variable type and support +in list-handling functions, also consider the difference between the entire +chain and the elements sent by the peer. */ + +/* Will have already noted peercert on a verify fail; possibly not the leaf */ +if (!tlsp->peercert) + tlsp->peercert = SSL_get_peer_certificate(ssl); +/* Beware anonymous ciphers which lead to server_cert being NULL */ +if (tlsp->peercert) + { + X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, bsize); + peerdn[bsize-1] = '\0'; + tlsp->peerdn = peerdn; /*XXX a static buffer... */ + } +else + tlsp->peerdn = NULL; +} + + @@ -1200,36 +1519,65 @@ if (!expand_check(certs, US"tls_verify_certificates", &expcerts)) if (expcerts != NULL && *expcerts != '\0') { - struct stat statbuf; - if (!SSL_CTX_set_default_verify_paths(sctx)) - return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL); - - if (Ustat(expcerts, &statbuf) < 0) + if (Ustrcmp(expcerts, "system") == 0) { - log_write(0, LOG_MAIN|LOG_PANIC, - "failed to stat %s for certificates", expcerts); - return DEFER; + /* Tell the library to use its compiled-in location for the system default + CA bundle, only */ + + if (!SSL_CTX_set_default_verify_paths(sctx)) + return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL); } else { - uschar *file, *dir; - if ((statbuf.st_mode & S_IFMT) == S_IFDIR) - { file = NULL; dir = expcerts; } - else - { file = expcerts; dir = NULL; } + struct stat statbuf; - /* If a certificate file is empty, the next function fails with an - unhelpful error message. If we skip it, we get the correct behaviour (no - certificates are recognized, but the error message is still misleading (it - says no certificate was supplied.) But this is better. */ + /* Tell the library to use its compiled-in location for the system default + CA bundle. Those given by the exim config are additional to these */ - if ((file == NULL || statbuf.st_size > 0) && - !SSL_CTX_load_verify_locations(sctx, CS file, CS dir)) - return tls_error(US"SSL_CTX_load_verify_locations", host, NULL); + if (!SSL_CTX_set_default_verify_paths(sctx)) + return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL); - if (file != NULL) + if (Ustat(expcerts, &statbuf) < 0) { - SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file)); + log_write(0, LOG_MAIN|LOG_PANIC, + "failed to stat %s for certificates", expcerts); + return DEFER; + } + else + { + uschar *file, *dir; + if ((statbuf.st_mode & S_IFMT) == S_IFDIR) + { file = NULL; dir = expcerts; } + else + { file = expcerts; dir = NULL; } + + /* If a certificate file is empty, the next function fails with an + unhelpful error message. If we skip it, we get the correct behaviour (no + certificates are recognized, but the error message is still misleading (it + says no certificate was supplied.) But this is better. */ + + if ((file == NULL || statbuf.st_size > 0) && + !SSL_CTX_load_verify_locations(sctx, CS file, CS dir)) + return tls_error(US"SSL_CTX_load_verify_locations", host, NULL); + + /* Load the list of CAs for which we will accept certs, for sending + to the client. This is only for the one-file tls_verify_certificates + variant. + If a list isn't loaded into the server, but + some verify locations are set, the server end appears to make + a wildcard reqest for client certs. + Meanwhile, the client library as deafult behaviour *ignores* the list + we send over the wire - see man SSL_CTX_set_client_cert_cb. + Because of this, and that the dir variant is likely only used for + the public-CA bundle (not for a private CA), not worth fixing. + */ + if (file != NULL) + { + STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file); + DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", + sk_X509_NAME_num(names)); + SSL_CTX_set_client_CA_list(sctx, names); + } } } @@ -1321,6 +1669,7 @@ tls_server_start(const uschar *require_ciphers) int rc; uschar *expciphers; tls_ext_ctx_cb *cbinfo; +static uschar peerdn[256]; static uschar cipherbuf[256]; /* Check for previous activation */ @@ -1336,7 +1685,7 @@ if (tls_in.active >= 0) the error. */ rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP tls_ocsp_file, #endif NULL, &server_static_cbinfo); @@ -1365,6 +1714,9 @@ if (expciphers != NULL) optional, set up appropriately. */ tls_in.certificate_verified = FALSE; +#ifdef EXPERIMENTAL_DANE +tls_in.dane_verified = FALSE; +#endif server_verify_callback_called = FALSE; if (verify_check_host(&tls_verify_hosts) == OK) @@ -1440,6 +1792,8 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n"); /* TLS has been set up. Adjust the input functions to read via TLS, and initialize things. */ +peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn)); + construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits); tls_in.cipher = cipherbuf; @@ -1478,6 +1832,104 @@ return OK; +static int +tls_client_basic_ctx_init(SSL_CTX * ctx, + host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo + ) +{ +int rc; +/* stick to the old behaviour for compatibility if tls_verify_certificates is + set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only + the specified host patterns if one of them is defined */ + +if ( ( !ob->tls_verify_hosts + && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts) + ) + || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK) + ) + client_verify_optional = FALSE; +else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK) + client_verify_optional = TRUE; +else + return OK; + +if ((rc = setup_certs(ctx, ob->tls_verify_certificates, + ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK) + return rc; + +if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK) + { + cbinfo->verify_cert_hostnames = +#ifdef EXPERIMENTAL_INTERNATIONAL + string_domain_utf8_to_alabel(host->name, NULL); +#else + host->name; +#endif + DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", + cbinfo->verify_cert_hostnames); + } +return OK; +} + + +#ifdef EXPERIMENTAL_DANE +static int +dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa) +{ +dns_record * rr; +dns_scan dnss; +const char * hostnames[2] = { CS host->name, NULL }; +int found = 0; + +if (DANESSL_init(ssl, NULL, hostnames) != 1) + return tls_error(US"hostnames load", host, NULL); + +for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) + ) if (rr->type == T_TLSA) + { + uschar * p = rr->data; + uint8_t usage, selector, mtype; + const char * mdname; + + usage = *p++; + + /* Only DANE-TA(2) and DANE-EE(3) are supported */ + if (usage != 2 && usage != 3) continue; + + selector = *p++; + mtype = *p++; + + switch (mtype) + { + default: continue; /* Only match-types 0, 1, 2 are supported */ + case 0: mdname = NULL; break; + case 1: mdname = "sha256"; break; + case 2: mdname = "sha512"; break; + } + + found++; + switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3)) + { + default: + case 0: /* action not taken */ + return tls_error(US"tlsa load", host, NULL); + case 1: break; + } + + tls_out.tlsa_usage |= 1<options_block; +static uschar peerdn[256]; +uschar * expciphers; int rc; static uschar cipherbuf[256]; -#ifdef EXPERIMENTAL_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; + +#ifndef DISABLE_OCSP +BOOL request_ocsp = FALSE; +BOOL require_ocsp = FALSE; +#endif + +#ifdef EXPERIMENTAL_DANE +tls_out.tlsa_usage = 0; +#endif + +#ifndef DISABLE_OCSP + { +# ifdef EXPERIMENTAL_DANE + if ( tlsa_dnsa + && ob->hosts_request_ocsp[0] == '*' + && ob->hosts_request_ocsp[1] == '\0' + ) + { + /* Unchanged from default. Use a safer one under DANE */ + request_ocsp = TRUE; + ob->hosts_request_ocsp = US"${if or { {= {0}{$tls_out_tlsa_usage}} " + " {= {4}{$tls_out_tlsa_usage}} } " + " {*}{}}"; + } +# endif + + if ((require_ocsp = + verify_check_given_host(&ob->hosts_require_ocsp, host) == OK)) + request_ocsp = TRUE; + else +# ifdef EXPERIMENTAL_DANE + if (!request_ocsp) +# endif + request_ocsp = + verify_check_given_host(&ob->hosts_request_ocsp, host) == OK; + } #endif rc = tls_init(&client_ctx, host, NULL, ob->tls_certificate, ob->tls_privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP (void *)(long)request_ocsp, #endif addr, &client_static_cbinfo); @@ -1542,24 +2029,25 @@ if (expciphers != NULL) return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -/* stick to the old behaviour for compatibility if tls_verify_certificates is - set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only - the specified host patterns if one of them is defined */ -if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) || - (verify_check_host(&ob->tls_verify_hosts) == OK)) +#ifdef EXPERIMENTAL_DANE +if (tlsa_dnsa) { - if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates, - ob->tls_crl, host, FALSE, verify_callback_client)) != OK) - return rc; - client_verify_optional = FALSE; + SSL_CTX_set_verify(client_ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_callback_client_dane); + + if (!DANESSL_library_init()) + return tls_error(US"library init", host, NULL); + if (DANESSL_CTX_init(client_ctx) <= 0) + return tls_error(US"context init", host, NULL); } -else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) - { - if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates, - ob->tls_crl, host, TRUE, verify_callback_client)) != OK) +else + +#endif + + if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo)) + != OK) return rc; - client_verify_optional = TRUE; - } if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); @@ -1590,9 +2078,32 @@ if (ob->tls_sni) } } -#ifdef EXPERIMENTAL_OCSP +#ifdef EXPERIMENTAL_DANE +if (tlsa_dnsa) + if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK) + return rc; +#endif + +#ifndef DISABLE_OCSP /* Request certificate status at connection-time. If the server does OCSP stapling we will get the callback (set in tls_init()) */ +# ifdef EXPERIMENTAL_DANE +if (request_ocsp) + { + const uschar * s; + if ( ((s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")) + || ((s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")) + ) + { /* Re-eval now $tls_out_tlsa_usage is populated. If + this means we avoid the OCSP request, we wasted the setup + cost in tls_init(). */ + require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; + request_ocsp = require_ocsp + || verify_check_given_host(&ob->hosts_request_ocsp, host) == OK; + } + } +# endif + if (request_ocsp) { SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp); @@ -1601,6 +2112,10 @@ if (request_ocsp) } #endif +#ifdef EXPERIMENTAL_EVENT +client_static_cbinfo->event_action = tb->event_action; +#endif + /* There doesn't seem to be a built-in timeout on connection. */ DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); @@ -1609,22 +2124,17 @@ alarm(ob->command_timeout); rc = SSL_connect(client_ssl); alarm(0); +#ifdef EXPERIMENTAL_DANE +if (tlsa_dnsa) + DANESSL_cleanup(client_ssl); +#endif + if (rc <= 0) return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL); DEBUG(D_tls) debug_printf("SSL_connect succeeded\n"); -/* Beware anonymous ciphers which lead to server_cert being NULL */ -/*XXX server_cert is never freed... use X509_free() */ -server_cert = SSL_get_peer_certificate (client_ssl); -if (server_cert) - { - tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert), - CS txt, sizeof(txt)); - tls_out.peerdn = txt; /*XXX a static buffer... */ - } -else - tls_out.peerdn = NULL; +peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn)); construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits); tls_out.cipher = cipherbuf;