TLS: add variables for the IETF standard name for the connection ciphersuite
[exim.git] / src / src / tls-openssl.c
index 8f4cf4d82fbd937502918de05501b883fbd866c3..ba9e7da11b1e06d738187751ca17bef75cc929dd 100644 (file)
@@ -54,23 +54,24 @@ functions from the OpenSSL library. */
 # define EXIM_HAVE_SHA256
 #endif
 
-/*
- * X509_check_host provides sane certificate hostname checking, but was added
- * to OpenSSL late, after other projects forked off the code-base.  So in
- * addition to guarding against the base version number, beware that LibreSSL
- * does not (at this time) support this function.
- *
- * If LibreSSL gains a different API, perhaps via libtls, then we'll probably
- * opt to disentangle and ask a LibreSSL user to provide glue for a third
- * crypto provider for libtls instead of continuing to tie the OpenSSL glue
- * into even twistier knots.  If LibreSSL gains the same API, we can just
- * change this guard and punt the issue for a while longer.
- */
+/* X509_check_host provides sane certificate hostname checking, but was added
+to OpenSSL late, after other projects forked off the code-base.  So in
+addition to guarding against the base version number, beware that LibreSSL
+does not (at this time) support this function.
+
+If LibreSSL gains a different API, perhaps via libtls, then we'll probably
+opt to disentangle and ask a LibreSSL user to provide glue for a third
+crypto provider for libtls instead of continuing to tie the OpenSSL glue
+into even twistier knots.  If LibreSSL gains the same API, we can just
+change this guard and punt the issue for a while longer. */
+
 #ifndef LIBRESSL_VERSION_NUMBER
 # if OPENSSL_VERSION_NUMBER >= 0x010100000L
 #  define EXIM_HAVE_OPENSSL_CHECKHOST
 #  define EXIM_HAVE_OPENSSL_DH_BITS
 #  define EXIM_HAVE_OPENSSL_TLS_METHOD
+#  define EXIM_HAVE_OPENSSL_KEYLOG
+#  define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
 # else
 #  define EXIM_NEED_OPENSSL_INIT
 # endif
@@ -92,6 +93,14 @@ functions from the OpenSSL library. */
 # endif
 #endif
 
+#ifndef LIBRESSL_VERSION_NUMBER
+# if OPENSSL_VERSION_NUMBER >= 0x010101000L
+#  define OPENSSL_HAVE_KEYLOG_CB
+#  define OPENSSL_HAVE_NUM_TICKETS
+#  define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# endif
+#endif
+
 #if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
 # warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
 # define DISABLE_OCSP
@@ -101,6 +110,13 @@ functions from the OpenSSL library. */
 # include <openssl/x509v3.h>
 #endif
 
+#ifndef EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# ifndef EXIM_HAVE_OPENSSL_CIPHER_GET_ID
+#  define SSL_CIPHER_get_id(c) (c->id)
+# endif
+# include "tls-cipher-stdname.c"
+#endif
+
 /*************************************************
 *        OpenSSL option parse                    *
 *************************************************/
@@ -220,10 +236,9 @@ static int exim_openssl_options_size = nelem(exim_openssl_options);
 void
 options_tls(void)
 {
-struct exim_openssl_option * o;
 uschar buf[64];
 
-for (o = exim_openssl_options;
+for (struct exim_openssl_option * o = exim_openssl_options;
      o < exim_openssl_options + nelem(exim_openssl_options); o++)
   {
   /* Trailing X is workaround for problem with _OPT_OPENSSL_NO_TLSV1
@@ -428,10 +443,9 @@ void
 x509_store_dump_cert_s_names(X509_STORE * store)
 {
 STACK_OF(X509_OBJECT) * roots= store->objs;
-int i;
 static uschar name[256];
 
-for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
+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)
@@ -777,6 +791,14 @@ DEBUG(D_tls)
   }
 }
 
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+static void
+keylog_callback(const SSL *ssl, const char *line)
+{
+DEBUG(D_tls) debug_printf("%.200s\n", line);
+}
+#endif
+
 
 
 /*************************************************
@@ -1141,8 +1163,7 @@ bad:
   if (f.running_in_test_harness)
     {
     extern char ** environ;
-    uschar ** p;
-    if (environ) for (p = USS environ; *p; p++)
+    if (environ) for (uschar ** p = USS environ; *p; p++)
       if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
        {
        DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
@@ -1772,6 +1793,9 @@ if (!RAND_status())
 level. */
 
 DEBUG(D_tls) SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+DEBUG(D_tls) 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);
@@ -1798,6 +1822,10 @@ if (init_options)
 else
   DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
 
+#ifdef OPENSSL_HAVE_NUM_TICKETS
+SSL_CTX_set_num_tickets(ctx, 0);       /* send no TLS1.3 stateful-tickets */
+#endif
+
 /* We'd like to disable session cache unconditionally, but foolish Outlook
 Express clients then give up the first TLS connection and make a second one
 (which works).  Only when there is an IMAP service on the same machine.
@@ -1892,28 +1920,46 @@ return OK;
 
 /*
 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
+Returns:    pointer to allocated string in perm-pool
 */
 
-static void
-construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
+static uschar *
+construct_cipher_name(SSL * ssl, 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;
 
 SSL_CIPHER_get_bits(c, bits);
 
-string_format(cipherbuf, bsize, "%s:%s:%u", ver,
-  SSL_CIPHER_get_name(c), *bits);
+store_pool = POOL_PERM;
+s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits);
+store_pool = pool;
+DEBUG(D_tls) debug_printf("Cipher: %s\n", s);
+return s;
+}
+
+
+/* Get IETF-standard name for ciphersuite.
+Argument:   pointer to an SSL structure for the connection
+Returns:    pointer to string
+*/
 
-DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
+static const uschar *
+cipher_stdname_ssl(SSL * ssl)
+{
+#ifdef EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+return CUS SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl));
+#else
+ushort id = 0xffff & SSL_CIPHER_get_id(SSL_get_current_cipher(ssl));
+return cipher_stdname(id >> 8, id & 0xff);
+#endif
 }
 
 
@@ -2052,14 +2098,13 @@ if (expcerts && *expcerts)
       /* 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 request for client certs.
+      If a list isn't loaded into the server, but some verify locations are set,
+      the server end appears to make a wildcard request for client certs.
       Meanwhile, the client library as default 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.
-      */
+      the public-CA bundle (not for a private CA), not worth fixing.  */
+
       if (file)
        {
        STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
@@ -2161,7 +2206,6 @@ int rc;
 uschar * expciphers;
 tls_ext_ctx_cb * cbinfo;
 static uschar peerdn[256];
-static uschar cipherbuf[256];
 
 /* Check for previous activation */
 
@@ -2289,14 +2333,26 @@ and initialize things. */
 
 peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
 
-construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
-tls_in.cipher = cipherbuf;
+tls_in.cipher = construct_cipher_name(server_ssl, &tls_in.bits);
+tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl);
 
 DEBUG(D_tls)
   {
   uschar buf[2048];
-  if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
+  if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)))
     debug_printf("Shared ciphers: %s\n", buf);
+
+#ifdef EXIM_HAVE_OPENSSL_KEYLOG
+  {
+  BIO * bp = BIO_new(BIO_s_mem());
+  uschar * s;
+  int len;
+  SSL_SESSION_print_keylog(bp, SSL_get_session(server_ssl));
+  len = (int) BIO_get_mem_data(bp, CSS &s);
+  debug_printf("%.*s", len, s);
+  BIO_free(bp);
+  }
+#endif
   }
 
 /* Record the certificate we presented */
@@ -2375,7 +2431,6 @@ return OK;
 static int
 dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa, uschar ** errstr)
 {
-dns_record * rr;
 dns_scan dnss;
 const char * hostnames[2] = { CS host->name, NULL };
 int found = 0;
@@ -2383,8 +2438,7 @@ int found = 0;
 if (DANESSL_init(ssl, NULL, hostnames) != 1)
   return tls_error(US"hostnames load", host, NULL, errstr);
 
-for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
-     rr;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
      rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
     ) if (rr->type == T_TLSA && rr->size > 3)
   {
@@ -2463,7 +2517,6 @@ exim_openssl_client_tls_ctx * exim_client_ctx;
 static uschar peerdn[256];
 uschar * expciphers;
 int rc;
-static uschar cipherbuf[256];
 
 #ifndef DISABLE_OCSP
 BOOL request_ocsp = FALSE;
@@ -2667,12 +2720,26 @@ if (rc <= 0)
   return NULL;
   }
 
-DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
+DEBUG(D_tls)
+  {
+  debug_printf("SSL_connect succeeded\n");
+#ifdef EXIM_HAVE_OPENSSL_KEYLOG
+  {
+  BIO * bp = BIO_new(BIO_s_mem());
+  uschar * s;
+  int len;
+  SSL_SESSION_print_keylog(bp, SSL_get_session(server_ssl));
+  len = (int) BIO_get_mem_data(bp, CSS &s);
+  debug_printf("%.*s", len, s);
+  BIO_free(bp);
+  }
+#endif
+  }
 
 peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
 
-construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits);
-tlsp->cipher = cipherbuf;
+tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits);
+tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl);
 
 /* Record the certificate we presented */
   {
@@ -2907,7 +2974,8 @@ Used by both server-side and client-side TLS.
 int
 tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
 {
-int outbytes, error, left;
+size_t olen = len;
+int outbytes, error;
 SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
 static gstring * corked = NULL;
 
@@ -2918,10 +2986,11 @@ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
 "more" is notified.  This hack is only ok if small amounts are involved AND only
 one stream does it, in one context (i.e. no store reset).  Currently it is used
 for the responses to the received SMTP MAIL , RCPT, DATA sequence, only. */
-/*XXX + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
-a store reset there. */
+/* + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+a store reset there, so use POOL_PERM. */
+/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */
 
-if (!ct_ctx && (more || corked))
+if ((more || corked))
   {
 #ifdef EXPERIMENTAL_PIPE_CONNECT
   int save_pool = store_pool;
@@ -2941,7 +3010,7 @@ if (!ct_ctx && (more || corked))
   corked = NULL;
   }
 
-for (left = len; left > 0;)
+for (int left = len; left > 0;)
   {
   DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
   outbytes = SSL_write(ssl, CS buff, left);
@@ -2974,7 +3043,7 @@ for (left = len; left > 0;)
       return -1;
     }
   }
-return len;
+return olen;
 }
 
 
@@ -3176,7 +3245,6 @@ unsigned int r;
 int i, needed_len;
 static pid_t pidlast = 0;
 pid_t pidnow;
-uschar *p;
 uschar smallbuf[sizeof(r)];
 
 if (max <= 1)
@@ -3234,11 +3302,8 @@ if (i < 0)
   }
 
 r = 0;
-for (p = smallbuf; needed_len; --needed_len, ++p)
-  {
-  r *= 256;
-  r += *p;
-  }
+for (uschar * p = smallbuf; needed_len; --needed_len, ++p)
+  r = 256 * r + *p;
 
 /* We don't particularly care about weighted results; if someone wants
 smooth distribution and cares enough then they should submit a patch then. */
@@ -3305,7 +3370,7 @@ BOOL
 tls_openssl_options_parse(uschar *option_spec, long *results)
 {
 long result, item;
-uschar *s, *end;
+uschar *end;
 uschar keep_c;
 BOOL adding, item_parsed;
 
@@ -3325,7 +3390,7 @@ if (!option_spec)
   return TRUE;
   }
 
-for (s=option_spec; *s != '\0'; /**/)
+for (uschar * s = option_spec; *s != '\0'; /**/)
   {
   while (isspace(*s)) ++s;
   if (*s == '\0')