# 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
to apply.
This list is current as of:
- ==> 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
+ ==> 1.1.1c <==
+
+XXX could we autobuild this list, as with predefined-macros?
+Seems just parsing ssl.h for SSL_OP_.* would be enough (except to exclude DTLS).
+Also allow a numeric literal?
*/
static exim_openssl_option exim_openssl_options[] = {
/* KEEP SORTED ALPHABETICALLY! */
#ifdef SSL_OP_ALL
{ US"all", (long) SSL_OP_ALL },
#endif
+#ifdef SSL_OP_ALLOW_NO_DHE_KEX
+ { US"allow_no_dhe_kex", SSL_OP_ALLOW_NO_DHE_KEX },
+#endif
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
{ US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
#endif
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
{ US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
#endif
+#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
+ { US"cryptopro_tlsext_bug", SSL_OP_CRYPTOPRO_TLSEXT_BUG },
+#endif
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
{ US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
#endif
+#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+ { US"enable_middlebox_compat", SSL_OP_ENABLE_MIDDLEBOX_COMPAT },
+#endif
#ifdef SSL_OP_EPHEMERAL_RSA
{ US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
#endif
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
{ US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
#endif
+#ifdef SSL_OP_NO_ANTI_REPLAY
+ { US"no_anti_replay", SSL_OP_NO_ANTI_REPLAY },
+#endif
#ifdef SSL_OP_NO_COMPRESSION
{ US"no_compression", SSL_OP_NO_COMPRESSION },
#endif
+#ifdef SSL_OP_NO_ENCRYPT_THEN_MAC
+ { US"no_encrypt_then_mac", SSL_OP_NO_ENCRYPT_THEN_MAC },
+#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 SSL_OP_NO_TLSv1_3
{ US"no_tlsv1_3", SSL_OP_NO_TLSv1_3 },
#endif
+#ifdef SSL_OP_PRIORITIZE_CHACHA
+ { US"prioritize_chacha", SSL_OP_PRIORITIZE_CHACHA },
+#endif
#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
{ US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
#endif
#ifdef SSL_OP_TLS_ROLLBACK_BUG
{ US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
#endif
+#ifdef SSL_OP_TLSEXT_PADDING
+ { US"tlsext_padding", SSL_OP_TLSEXT_PADDING },
+#endif
};
#ifndef MACRO_PREDEF
# 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
sctx the SSL_CTX* to update
cbinfo various parts of session state
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 * filename)
+ const uschar * filename, BOOL is_pem)
{
BIO * bio;
OCSP_RESPONSE * resp;
unsigned long verify_flags;
int status, reason, i;
-DEBUG(D_tls) debug_printf("tls_ocsp_file '%s'\n", filename);
+DEBUG(D_tls)
+ debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename);
if (!(bio = BIO_new_file(CS filename, "rb")))
{
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");
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))
#ifndef DISABLE_OCSP
if (olist)
if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
- ocsp_load_response(sctx, cbinfo, ofile);
+ {
+ 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
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);
*/
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;
}
}
/* Handle genuine errors */
case SSL_ERROR_SSL:
- (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+ {
+ 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);
}
#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. */
+#ifdef SSL_get_extms_support
+tls_in.ext_master_secret = SSL_get_extms_support(server_ssl) == 1;
+#endif
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().
tls_client_resume_posthandshake(exim_client_ctx, tlsp);
#endif
+#ifdef SSL_get_extms_support
+tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1;
+#endif
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;
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;