+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_pkeyfile(sctx, state, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_pkeyfile(sctx, state, expanded, errstr)))
+ return err;
+ }
+
+return OK;
+}
+
+
+
+
+/**************************************************
+* One-time init credentials for server and client *
+**************************************************/
+
+static void
+normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers)
+{
+uschar * s = *ciphers;
+
+if (!s || !Ustrchr(s, '_')) return; /* no change needed */
+
+if (s == pre_expansion_ciphers)
+ s = string_copy(s); /* get writable copy */
+
+for (uschar * t = s; *t; t++) if (*t == '_') *t = '-';
+*ciphers = s;
+}
+
+static int
+server_load_ciphers(SSL_CTX * ctx, exim_openssl_state_st * state,
+ uschar * ciphers, uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("required ciphers: %s\n", ciphers);
+if (!SSL_CTX_set_cipher_list(ctx, CS ciphers))
+ return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
+state->server_cipher_list = ciphers;
+return OK;
+}
+
+
+
+static int
+lib_ctx_new(SSL_CTX ** ctxp, host_item * host, uschar ** errstr)
+{
+SSL_CTX * ctx;
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method())))
+#endif
+ return tls_error(US"SSL_CTX_new", host, NULL, errstr);
+
+/* Set up the information callback, which outputs if debugging is at a suitable
+level. */
+
+DEBUG(D_tls)
+ {
+ SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
+ /* this needs a debug build of OpenSSL */
+ SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace);
+#endif
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+ SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback);
+#endif
+ }
+
+/* Automatically re-try reads/writes after renegotiation. */
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+*ctxp = ctx;
+return OK;
+}
+
+
+static unsigned
+tls_server_creds_init(void)
+{
+SSL_CTX * ctx;
+uschar * dummy_errstr;
+unsigned lifetime = 0;
+
+tls_openssl_init();
+
+state_server.lib_state = null_tls_preload;
+
+if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK)
+ return 0;
+state_server.lib_state.lib_ctx = ctx;
+
+/* Preload DH params and EC curve */
+
+if (opt_unset_or_noexpand(tls_dhparam))
+ {
+ DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n");
+ if (init_dh(ctx, tls_dhparam, &dummy_errstr))
+ state_server.lib_state.dh = TRUE;
+ }
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n");
+if (opt_unset_or_noexpand(tls_eccurve))
+ {
+ DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n");
+ if (init_ecdh(ctx, &dummy_errstr))
+ state_server.lib_state.ecdh = TRUE;
+ }
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n");
+
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+/* If we can, preload the Authorities for checking client certs against.
+Actual choice to do verify is made (tls_{,try_}verify_hosts)
+at TLS conn startup.
+Do this before the server ocsp so that its info can verify the ocsp. */
+
+if ( opt_set_and_noexpand(tls_verify_certificates)
+ && opt_unset_or_noexpand(tls_crl))
+ {
+ /* Watch the default dir also as they are always included */
+
+ if ( tls_set_watch(CUS X509_get_default_cert_file(), FALSE)
+ && tls_set_watch(tls_verify_certificates, FALSE)
+ && tls_set_watch(tls_crl, FALSE))
+ {
+ uschar * v_certs = tls_verify_certificates;
+ DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n");
+
+ if (setup_certs(ctx, &v_certs, tls_crl, NULL, &dummy_errstr) == OK)
+ state_server.lib_state.cabundle = TRUE;
+
+ /* If we can, preload the server-side cert, key and ocsp */
+
+ if ( opt_set_and_noexpand(tls_certificate)
+# ifndef DISABLE_OCSP
+ && opt_unset_or_noexpand(tls_ocsp_file)
+# endif
+ && opt_unset_or_noexpand(tls_privatekey))
+ {
+ /* Set watches on the filenames. The implementation does de-duplication
+ so we can just blindly do them all. */
+
+ if ( tls_set_watch(tls_certificate, TRUE)
+# ifndef DISABLE_OCSP
+ && tls_set_watch(tls_ocsp_file, TRUE)
+# endif
+ && tls_set_watch(tls_privatekey, TRUE))
+ {
+ state_server.certificate = tls_certificate;
+ state_server.privatekey = tls_privatekey;
+#ifndef DISABLE_OCSP
+ state_server.u_ocsp.server.file = tls_ocsp_file;
+# endif
+
+ DEBUG(D_tls) debug_printf("TLS: preloading server certs\n");
+ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK)
+ state_server.lib_state.conn_certs = TRUE;
+ }
+ }
+ else if ( !tls_certificate && !tls_privatekey
+# ifndef DISABLE_OCSP
+ && !tls_ocsp_file
+# endif
+ )
+ { /* Generate & preload a selfsigned cert. No files to watch. */
+ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK)
+ {
+ state_server.lib_state.conn_certs = TRUE;
+ lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */
+ }
+ }
+ else
+ DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
+ }
+ }
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n");
+
+
+#endif /* EXIM_HAVE_INOTIFY */
+
+
+/* If we can, preload the ciphers control string */
+
+if (opt_set_and_noexpand(tls_require_ciphers))
+ {
+ DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n");
+ normalise_ciphers(&tls_require_ciphers, tls_require_ciphers);
+ if (server_load_ciphers(ctx, &state_server, tls_require_ciphers,
+ &dummy_errstr) == OK)
+ state_server.lib_state.pri_string = TRUE;
+ }
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n");
+return lifetime;
+}
+
+
+
+
+/* Preload whatever creds are static, onto a transport. The client can then
+just copy the pointer as it starts up.
+Called from the daemon after a cache-invalidate with watch set; called from
+a queue-run startup with watch clear. */
+
+static void
+tls_client_creds_init(transport_instance * t, BOOL watch)
+{
+smtp_transport_options_block * ob = t->options_block;
+exim_openssl_state_st tpt_dummy_state;
+host_item * dummy_host = (host_item *)1;
+uschar * dummy_errstr;
+SSL_CTX * ctx;
+
+tls_openssl_init();
+
+ob->tls_preload = null_tls_preload;
+if (lib_ctx_new(&ctx, dummy_host, &dummy_errstr) != OK)
+ return;
+ob->tls_preload.lib_ctx = ctx;
+
+tpt_dummy_state.lib_state = ob->tls_preload;
+
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+if ( opt_set_and_noexpand(ob->tls_certificate)
+ && opt_unset_or_noexpand(ob->tls_privatekey))
+ {
+ if ( !watch
+ || ( tls_set_watch(ob->tls_certificate, FALSE)
+ && tls_set_watch(ob->tls_privatekey, FALSE)
+ ) )
+ {
+ uschar * pkey = ob->tls_privatekey;
+
+ DEBUG(D_tls)
+ debug_printf("TLS: preloading client certs for transport '%s'\n",t->name);
+
+ if ( tls_add_certfile(ctx, &tpt_dummy_state, ob->tls_certificate,
+ &dummy_errstr) == 0
+ && tls_add_pkeyfile(ctx, &tpt_dummy_state,
+ pkey ? pkey : ob->tls_certificate,
+ &dummy_errstr) == 0
+ )
+ ob->tls_preload.conn_certs = TRUE;
+ }
+ }
+else
+ DEBUG(D_tls)
+ debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name);
+
+
+if ( opt_set_and_noexpand(ob->tls_verify_certificates)
+ && opt_unset_or_noexpand(ob->tls_crl))
+ {
+ if ( !watch
+ || tls_set_watch(CUS X509_get_default_cert_file(), FALSE)
+ && tls_set_watch(ob->tls_verify_certificates, FALSE)
+ && tls_set_watch(ob->tls_crl, FALSE)
+ )
+ {
+ uschar * v_certs = ob->tls_verify_certificates;
+ DEBUG(D_tls)
+ debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name);
+
+ if (setup_certs(ctx, &v_certs,
+ ob->tls_crl, dummy_host, &dummy_errstr) == OK)
+ ob->tls_preload.cabundle = TRUE;
+ }
+ }
+else
+ DEBUG(D_tls)
+ debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name);
+
+#endif /*EXIM_HAVE_INOTIFY*/
+}
+
+
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+/* Invalidate the creds cached, by dropping the current ones.
+Call when we notice one of the source files has changed. */
+
+static void
+tls_server_creds_invalidate(void)
+{
+SSL_CTX_free(state_server.lib_state.lib_ctx);
+state_server.lib_state = null_tls_preload;
+#ifndef DISABLE_OCSP
+state_server.u_ocsp.server.file_expanded = NULL;
+#endif
+}
+
+
+static void
+tls_client_creds_invalidate(transport_instance * t)
+{
+smtp_transport_options_block * ob = t->options_block;
+SSL_CTX_free(ob->tls_preload.lib_ctx);
+ob->tls_preload = null_tls_preload;
+}
+
+#else
+
+static void
+tls_server_creds_invalidate(void)
+{ return; }
+
+static void
+tls_client_creds_invalidate(transport_instance * t)
+{ return; }
+
+#endif /*EXIM_HAVE_INOTIFY*/
+
+
+/* Extreme debug
+#ifndef DISABLE_OCSP
+void
+x509_store_dump_cert_s_names(X509_STORE * store)
+{
+STACK_OF(X509_OBJECT) * roots= store->objs;
+static uschar name[256];
+
+for (int i= 0; i < sk_X509_OBJECT_num(roots); i++)
+ {
+ X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
+ if(tmp_obj->type == X509_LU_X509)
+ {
+ X509_NAME * sn = X509_get_subject_name(tmp_obj->data.x509);
+ if (X509_NAME_oneline(sn, CS name, sizeof(name)))
+ {
+ name[sizeof(name)-1] = '\0';
+ debug_printf(" %s\n", name);
+ }
+ }
+ }
+}
+#endif
+*/
+
+
+#ifndef DISABLE_TLS_RESUME
+/* Manage the keysets used for encrypting the session tickets, on the server. */
+
+typedef struct { /* Session ticket encryption key */
+ uschar name[16];
+
+ const EVP_CIPHER * aes_cipher;
+ uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ const EVP_MD * hmac_hash;
+# else
+ const uschar * hmac_hashname;
+# endif
+ uschar hmac_key[16];
+ time_t renew;
+ time_t expire;
+} exim_stek;
+
+static exim_stek exim_tk; /* current key */
+static exim_stek exim_tk_old; /* previous key */
+
+static void
+tk_init(void)
+{
+time_t t = time(NULL);
+
+if (exim_tk.name[0])
+ {
+ if (exim_tk.renew >= t) return;
+ exim_tk_old = exim_tk;
+ }
+
+if (f.running_in_test_harness) ssl_session_timeout = 6;
+
+DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating");
+if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return;
+if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return;
+if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
+
+exim_tk.name[0] = 'E';
+exim_tk.aes_cipher = EVP_aes_256_cbc();
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+exim_tk.hmac_hash = EVP_sha256();
+# else
+exim_tk.hmac_hashname = US "sha256";
+# endif
+exim_tk.expire = t + ssl_session_timeout;
+exim_tk.renew = t + ssl_session_timeout/2;
+}
+
+static exim_stek *
+tk_current(void)
+{
+if (!exim_tk.name[0]) return NULL;
+return &exim_tk;
+}
+
+static exim_stek *
+tk_find(const uschar * name)
+{
+return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk
+ : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old
+ : NULL;
+}
+
+
+static int
+tk_hmac_init(
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX * hctx,
+#else
+ EVP_MAC_CTX * hctx,
+#endif
+ exim_stek * key
+ )
+{
+/*XXX will want these dependent on the ssl session strength */
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
+ key->hmac_hash, NULL);
+#else
+ {
+ OSSL_PARAM params[3];
+ uschar * hk = string_copy(key->hmac_hashname); /* need nonconst */
+ params[0] = OSSL_PARAM_construct_octet_string("key", key->hmac_key, sizeof(key->hmac_key));
+ params[1] = OSSL_PARAM_construct_utf8_string("digest", CS hk, 0);
+ params[2] = OSSL_PARAM_construct_end();
+ if (EVP_MAC_CTX_set_params(hctx, params) == 0)
+ {
+ DEBUG(D_tls) debug_printf("EVP_MAC_CTX_set_params: %s\n",
+ ERR_reason_error_string(ERR_get_error()));
+ return 0; /* error in mac initialisation */
+ }
+}
+#endif
+return 1;
+}
+
+/* Callback for session tickets, on server */
+static int
+ticket_key_callback(SSL * ssl, uschar key_name[16],
+ uschar * iv, EVP_CIPHER_CTX * c_ctx,
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX * hctx,
+#else
+ EVP_MAC_CTX * hctx,
+#endif
+ int enc)
+{
+tls_support * tlsp = state_server.tlsp;
+exim_stek * key;
+
+if (enc)
+ {
+ DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n");
+ tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+
+ if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0)
+ return -1; /* insufficient random */
+
+ if (!(key = tk_current())) /* current key doesn't exist or isn't valid */
+ return 0; /* key couldn't be created */
+ memcpy(key_name, key->name, 16);
+ DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL));