X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/c31e16a54f91476ce97ccd4d53a18c5bced1b320..17ba0f52b8df4e6ece849deac1b9d6b88bdc26c6:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index e072406fa..c97dc1bff 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -46,6 +46,7 @@ functions from the OpenSSL library. */ #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L # define EXIM_HAVE_OCSP_RESP_COUNT +# define OPENSSL_AUTO_SHA256 #else # define EXIM_HAVE_EPHEM_RSA_KEX # define EXIM_HAVE_RAND_PSEUDO @@ -75,6 +76,9 @@ change this guard and punt the issue for a while longer. */ # define EXIM_HAVE_SESSION_TICKET # define EXIM_HAVE_OPESSL_TRACE # define EXIM_HAVE_OPESSL_GET0_SERIAL +# ifndef DISABLE_OCSP +# define EXIM_HAVE_OCSP +# endif # else # define EXIM_NEED_OPENSSL_INIT # endif @@ -101,6 +105,8 @@ change this guard and punt the issue for a while longer. */ # define OPENSSL_HAVE_KEYLOG_CB # define OPENSSL_HAVE_NUM_TICKETS # define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME +# else +# define OPENSSL_BAD_SRVR_OURCERT # endif #endif @@ -145,6 +151,11 @@ This list is current as of: ==> 1.0.1b <== Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev +Plus SSL_OP_NO_RENEGOTIATION for 1.1.1 + +XXX could we autobuild this list, as with predefined-macros? +Seems just parsing ssl.h for SSL_OP_.* would be enough. +Also allow a numeric literal? */ static exim_openssl_option exim_openssl_options[] = { /* KEEP SORTED ALPHABETICALLY! */ @@ -184,6 +195,9 @@ static exim_openssl_option exim_openssl_options[] = { #ifdef SSL_OP_NO_COMPRESSION { US"no_compression", SSL_OP_NO_COMPRESSION }, #endif +#ifdef SSL_OP_NO_RENEGOTIATION + { US"no_renegotiation", SSL_OP_NO_RENEGOTIATION }, +#endif #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION }, #endif @@ -265,6 +279,13 @@ builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING ); # ifdef SSL_OP_NO_TLSv1_3 builtin_macro_create(US"_HAVE_TLS1_3"); # endif +# ifdef OPENSSL_BAD_SRVR_OURCERT +builtin_macro_create(US"_TLS_BAD_MULTICERT_IN_OURCERT"); +# endif +# ifdef EXIM_HAVE_OCSP +builtin_macro_create(US"_HAVE_TLS_OCSP"); +builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); +# endif } #else @@ -840,7 +861,13 @@ DEBUG(D_tls) static void keylog_callback(const SSL *ssl, const char *line) { +char * filename; +FILE * fp; DEBUG(D_tls) debug_printf("%.200s\n", line); +if (!(filename = getenv("SSLKEYLOGFILE"))) return; +if (!(fp = fopen(filename, "a"))) return; +fprintf(fp, "%s\n", line); +fclose(fp); } #endif @@ -1199,12 +1226,13 @@ Arguments: sctx the SSL_CTX* to update cbinfo various parts of session state filename the filename putatively holding an OCSP response + is_pem file is PEM format; otherwise is DER */ static void ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, - const uschar * filename) + const uschar * filename, BOOL is_pem) { BIO * bio; OCSP_RESPONSE * resp; @@ -1215,7 +1243,8 @@ STACK_OF(X509) * sk; unsigned long verify_flags; int status, reason, i; -DEBUG(D_tls) debug_printf("tls_ocsp_file '%s'\n", filename); +DEBUG(D_tls) + debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); if (!(bio = BIO_new_file(CS filename, "rb"))) { @@ -1224,8 +1253,26 @@ if (!(bio = BIO_new_file(CS filename, "rb"))) return; } -resp = d2i_OCSP_RESPONSE_bio(bio, NULL); +if (is_pem) + { + uschar * data, * freep; + char * dummy; + long len; + if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) + { + DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", + filename); + return; + } +debug_printf("read pem file\n"); + freep = data; + resp = d2i_OCSP_RESPONSE(NULL, CUSS &data, len); + OPENSSL_free(freep); + } +else + resp = d2i_OCSP_RESPONSE_bio(bio, NULL); BIO_free(bio); + if (!resp) { DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); @@ -1511,6 +1558,7 @@ else const uschar * olist = cbinfo->u_ocsp.server.file; int osep = 0; uschar * ofile; + BOOL fmt_pem = FALSE; if (olist) if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr)) @@ -1539,7 +1587,19 @@ else #ifndef DISABLE_OCSP if (olist) if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) - ocsp_load_response(sctx, cbinfo, ofile); + { + if (Ustrncmp(ofile, US"PEM ", 4) == 0) + { + fmt_pem = TRUE; + ofile += 4; + } + else if (Ustrncmp(ofile, US"DER ", 4) == 0) + { + fmt_pem = FALSE; + ofile += 4; + } + ocsp_load_response(sctx, cbinfo, ofile, fmt_pem); + } else DEBUG(D_tls) debug_printf("ran out of ocsp file list\n"); #endif @@ -1801,13 +1861,13 @@ OCSP_RESPONSE * rsp; OCSP_BASICRESP * bs; int i; -DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):"); +DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n"); 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 && LOGGING(tls_cipher)) - log_write(0, LOG_MAIN, "Received TLS status callback, null content"); + log_write(0, LOG_MAIN, "Required TLS certificate status not received"); else DEBUG(D_tls) debug_printf(" null\n"); return cbinfo->u_ocsp.client.verify_required ? 0 : 1; @@ -1843,8 +1903,9 @@ if (!(bs = OCSP_response_get1_basic(rsp))) */ { BIO * bp = NULL; - int status, reason; - ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; +#ifndef EXIM_HAVE_OCSP_RESP_COUNT + STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses; +#endif DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE); @@ -1854,19 +1915,23 @@ if (!(bs = OCSP_response_get1_basic(rsp))) /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */ if ((i = OCSP_basic_verify(bs, cbinfo->verify_stack, - 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: %s", - ERR_reason_error_string(ERR_peek_error())); - BIO_printf(bp, "OCSP response verify failure\n"); - ERR_print_errors(bp); - OCSP_RESPONSE_print(bp, rsp, 0); - goto failed; - } + cbinfo->u_ocsp.client.verify_store, OCSP_NOEXPLICIT)) <= 0) + if (ERR_peek_error()) + { + tls_out.ocsp = OCSP_FAILED; + if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN, + "Received TLS cert status response, itself unverifiable: %s", + ERR_reason_error_string(ERR_peek_error())); + BIO_printf(bp, "OCSP response verify failure\n"); + ERR_print_errors(bp); + OCSP_RESPONSE_print(bp, rsp, 0); + goto failed; + } + else + DEBUG(D_tls) debug_printf("no explicit trust for OCSP signing" + " in the root CA certificate; ignoring\n"); - BIO_printf(bp, "OCSP response well-formed and signed OK\n"); + DEBUG(D_tls) debug_printf("OCSP response well-formed and signed OK\n"); /*XXX So we have a good stapled OCSP status. How do we know it is for the cert of interest? OpenSSL 1.1.0 has a routine @@ -1876,60 +1941,65 @@ if (!(bs = OCSP_response_get1_basic(rsp))) For now, carry on blindly accepting the resp. */ - { - OCSP_SINGLERESP * single; - + for (int idx = #ifdef EXIM_HAVE_OCSP_RESP_COUNT - if (OCSP_resp_count(bs) != 1) + OCSP_resp_count(bs) - 1; #else - STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses; - if (sk_OCSP_SINGLERESP_num(sresp) != 1) + sk_OCSP_SINGLERESP_num(sresp) - 1; #endif - { - tls_out.ocsp = OCSP_FAILED; - log_write(0, LOG_MAIN, "OCSP stapling " - "with multiple responses not handled"); - goto failed; - } - single = OCSP_resp_get0(bs, 0); + idx >= 0; idx--) + { + OCSP_SINGLERESP * single = OCSP_resp_get0(bs, idx); + int status, reason; + ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd; + + /*XXX so I can see putting a loop in here to handle a rsp with >1 singleresp + - but what happens with a GnuTLS-style input? + + we could do with a debug label for each singleresp + - it has a certID with a serialNumber, but I see no API to get that + */ status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd); - } - DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd); - DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd); - if (!OCSP_check_validity(thisupd, nextupd, - EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) - { - tls_out.ocsp = OCSP_FAILED; - DEBUG(D_tls) ERR_print_errors(bp); - log_write(0, LOG_MAIN, "Server OSCP dates invalid"); - } - else - { + DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd); + DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd); + if (!OCSP_check_validity(thisupd, nextupd, + EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) + { + tls_out.ocsp = OCSP_FAILED; + DEBUG(D_tls) ERR_print_errors(bp); + log_write(0, LOG_MAIN, "Server OSCP dates invalid"); + goto failed; + } + DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n", OCSP_cert_status_str(status)); switch(status) { case V_OCSP_CERTSTATUS_GOOD: - tls_out.ocsp = OCSP_VFIED; - i = 1; - goto good; + continue; /* the idx loop */ case V_OCSP_CERTSTATUS_REVOKED: - tls_out.ocsp = OCSP_FAILED; log_write(0, LOG_MAIN, "Server certificate revoked%s%s", reason != -1 ? "; reason: " : "", reason != -1 ? OCSP_crl_reason_str(reason) : ""); DEBUG(D_tls) time_print(bp, "Revocation Time", rev); break; default: - tls_out.ocsp = OCSP_FAILED; log_write(0, LOG_MAIN, "Server certificate status unknown, in OCSP stapling"); break; } + + goto failed; } + + i = 1; + tls_out.ocsp = OCSP_VFIED; + goto good; + failed: + tls_out.ocsp = OCSP_FAILED; i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; good: BIO_free(bp); @@ -1945,6 +2015,23 @@ return i; * Initialize for TLS * *************************************************/ +static void +tls_openssl_init(void) +{ +#ifdef EXIM_NEED_OPENSSL_INIT +SSL_load_error_strings(); /* basic set up */ +OpenSSL_add_ssl_algorithms(); +#endif + +#if defined(EXIM_HAVE_SHA256) && !defined(OPENSSL_AUTO_SHA256) +/* SHA256 is becoming ever more popular. This makes sure it gets added to the +list of available digests. */ +EVP_add_digest(EVP_sha256()); +#endif +} + + + /* Called from both server and client code, to do preliminary initialization of the library. We allocate and return a context structure. @@ -2000,16 +2087,7 @@ cbinfo->host = host; cbinfo->event_action = NULL; #endif -#ifdef EXIM_NEED_OPENSSL_INIT -SSL_load_error_strings(); /* basic set up */ -OpenSSL_add_ssl_algorithms(); -#endif - -#ifdef EXIM_HAVE_SHA256 -/* SHA256 is becoming ever more popular. This makes sure it gets added to the -list of available digests. */ -EVP_add_digest(EVP_sha256()); -#endif +tls_openssl_init(); /* Create a context. The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant @@ -2203,14 +2281,13 @@ Returns: pointer to allocated string in perm-pool */ static uschar * -construct_cipher_name(SSL * ssl, int * bits) +construct_cipher_name(SSL * ssl, const uschar * ver, int * bits) { int pool = store_pool; /* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't yet reflect that. It should be a safe change anyway, even 0.9.8 versions have the accessor functions use const in the prototype. */ -const uschar * ver = CUS SSL_get_version(ssl); const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl); uschar * s; @@ -2241,6 +2318,21 @@ return cipher_stdname(id >> 8, id & 0xff); } +static const uschar * +tlsver_name(SSL * ssl) +{ +uschar * s, * p; +int pool = store_pool; + +store_pool = POOL_PERM; +s = string_copy(US SSL_get_version(ssl)); +store_pool = pool; +if ((p = Ustrchr(s, 'v'))) /* TLSv1.2 -> TLS1.2 */ + for (;; p++) if (!(*p = p[1])) break; +return CUS s; +} + + static void peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned siz) { @@ -2277,7 +2369,11 @@ if (tlsp->peercert) for resumption next to the TLS session, and used here. */ if (!tlsp->verify_override) - tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK; + tlsp->certificate_verified = +#ifdef SUPPORT_DANE + tlsp->dane_verified || +#endif + SSL_get_verify_result(ssl) == X509_V_OK; } } @@ -2648,8 +2744,14 @@ if (rc <= 0) /* Handle genuine errors */ case SSL_ERROR_SSL: - (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr); + { + uschar * s = US"SSL_accept"; + unsigned long e = ERR_peek_error(); + if (ERR_GET_REASON(e) == SSL_R_WRONG_VERSION_NUMBER) + s = string_sprintf("%s (%s)", s, SSL_get_version(server_ssl)); + (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr); return FAIL; + } default: DEBUG(D_tls) debug_printf("Got SSL error %d\n", error); @@ -2679,12 +2781,16 @@ if (SSL_session_reused(server_ssl)) } #endif -/* TLS has been set up. Adjust the input functions to read via TLS, -and initialize things. */ +/* TLS has been set up. Record data for the connection, +adjust the input functions to read via TLS, and initialize things. */ +#ifdef SSL_get_extms_support +tls_in.ext_master_secret = SSL_get_extms_support(server_ssl) == 1; +#endif peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn)); -tls_in.cipher = construct_cipher_name(server_ssl, &tls_in.bits); +tls_in.ver = tlsver_name(server_ssl); +tls_in.cipher = construct_cipher_name(server_ssl, tls_in.ver, &tls_in.bits); tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl); DEBUG(D_tls) @@ -2717,6 +2823,20 @@ DEBUG(D_tls) tls_in.ourcert = crt ? X509_dup(crt) : NULL; } +/* Channel-binding info for authenticators +See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */ + { + uschar c, * s; + size_t len = SSL_get_peer_finished(server_ssl, &c, 0); + int old_pool = store_pool; + + SSL_get_peer_finished(server_ssl, s = store_get((int)len, FALSE), len); + store_pool = POOL_PERM; + tls_in.channelbinding = b64encode_taint(CUS s, (int)len, FALSE); + store_pool = old_pool; + DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding); + } + /* Only used by the server-side tls (tls_in), including tls_getc. Client-side (tls_out) reads (seem to?) go via smtp_read_response()/ip_recv(). @@ -3267,9 +3387,13 @@ DEBUG(D_tls) tls_client_resume_posthandshake(exim_client_ctx, tlsp); #endif +#ifdef SSL_get_extms_support +tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1; +#endif peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn)); -tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits); +tlsp->ver = tlsver_name(exim_client_ctx->ssl); +tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, tlsp->ver, &tlsp->bits); tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl); /* Record the certificate we presented */ @@ -3278,6 +3402,20 @@ tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl); tlsp->ourcert = crt ? X509_dup(crt) : NULL; } +/*XXX will this work with continued-TLS? */ +/* Channel-binding info for authenticators */ + { + uschar c, * s; + size_t len = SSL_get_finished(exim_client_ctx->ssl, &c, 0); + int old_pool = store_pool; + + SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, TRUE), len); + store_pool = POOL_PERM; + tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE); + store_pool = old_pool; + DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp); + } + tlsp->active.sock = cctx->sock; tlsp->active.tls_ctx = exim_client_ctx; cctx->tls_ctx = exim_client_ctx; @@ -3478,11 +3616,12 @@ Arguments: Returns: the number of bytes after a successful write, -1 after a failed write -Used by both server-side and client-side TLS. +Used by both server-side and client-side TLS. Calling with len zero and more unset +will flush buffered writes; buff can be null for this case. */ int -tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more) +tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) { size_t olen = len; int outbytes, error; @@ -3508,14 +3647,16 @@ a store reset there, so use POOL_PERM. */ if ((more || corked)) { -#ifdef SUPPORT_PIPE_CONNECT + if (!len) buff = US &error; /* dummy just so that string_catn is ok */ + +#ifndef DISABLE_PIPE_CONNECT int save_pool = store_pool; store_pool = POOL_PERM; #endif corked = string_catn(corked, buff, len); -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT store_pool = save_pool; #endif @@ -3537,16 +3678,16 @@ for (int left = len; left > 0;) DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error); switch (error) { + case SSL_ERROR_NONE: /* the usual case */ + left -= outbytes; + buff += outbytes; + break; + case SSL_ERROR_SSL: ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring); return -1; - case SSL_ERROR_NONE: - left -= outbytes; - buff += outbytes; - break; - case SSL_ERROR_ZERO_RETURN: log_write(0, LOG_MAIN, "SSL channel closed on write"); return -1; @@ -3661,18 +3802,7 @@ tls_validate_require_cipher(void) SSL_CTX *ctx; uschar *s, *expciphers, *err; -/* this duplicates from tls_init(), we need a better "init just global -state, for no specific purpose" singleton function of our own */ - -#ifdef EXIM_NEED_OPENSSL_INIT -SSL_load_error_strings(); -OpenSSL_add_ssl_algorithms(); -#endif -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) -/* SHA256 is becoming ever more popular. This makes sure it gets added to the -list of available digests. */ -EVP_add_digest(EVP_sha256()); -#endif +tls_openssl_init(); if (!(tls_require_ciphers && *tls_require_ciphers)) return NULL; @@ -3900,7 +4030,7 @@ BOOL tls_openssl_options_parse(uschar *option_spec, long *results) { long result, item; -uschar *end; +uschar * exp, * end; uschar keep_c; BOOL adding, item_parsed; @@ -3908,7 +4038,7 @@ BOOL adding, item_parsed; result = SSL_OP_NO_TICKET; /* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed - * from default because it increases BEAST susceptibility. */ +from default because it increases BEAST susceptibility. */ #ifdef SSL_OP_NO_SSLv2 result |= SSL_OP_NO_SSLv2; #endif @@ -3918,6 +4048,9 @@ result |= SSL_OP_NO_SSLv3; #ifdef SSL_OP_SINGLE_DH_USE result |= SSL_OP_SINGLE_DH_USE; #endif +#ifdef SSL_OP_NO_RENEGOTIATION +result |= SSL_OP_NO_RENEGOTIATION; +#endif if (!option_spec) { @@ -3925,7 +4058,10 @@ if (!option_spec) return TRUE; } -for (uschar * s = option_spec; *s; /**/) +if (!expand_check(option_spec, US"openssl_options", &exp, &end)) + return FALSE; + +for (uschar * s = exp; *s; /**/) { while (isspace(*s)) ++s; if (*s == '\0') @@ -3947,7 +4083,7 @@ for (uschar * s = option_spec; *s; /**/) DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s); return FALSE; } - DEBUG(D_tls) debug_printf("openssl option, %s %8lx: %lx (%s)\n", + DEBUG(D_tls) debug_printf("openssl option, %s %08lx: %08lx (%s)\n", adding ? "adding to " : "removing from", result, item, s); if (adding) result |= item;