*/
typedef struct exim_gnutls_state {
- gnutls_session_t session;
+ gnutls_session_t session;
gnutls_certificate_credentials_t x509_cred;
- gnutls_priority_t priority_cache;
+ gnutls_priority_t priority_cache;
enum peer_verify_requirement verify_requirement;
- int fd_in;
- int fd_out;
- BOOL peer_cert_verified;
- BOOL trigger_sni_changes;
- BOOL have_set_peerdn;
+ int fd_in;
+ int fd_out;
+ BOOL peer_cert_verified;
+ BOOL trigger_sni_changes;
+ BOOL have_set_peerdn;
const struct host_item *host;
- uschar *peerdn;
- uschar *ciphersuite;
- uschar *received_sni;
+ gnutls_x509_crt_t peercert;
+ uschar *peerdn;
+ uschar *ciphersuite;
+ uschar *received_sni;
const uschar *tls_certificate;
const uschar *tls_privatekey;
uschar *exp_tls_verify_certificates;
uschar *exp_tls_crl;
uschar *exp_tls_require_ciphers;
+ uschar *exp_tls_ocsp_file;
tls_support *tlsp; /* set in tls_init() */
NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL,
NULL, 0, 0, 0, 0,
};
static int exim_sni_handling_cb(gnutls_session_t session);
+#ifdef EXPERIMENTAL_OCSP
+static int server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+ gnutls_datum_t * ocsp_response);
+#endif
* Set various Exim expansion vars *
*************************************************/
+#define exim_gnutls_cert_err(Label) do { \
+ if (rc != GNUTLS_E_SUCCESS) { \
+ DEBUG(D_tls) debug_printf("TLS: cert problem: %s: %s\n", (Label), gnutls_strerror(rc)); \
+ return rc; } } while (0)
+
+static int
+import_cert(const gnutls_datum * cert, gnutls_x509_crt_t * crtp)
+{
+int rc;
+
+rc = gnutls_x509_crt_init(crtp);
+exim_gnutls_cert_err(US"gnutls_x509_crt_init (crt)");
+
+rc = gnutls_x509_crt_import(*crtp, cert, GNUTLS_X509_FMT_DER);
+exim_gnutls_cert_err(US"failed to import certificate [gnutls_x509_crt_import(cert)]");
+
+return rc;
+}
+
+#undef exim_gnutls_cert_err
+
+
/* We set various Exim global variables from the state, once a session has
been established. With TLS callouts, may need to change this to stack
variables, or just re-call it with the server state after client callout
has finished.
-Make sure anything set here is inset in tls_getc().
+Make sure anything set here is unset in tls_getc().
Sets:
tls_active fd
tls_certificate_verified bool indicator
tls_channelbinding_b64 for some SASL mechanisms
tls_cipher a string
+ tls_peercert pointer to library internal
tls_peerdn a string
tls_sni a (UTF-8) string
+ tls_ourcert pointer to library internal
Argument:
state the relevant exim_gnutls_state_st *
*/
static void
-extract_exim_vars_from_tls_state(exim_gnutls_state_st *state, BOOL is_server)
+extract_exim_vars_from_tls_state(exim_gnutls_state_st * state)
{
gnutls_cipher_algorithm_t cipher;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
int rc;
gnutls_datum_t channel;
#endif
+tls_support * tlsp = state->tlsp;
-state->tlsp->active = state->fd_out;
+tlsp->active = state->fd_out;
cipher = gnutls_cipher_get(state->session);
/* returns size in "bytes" */
-state->tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
+tlsp->bits = gnutls_cipher_get_key_size(cipher) * 8;
-state->tlsp->cipher = state->ciphersuite;
+tlsp->cipher = state->ciphersuite;
DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite);
-state->tlsp->certificate_verified = state->peer_cert_verified;
+tlsp->certificate_verified = state->peer_cert_verified;
/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
}
#endif
-state->tlsp->peerdn = state->peerdn;
-state->tlsp->sni = state->received_sni;
+/* peercert is set in peer_status() */
+tlsp->peerdn = state->peerdn;
+tlsp->sni = state->received_sni;
+
+/* record our certificate */
+ {
+ const gnutls_datum * cert = gnutls_certificate_get_ours(state->session);
+ gnutls_x509_crt_t crt;
+
+ tlsp->ourcert = cert && import_cert(cert, &crt)==0 ? crt : NULL;
+ }
}
&& tls_ocsp_file
)
{
- uschar * expanded;
- int rc;
-
- if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &expanded))
+ if (!expand_check(tls_ocsp_file, US"tls_ocsp_file",
+ &state->exp_tls_ocsp_file))
return DEFER;
- /* Lazy way; would like callback to emit debug on actual response */
+ /* Use the full callback method for stapling just to get observability.
+ More efficient would be to read the file once only, if it never changed
+ (due to SNI). Would need restart on file update, or watch datestamp. */
+
+ gnutls_certificate_set_ocsp_status_request_function(state->x509_cred,
+ server_ocsp_stapling_cb, state->exp_tls_ocsp_file);
- rc = gnutls_certificate_set_ocsp_status_request_file(state->x509_cred,
- expanded, 0);
- exim_gnutls_err_check(US"gnutls_certificate_set_ocsp_status_request_file");
- DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", expanded);
+ DEBUG(D_tls) debug_printf("Set OCSP response file %s\n", &state->exp_tls_ocsp_file);
}
#endif
-
/*************************************************
* Extract peer information *
*************************************************/
if (state->verify_requirement == VERIFY_REQUIRED) { return tls_error((Label), gnutls_strerror(rc), state->host); } \
return OK; } } while (0)
-rc = gnutls_x509_crt_init(&crt);
-exim_gnutls_peer_err(US"gnutls_x509_crt_init (crt)");
+rc = import_cert(&cert_list[0], &crt);
+exim_gnutls_peer_err(US"cert 0");
+
+state->tlsp->peercert = state->peercert = crt;
-rc = gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER);
-exim_gnutls_peer_err(US"failed to import certificate [gnutls_x509_crt_import(cert 0)]");
sz = 0;
rc = gnutls_x509_crt_get_dn(crt, NULL, &sz);
if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER)
dn_buf = store_get_perm(sz);
rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz);
exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]");
+
state->peerdn = dn_buf;
return OK;
+#ifdef EXPERIMENTAL_OCSP
+
+static int
+server_ocsp_stapling_cb(gnutls_session_t session, void * ptr,
+ gnutls_datum_t * ocsp_response)
+{
+int ret;
+
+if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0)
+ {
+ DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n",
+ (char *)ptr);
+ tls_in.ocsp = OCSP_NOT_RESP;
+ return GNUTLS_E_NO_CERTIFICATE_STATUS;
+ }
+
+tls_in.ocsp = OCSP_VFY_NOT_TRIED;
+return 0;
+}
+
+#endif
+
+
+
+
/* ------------------------------------------------------------------------ */
/* Exported functions */
if (!state->tlsp->on_connect)
{
smtp_printf("220 TLS go ahead\r\n");
- fflush(smtp_out); /*XXX JGH */
+ fflush(smtp_out);
}
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the GnuTLS library doesn't. */
gnutls_transport_set_ptr2(state->session,
- (gnutls_transport_ptr)fileno(smtp_in),
- (gnutls_transport_ptr)fileno(smtp_out));
+ (gnutls_transport_ptr)(long) fileno(smtp_in),
+ (gnutls_transport_ptr)(long) fileno(smtp_out));
state->fd_in = fileno(smtp_in);
state->fd_out = fileno(smtp_out);
/* Verify after the fact */
-if (state->verify_requirement != VERIFY_NONE)
+if ( state->verify_requirement != VERIFY_NONE
+ && !verify_certificate(state, &error))
{
- if (!verify_certificate(state, &error))
+ if (state->verify_requirement != VERIFY_OPTIONAL)
{
- if (state->verify_requirement == VERIFY_OPTIONAL)
- {
- DEBUG(D_tls)
- debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
- error);
- }
- else
- {
- tls_error(US"certificate verification failed", error, NULL);
- return FAIL;
- }
+ tls_error(US"certificate verification failed", error, NULL);
+ return FAIL;
}
+ DEBUG(D_tls)
+ debug_printf("TLS: continuing on only because verification was optional, after: %s\n",
+ error);
}
/* Figure out peer DN, and if authenticated, etc. */
/* Sets various Exim expansion variables; always safe within server */
-extract_exim_vars_from_tls_state(state, TRUE);
+extract_exim_vars_from_tls_state(state);
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize appropriately. */
fd the fd of the connection
host connected host (for messages)
addr the first address (not used)
- certificate certificate file
- privatekey private key file
- sni TLS SNI to send to remote host
- verify_certs file for certificate verify
- verify_crl CRL for verify
- require_ciphers list of allowed ciphers or NULL
- hosts_require_ocsp hosts for which to request certificate-status (OCSP)
- dh_min_bits minimum number of bits acceptable in server's DH prime
- timeout startup timeout
- verify_hosts mandatory client verification
- try_verify_hosts optional client verification
+ ob smtp transport options
Returns: OK/DEFER/FAIL (because using common functions),
but for a client, DEFER and FAIL have the same meaning
int
tls_client_start(int fd, host_item *host,
address_item *addr ARG_UNUSED,
- uschar *certificate, uschar *privatekey, uschar *sni,
- uschar *verify_certs, uschar *verify_crl,
- uschar *require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
- uschar *hosts_require_ocsp,
-#endif
- int dh_min_bits, int timeout,
- uschar *verify_hosts, uschar *try_verify_hosts)
+ void *v_ob)
{
+smtp_transport_options_block *ob = v_ob;
int rc;
const char *error;
exim_gnutls_state_st *state = NULL;
#ifdef EXPERIMENTAL_OCSP
-BOOL require_ocsp = verify_check_this_host(&hosts_require_ocsp,
+BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
NULL, host->name, host->address, NULL) == OK;
+BOOL request_ocsp = require_ocsp ? TRUE
+ : verify_check_this_host(&ob->hosts_request_ocsp,
+ NULL, host->name, host->address, NULL) == OK;
#endif
DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd);
-if ((rc = tls_init(host, certificate, privatekey,
- sni, verify_certs, verify_crl, require_ciphers, &state)) != OK)
+if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+ ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
+ ob->tls_require_ciphers, &state)) != OK)
return rc;
-if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
{
- DEBUG(D_tls)
- debug_printf("WARNING: tls_dh_min_bits far too low, clamping %d up to %d\n",
- dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
- dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
- }
+ int dh_min_bits = ob->tls_dh_min_bits;
+ if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS)
+ {
+ DEBUG(D_tls)
+ debug_printf("WARNING: tls_dh_min_bits far too low,"
+ " clamping %d up to %d\n",
+ dh_min_bits, EXIM_CLIENT_DH_MIN_MIN_BITS);
+ dh_min_bits = EXIM_CLIENT_DH_MIN_MIN_BITS;
+ }
-DEBUG(D_tls) debug_printf("Setting D-H prime minimum acceptable bits to %d\n",
- dh_min_bits);
-gnutls_dh_set_prime_bits(state->session, dh_min_bits);
+ DEBUG(D_tls) debug_printf("Setting D-H prime minimum"
+ " acceptable bits to %d\n",
+ dh_min_bits);
+ gnutls_dh_set_prime_bits(state->session, dh_min_bits);
+ }
/* Stick to the old behaviour for compatibility if tls_verify_certificates is
set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only
the specified host patterns if one of them is defined */
if (( state->exp_tls_verify_certificates
- && !verify_hosts
- && !try_verify_hosts
+ && !ob->tls_verify_hosts
+ && !ob->tls_try_verify_hosts
)
||
- verify_check_host(&verify_hosts) == OK
+ verify_check_host(&ob->tls_verify_hosts) == OK
)
{
DEBUG(D_tls) debug_printf("TLS: server certificate verification required.\n");
state->verify_requirement = VERIFY_REQUIRED;
gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE);
}
-else if (verify_check_host(&try_verify_hosts) == OK)
+else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
{
DEBUG(D_tls) debug_printf("TLS: server certificate verification optional.\n");
state->verify_requirement = VERIFY_OPTIONAL;
}
#ifdef EXPERIMENTAL_OCSP /* since GnuTLS 3.1.3 */
-if (require_ocsp &&
- (rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL))
- != OK)
- return tls_error(US"cert-status-req", gnutls_strerror(rc), state->host);
+if (request_ocsp)
+ {
+ DEBUG(D_tls) debug_printf("TLS: will request OCSP stapling\n");
+ if ((rc = gnutls_ocsp_status_request_enable_client(state->session,
+ NULL, 0, NULL)) != OK)
+ return tls_error(US"cert-status-req",
+ gnutls_strerror(rc), state->host);
+ tls_out.ocsp = OCSP_NOT_RESP;
+ }
#endif
-gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)fd);
+gnutls_transport_set_ptr(state->session, (gnutls_transport_ptr)(long) fd);
state->fd_in = fd;
state->fd_out = fd;
+DEBUG(D_tls) debug_printf("about to gnutls_handshake\n");
/* There doesn't seem to be a built-in timeout on connection. */
sigalrm_seen = FALSE;
-alarm(timeout);
+alarm(ob->command_timeout);
do
{
rc = gnutls_handshake(state->session);
&& (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0
)
{
- fprintf(stderr, "%.4096s", printed.data);
+ debug_printf("%.4096s", printed.data);
gnutls_free(printed.data);
}
else
(void) tls_error(US"ocsp decode", gnutls_strerror(rc), state->host);
}
- fprintf(stderr, "%s: checking ocsp\n", __FUNCTION__);
if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0)
+ {
+ tls_out.ocsp = OCSP_FAILED;
return tls_error(US"certificate status check failed", NULL, state->host);
+ }
DEBUG(D_tls) debug_printf("Passed OCSP checking\n");
+ tls_out.ocsp = OCSP_VFIED;
}
#endif
/* Sets various Exim expansion variables; may need to adjust for ACL callouts */
-extract_exim_vars_from_tls_state(state, FALSE);
+extract_exim_vars_from_tls_state(state);
return OK;
}
state->tlsp->active = -1;
state->tlsp->bits = 0;
state->tlsp->certificate_verified = FALSE;
- tls_channelbinding_b64 = NULL; /*XXX JGH */
+ tls_channelbinding_b64 = NULL;
state->tlsp->cipher = NULL;
+ state->tlsp->peercert = NULL;
state->tlsp->peerdn = NULL;
return smtp_getc();