#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# define EXIM_HAVE_OCSP_RESP_COUNT
+# define OPENSSL_AUTO_SHA256
#else
# define EXIM_HAVE_EPHEM_RSA_KEX
# define EXIM_HAVE_RAND_PSEUDO
# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
# define EXIM_HAVE_SESSION_TICKET
# define EXIM_HAVE_OPESSL_TRACE
+# define EXIM_HAVE_OPESSL_GET0_SERIAL
+# ifndef DISABLE_OCSP
+# define EXIM_HAVE_OCSP
+# endif
# else
# define EXIM_NEED_OPENSSL_INIT
# endif
# define OPENSSL_HAVE_KEYLOG_CB
# define OPENSSL_HAVE_NUM_TICKETS
# define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# else
+# define OPENSSL_BAD_SRVR_OURCERT
# endif
#endif
==> 1.0.1b <==
Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
+Plus SSL_OP_NO_RENEGOTIATION for 1.1.1
+
+XXX could we autobuild this list, as with predefined-macros?
+Seems just parsing ssl.h for SSL_OP_.* would be enough.
+Also allow a numeric literal?
*/
static exim_openssl_option exim_openssl_options[] = {
/* KEEP SORTED ALPHABETICALLY! */
#ifdef SSL_OP_NO_COMPRESSION
{ US"no_compression", SSL_OP_NO_COMPRESSION },
#endif
+#ifdef SSL_OP_NO_RENEGOTIATION
+ { US"no_renegotiation", SSL_OP_NO_RENEGOTIATION },
+#endif
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
{ US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
#endif
# ifdef EXPERIMENTAL_TLS_RESUME
builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
# endif
+# ifdef SSL_OP_NO_TLSv1_3
+builtin_macro_create(US"_HAVE_TLS1_3");
+# endif
+# ifdef OPENSSL_BAD_SRVR_OURCERT
+builtin_macro_create(US"_TLS_BAD_MULTICERT_IN_OURCERT");
+# endif
+# ifdef EXIM_HAVE_OCSP
+builtin_macro_create(US"_HAVE_TLS_OCSP");
+builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
+# endif
}
#else
static BOOL reexpand_tls_files_for_sni = FALSE;
+typedef struct ocsp_resp {
+ struct ocsp_resp * next;
+ OCSP_RESPONSE * resp;
+} ocsp_resplist;
+
typedef struct tls_ext_ctx_cb {
tls_support * tlsp;
uschar *certificate;
union {
struct {
uschar *file;
- uschar *file_expanded;
- OCSP_RESPONSE *response;
+ const uschar *file_expanded;
+ ocsp_resplist *olist;
} server;
struct {
X509_STORE *verify_store; /* non-null if status requested */
static void
keylog_callback(const SSL *ssl, const char *line)
{
+char * filename;
+FILE * fp;
DEBUG(D_tls) debug_printf("%.200s\n", line);
+if (!(filename = getenv("SSLKEYLOGFILE"))) return;
+if (!(fp = fopen(filename, "a"))) return;
+fprintf(fp, "%s\n", line);
+fclose(fp);
}
#endif
Arguments:
sctx the SSL_CTX* to update
cbinfo various parts of session state
- expanded the filename putatively holding an OCSP response
+ filename the filename putatively holding an OCSP response
+ is_pem file is PEM format; otherwise is DER
*/
static void
-ocsp_load_response(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo, const uschar *expanded)
+ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo,
+ const uschar * filename, BOOL is_pem)
{
BIO * bio;
OCSP_RESPONSE * resp;
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;
- }
+DEBUG(D_tls)
+ debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename);
-if (!(bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb")))
+if (!(bio = BIO_new_file(CS filename, "rb")))
{
DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
- cbinfo->u_ocsp.server.file_expanded);
+ filename);
return;
}
-resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
+if (is_pem)
+ {
+ uschar * data, * freep;
+ char * dummy;
+ long len;
+ if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len))
+ {
+ DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n",
+ filename);
+ return;
+ }
+debug_printf("read pem file\n");
+ freep = data;
+ resp = d2i_OCSP_RESPONSE(NULL, CUSS &data, len);
+ OPENSSL_free(freep);
+ }
+else
+ resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
BIO_free(bio);
+
if (!resp)
{
DEBUG(D_tls) debug_printf("Error reading OCSP response.\n");
goto bad;
}
+#ifdef notdef
+ {
+ BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
+ OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */
+ BIO_free(bp);
+ }
+#endif
+
if (!(basic_response = OCSP_response_get1_basic(resp)))
{
DEBUG(D_tls)
(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. */
+I'm hoping to avoid reworking a bunch more of how we handle state here.
+
+XXX that will change when we add support for (TLS1.3) whole-chain stapling
+*/
if (!(single_response = OCSP_resp_get0(basic_response, 0)))
{
}
supply_response:
- /*XXX stack? (these tag points are for multiple leaf-cert support for ocsp */
- cbinfo->u_ocsp.server.response = resp;
+ /* Add the resp to the list used by tls_server_stapling_cb() */
+ {
+ ocsp_resplist ** op = &cbinfo->u_ocsp.server.olist, * oentry;
+ while (oentry = *op)
+ op = &oentry->next;
+ *op = oentry = store_get(sizeof(ocsp_resplist), FALSE);
+ oentry->next = NULL;
+ oentry->resp = resp;
+ }
return;
bad:
}
return;
}
+
+
+static void
+ocsp_free_response_list(tls_ext_ctx_cb * cbinfo)
+{
+for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist;
+ olist = olist->next)
+ OCSP_RESPONSE_free(olist->resp);
+cbinfo->u_ocsp.server.olist = NULL;
+}
#endif /*!DISABLE_OCSP*/
tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
uschar ** errstr)
{
-DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+DEBUG(D_tls) debug_printf("tls_certificate file '%s'\n", file);
if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
return tls_error(string_sprintf(
"SSL_CTX_use_certificate_chain_file file=%s", file),
tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
uschar ** errstr)
{
-DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+DEBUG(D_tls) debug_printf("tls_privatekey file '%s'\n", file);
if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
return tls_error(string_sprintf(
"SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
*/
static int
-tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo,
+tls_expand_session_files(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo,
uschar ** errstr)
{
-uschar *expanded;
+uschar * expanded;
if (!cbinfo->certificate)
{
{
int err;
- if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
- Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
- Ustrstr(cbinfo->certificate, US"tls_out_sni")
- )
+ if ( !reexpand_tls_files_for_sni
+ && ( 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))
const uschar * file_list = expanded;
int sep = 0;
uschar * file;
+#ifndef DISABLE_OCSP
+ const uschar * olist = cbinfo->u_ocsp.server.file;
+ int osep = 0;
+ uschar * ofile;
+ BOOL fmt_pem = FALSE;
+
+ if (olist)
+ if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr))
+ return DEFER;
+ if (olist && !*olist)
+ olist = NULL;
+
+ if ( cbinfo->u_ocsp.server.file_expanded && olist
+ && (Ustrcmp(olist, cbinfo->u_ocsp.server.file_expanded) == 0))
+ {
+ DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n");
+ olist = NULL;
+ }
+ else
+ {
+ ocsp_free_response_list(cbinfo);
+ cbinfo->u_ocsp.server.file_expanded = olist;
+ }
+#endif
while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ {
if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
return err;
+
+#ifndef DISABLE_OCSP
+ if (olist)
+ if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
+ {
+ if (Ustrncmp(ofile, US"PEM ", 4) == 0)
+ {
+ fmt_pem = TRUE;
+ ofile += 4;
+ }
+ else if (Ustrncmp(ofile, US"DER ", 4) == 0)
+ {
+ fmt_pem = FALSE;
+ ofile += 4;
+ }
+ ocsp_load_response(sctx, cbinfo, ofile, fmt_pem);
+ }
+ else
+ DEBUG(D_tls) debug_printf("ran out of ocsp file list\n");
+#endif
+ }
}
else /* would there ever be a need for multiple client certs? */
if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
return err;
}
-#ifndef DISABLE_OCSP
-if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
- {
- /*XXX stack*/
- 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;
}
static int
tls_server_stapling_cb(SSL *s, void *arg)
{
-const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der; /*XXX blob */
+const tls_ext_ctx_cb * cbinfo = (tls_ext_ctx_cb *) arg;
+ocsp_resplist * olist = cbinfo->u_ocsp.server.olist;
+uschar * response_der; /*XXX blob */
int response_der_len;
-/*XXX stack: use SSL_get_certificate() to see which cert; from that work
-out which ocsp blob to send. Unfortunately, SSL_get_certificate is known
-buggy in current OpenSSL; it returns the last cert loaded always rather than
-the one actually presented. So we can't support a stack of OCSP proofs at
-this time. */
-
DEBUG(D_tls)
- debug_printf("Received TLS status request (OCSP stapling); %s response\n",
- cbinfo->u_ocsp.server.response ? "have" : "lack");
+ debug_printf("Received TLS status request (OCSP stapling); %s response list\n",
+ olist ? "have" : "lack");
tls_in.ocsp = OCSP_NOT_RESP;
-if (!cbinfo->u_ocsp.server.response)
+if (!olist)
return SSL_TLSEXT_ERR_NOACK;
+#ifdef EXIM_HAVE_OPESSL_GET0_SERIAL
+ {
+ const X509 * cert_sent = SSL_get_certificate(s);
+ const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent);
+ const BIGNUM * cert_bn = ASN1_INTEGER_to_BN(cert_serial, NULL);
+ const X509_NAME * cert_issuer = X509_get_issuer_name(cert_sent);
+ uschar * chash;
+ uint chash_len;
+
+ for (; olist; olist = olist->next)
+ {
+ OCSP_BASICRESP * bs = OCSP_response_get1_basic(olist->resp);
+ const OCSP_SINGLERESP * single = OCSP_resp_get0(bs, 0);
+ const OCSP_CERTID * cid = OCSP_SINGLERESP_get0_id(single);
+ ASN1_INTEGER * res_cert_serial;
+ const BIGNUM * resp_bn;
+ ASN1_OCTET_STRING * res_cert_iNameHash;
+
+
+ (void) OCSP_id_get0_info(&res_cert_iNameHash, NULL, NULL, &res_cert_serial,
+ (OCSP_CERTID *) cid);
+ resp_bn = ASN1_INTEGER_to_BN(res_cert_serial, NULL);
+
+ DEBUG(D_tls)
+ {
+ debug_printf("cert serial: %s\n", BN_bn2hex(cert_bn));
+ debug_printf("resp serial: %s\n", BN_bn2hex(resp_bn));
+ }
+
+ if (BN_cmp(cert_bn, resp_bn) == 0)
+ {
+ DEBUG(D_tls) debug_printf("matched serial for ocsp\n");
+
+ /*XXX TODO: check the rest of the list for duplicate matches.
+ If any, need to also check the Issuer Name hash.
+ Without this, we will provide the wrong status in the case of
+ duplicate id. */
+
+ break;
+ }
+ DEBUG(D_tls) debug_printf("not match serial for ocsp\n");
+ }
+ if (!olist)
+ {
+ DEBUG(D_tls) debug_printf("failed to find match for ocsp\n");
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ }
+#else
+if (olist->next)
+ {
+ DEBUG(D_tls) debug_printf("OpenSSL version too early to support multi-leaf OCSP\n");
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+#endif
+
+/*XXX could we do the i2d earlier, rather than during the callback? */
response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/
- &response_der);
+response_der_len = i2d_OCSP_RESPONSE(olist->resp, &response_der);
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
OCSP_BASICRESP * bs;
int i;
-DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
+DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n");
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 && LOGGING(tls_cipher))
- log_write(0, LOG_MAIN, "Received TLS status callback, null content");
+ log_write(0, LOG_MAIN, "Required TLS certificate status not received");
else
DEBUG(D_tls) debug_printf(" null\n");
return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
*/
{
BIO * bp = NULL;
- int status, reason;
- ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+#ifndef EXIM_HAVE_OCSP_RESP_COUNT
+ STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
+#endif
DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
/* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */
if ((i = OCSP_basic_verify(bs, cbinfo->verify_stack,
- cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
- {
- tls_out.ocsp = OCSP_FAILED;
- if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
- "Received TLS cert status response, itself unverifiable: %s",
- ERR_reason_error_string(ERR_peek_error()));
- BIO_printf(bp, "OCSP response verify failure\n");
- ERR_print_errors(bp);
- OCSP_RESPONSE_print(bp, rsp, 0);
- goto failed;
- }
+ cbinfo->u_ocsp.client.verify_store, OCSP_NOEXPLICIT)) <= 0)
+ if (ERR_peek_error())
+ {
+ tls_out.ocsp = OCSP_FAILED;
+ if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
+ "Received TLS cert status response, itself unverifiable: %s",
+ ERR_reason_error_string(ERR_peek_error()));
+ BIO_printf(bp, "OCSP response verify failure\n");
+ ERR_print_errors(bp);
+ OCSP_RESPONSE_print(bp, rsp, 0);
+ goto failed;
+ }
+ else
+ DEBUG(D_tls) debug_printf("no explicit trust for OCSP signing"
+ " in the root CA certificate; ignoring\n");
- BIO_printf(bp, "OCSP response well-formed and signed OK\n");
+ DEBUG(D_tls) debug_printf("OCSP response well-formed and signed OK\n");
/*XXX So we have a good stapled OCSP status. How do we know
it is for the cert of interest? OpenSSL 1.1.0 has a routine
For now, carry on blindly accepting the resp. */
- {
- OCSP_SINGLERESP * single;
-
+ for (int idx =
#ifdef EXIM_HAVE_OCSP_RESP_COUNT
- if (OCSP_resp_count(bs) != 1)
+ OCSP_resp_count(bs) - 1;
#else
- STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
- if (sk_OCSP_SINGLERESP_num(sresp) != 1)
+ sk_OCSP_SINGLERESP_num(sresp) - 1;
#endif
- {
- tls_out.ocsp = OCSP_FAILED;
- log_write(0, LOG_MAIN, "OCSP stapling "
- "with multiple responses not handled");
- goto failed;
- }
- single = OCSP_resp_get0(bs, 0);
+ idx >= 0; idx--)
+ {
+ OCSP_SINGLERESP * single = OCSP_resp_get0(bs, idx);
+ int status, reason;
+ ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd;
+
+ /*XXX so I can see putting a loop in here to handle a rsp with >1 singleresp
+ - but what happens with a GnuTLS-style input?
+
+ we could do with a debug label for each singleresp
+ - it has a certID with a serialNumber, but I see no API to get that
+ */
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");
- }
- else
- {
+ 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");
+ goto failed;
+ }
+
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;
- goto good;
+ continue; /* the idx loop */
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);
break;
default:
- tls_out.ocsp = OCSP_FAILED;
log_write(0, LOG_MAIN,
"Server certificate status unknown, in OCSP stapling");
break;
}
+
+ goto failed;
}
+
+ i = 1;
+ tls_out.ocsp = OCSP_VFIED;
+ goto good;
+
failed:
+ tls_out.ocsp = OCSP_FAILED;
i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
good:
BIO_free(bp);
* Initialize for TLS *
*************************************************/
+static void
+tls_openssl_init(void)
+{
+#ifdef EXIM_NEED_OPENSSL_INIT
+SSL_load_error_strings(); /* basic set up */
+OpenSSL_add_ssl_algorithms();
+#endif
+
+#if defined(EXIM_HAVE_SHA256) && !defined(OPENSSL_AUTO_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
+}
+
+
+
/* Called from both server and client code, to do preliminary initialization
of the library. We allocate and return a context structure.
tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifndef DISABLE_OCSP
- uschar *ocsp_file, /*XXX stack, in server*/
+ uschar *ocsp_file,
#endif
address_item *addr, tls_ext_ctx_cb ** cbp,
tls_support * tlsp,
{
cbinfo->u_ocsp.server.file = ocsp_file;
cbinfo->u_ocsp.server.file_expanded = NULL;
- cbinfo->u_ocsp.server.response = NULL;
+ cbinfo->u_ocsp.server.olist = NULL;
}
else
cbinfo->u_ocsp.client.verify_store = NULL;
cbinfo->event_action = NULL;
#endif
-#ifdef EXIM_NEED_OPENSSL_INIT
-SSL_load_error_strings(); /* basic set up */
-OpenSSL_add_ssl_algorithms();
-#endif
-
-#ifdef EXIM_HAVE_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
+tls_openssl_init();
/* Create a context.
The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant
if (!host) /* server */
{
# ifndef DISABLE_OCSP
- /* We check u_ocsp.server.file, not server.response, because we care about if
+ /* We check u_ocsp.server.file, not server.olist, 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. */
*/
static uschar *
-construct_cipher_name(SSL * ssl, int * bits)
+construct_cipher_name(SSL * ssl, const uschar * ver, int * bits)
{
int pool = store_pool;
/* With OpenSSL 1.0.0a, 'c' 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 uschar * ver = CUS SSL_get_version(ssl);
const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
uschar * s;
}
+static const uschar *
+tlsver_name(SSL * ssl)
+{
+uschar * s, * p;
+int pool = store_pool;
+
+store_pool = POOL_PERM;
+s = string_copy(US SSL_get_version(ssl));
+store_pool = pool;
+if ((p = Ustrchr(s, 'v'))) /* TLSv1.2 -> TLS1.2 */
+ for (;; p++) if (!(*p = p[1])) break;
+return CUS s;
+}
+
+
static void
peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned siz)
{
for resumption next to the TLS session, and used here. */
if (!tlsp->verify_override)
- tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK;
+ tlsp->certificate_verified =
+#ifdef SUPPORT_DANE
+ tlsp->dane_verified ||
+#endif
+ SSL_get_verify_result(ssl) == X509_V_OK;
}
}
/* In the server if we will be offering an OCSP proof, load chain from
file for verifying the OCSP proof at load time. */
+/*XXX Glitch! The file here is tls_verify_certs: the chain for verifying the client cert.
+This is inconsistent with the need to verify the OCSP proof of the server cert.
+*/
+
if ( !host
&& statbuf.st_size > 0
&& server_static_cbinfo->u_ocsp.server.file
rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
#ifndef DISABLE_OCSP
- tls_ocsp_file, /*XXX stack*/
+ tls_ocsp_file,
#endif
NULL, &server_static_cbinfo, &tls_in, errstr);
if (rc != OK) return rc;
if (rc <= 0)
{
- (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
- return FAIL;
+ int error = SSL_get_error(server_ssl, rc);
+ switch(error)
+ {
+ case SSL_ERROR_NONE:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
+ (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+
+ if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
+ SSL_shutdown(server_ssl);
+
+ tls_close(NULL, TLS_NO_SHUTDOWN);
+ return FAIL;
+
+ /* Handle genuine errors */
+ case SSL_ERROR_SSL:
+ {
+ uschar * s = US"SSL_accept";
+ unsigned long e = ERR_peek_error();
+ if (ERR_GET_REASON(e) == SSL_R_WRONG_VERSION_NUMBER)
+ s = string_sprintf("%s (%s)", s, SSL_get_version(server_ssl));
+ (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+ return FAIL;
+ }
+
+ default:
+ DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
+ if (error == SSL_ERROR_SYSCALL)
+ {
+ if (!errno)
+ {
+ *errstr = US"SSL_accept: TCP connection closed by peer";
+ return FAIL;
+ }
+ DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno));
+ }
+ (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+ return FAIL;
+ }
}
DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
}
#endif
-/* TLS has been set up. Adjust the input functions to read via TLS,
-and initialize things. */
+/* TLS has been set up. Record data for the connection,
+adjust the input functions to read via TLS, and initialize things. */
peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
-tls_in.cipher = construct_cipher_name(server_ssl, &tls_in.bits);
+tls_in.ver = tlsver_name(server_ssl);
+tls_in.cipher = construct_cipher_name(server_ssl, tls_in.ver, &tls_in.bits);
tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl);
DEBUG(D_tls)
tls_in.ourcert = crt ? X509_dup(crt) : NULL;
}
+/* Channel-binding info for authenticators
+See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */
+ {
+ uschar c, * s;
+ size_t len = SSL_get_peer_finished(server_ssl, &c, 0);
+ int old_pool = store_pool;
+
+ SSL_get_peer_finished(server_ssl, s = store_get((int)len, FALSE), len);
+ store_pool = POOL_PERM;
+ tls_in.channelbinding = b64encode_taint(CUS s, (int)len, FALSE);
+ store_pool = old_pool;
+ DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding);
+ }
+
/* Only used by the server-side tls (tls_in), including tls_getc.
Client-side (tls_out) reads (seem to?) go via
smtp_read_response()/ip_recv().
peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
-tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits);
+tlsp->ver = tlsver_name(exim_client_ctx->ssl);
+tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, tlsp->ver, &tlsp->bits);
tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl);
/* Record the certificate we presented */
tlsp->ourcert = crt ? X509_dup(crt) : NULL;
}
+/*XXX will this work with continued-TLS? */
+/* Channel-binding info for authenticators */
+ {
+ uschar c, * s;
+ size_t len = SSL_get_finished(exim_client_ctx->ssl, &c, 0);
+ int old_pool = store_pool;
+
+ SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, TRUE), len);
+ store_pool = POOL_PERM;
+ tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE);
+ store_pool = old_pool;
+ DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp);
+ }
+
tlsp->active.sock = cctx->sock;
tlsp->active.tls_ctx = exim_client_ctx;
cctx->tls_ctx = exim_client_ctx;
Returns: the number of bytes after a successful write,
-1 after a failed write
-Used by both server-side and client-side TLS.
+Used by both server-side and client-side TLS. Calling with len zero and more unset
+will flush buffered writes; buff can be null for this case.
*/
int
-tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
+tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more)
{
size_t olen = len;
int outbytes, error;
if ((more || corked))
{
-#ifdef SUPPORT_PIPE_CONNECT
+ if (!len) buff = US &error; /* dummy just so that string_catn is ok */
+
+#ifndef DISABLE_PIPE_CONNECT
int save_pool = store_pool;
store_pool = POOL_PERM;
#endif
corked = string_catn(corked, buff, len);
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
store_pool = save_pool;
#endif
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
switch (error)
{
+ case SSL_ERROR_NONE: /* the usual case */
+ left -= outbytes;
+ buff += outbytes;
+ break;
+
case SSL_ERROR_SSL:
ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
return -1;
- case SSL_ERROR_NONE:
- left -= outbytes;
- buff += outbytes;
- break;
-
case SSL_ERROR_ZERO_RETURN:
log_write(0, LOG_MAIN, "SSL channel closed on write");
return -1;
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 */
-
-#ifdef EXIM_NEED_OPENSSL_INIT
-SSL_load_error_strings();
-OpenSSL_add_ssl_algorithms();
-#endif
-#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
+tls_openssl_init();
if (!(tls_require_ciphers && *tls_require_ciphers))
return NULL;
tls_openssl_options_parse(uschar *option_spec, long *results)
{
long result, item;
-uschar *end;
+uschar * exp, * end;
uschar keep_c;
BOOL adding, item_parsed;
result = SSL_OP_NO_TICKET;
/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
- * from default because it increases BEAST susceptibility. */
+from default because it increases BEAST susceptibility. */
#ifdef SSL_OP_NO_SSLv2
result |= SSL_OP_NO_SSLv2;
#endif
#ifdef SSL_OP_SINGLE_DH_USE
result |= SSL_OP_SINGLE_DH_USE;
#endif
+#ifdef SSL_OP_NO_RENEGOTIATION
+result |= SSL_OP_NO_RENEGOTIATION;
+#endif
if (!option_spec)
{
return TRUE;
}
-for (uschar * s = option_spec; *s; /**/)
+if (!expand_check(option_spec, US"openssl_options", &exp, &end))
+ return FALSE;
+
+for (uschar * s = exp; *s; /**/)
{
while (isspace(*s)) ++s;
if (*s == '\0')
DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
return FALSE;
}
- DEBUG(D_tls) debug_printf("openssl option, %s %8lx: %lx (%s)\n",
+ DEBUG(D_tls) debug_printf("openssl option, %s %08lx: %08lx (%s)\n",
adding ? "adding to " : "removing from", result, item, s);
if (adding)
result |= item;