From: Jeremy Harris Date: Tue, 18 Sep 2018 14:05:59 +0000 (+0100) Subject: GnuTLS: simplify cert hostname checking X-Git-Url: https://git.exim.org/users/jgh/exim.git/commitdiff_plain/5fd28bb83f80141b9f7671ed9ae3e1a4263134e3?ds=inline GnuTLS: simplify cert hostname checking --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index a93b39474..8fde6397c 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -27773,7 +27773,7 @@ session with a client, you must set either &%tls_verify_hosts%& or apply to all TLS connections. For any host that matches one of these options, Exim requests a certificate as part of the setup of the TLS session. The contents of the certificate are verified by comparing it with a list of -expected certificates. +expected trust-anchors or certificates. These may be the system default set (depending on library version), an explicit file or, depending on library version, a directory, identified by @@ -27790,6 +27790,9 @@ openssl x509 -hash -noout -in /cert/file .endd where &_/cert/file_& contains a single certificate. +There is no checking of names of the client against the certificate +Subject Name or Subject Alternate Names. + The difference between &%tls_verify_hosts%& and &%tls_try_verify_hosts%& is what happens if the client does not supply a certificate, or if the certificate does not match any of the certificates in the collection named by @@ -27951,6 +27954,11 @@ The &%tls_verify_hosts%& and &%tls_try_verify_hosts%& options restrict certificate verification to the listed servers. Verification either must or need not succeed respectively. +The &%tls_verify_cert_hostnames%& option lists hosts for which additional +checks are made: that the host name (the one in the DNS A record) +is valid for the certificate. +The option defaults to always checking. + The &(smtp)& transport has two OCSP-related options: &%hosts_require_ocsp%&; a host-list for which a Certificate Status is requested and required for the connection to proceed. The default @@ -28256,7 +28264,7 @@ this is appropriate for a single system, using a self-signed certificate. DANE-TA usage is effectively declaring a specific CA to be used; this might be a private CA or a public, well-known one. A private CA at simplest is just a self-signed certificate (with certain -attributes) which is used to sign cerver certificates, but running one securely +attributes) which is used to sign server certificates, but running one securely does require careful arrangement. With DANE-TA, as implemented in Exim and commonly in other MTAs, the server TLS handshake must transmit the entire certificate chain from CA to server-certificate. diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 9fcb50dfe..ff8064bab 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -124,7 +124,7 @@ typedef struct exim_gnutls_state { BOOL peer_dane_verified; BOOL trigger_sni_changes; BOOL have_set_peerdn; - const struct host_item *host; + const struct host_item *host; /* NULL if server */ gnutls_x509_crt_t peercert; uschar *peerdn; uschar *ciphersuite; @@ -1757,23 +1757,23 @@ if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) else { - if (state->exp_tls_verify_cert_hostnames) + /* Client side, check the server's certificate name versus the name on the + A-record for the connection we made. What to do for server side - what name + to use for client? We document that there is no such checking for server + side. */ + + if ( state->exp_tls_verify_cert_hostnames + && !gnutls_x509_crt_check_hostname(state->tlsp->peercert, + CS state->exp_tls_verify_cert_hostnames) + ) { - int sep = 0; - const uschar * list = state->exp_tls_verify_cert_hostnames; - uschar * name; - while ((name = string_nextinlist(&list, &sep, NULL, 0))) - if (gnutls_x509_crt_check_hostname(state->tlsp->peercert, CS name)) - break; - if (!name) - { - DEBUG(D_tls) - debug_printf("TLS certificate verification failed: cert name mismatch\n"); - if (state->verify_requirement >= VERIFY_REQUIRED) - goto badcert; - return TRUE; - } + DEBUG(D_tls) + debug_printf("TLS certificate verification failed: cert name mismatch\n"); + if (state->verify_requirement >= VERIFY_REQUIRED) + goto badcert; + return TRUE; } + state->peer_cert_verified = TRUE; DEBUG(D_tls) debug_printf("TLS certificate verified: peerdn=\"%s\"\n", state->peerdn ? state->peerdn : US""); @@ -2591,6 +2591,7 @@ else if (inbytes == 0) else if (inbytes < 0) { +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; @@ -2718,7 +2719,11 @@ if (inbytes == 0) { DEBUG(D_tls) debug_printf("Got TLS_EOF\n"); } -else record_io_error(state, (int)inbytes, US"recv", NULL); +else +{ +debug_printf("%s: err from gnutls_record_recv(\n", __FUNCTION__); +record_io_error(state, (int)inbytes, US"recv", NULL); +} return -1; } @@ -2765,6 +2770,7 @@ while (left > 0) DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes); if (outbytes < 0) { +debug_printf("%s: err from gnutls_record_send(\n", __FUNCTION__); record_io_error(state, outbytes, US"send", NULL); return -1; } diff --git a/test/confs/2001 b/test/confs/2001 index 9ab4cc384..715da4bf6 100644 --- a/test/confs/2001 +++ b/test/confs/2001 @@ -27,6 +27,9 @@ tls_verify_hosts = * tls_verify_certificates = ${if eq {SERVER}{server}{DIR/aux-fixed/cert2}fail} +# so we can decode in wireshark +tls_require_ciphers = NORMAL:-KX-ALL:+RSA + # ----- Routers ----- begin routers