#if GNUTLS_VERSION_NUMBER >= 0x030109
# define SUPPORT_CORK
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x03010a
+# define SUPPORT_GNUTLS_SESS_DESC
+#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030500
+# define SUPPORT_GNUTLS_KEYLOG
+#endif
#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP)
# define SUPPORT_SRV_OCSP_STACK
#endif
# include <gnutls/dane.h>
#endif
+#include "tls-cipher-stdname.c"
+
+
/* GnuTLS 2 vs 3
GnuTLS 3 only:
static const int ssl_session_timeout = 200;
-static const char * const exim_default_gnutls_priority = "NORMAL";
+static const uschar * const exim_default_gnutls_priority = US"NORMAL";
/* Guard library core initialisation */
state the current GnuTLS exim state container
rc the GnuTLS error code, or 0 if it's a local error
when text identifying read or write
- text local error text when ec is 0
+ text local error text when rc is 0
Returns: nothing
*/
uschar * errstr;
if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
- msg = string_sprintf("%s: %s", US gnutls_strerror(rc),
+ msg = string_sprintf("A TLS fatal alert has been received: %s",
US gnutls_alert_get_name(gnutls_alert_get(state->session)));
else
msg = US gnutls_strerror(rc);
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
channel.data = NULL;
channel.size = 0;
-rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel);
-if (rc) {
- DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc));
-} else {
+if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel)))
+ { DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); }
+else
+ {
old_pool = store_pool;
store_pool = POOL_PERM;
- tls_channelbinding_b64 = b64encode(channel.data, (int)channel.size);
+ tls_channelbinding_b64 = b64encode(CUS channel.data, (int)channel.size);
store_pool = old_pool;
DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
-}
+ }
#endif
/* peercert is set in peer_status() */
size_t sz;
const char *errpos;
uschar *p;
-BOOL want_default_priorities;
if (!exim_gnutls_base_init_done)
{
DEBUG(D_tls)
{
gnutls_global_set_log_function(exim_gnutls_logger_cb);
- /* arbitrarily chosen level; bump upto 9 for more */
+ /* arbitrarily chosen level; bump up to 9 for more */
gnutls_global_set_log_level(EXIM_GNUTLS_LIBRARY_LOG_LEVEL);
}
#endif
This was backwards incompatible, but means Exim no longer needs to track
all algorithms and provide string forms for them. */
-want_default_priorities = TRUE;
-
+p = NULL;
if (state->tls_require_ciphers && *state->tls_require_ciphers)
{
if (!expand_check_tlsvar(tls_require_ciphers, errstr))
return DEFER;
if (state->exp_tls_require_ciphers && *state->exp_tls_require_ciphers)
{
- DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n",
- state->exp_tls_require_ciphers);
-
- rc = gnutls_priority_init(&state->priority_cache,
- CS state->exp_tls_require_ciphers, &errpos);
- want_default_priorities = FALSE;
p = state->exp_tls_require_ciphers;
+ DEBUG(D_tls) debug_printf("GnuTLS session cipher/priority \"%s\"\n", p);
}
}
-if (want_default_priorities)
+if (!p)
{
+ p = exim_default_gnutls_priority;
DEBUG(D_tls)
- debug_printf("GnuTLS using default session cipher/priority \"%s\"\n",
- exim_default_gnutls_priority);
- rc = gnutls_priority_init(&state->priority_cache,
- exim_default_gnutls_priority, &errpos);
- p = US exim_default_gnutls_priority;
+ debug_printf("GnuTLS using default session cipher/priority \"%s\"\n", p);
}
+rc = gnutls_priority_init(&state->priority_cache, CCS p, &errpos);
exim_gnutls_err_check(rc, string_sprintf(
"gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"",
* Extract peer information *
*************************************************/
+static const uschar *
+cipher_stdname_kcm(gnutls_kx_algorithm_t kx, gnutls_cipher_algorithm_t cipher,
+ gnutls_mac_algorithm_t mac)
+{
+uschar cs_id[2];
+gnutls_kx_algorithm_t kx_i;
+gnutls_cipher_algorithm_t cipher_i;
+gnutls_mac_algorithm_t mac_i;
+
+for (size_t i = 0;
+ gnutls_cipher_suite_info(i, cs_id, &kx_i, &cipher_i, &mac_i, NULL);
+ i++)
+ if (kx_i == kx && cipher_i == cipher && mac_i == mac)
+ return cipher_stdname(cs_id[0], cs_id[1]);
+return NULL;
+}
+
+
+
/* Called from both server and client code.
Only this is allowed to set state->peerdn and state->have_set_peerdn
and we use that to detect double-calls.
static int
peer_status(exim_gnutls_state_st *state, uschar ** errstr)
{
-uschar cipherbuf[256];
const gnutls_datum_t *cert_list;
int old_pool, rc;
unsigned int cert_list_size = 0;
mac = gnutls_mac_get(state->session);
kx = gnutls_kx_get(state->session);
-string_format(cipherbuf, sizeof(cipherbuf),
- "%s:%s:%d",
- gnutls_protocol_get_name(protocol),
- gnutls_cipher_suite_get_name(kx, cipher, mac),
- (int) gnutls_cipher_get_key_size(cipher) * 8);
-
-/* I don't see a way that spaces could occur, in the current GnuTLS
-code base, but it was a concern in the old code and perhaps older GnuTLS
-releases did return "TLS 1.0"; play it safe, just in case. */
-for (uschar * p = cipherbuf; *p != '\0'; ++p)
- if (isspace(*p))
- *p = '-';
old_pool = store_pool;
-store_pool = POOL_PERM;
-state->ciphersuite = string_copy(cipherbuf);
+ {
+ store_pool = POOL_PERM;
+ state->ciphersuite = string_sprintf("%s:%s:%d",
+ gnutls_protocol_get_name(protocol),
+ gnutls_cipher_suite_get_name(kx, cipher, mac),
+ (int) gnutls_cipher_get_key_size(cipher) * 8);
+
+ /* I don't see a way that spaces could occur, in the current GnuTLS
+ code base, but it was a concern in the old code and perhaps older GnuTLS
+ releases did return "TLS 1.0"; play it safe, just in case. */
+
+ for (uschar * p = state->ciphersuite; *p; p++) if (isspace(*p)) *p = '-';
+ state->tlsp->cipher = state->ciphersuite;
+
+ state->tlsp->cipher_stdname = cipher_stdname_kcm(kx, cipher, mac);
+ }
store_pool = old_pool;
-state->tlsp->cipher = state->ciphersuite;
/* tls_peerdn */
cert_list = gnutls_certificate_get_peers(state->session, &cert_list_size);
-if (cert_list == NULL || cert_list_size == 0)
+if (!cert_list || cert_list_size == 0)
{
DEBUG(D_tls) debug_printf("TLS: no certificate from peer (%p & %d)\n",
cert_list, cert_list_size);
return OK;
}
-ct = gnutls_certificate_type_get(state->session);
-if (ct != GNUTLS_CRT_X509)
+if ((ct = gnutls_certificate_type_get(state->session)) != GNUTLS_CRT_X509)
{
- const uschar *ctn = US gnutls_certificate_type_get_name(ct);
+ const uschar * ctn = US gnutls_certificate_type_get_name(ct);
DEBUG(D_tls)
debug_printf("TLS: peer cert not X.509 but instead \"%s\"\n", ctn);
if (state->verify_requirement >= VERIFY_REQUIRED)
DEBUG(D_tls) debug_printf("TLS: checking peer certificate\n");
*errstr = NULL;
-if ((rc = peer_status(state, errstr)) != OK)
+if ((rc = peer_status(state, errstr)) != OK || !state->peerdn)
{
verify = GNUTLS_CERT_INVALID;
*errstr = US"certificate not supplied";
#endif
+static gstring *
+ddump(gnutls_datum_t * d)
+{
+gstring * g = string_get((d->size+1) * 2);
+uschar * s = d->data;
+for (unsigned i = d->size; i > 0; i--, s++)
+ {
+ g = string_catn(g, US "0123456789abcdef" + (*s >> 4), 1);
+ g = string_catn(g, US "0123456789abcdef" + (*s & 0xf), 1);
+ }
+return g;
+}
/* ------------------------------------------------------------------------ */
/* Exported functions */
return FAIL;
}
-DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
+DEBUG(D_tls)
+ {
+ debug_printf("gnutls_handshake was successful\n");
+#ifdef SUPPORT_GNUTLS_SESS_DESC
+ debug_printf("%s\n", gnutls_session_get_desc(state->session));
+#endif
+#ifdef SUPPORT_GNUTLS_KEYLOG
+ {
+ gnutls_datum_t c, s;
+ gstring * gc, * gs;
+ gnutls_session_get_random(state->session, &c, &s);
+ gnutls_session_get_master_secret(state->session, &s);
+ gc = ddump(&c);
+ gs = ddump(&s);
+ debug_printf("CLIENT_RANDOM %.*s %.*s\n", (int)gc->ptr, gc->s, (int)gs->ptr, gs->s);
+ }
+#endif
+ }
/* Verify after the fact */
/* Called from the smtp transport after STARTTLS has been accepted.
Arguments:
- fd the fd of the connection
- host connected host (for messages and option-tests)
- addr the first address (not used)
- tb transport (always smtp)
- tlsa_dnsa non-NULL, either request or require dane for this host, and
- a TLSA record found. Therefore, dane verify required.
- Which implies cert must be requested and supplied, dane
- verify must pass, and cert verify irrelevant (incl.
- hostnames), and (caller handled) require_tls
- tlsp record details of channel configuration
- errstr error string pointer
-
-Returns: Pointer to TLS session context, or NULL on error
+ cctx connection context
+ conn_args connection details
+ cookie datum for randomness (not used)
+ tlsp record details of channel configuration here; must be non-NULL
+ errstr error string pointer
+
+Returns: TRUE for success with TLS session context set in smtp context,
+ FALSE on error
*/
-void *
-tls_client_start(int fd, host_item *host,
- address_item *addr ARG_UNUSED,
- transport_instance * tb,
-#ifdef SUPPORT_DANE
- dns_answer * tlsa_dnsa,
-#endif
- tls_support * tlsp, uschar ** errstr)
+BOOL
+tls_client_start(client_conn_ctx * cctx, smtp_connect_args * conn_args,
+ void * cookie ARG_UNUSED,
+ tls_support * tlsp, uschar ** errstr)
{
-smtp_transport_options_block *ob = tb
+host_item * host = conn_args->host; /* for msgs and option-tests */
+transport_instance * tb = conn_args->tblock; /* always smtp or NULL */
+smtp_transport_options_block * ob = tb
? (smtp_transport_options_block *)tb->options_block
: &smtp_transport_option_defaults;
int rc;
exim_gnutls_state_st * state = NULL;
-uschar *cipher_list = NULL;
+uschar * cipher_list = NULL;
#ifndef DISABLE_OCSP
BOOL require_ocsp =
: verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
#endif
-DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
+DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", cctx->sock);
#ifdef SUPPORT_DANE
-if (tlsa_dnsa && ob->dane_require_tls_ciphers)
+/* If dane is flagged, have either request or require dane for this host, and
+a TLSA record found. Therefore, dane verify required. Which implies cert must
+be requested and supplied, dane verify must pass, and cert verify irrelevant
+(incl. hostnames), and (caller handled) require_tls */
+
+if (conn_args->dane && ob->dane_require_tls_ciphers)
{
/* not using expand_check_tlsvar because not yet in state */
if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
&cipher_list, errstr))
- return NULL;
+ return FALSE;
cipher_list = cipher_list && *cipher_list
? ob->dane_require_tls_ciphers : ob->tls_require_ciphers;
}
if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
cipher_list, &state, tlsp, errstr) != OK)
- return NULL;
+ return FALSE;
{
int dh_min_bits = ob->tls_dh_min_bits;
the specified host patterns if one of them is defined */
#ifdef SUPPORT_DANE
-if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa))
+if (conn_args->dane && dane_tlsa_load(state, &conn_args->tlsa_dnsa))
{
DEBUG(D_tls)
debug_printf("TLS: server certificate DANE required.\n");
NULL, 0, NULL)) != OK)
{
tls_error(US"cert-status-req", US gnutls_strerror(rc), state->host, errstr);
- return NULL;
+ return FALSE;
}
tlsp->ocsp = OCSP_NOT_RESP;
}
}
#endif
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) fd);
-state->fd_in = fd;
-state->fd_out = fd;
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr_t)(long) cctx->sock);
+state->fd_in = cctx->sock;
+state->fd_out = cctx->sock;
DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
/* There doesn't seem to be a built-in timeout on connection. */
}
else
tls_error(US"gnutls_handshake", US gnutls_strerror(rc), state->host, errstr);
- return NULL;
+ return FALSE;
}
-DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n");
+DEBUG(D_tls)
+ {
+ debug_printf("gnutls_handshake was successful\n");
+#ifdef SUPPORT_GNUTLS_SESS_DESC
+ debug_printf("%s\n", gnutls_session_get_desc(state->session));
+#endif
+#ifdef SUPPORT_GNUTLS_KEYLOG
+ {
+ gnutls_datum_t c, s;
+ gstring * gc, * gs;
+ gnutls_session_get_random(state->session, &c, &s);
+ gnutls_session_get_master_secret(state->session, &s);
+ gc = ddump(&c);
+ gs = ddump(&s);
+ debug_printf("CLIENT_RANDOM %.*s %.*s\n", (int)gc->ptr, gc->s, (int)gs->ptr, gs->s);
+ }
+#endif
+ }
/* Verify late */
if (!verify_certificate(state, errstr))
{
tls_error(US"certificate verification failed", *errstr, state->host, errstr);
- return NULL;
+ return FALSE;
}
#ifndef DISABLE_OCSP
{
tlsp->ocsp = OCSP_FAILED;
tls_error(US"certificate status check failed", NULL, state->host, errstr);
- return NULL;
+ return FALSE;
}
DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
tlsp->ocsp = OCSP_VFIED;
/* Figure out peer DN, and if authenticated, etc. */
if (peer_status(state, errstr) != OK)
- return NULL;
+ return FALSE;
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
extract_exim_vars_from_tls_state(state);
-return state;
+cctx->tls_ctx = state;
+return TRUE;
}
else if (inbytes < 0)
{
- DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+ DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
record_io_error(state, (int) inbytes, US"recv", NULL);
state->xfer_error = TRUE;
return FALSE;
This feeds DKIM and should be used for all message-body reads.
-Arguments: lim Maximum amount to read/bufffer
+Arguments: lim Maximum amount to read/buffer
Returns: the next character or EOF
*/
}
else
{
- DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__);
+ DEBUG(D_tls) debug_printf("%s: err from gnutls_record_recv\n", __FUNCTION__);
record_io_error(state, (int)inbytes, US"recv", NULL);
}