X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/817d9f576cdfbc27cf0536be348645baf27d7836..de6135a0cbbeb4fbae7233a40563a241de1c237b:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 31405606c..18cb787a5 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -42,22 +42,43 @@ typedef struct randstuff { /* Local static variables */ -static BOOL verify_callback_called = FALSE; +static BOOL client_verify_callback_called = FALSE; +static BOOL server_verify_callback_called = FALSE; static const uschar *sid_ctx = US"exim"; +/* We have three different contexts to care about. + +Simple case: client, `client_ctx` + As a client, we can be doing a callout or cut-through delivery while receiving + a message. So we have a client context, which should have options initialised + from the SMTP Transport. + +Server: + There are two cases: with and without ServerNameIndication from the client. + Given TLS SNI, we can be using different keys, certs and various other + configuration settings, because they're re-expanded with $tls_sni set. This + allows vhosting with TLS. This SNI is sent in the handshake. + A client might not send SNI, so we need a fallback, and an initial setup too. + So as a server, we start out using `server_ctx`. + If SNI is sent by the client, then we as server, mid-negotiation, try to clone + `server_sni` from `server_ctx` and then initialise settings by re-expanding + configuration. +*/ + static SSL_CTX *client_ctx = NULL; static SSL_CTX *server_ctx = NULL; static SSL *client_ssl = NULL; static SSL *server_ssl = NULL; + #ifdef EXIM_HAVE_OPENSSL_TLSEXT -static SSL_CTX *client_sni = NULL; static SSL_CTX *server_sni = NULL; #endif static char ssl_errstring[256]; static int ssl_session_timeout = 200; -static BOOL verify_optional = FALSE; +static BOOL client_verify_optional = FALSE; +static BOOL server_verify_optional = FALSE; static BOOL reexpand_tls_files_for_sni = FALSE; @@ -84,7 +105,7 @@ tls_ext_ctx_cb *client_static_cbinfo = NULL; tls_ext_ctx_cb *server_static_cbinfo = NULL; static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional); +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client); /* Callbacks */ #ifdef EXIM_HAVE_OPENSSL_TLSEXT @@ -200,14 +221,31 @@ setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case. Arguments: state current yes/no state as 1/0 x509ctx certificate information. + client TRUE for client startup, FALSE for server startup Returns: 1 if verified, 0 if not */ static int -verify_callback(int state, X509_STORE_CTX *x509ctx) +verify_callback(int state, X509_STORE_CTX *x509ctx, BOOL client) { static uschar txt[256]; +tls_support * tlsp; +BOOL * calledp; +BOOL * optionalp; + +if (client) + { + tlsp= &tls_out; + calledp= &client_verify_callback_called; + optionalp= &client_verify_optional; + } +else + { + tlsp= &tls_in; + calledp= &server_verify_callback_called; + optionalp= &server_verify_optional; + } X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert), CS txt, sizeof(txt)); @@ -218,9 +256,9 @@ if (state == 0) x509ctx->error_depth, X509_verify_cert_error_string(x509ctx->error), txt); - tls_in.certificate_verified = FALSE; - verify_callback_called = TRUE; - if (!verify_optional) return 0; /* reject */ + tlsp->certificate_verified = FALSE; + *calledp = TRUE; + if (!*optionalp) return 0; /* reject */ DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in " "tls_try_verify_hosts)\n"); return 1; /* accept */ @@ -234,16 +272,28 @@ if (x509ctx->error_depth != 0) else { DEBUG(D_tls) debug_printf("SSL%s peer: %s\n", - verify_callback_called? "" : " authenticated", txt); - tls_in.peerdn = txt; + *calledp ? "" : " authenticated", txt); + tlsp->peerdn = txt; } -if (!verify_callback_called) tls_in.certificate_verified = TRUE; -verify_callback_called = TRUE; +if (!*calledp) tlsp->certificate_verified = TRUE; +*calledp = TRUE; return 1; /* accept */ } +static int +verify_callback_client(int state, X509_STORE_CTX *x509ctx) +{ +return verify_callback(state, x509ctx, TRUE); +} + +static int +verify_callback_server(int state, X509_STORE_CTX *x509ctx) +{ +return verify_callback(state, x509ctx, FALSE); +} + /************************************************* @@ -286,11 +336,7 @@ Returns: TRUE if OK (nothing to set up, or setup worked) */ static BOOL -<<<<<<< HEAD init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host) -======= -init_dh(SSL_CTX *ctx, uschar *dhparam, host_item *host) ->>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling. { BIO *bio; DH *dh; @@ -515,7 +561,10 @@ uschar *expanded; if (cbinfo->certificate == NULL) return OK; -if (Ustrstr(cbinfo->certificate, US"tls_sni")) +if (Ustrstr(cbinfo->certificate, US"tls_sni") || + Ustrstr(cbinfo->certificate, US"tls_in_sni") || + Ustrstr(cbinfo->certificate, US"tls_out_sni") + ) reexpand_tls_files_for_sni = TRUE; if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded)) @@ -640,11 +689,11 @@ if (cbinfo->server_cipher_list) if (cbinfo->ocsp_file) { SSL_CTX_set_tlsext_status_cb(server_sni, tls_stapling_cb); - SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); + SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo); } #endif -rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; /* do this after setup_certs, because this can require the certs for verifying @@ -652,7 +701,7 @@ OCSP information. */ rc = tls_expand_session_files(server_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = init_dh(ctx_sni, cbinfo->dhparam, NULL); +rc = init_dh(server_sni, cbinfo->dhparam, NULL); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); @@ -695,7 +744,7 @@ response_der_len = i2d_OCSP_RESPONSE(cbinfo->ocsp_response, &response_der); if (response_der_len <= 0) return SSL_TLSEXT_ERR_NOACK; -SSL_set_tlsext_status_ocsp_resp(ssl, response_der, response_der_len); +SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len); return SSL_TLSEXT_ERR_OK; } @@ -739,6 +788,8 @@ cbinfo->certificate = certificate; cbinfo->privatekey = privatekey; #ifdef EXPERIMENTAL_OCSP cbinfo->ocsp_file = ocsp_file; +cbinfo->ocsp_file_expanded = NULL; +cbinfo->ocsp_response = NULL; #endif cbinfo->dhparam = dhparam; cbinfo->host = host; @@ -821,11 +872,7 @@ else /* Initialize with DH parameters if supplied */ -<<<<<<< HEAD -if (!init_dh(ctx, dhparam, host)) return DEFER; -======= if (!init_dh(*ctxp, dhparam, host)) return DEFER; ->>>>>>> Dual-tls - split management of TLS into in- and out-bound connection-handling. /* Set up certificate and key (and perhaps OCSP info) */ @@ -843,8 +890,8 @@ if (host == NULL) callback is invoked. */ if (cbinfo->ocsp_file) { - SSL_CTX_set_tlsext_status_cb(ctx, tls_stapling_cb); - SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); + SSL_CTX_set_tlsext_status_cb(server_ctx, tls_stapling_cb); + SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo); } #endif /* We always do this, so that $tls_sni is available even if not used in @@ -948,19 +995,20 @@ Arguments: host NULL in a server; the remote host in a client optional TRUE if called from a server for a host in tls_try_verify_hosts; otherwise passed as FALSE + client TRUE if called for client startup, FALSE for server startup Returns: OK/DEFER/FAIL */ static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional) +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client) { uschar *expcerts, *expcrl; if (!expand_check(certs, US"tls_verify_certificates", &expcerts)) return DEFER; -if (expcerts != NULL) +if (expcerts != NULL && *expcerts != '\0') { struct stat statbuf; if (!SSL_CTX_set_default_verify_paths(sctx)) @@ -1052,7 +1100,7 @@ if (expcerts != NULL) SSL_CTX_set_verify(sctx, SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), - verify_callback); + client ? verify_callback_client : verify_callback_server); } return OK; @@ -1127,19 +1175,19 @@ if (expciphers != NULL) optional, set up appropriately. */ tls_in.certificate_verified = FALSE; -verify_callback_called = FALSE; +server_verify_callback_called = FALSE; if (verify_check_host(&tls_verify_hosts) == OK) { - rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE); + rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE); if (rc != OK) return rc; - verify_optional = FALSE; + server_verify_optional = FALSE; } else if (verify_check_host(&tls_try_verify_hosts) == OK) { - rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE); + rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE, FALSE); if (rc != OK) return rc; - verify_optional = TRUE; + server_verify_optional = TRUE; } /* Prepare for new connection */ @@ -1280,7 +1328,7 @@ rc = tls_init(&client_ctx, host, dhparam, certificate, privatekey, if (rc != OK) return rc; tls_out.certificate_verified = FALSE; -verify_callback_called = FALSE; +client_verify_callback_called = FALSE; if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers)) return FAIL; @@ -1298,7 +1346,7 @@ if (expciphers != NULL) return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE); +rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, TRUE); if (rc != OK) return rc; if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); @@ -1310,7 +1358,11 @@ if (sni) { if (!expand_check(sni, US"tls_sni", &tls_out.sni)) return FAIL; - if (!Ustrlen(tls_out.sni)) + if (tls_out.sni == NULL) + { + DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); + } + else if (!Ustrlen(tls_out.sni)) tls_out.sni = NULL; else { @@ -1320,7 +1372,7 @@ if (sni) #else DEBUG(D_tls) debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n", - tls_sni); + tls_out.sni); #endif } } @@ -1461,16 +1513,17 @@ Only used by the client-side TLS. */ int -tls_read(uschar *buff, size_t len) +tls_read(BOOL is_server, uschar *buff, size_t len) { +SSL *ssl = is_server ? server_ssl : client_ssl; int inbytes; int error; -DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", client_ssl, +DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, buff, (unsigned int)len); -inbytes = SSL_read(client_ssl, CS buff, len); -error = SSL_get_error(client_ssl, inbytes); +inbytes = SSL_read(ssl, CS buff, len); +error = SSL_get_error(ssl, inbytes); if (error == SSL_ERROR_ZERO_RETURN) { @@ -1569,6 +1622,7 @@ void tls_close(BOOL is_server, BOOL shutdown) { SSL **sslp = is_server ? &server_ssl : &client_ssl; +int *fdp = is_server ? &tls_in.active : &tls_out.active; if (*fdp < 0) return; /* TLS was not active */ @@ -1699,12 +1753,26 @@ vaguely_random_number(int max) { unsigned int r; int i, needed_len; +static pid_t pidlast = 0; +pid_t pidnow; uschar *p; uschar smallbuf[sizeof(r)]; if (max <= 1) return 0; +pidnow = getpid(); +if (pidnow != pidlast) + { + /* Although OpenSSL documents that "OpenSSL makes sure that the PRNG state + is unique for each thread", this doesn't apparently apply across processes, + so our own warning from vaguely_random_number_fallback() applies here too. + Fix per PostgreSQL. */ + if (pidlast != 0) + RAND_cleanup(); + pidlast = pidnow; + } + /* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */ if (!RAND_status()) {