+/* 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;
+ }
+
+/* Not sure how many of these are actually needed, since SSL object
+already exists. Might even need this selfsame callback, for reneg? */
+
+SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx));
+SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx));
+SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx));
+SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
+SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
+SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
+
+if ( !init_dh(server_sni, cbinfo->dhparam, NULL)
+ || !init_ecdh(server_sni, NULL)
+ )
+ return SSL_TLSEXT_ERR_NOACK;
+
+if (cbinfo->server_cipher_list)
+ SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
+#ifndef DISABLE_OCSP
+if (cbinfo->u_ocsp.server.file)
+ {
+ SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo);
+ }
+#endif
+
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, verify_callback_server);
+if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+
+/* do this after setup_certs, because this can require the certs for verifying
+OCSP information. */
+if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK)
+ return SSL_TLSEXT_ERR_NOACK;
+
+DEBUG(D_tls) debug_printf("Switching SSL context.\n");
+SSL_set_SSL_CTX(s, server_sni);
+
+return SSL_TLSEXT_ERR_OK;
+}
+#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
+
+
+
+
+#ifndef DISABLE_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_server_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->u_ocsp.server.response ? "have" : "lack");
+
+tls_in.ocsp = OCSP_NOT_RESP;
+if (!cbinfo->u_ocsp.server.response)
+ return SSL_TLSEXT_ERR_NOACK;
+
+response_der = NULL;
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+ &response_der);
+if (response_der_len <= 0)
+ return SSL_TLSEXT_ERR_NOACK;
+
+SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len);
+tls_in.ocsp = OCSP_VFIED;
+return SSL_TLSEXT_ERR_OK;
+}
+
+
+static void
+time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time)
+{
+BIO_printf(bp, "\t%s: ", str);
+ASN1_GENERALIZEDTIME_print(bp, time);
+BIO_puts(bp, "\n");
+}
+
+static int
+tls_client_stapling_cb(SSL *s, void *arg)
+{
+tls_ext_ctx_cb * cbinfo = arg;
+const unsigned char * p;
+int len;
+OCSP_RESPONSE * rsp;
+OCSP_BASICRESP * bs;
+int i;
+
+DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
+len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+if(!p)
+ {
+ /* Expect this when we requested ocsp but got none */
+ if ( cbinfo->u_ocsp.client.verify_required
+ && log_extra_selector & LX_tls_cipher)
+ log_write(0, LOG_MAIN, "Received TLS status callback, null content");
+ else
+ DEBUG(D_tls) debug_printf(" null\n");
+ return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ }
+
+if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ if (log_extra_selector & LX_tls_cipher)
+ log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
+ else
+ DEBUG(D_tls) debug_printf(" parse error\n");
+ return 0;
+ }
+
+if(!(bs = OCSP_response_get1_basic(rsp)))
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ if (log_extra_selector & LX_tls_cipher)
+ log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
+ else
+ DEBUG(D_tls) debug_printf(" error parsing response\n");
+ OCSP_RESPONSE_free(rsp);
+ return 0;
+ }
+
+/* We'd check the nonce here if we'd put one in the request. */
+/* However that would defeat cacheability on the server so we don't. */
+
+/* This section of code reworked from OpenSSL apps source;
+ The OpenSSL Project retains copyright:
+ Copyright (c) 1999 The OpenSSL Project. All rights reserved.
+*/
+ {
+ BIO * bp = NULL;
+ int status, reason;
+ ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+
+ DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+
+ /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
+
+ /* Use the chain that verified the server cert to verify the stapled info */
+ /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */
+
+ if ((i = OCSP_basic_verify(bs, NULL,
+ cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ if (log_extra_selector & LX_tls_cipher)
+ log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
+ BIO_printf(bp, "OCSP response verify failure\n");
+ ERR_print_errors(bp);
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ goto out;
+ }
+
+ BIO_printf(bp, "OCSP response well-formed and signed OK\n");
+
+ {
+ STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
+ OCSP_SINGLERESP * single;
+
+ if (sk_OCSP_SINGLERESP_num(sresp) != 1)
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ log_write(0, LOG_MAIN, "OCSP stapling "
+ "with multiple responses not handled");
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ goto out;
+ }
+ single = OCSP_resp_get0(bs, 0);
+ status = OCSP_single_get0_status(single, &reason, &rev,
+ &thisupd, &nextupd);
+ }
+
+ DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd);
+ DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd);
+ if (!OCSP_check_validity(thisupd, nextupd,
+ EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ DEBUG(D_tls) ERR_print_errors(bp);
+ log_write(0, LOG_MAIN, "Server OSCP dates invalid");
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ }
+ else
+ {
+ DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n",
+ OCSP_cert_status_str(status));
+ switch(status)
+ {
+ case V_OCSP_CERTSTATUS_GOOD:
+ tls_out.ocsp = OCSP_VFIED;
+ i = 1;
+ break;
+ case V_OCSP_CERTSTATUS_REVOKED:
+ tls_out.ocsp = OCSP_FAILED;
+ log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
+ reason != -1 ? "; reason: " : "",
+ reason != -1 ? OCSP_crl_reason_str(reason) : "");
+ DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ break;
+ default:
+ tls_out.ocsp = OCSP_FAILED;
+ log_write(0, LOG_MAIN,
+ "Server certificate status unknown, in OCSP stapling");
+ i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+ break;
+ }
+ }
+ out:
+ BIO_free(bp);
+ }
+
+OCSP_RESPONSE_free(rsp);
+return i;
+}
+#endif /*!DISABLE_OCSP*/
+
+
+/*************************************************
+* Initialize for TLS *
+*************************************************/
+
+/* Called from both server and client code, to do preliminary initialization
+of the library. We allocate and return a context structure.
+
+Arguments:
+ ctxp returned SSL context
+ host connected host, if client; NULL if server
+ dhparam DH parameter file
+ certificate certificate file
+ privatekey private key
+ ocsp_file file of stapling info (server); flag for require ocsp (client)
+ addr address if client; NULL if server (for some randomness)
+ cbp place to put allocated callback context
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
+ uschar *privatekey,
+#ifndef DISABLE_OCSP
+ uschar *ocsp_file,
+#endif
+ address_item *addr, tls_ext_ctx_cb ** cbp)
+{
+long init_options;
+int rc;
+BOOL okay;
+tls_ext_ctx_cb * cbinfo;
+
+cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
+cbinfo->certificate = certificate;
+cbinfo->privatekey = privatekey;
+#ifndef DISABLE_OCSP
+if ((cbinfo->is_server = host==NULL))
+ {
+ cbinfo->u_ocsp.server.file = ocsp_file;
+ cbinfo->u_ocsp.server.file_expanded = NULL;
+ cbinfo->u_ocsp.server.response = NULL;
+ }
+else
+ cbinfo->u_ocsp.client.verify_store = NULL;
+#endif
+cbinfo->dhparam = dhparam;
+cbinfo->server_cipher_list = NULL;
+cbinfo->host = host;
+#ifdef EXPERIMENTAL_EVENT
+cbinfo->event_action = NULL;
+#endif
+
+SSL_load_error_strings(); /* basic set up */
+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
+
+/* 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. */
+
+*ctxp = SSL_CTX_new((host == NULL)?
+ SSLv23_server_method() : SSLv23_client_method());
+
+if (*ctxp == NULL) return tls_error(US"SSL_CTX_new", host, NULL);
+
+/* It turns out that we need to seed the random number generator this early in
+order to get the full complement of ciphers to work. It took me roughly a day
+of work to discover this by experiment.
+
+On systems that have /dev/urandom, SSL may automatically seed itself from
+there. Otherwise, we have to make something up as best we can. Double check
+afterwards. */
+
+if (!RAND_status())
+ {
+ randstuff r;
+ gettimeofday(&r.tv, NULL);
+ r.p = getpid();
+
+ RAND_seed((uschar *)(&r), sizeof(r));
+ RAND_seed((uschar *)big_buffer, big_buffer_size);
+ if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
+
+ if (!RAND_status())
+ return tls_error(US"RAND_status", host,
+ US"unable to seed random number generator");
+ }
+
+/* Set up the information callback, which outputs if debugging is at a suitable
+level. */
+
+DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
+
+/* Automatically re-try reads/writes after renegotiation. */
+(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
+
+/* Apply administrator-supplied work-arounds.
+Historically we applied just one requested option,
+SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, but when bug 994 requested a second, we
+moved to an administrator-controlled list of options to specify and
+grandfathered in the first one as the default value for "openssl_options".
+
+No OpenSSL version number checks: the options we accept depend upon the
+availability of the option value macros from OpenSSL. */
+
+okay = tls_openssl_options_parse(openssl_options, &init_options);
+if (!okay)
+ return tls_error(US"openssl_options parsing failed", host, NULL);
+
+if (init_options)
+ {
+ DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
+ if (!(SSL_CTX_set_options(*ctxp, init_options)))
+ return tls_error(string_sprintf(
+ "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+ }
+else
+ DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
+
+/* Initialize with DH parameters if supplied */
+/* Initialize ECDH temp key parameter selection */
+
+if ( !init_dh(*ctxp, dhparam, host)
+ || !init_ecdh(*ctxp, host)
+ )
+ return DEFER;
+
+/* Set up certificate and key (and perhaps OCSP info) */
+
+rc = tls_expand_session_files(*ctxp, cbinfo);
+if (rc != OK) return rc;
+
+/* If we need to handle SNI, do so */
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+if (host == NULL) /* server */
+ {
+# ifndef DISABLE_OCSP
+ /* We check u_ocsp.server.file, not server.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->u_ocsp.server.file)
+ {
+ SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_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
+ tls_certificate */
+ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+ }
+# ifndef DISABLE_OCSP
+else /* client */
+ if(ocsp_file) /* wanting stapling */
+ {
+ if (!(cbinfo->u_ocsp.client.verify_store = X509_STORE_new()))
+ {
+ DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
+ return FAIL;
+ }
+ SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+ }
+# endif
+#endif
+
+cbinfo->verify_cert_hostnames = NULL;
+
+/* Set up the RSA callback */
+
+SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
+
+/* Finally, set the timeout, and we are done */
+
+SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
+DEBUG(D_tls) debug_printf("Initialized TLS\n");
+
+*cbp = cbinfo;
+
+return OK;
+}
+
+
+
+
+/*************************************************
+* Get name of cipher in use *
+*************************************************/
+
+/*
+Argument: pointer to an SSL structure for the connection
+ buffer to use for answer
+ size of buffer
+ pointer to number of bits for cipher
+Returns: nothing
+*/
+
+static void
+construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
+{
+/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
+yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
+the accessor functions use const in the prototype. */
+const SSL_CIPHER *c;
+const uschar *ver;
+
+ver = (const uschar *)SSL_get_version(ssl);
+
+c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
+SSL_CIPHER_get_bits(c, bits);
+
+string_format(cipherbuf, bsize, "%s:%s:%u", ver,
+ SSL_CIPHER_get_name(c), *bits);
+
+DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
+}
+
+
+static void
+peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned bsize)
+{
+/*XXX we might consider a list-of-certs variable for the cert chain.
+SSL_get_peer_cert_chain(SSL*). We'd need a new variable type and support
+in list-handling functions, also consider the difference between the entire
+chain and the elements sent by the peer. */
+
+/* Will have already noted peercert on a verify fail; possibly not the leaf */
+if (!tlsp->peercert)
+ tlsp->peercert = SSL_get_peer_certificate(ssl);
+/* Beware anonymous ciphers which lead to server_cert being NULL */
+if (tlsp->peercert)
+ {
+ X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, bsize);
+ peerdn[bsize-1] = '\0';
+ tlsp->peerdn = peerdn; /*XXX a static buffer... */
+ }
+else
+ tlsp->peerdn = NULL;
+}
+
+
+
+
+
+/*************************************************
+* Set up for verifying certificates *
+*************************************************/
+
+/* Called by both client and server startup
+
+Arguments:
+ sctx SSL_CTX* to initialise
+ certs certs file or NULL
+ crl CRL file or NULL
+ 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
+ cert_vfy_cb Callback function for certificate verification
+
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
+{
+uschar *expcerts, *expcrl;
+
+if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
+ return DEFER;
+
+if (expcerts != NULL && *expcerts != '\0')
+ {
+ if (Ustrcmp(expcerts, "system") == 0)
+ {
+ /* Tell the library to use its compiled-in location for the system default
+ CA bundle, only */
+
+ if (!SSL_CTX_set_default_verify_paths(sctx))
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+ }
+ else
+ {
+ struct stat statbuf;
+
+ /* Tell the library to use its compiled-in location for the system default
+ CA bundle. Those given by the exim config are additional to these */
+
+ if (!SSL_CTX_set_default_verify_paths(sctx))
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+
+ if (Ustat(expcerts, &statbuf) < 0)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to stat %s for certificates", expcerts);
+ return DEFER;
+ }
+ else
+ {
+ uschar *file, *dir;
+ if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+ { file = NULL; dir = expcerts; }
+ else
+ { file = expcerts; dir = NULL; }
+
+ /* If a certificate file is empty, the next function fails with an
+ unhelpful error message. If we skip it, we get the correct behaviour (no
+ certificates are recognized, but the error message is still misleading (it
+ says no certificate was supplied.) But this is better. */
+
+ if ((file == NULL || statbuf.st_size > 0) &&
+ !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+
+ /* Load the list of CAs for which we will accept certs, for sending
+ to the client. This is only for the one-file tls_verify_certificates
+ variant.
+ If a list isn't loaded into the server, but
+ some verify locations are set, the server end appears to make
+ a wildcard reqest for client certs.
+ Meanwhile, the client library as deafult behaviour *ignores* the list
+ we send over the wire - see man SSL_CTX_set_client_cert_cb.
+ Because of this, and that the dir variant is likely only used for
+ the public-CA bundle (not for a private CA), not worth fixing.
+ */
+ if (file != NULL)
+ {
+ STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+ DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
+ sk_X509_NAME_num(names));
+ SSL_CTX_set_client_CA_list(sctx, names);
+ }
+ }
+ }