X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fsrc%2Ftls-openssl.c;h=17cc72133daf5e1afd9f2aed0678c59d37a52eb2;hb=2c9a0e86055f1e86ca5cdde421f5f8c9a48b0194;hp=e609670ee73e57d0ebb311dfdf98178e9d222e75;hpb=3f0945ffae8acee547d11ae53d38fbdf9a2cc81f;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index e609670ee..17cc72133 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 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL @@ -20,6 +20,18 @@ functions from the OpenSSL library. */ #include #include #include +#ifdef EXPERIMENTAL_OCSP +#include +#endif + +#ifdef EXPERIMENTAL_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 +#endif /* Structure for collecting random data for seeding. */ @@ -34,7 +46,9 @@ static BOOL verify_callback_called = FALSE; static const uschar *sid_ctx = US"exim"; static SSL_CTX *ctx = NULL; +#ifdef EXIM_HAVE_OPENSSL_TLSEXT static SSL_CTX *ctx_sni = NULL; +#endif static SSL *ssl = NULL; static char ssl_errstring[256]; @@ -48,6 +62,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 +82,14 @@ tls_ext_ctx_cb *static_cbinfo = NULL; static int setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional); +/* Callbacks */ +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); +#endif +#ifdef EXPERIMENTAL_OCSP +static int tls_stapling_cb(SSL *s, void *arg); +#endif + /************************************************* * Handle TLS error * @@ -248,52 +275,211 @@ 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: - dhparam DH parameter file + dhparam DH parameter file or fixed parameter identity string host connected host, if client; NULL if server Returns: TRUE if OK (nothing to set up, or setup worked) */ static BOOL -init_dh(uschar *dhparam, host_item *host) +init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host) { -BOOL yield = TRUE; BIO *bio; DH *dh; uschar *dhexpanded; +const char *pem; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded)) return FALSE; -if (dhexpanded == NULL) return TRUE; - -if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL) +if (dhexpanded == NULL || *dhexpanded == '\0') { - tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), - host, (uschar *)strerror(errno)); - yield = FALSE; + bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); } -else +else if (dhexpanded[0] == '/') { - if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL) + bio = BIO_new_file(CS dhexpanded, "r"); + if (bio == NULL) { tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), - host, NULL); - yield = FALSE; + host, US strerror(errno)); + return FALSE; } - else + } +else + { + if (Ustrcmp(dhexpanded, "none") == 0) { - SSL_CTX_set_tmp_dh(ctx, dh); - DEBUG(D_tls) - debug_printf("Diffie-Hellman initialized from %s with %d-bit key\n", - dhexpanded, 8*DH_size(dh)); - DH_free(dh); + DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); + return TRUE; + } + + pem = std_dh_prime_named(dhexpanded); + if (!pem) + { + tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), + host, US strerror(errno)); + return FALSE; } + bio = BIO_new_mem_buf(CS pem, -1); + } + +dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); +if (dh == NULL) + { BIO_free(bio); + tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), + host, NULL); + return FALSE; + } + +/* Even if it is larger, we silently return success rather than cause things + * to fail out, so that a too-large DH will not knock out all TLS; it's a + * debatable choice. */ +if ((8*DH_size(dh)) > tls_dh_max_bits) + { + DEBUG(D_tls) + debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d", + 8*DH_size(dh), tls_dh_max_bits); + } +else + { + SSL_CTX_set_tmp_dh(sctx, dh); + DEBUG(D_tls) + debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", + dhexpanded ? dhexpanded : US"default", 8*DH_size(dh)); + } + +DH_free(dh); +BIO_free(bio); + +return TRUE; +} + + + + +#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; } -return yield; +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 @@ -314,7 +500,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 +538,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; } @@ -375,15 +582,12 @@ Arguments: 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 */ - +#ifdef EXIM_HAVE_OPENSSL_TLSEXT 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 +628,23 @@ 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 = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +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(ctx_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = setup_certs(ctx_sni, tls_verify_certificates, tls_crl, NULL, FALSE); +rc = init_dh(ctx_sni, cbinfo->dhparam, NULL); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); @@ -436,6 +652,46 @@ SSL_set_SSL_CTX(s, ctx_sni); return SSL_TLSEXT_ERR_OK; } +#endif /* EXIM_HAVE_OPENSSL_TLSEXT */ + + + + +#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 */ @@ -459,7 +715,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 +729,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; @@ -481,7 +744,13 @@ list of available digests. */ EVP_add_digest(EVP_sha256()); #endif -/* Create a context */ +/* Create a context. +The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant +negotiation in the different methods; as far as I can tell, the only +*_{server,client}_method which allows negotiation is SSLv23, which exists even +when OpenSSL is built without SSLv2 support. +By disabling with openssl_options, we can let admins re-enable with the +existing knob. */ ctx = SSL_CTX_new((host == NULL)? SSLv23_server_method() : SSLv23_client_method()); @@ -544,17 +813,28 @@ else /* Initialize with DH parameters if supplied */ -if (!init_dh(dhparam, host)) return DEFER; +if (!init_dh(ctx, 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; /* If we need to handle SNI, do so */ -#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) +#ifdef EXIM_HAVE_OPENSSL_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); @@ -779,11 +1059,6 @@ a TLS session. Arguments: require_ciphers allowed ciphers - ------------------------------------------------------ - require_mac list of allowed MACs ) Not used - require_kx list of allowed key_exchange methods ) for - require_proto list of allowed protocols ) OpenSSL - ------------------------------------------------------ Returns: OK on success DEFER for errors before the start of the negotiation @@ -792,8 +1067,7 @@ Returns: OK on success */ int -tls_server_start(uschar *require_ciphers, uschar *require_mac, - uschar *require_kx, uschar *require_proto) +tls_server_start(const uschar *require_ciphers) { int rc; uschar *expciphers; @@ -811,7 +1085,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; @@ -819,8 +1097,9 @@ if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers)) return FAIL; /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they -are separated by underscores. So that I can use either form in my tests, and -also for general convenience, we turn underscores into hyphens here. */ +were historically separated by underscores. So that I can use either form in my +tests, and also for general convenience, we turn underscores into hyphens here. +*/ if (expciphers != NULL) { @@ -954,11 +1233,6 @@ Argument: verify_certs file for certificate verify crl file containing CRL require_ciphers list of allowed ciphers - ------------------------------------------------------ - require_mac list of allowed MACs ) Not used - require_kx list of allowed key_exchange methods ) for - require_proto list of allowed protocols ) OpenSSL - ------------------------------------------------------ timeout startup timeout Returns: OK on success @@ -970,15 +1244,18 @@ int tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam, uschar *certificate, uschar *privatekey, uschar *sni, uschar *verify_certs, uschar *crl, - uschar *require_ciphers, uschar *require_mac, uschar *require_kx, - uschar *require_proto, int timeout) + uschar *require_ciphers, int timeout) { static uschar txt[256]; 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; @@ -1012,12 +1289,22 @@ if (sni) { if (!expand_check(sni, US"tls_sni", &tls_sni)) return FAIL; - if (!Ustrlen(tls_sni)) + if (tls_sni == NULL) + { + DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); + } + else if (!Ustrlen(tls_sni)) tls_sni = NULL; else { +#ifdef EXIM_HAVE_OPENSSL_TLSEXT DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni); SSL_set_tlsext_host_name(ssl, tls_sni); +#else + DEBUG(D_tls) + debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n", + tls_sni); +#endif } } @@ -1265,6 +1552,72 @@ tls_active = -1; +/************************************************* +* Let tls_require_ciphers be checked at startup * +*************************************************/ + +/* The tls_require_ciphers option, if set, must be something which the +library can parse. + +Returns: NULL on success, or error message +*/ + +uschar * +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 */ + +SSL_load_error_strings(); +OpenSSL_add_ssl_algorithms(); +#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 + +if (!(tls_require_ciphers && *tls_require_ciphers)) + return NULL; + +if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers)) + return US"failed to expand tls_require_ciphers"; + +if (!(expciphers && *expciphers)) + return NULL; + +/* normalisation ripped from above */ +s = expciphers; +while (*s != 0) { if (*s == '_') *s = '-'; s++; } + +err = NULL; + +ctx = SSL_CTX_new(SSLv23_server_method()); +if (!ctx) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring); + } + +DEBUG(D_tls) + debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers); + +if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) + { + ERR_error_string(ERR_get_error(), ssl_errstring); + err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers); + } + +SSL_CTX_free(ctx); + +return err; +} + + + + /************************************************* * Report the library versions. * *************************************************/ @@ -1292,7 +1645,7 @@ fprintf(f, "Library version: OpenSSL: Compile: %s\n" /************************************************* -* Pseudo-random number generation * +* Random number generation * *************************************************/ /* Pseudo-random number generation. The result is not expected to be @@ -1307,7 +1660,7 @@ Returns a random number in range [0, max-1] */ int -pseudo_random_number(int max) +vaguely_random_number(int max) { unsigned int r; int i, needed_len; @@ -1343,7 +1696,14 @@ if (i < needed_len) needed_len = i; /* We do not care if crypto-strong */ -(void) RAND_pseudo_bytes(smallbuf, needed_len); +i = RAND_pseudo_bytes(smallbuf, needed_len); +if (i < 0) + { + DEBUG(D_all) + debug_printf("OpenSSL RAND_pseudo_bytes() not supported by RAND method, using fallback.\n"); + return vaguely_random_number_fallback(max); + } + r = 0; for (p = smallbuf; needed_len; --needed_len, ++p) { @@ -1520,8 +1880,11 @@ uschar keep_c; BOOL adding, item_parsed; result = 0L; -/* Prior to 4.78 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed +/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed * from default because it increases BEAST susceptibility. */ +#ifdef SSL_OP_NO_SSLv2 +result |= SSL_OP_NO_SSLv2; +#endif if (option_spec == NULL) {