diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/src/EDITME | 5 | ||||
-rw-r--r-- | src/src/config.h.defaults | 5 | ||||
-rw-r--r-- | src/src/globals.c | 3 | ||||
-rw-r--r-- | src/src/globals.h | 3 | ||||
-rw-r--r-- | src/src/readconf.c | 3 | ||||
-rw-r--r-- | src/src/tls-openssl.c | 259 |
6 files changed, 264 insertions, 14 deletions
diff --git a/src/src/EDITME b/src/src/EDITME index f247f44a9..f4e788ae5 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -439,6 +439,11 @@ EXIM_MONITOR=eximon.bin # CFLAGS += -I/opt/brightmail/bsdk-6.0/include # LDFLAGS += -lxml2_single -lbmiclient_single -L/opt/brightmail/bsdk-6.0/lib +# Uncomment the following line to add OCSP stapling support in TLS, if Exim +# was built using OpenSSL. + +# EXPERIMENTAL_OCSP=yes + ############################################################################### diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index c082b9269..a5e12d2ab 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -158,10 +158,11 @@ it's a default value. */ #define WITH_OLD_CLAMAV_STREAM /* EXPERIMENTAL features */ -#define EXPERIMENTAL_SPF -#define EXPERIMENTAL_SRS #define EXPERIMENTAL_BRIGHTMAIL #define EXPERIMENTAL_DCC +#define EXPERIMENTAL_OCSP +#define EXPERIMENTAL_SPF +#define EXPERIMENTAL_SRS /* Things that are not routinely changed but are nevertheless configurable just in case. */ diff --git a/src/src/globals.c b/src/src/globals.c index af0c14b02..5ea432912 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -112,6 +112,9 @@ uschar *tls_advertise_hosts = NULL; /* This is deliberate */ uschar *tls_certificate = NULL; uschar *tls_crl = NULL; uschar *tls_dhparam = NULL; +#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS) +uschar *tls_ocsp_file = NULL; +#endif BOOL tls_offered = FALSE; uschar *tls_privatekey = NULL; BOOL tls_remember_esmtp = FALSE; diff --git a/src/src/globals.h b/src/src/globals.h index f9540785c..ec19d0a23 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -94,6 +94,9 @@ extern uschar *tls_certificate; /* Certificate file */ extern uschar *tls_channelbinding_b64; /* string of base64 channel binding */ extern uschar *tls_crl; /* CRL File */ extern uschar *tls_dhparam; /* DH param file */ +#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS) +extern uschar *tls_ocsp_file; /* OCSP stapling proof file */ +#endif extern BOOL tls_offered; /* Server offered TLS */ extern uschar *tls_privatekey; /* Private key file */ extern BOOL tls_remember_esmtp; /* For YAEB */ diff --git a/src/src/readconf.c b/src/src/readconf.c index b35811e48..badb6a276 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -415,6 +415,9 @@ static optionlist optionlist_config[] = { { "tls_certificate", opt_stringptr, &tls_certificate }, { "tls_crl", opt_stringptr, &tls_crl }, { "tls_dhparam", opt_stringptr, &tls_dhparam }, +#if defined(EXPERIMENTAL_OCSP) && !defined(USE_GNUTLS) + { "tls_ocsp_file", opt_stringptr, &tls_ocsp_file }, +#endif { "tls_on_connect_ports", opt_stringptr, &tls_on_connect_ports }, { "tls_privatekey", opt_stringptr, &tls_privatekey }, { "tls_remember_esmtp", opt_bool, &tls_remember_esmtp }, diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index e609670ee..9ead7945d 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -20,6 +20,14 @@ functions from the OpenSSL library. */ #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> +#ifdef EXPERIMENTAL_OCSP +#include <openssl/ocsp.h> +#endif + +#ifdef EXPERIMENTAL_OCSP +#define EXIM_OCSP_SKEW_SECONDS (300L) +#define EXIM_OCSP_MAX_AGE (-1L) +#endif /* Structure for collecting random data for seeding. */ @@ -48,6 +56,11 @@ static BOOL reexpand_tls_files_for_sni = FALSE; typedef struct tls_ext_ctx_cb { uschar *certificate; uschar *privatekey; +#ifdef EXPERIMENTAL_OCSP + uschar *ocsp_file; + uschar *ocsp_file_expanded; + OCSP_RESPONSE *ocsp_response; +#endif uschar *dhparam; /* these are cached from first expand */ uschar *server_cipher_list; @@ -63,6 +76,12 @@ tls_ext_ctx_cb *static_cbinfo = NULL; static int setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional); +/* Callbacks */ +static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); +#ifdef EXPERIMENTAL_OCSP +static int tls_stapling_cb(SSL *s, void *arg); +#endif + /************************************************* * Handle TLS error * @@ -298,6 +317,131 @@ return yield; +#ifdef EXPERIMENTAL_OCSP +/************************************************* +* Load OCSP information into state * +*************************************************/ + +/* Called to load the OCSP response from the given file into memory, once +caller has determined this is needed. Checks validity. Debugs a message +if invalid. + +ASSUMES: single response, for single cert. + +Arguments: + sctx the SSL_CTX* to update + cbinfo various parts of session state + expanded the filename putatively holding an OCSP response + +*/ + +static void +ocsp_load_response(SSL_CTX *sctx, + tls_ext_ctx_cb *cbinfo, + const uschar *expanded) +{ +BIO *bio; +OCSP_RESPONSE *resp; +OCSP_BASICRESP *basic_response; +OCSP_SINGLERESP *single_response; +ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; +X509_STORE *store; +unsigned long verify_flags; +int status, reason, i; + +cbinfo->ocsp_file_expanded = string_copy(expanded); +if (cbinfo->ocsp_response) + { + OCSP_RESPONSE_free(cbinfo->ocsp_response); + cbinfo->ocsp_response = NULL; + } + +bio = BIO_new_file(CS cbinfo->ocsp_file_expanded, "rb"); +if (!bio) + { + DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", + cbinfo->ocsp_file_expanded); + return; + } + +resp = d2i_OCSP_RESPONSE_bio(bio, NULL); +BIO_free(bio); +if (!resp) + { + DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); + return; + } + +status = OCSP_response_status(resp); +if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) + { + DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n", + OCSP_response_status_str(status), status); + return; + } + +basic_response = OCSP_response_get1_basic(resp); +if (!basic_response) + { + DEBUG(D_tls) + debug_printf("OCSP response parse error: unable to extract basic response.\n"); + return; + } + +store = SSL_CTX_get_cert_store(sctx); +verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ + +/* May need to expose ability to adjust those flags? +OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT +OCSP_TRUSTOTHER OCSP_NOINTERN */ + +i = OCSP_basic_verify(basic_response, NULL, store, verify_flags); +if (i <= 0) + { + DEBUG(D_tls) { + ERR_error_string(ERR_get_error(), ssl_errstring); + debug_printf("OCSP response verify failure: %s\n", US ssl_errstring); + } + return; + } + +/* Here's the simplifying assumption: there's only one response, for the +one certificate we use, and nothing for anything else in a chain. If this +proves false, we need to extract a cert id from our issued cert +(tls_certificate) and use that for OCSP_resp_find_status() (which finds the +right cert in the stack and then calls OCSP_single_get0_status()). + +I'm hoping to avoid reworking a bunch more of how we handle state here. */ +single_response = OCSP_resp_get0(basic_response, 0); +if (!single_response) + { + DEBUG(D_tls) + debug_printf("Unable to get first response from OCSP basic response.\n"); + return; + } + +status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd); +/* how does this status differ from the one above? */ +if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) + { + DEBUG(D_tls) debug_printf("OCSP response not valid (take 2): %s (%d)\n", + OCSP_response_status_str(status), status); + return; + } + +if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) + { + DEBUG(D_tls) debug_printf("OCSP status invalid times.\n"); + return; + } + +cbinfo->ocsp_response = resp; +} +#endif + + + + /************************************************* * Expand key and cert file specs * *************************************************/ @@ -314,7 +458,7 @@ Returns: OK/DEFER/FAIL */ static int -tls_expand_session_files(SSL_CTX *sctx, const tls_ext_ctx_cb *cbinfo) +tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo) { uschar *expanded; @@ -352,6 +496,27 @@ if (expanded != NULL && *expanded != 0) "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL); } +#ifdef EXPERIMENTAL_OCSP +if (cbinfo->ocsp_file != NULL) + { + if (!expand_check(cbinfo->ocsp_file, US"tls_ocsp_file", &expanded)) + return DEFER; + + if (expanded != NULL && *expanded != 0) + { + DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded); + if (cbinfo->ocsp_file_expanded && + (Ustrcmp(expanded, cbinfo->ocsp_file_expanded) == 0)) + { + DEBUG(D_tls) + debug_printf("tls_ocsp_file value unchanged, using existing values.\n"); + } else { + ocsp_load_response(sctx, cbinfo, expanded); + } + } + } +#endif + return OK; } @@ -376,14 +541,10 @@ Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK} */ static int -tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); -/* pre-declared for SSL_CTX_set_tlsext_servername_callback call within func */ - -static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg) { const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); -const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; int rc; int old_pool = store_pool; @@ -424,11 +585,20 @@ SSL_CTX_set_tlsext_servername_callback(ctx_sni, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(ctx_sni, cbinfo); if (cbinfo->server_cipher_list) SSL_CTX_set_cipher_list(ctx_sni, CS cbinfo->server_cipher_list); +#ifdef EXPERIMENTAL_OCSP +if (cbinfo->ocsp_file) + { + SSL_CTX_set_tlsext_status_cb(ctx_sni, tls_stapling_cb); + SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); + } +#endif -rc = tls_expand_session_files(ctx_sni, cbinfo); +rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +/* do this after setup_certs, because this can require the certs for verifying +OCSP information. */ +rc = tls_expand_session_files(ctx_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); @@ -440,6 +610,45 @@ return SSL_TLSEXT_ERR_OK; +#ifdef EXPERIMENTAL_OCSP +/************************************************* +* Callback to handle OCSP Stapling * +*************************************************/ + +/* Called when acting as server during the TLS session setup if the client +requests OCSP information with a Certificate Status Request. + +Documentation via openssl s_server.c and the Apache patch from the OpenSSL +project. + +*/ + +static int +tls_stapling_cb(SSL *s, void *arg) +{ +const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +uschar *response_der; +int response_der_len; + +DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.\n", + cbinfo->ocsp_response ? "have" : "lack"); +if (!cbinfo->ocsp_response) + return SSL_TLSEXT_ERR_NOACK; + +response_der = NULL; +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); +return SSL_TLSEXT_ERR_OK; +} + +#endif /* EXPERIMENTAL_OCSP */ + + + + /************************************************* * Initialize for TLS * *************************************************/ @@ -459,7 +668,11 @@ Returns: OK/DEFER/FAIL static int tls_init(host_item *host, uschar *dhparam, uschar *certificate, - uschar *privatekey, address_item *addr) + uschar *privatekey, +#ifdef EXPERIMENTAL_OCSP + uschar *ocsp_file, +#endif + address_item *addr) { long init_options; int rc; @@ -469,6 +682,9 @@ tls_ext_ctx_cb *cbinfo; cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); cbinfo->certificate = certificate; cbinfo->privatekey = privatekey; +#ifdef EXPERIMENTAL_OCSP +cbinfo->ocsp_file = ocsp_file; +#endif cbinfo->dhparam = dhparam; cbinfo->host = host; @@ -546,7 +762,7 @@ else if (!init_dh(dhparam, host)) return DEFER; -/* Set up certificate and key */ +/* Set up certificate and key (and perhaps OCSP info) */ rc = tls_expand_session_files(ctx, cbinfo); if (rc != OK) return rc; @@ -555,6 +771,17 @@ if (rc != OK) return rc; #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) if (host == NULL) { +#ifdef EXPERIMENTAL_OCSP + /* We check ocsp_file, not ocsp_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 + 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); + } +#endif /* We always do this, so that $tls_sni is available even if not used in tls_certificate */ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); @@ -811,7 +1038,11 @@ if (tls_active >= 0) /* Initialize the SSL library. If it fails, it will already have logged the error. */ -rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, NULL); +rc = tls_init(NULL, tls_dhparam, tls_certificate, tls_privatekey, +#ifdef EXPERIMENTAL_OCSP + tls_ocsp_file, +#endif + NULL); if (rc != OK) return rc; cbinfo = static_cbinfo; @@ -978,7 +1209,11 @@ uschar *expciphers; X509* server_cert; int rc; -rc = tls_init(host, dhparam, certificate, privatekey, addr); +rc = tls_init(host, dhparam, certificate, privatekey, +#ifdef EXPERIMENTAL_OCSP + NULL, +#endif + addr); if (rc != OK) return rc; tls_certificate_verified = FALSE; |