+ tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
+ host, NULL, errstr);
+ 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;
+}
+
+
+
+
+/*************************************************
+* 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
+ errstr error string pointer
+
+Returns: TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
+{
+#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, errstr))
+ return FALSE;
+if (!exp_curve || !*exp_curve)
+ return TRUE;
+
+/* "auto" needs to be handled carefully.
+ * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1
+ * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto
+ * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO)
+ * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection
+ * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b
+ */
+if (Ustrcmp(exp_curve, "auto") == 0)
+ {
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
+ exp_curve = "prime256v1";
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n");
+ SSL_CTX_set_ecdh_auto(sctx, 1);
+ return TRUE;
+# else
+ DEBUG(D_tls) debug_printf(
+ "ECDH OpenSSL 1.1.0+ temp key parameter settings: default selection\n");
+ return TRUE;
+# endif
+#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, errstr);
+ return FALSE;
+ }
+
+if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+ {
+ tls_error(US"Unable to create ec curve", host, NULL, errstr);
+ 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, errstr);
+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 *
+*************************************************/
+/* Called to load the server 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;
+STACK_OF(X509) * sk;
+unsigned long verify_flags;
+int status, reason, i;
+
+cbinfo->u_ocsp.server.file_expanded = string_copy(expanded);
+if (cbinfo->u_ocsp.server.response)
+ {
+ OCSP_RESPONSE_free(cbinfo->u_ocsp.server.response);
+ cbinfo->u_ocsp.server.response = NULL;
+ }
+
+if (!(bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb")))
+ {
+ DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
+ cbinfo->u_ocsp.server.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;
+ }
+
+if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
+ OCSP_response_status_str(status), status);
+ goto bad;
+ }
+
+if (!(basic_response = OCSP_response_get1_basic(resp)))
+ {
+ DEBUG(D_tls)
+ debug_printf("OCSP response parse error: unable to extract basic response.\n");
+ goto bad;
+ }
+
+sk = cbinfo->verify_stack;
+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 */
+
+/* This does a full verify on the OCSP proof before we load it for serving
+up; possibly overkill - just date-checks might be nice enough.
+
+OCSP_basic_verify takes a "store" arg, but does not
+use it for the chain verification, which is all we do
+when OCSP_NOVERIFY is set. The content from the wire
+"basic_response" and a cert-stack "sk" are all that is used.
+
+We have a stack, loaded in setup_certs() if tls_verify_certificates
+was a file (not a directory, or "system"). It is unfortunate we
+cannot used the connection context store, as that would neatly
+handle the "system" case too, but there seems to be no library
+function for getting a stack from a store.
+[ In OpenSSL 1.1 - ? X509_STORE_CTX_get0_chain(ctx) ? ]
+We do not free the stack since it could be needed a second time for
+SNI handling.
+
+Separately we might try to replace using OCSP_basic_verify() - which seems to not
+be a public interface into the OpenSSL library (there's no manual entry) -
+But what with? We also use OCSP_basic_verify in the client stapling callback.
+And there we NEED it; we must verify that status... unless the
+library does it for us anyway? */
+
+if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0)
+ {
+ DEBUG(D_tls)
+ {
+ ERR_error_string(ERR_get_error(), ssl_errstring);
+ debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
+ }
+ goto bad;
+ }
+
+/* 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. */
+
+if (!(single_response = OCSP_resp_get0(basic_response, 0)))
+ {
+ DEBUG(D_tls)
+ debug_printf("Unable to get first response from OCSP basic response.\n");
+ goto bad;
+ }
+
+status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
+if (status != V_OCSP_CERTSTATUS_GOOD)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
+ OCSP_cert_status_str(status), status,
+ OCSP_crl_reason_str(reason), reason);
+ goto bad;
+ }
+
+if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+ {
+ DEBUG(D_tls) debug_printf("OCSP status invalid times.\n");
+ goto bad;
+ }
+
+supply_response:
+ cbinfo->u_ocsp.server.response = resp;
+return;
+
+bad:
+ if (running_in_test_harness)
+ {
+ extern char ** environ;
+ uschar ** p;
+ if (environ) for (p = USS environ; *p != NULL; p++)
+ if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
+ {
+ DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
+ goto supply_response;
+ }
+ }
+return;
+}
+#endif /*!DISABLE_OCSP*/
+
+
+
+
+/* Create and install a selfsigned certificate, for use in server mode */
+
+static int
+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
+{
+X509 * x509 = NULL;
+EVP_PKEY * pkey;
+RSA * rsa;
+X509_NAME * name;
+uschar * where;
+
+where = US"allocating pkey";
+if (!(pkey = EVP_PKEY_new()))
+ goto err;
+
+where = US"allocating cert";
+if (!(x509 = X509_new()))
+ goto err;
+
+where = US"generating pkey";
+ /* deprecated, use RSA_generate_key_ex() */
+if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+ goto err;
+
+where = US"assigning pkey";
+if (!EVP_PKEY_assign_RSA(pkey, rsa))
+ goto err;
+
+X509_set_version(x509, 2); /* N+1 - version 3 */
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+X509_gmtime_adj(X509_get_notBefore(x509), 0);
+X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */
+X509_set_pubkey(x509, pkey);
+
+name = X509_get_subject_name(x509);
+X509_NAME_add_entry_by_txt(name, "C",
+ MBSTRING_ASC, CUS "UK", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "O",
+ MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0);
+X509_NAME_add_entry_by_txt(name, "CN",
+ MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0);
+X509_set_issuer_name(x509, name);
+
+where = US"signing cert";
+if (!X509_sign(x509, pkey, EVP_md5()))
+ goto err;
+
+where = US"installing selfsign cert";
+if (!SSL_CTX_use_certificate(sctx, x509))
+ goto err;
+
+where = US"installing selfsign key";
+if (!SSL_CTX_use_PrivateKey(sctx, pkey))
+ goto err;
+
+return OK;
+
+err:
+ (void) tls_error(where, NULL, NULL, errstr);
+ if (x509) X509_free(x509);
+ if (pkey) EVP_PKEY_free(pkey);
+ return DEFER;
+}
+
+
+
+
+/*************************************************
+* Expand key and cert file specs *
+*************************************************/
+
+/* Called once during tls_init and possibly again during TLS setup, for a
+new context, if Server Name Indication was used and tls_sni was seen in
+the certificate string.
+
+Arguments:
+ sctx the SSL_CTX* to update
+ cbinfo various parts of session state
+ errstr error string pointer
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo,
+ uschar ** errstr)
+{
+uschar *expanded;
+
+if (!cbinfo->certificate)
+ {
+ if (cbinfo->host) /* client */
+ return OK;
+ /* server */
+ if (tls_install_selfsign(sctx, errstr) != OK)
+ return DEFER;
+ }
+else
+ {
+ 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, errstr))
+ return DEFER;
+
+ if (expanded != NULL)
+ {
+ DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
+ if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_certificate_chain_file file=%s", expanded),
+ cbinfo->host, NULL, errstr);
+ }
+
+ if (cbinfo->privatekey != NULL &&
+ !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
+ return DEFER;
+
+ /* If expansion was forced to fail, key_expanded will be NULL. If the result
+ of the expansion is an empty string, ignore it also, and assume the private
+ key is in the same file as the certificate. */
+
+ if (expanded && *expanded)
+ {
+ DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
+ if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL, errstr);
+ }
+ }
+
+#ifndef DISABLE_OCSP
+if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
+ {
+ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
+ return DEFER;
+
+ if (expanded && *expanded)
+ {
+ DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded);
+ if ( cbinfo->u_ocsp.server.file_expanded
+ && (Ustrcmp(expanded, cbinfo->u_ocsp.server.file_expanded) == 0))
+ {
+ DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n");
+ }
+ else
+ ocsp_load_response(sctx, cbinfo, expanded);
+ }
+ }
+#endif
+
+return OK;
+}
+
+
+
+
+/*************************************************
+* Callback to handle SNI *
+*************************************************/
+
+/* Called when acting as server during the TLS session setup if a Server Name
+Indication extension was sent by the client.
+
+API documentation is OpenSSL s_server.c implementation.
+
+Arguments:
+ s SSL* of the current session
+ ad unknown (part of OpenSSL API) (unused)
+ arg Callback of "our" registered data
+
+Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
+*/
+
+#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);
+tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
+int rc;
+int old_pool = store_pool;
+uschar * dummy_errstr;
+
+if (!servername)
+ return SSL_TLSEXT_ERR_OK;
+
+DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername,
+ reexpand_tls_files_for_sni ? "" : " (unused for certificate selection)");
+
+/* Make the extension value available for expansion */
+store_pool = POOL_PERM;
+tls_in.sni = string_copy(US servername);
+store_pool = old_pool;
+
+if (!reexpand_tls_files_for_sni)
+ return SSL_TLSEXT_ERR_OK;
+
+/* Can't find an SSL_CTX_clone() or equivalent, so we do it manually;
+not confident that memcpy wouldn't break some internal reference counting.
+Especially since there's a references struct member, which would be off. */
+
+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);
+ return SSL_TLSEXT_ERR_NOACK;