#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. */
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;
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 *
+ #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 *
*************************************************/
*/
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;
"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;
}
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;
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");
+ #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 *
*************************************************/
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;
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;
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;
#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);
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
*/
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;
/* 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;
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)
{
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
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;
/*************************************************
-* Pseudo-random number generation *
+* Random number generation *
*************************************************/
/* Pseudo-random number generation. The result is not expected to be
*/
int
-pseudo_random_number(int max)
+vaguely_random_number(int max)
{
unsigned int r;
int 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)
{