From: Jeremy Harris Date: Sun, 3 Dec 2017 22:40:43 +0000 (+0000) Subject: GnuTLS: multiple server certs, OCSP stapling. Bug 2092 X-Git-Url: https://git.exim.org/users/jgh/exim.git/commitdiff_plain/47195144861c416c402191b697c5d3d489b1dcb2 GnuTLS: multiple server certs, OCSP stapling. Bug 2092 --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 285849122..5778ce6a8 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -17282,8 +17282,9 @@ Certificate Authority. Usable for GnuTLS 3.4.4 or 3.3.17 or OpenSSL 1.1.0 (or later). .new -&*Note*&: There is currently no support for multiple OCSP proofs to match the -multiple certificates facility. +For GnuTLS 3.5.6 or later the expanded value of this option can be a list +of files, to match a list given for the &%tls_certificate%& option. +The ordering of the two lists must match. .wen diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 4261beb76..ddadd3eb6 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -6,6 +6,13 @@ Before a formal release, there may be quite a lot of detail so that people can test from the snapshots or the Git before the documentation is updated. Once the documentation is updated, this file is reduced to a short list. +Version 4.next +-------------- + + 1. Dual-certificate stacks on servers now support OCSP stapling, under GnuTLS + version 3.5.6 or later. + + Version 4.90 ------------ diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 527ad28b2..e69fc8bee 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -63,6 +63,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030109 # define SUPPORT_CORK #endif +#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) +# define SUPPORT_SRV_OCSP_STACK +#endif #ifndef DISABLE_OCSP # include @@ -123,7 +126,6 @@ typedef struct exim_gnutls_state { uschar *exp_tls_verify_certificates; uschar *exp_tls_crl; uschar *exp_tls_require_ciphers; - uschar *exp_tls_ocsp_file; const uschar *exp_tls_verify_cert_hostnames; #ifndef DISABLE_EVENT uschar *event_action; @@ -166,7 +168,6 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { .exp_tls_verify_certificates = NULL, .exp_tls_crl = NULL, .exp_tls_require_ciphers = NULL, - .exp_tls_ocsp_file = NULL, .exp_tls_verify_cert_hostnames = NULL, #ifndef DISABLE_EVENT .event_action = NULL, @@ -238,8 +239,8 @@ before, for now. */ # define EXIM_SERVER_DH_BITS_PRE2_12 1024 #endif -#define exim_gnutls_err_check(Label) do { \ - if (rc != GNUTLS_E_SUCCESS) \ +#define exim_gnutls_err_check(rc, Label) do { \ + if ((rc) != GNUTLS_E_SUCCESS) \ return tls_error((Label), gnutls_strerror(rc), host, errstr); \ } while (0) @@ -507,7 +508,7 @@ host_item *host = NULL; /* dummy for macros */ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n"); rc = gnutls_dh_params_init(&dh_server_params); -exim_gnutls_err_check(US"gnutls_dh_params_init"); +exim_gnutls_err_check(rc, US"gnutls_dh_params_init"); m.data = NULL; m.size = 0; @@ -543,7 +544,7 @@ else if (m.data) { rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); - exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3"); DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n"); return OK; } @@ -626,7 +627,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0) rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); free(m.data); - exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3"); DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename); } @@ -683,7 +684,7 @@ if (rc < 0) debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n", dh_bits_gen); rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen); - exim_gnutls_err_check(US"gnutls_dh_params_generate2"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_generate2"); /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time, and I confirmed that a NULL call to get the size first is how the GnuTLS @@ -694,7 +695,7 @@ if (rc < 0) rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM, m.data, &sz); if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) - exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing"); m.size = sz; if (!(m.data = malloc(m.size))) return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr); @@ -705,7 +706,7 @@ if (rc < 0) if (rc != GNUTLS_E_SUCCESS) { free(m.data); - exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real"); } m.size = sz; /* shrink by 1, probably */ @@ -805,15 +806,24 @@ err: +/* Add certificate and key, from files. + +Return: + Zero or negative: good. Negate value for certificate index if < 0. + Greater than zero: FAIL or DEFER code. +*/ + static int tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, uschar * certfile, uschar * keyfile, uschar ** errstr) { int rc = gnutls_certificate_set_x509_key_file(state->x509_cred, CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM); -exim_gnutls_err_check( - string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile)); -return OK; +if (rc < 0) + return tls_error( + string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile), + gnutls_strerror(rc), host, errstr); +return -rc; } @@ -872,7 +882,11 @@ if (!host) /* server */ } rc = gnutls_certificate_allocate_credentials(&state->x509_cred); -exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials"); +exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials"); + +#ifdef SUPPORT_SRV_OCSP_STACK +gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2); +#endif /* remember: expand_check_tlsvar() is expand_check() but fiddling with state members, assuming consistent naming; and expand_check() returns @@ -927,20 +941,71 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) { const uschar * clist = state->exp_tls_certificate; const uschar * klist = state->exp_tls_privatekey; - int csep = 0, ksep = 0; - uschar * cfile, * kfile; + const uschar * olist; + int csep = 0, ksep = 0, osep = 0, cnt = 0; + uschar * cfile, * kfile, * ofile; + +#ifndef DISABLE_OCSP + if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr)) + return DEFER; + olist = ofile; +#endif while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) + if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) return tls_error(US"cert/key setup: out of keys", NULL, host, errstr); - else if ((rc = tls_add_certfile(state, host, cfile, kfile, errstr))) + else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr))) return rc; else + { + int gnutls_cert_index = -rc; DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile); + + /* Set the OCSP stapling server info */ + +#ifndef DISABLE_OCSP + if (tls_ocsp_file) + if (gnutls_buggy_ocsp) + { + DEBUG(D_tls) + debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); + } + else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) + { + /* 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. */ + +# ifdef SUPPORT_SRV_OCSP_STACK + rc = gnutls_certificate_set_ocsp_status_request_function2( + state->x509_cred, gnutls_cert_index, + server_ocsp_stapling_cb, ofile); + + exim_gnutls_err_check(rc, + US"gnutls_certificate_set_ocsp_status_request_function2"); +# else + if (cnt++ > 0) + { + DEBUG(D_tls) + debug_printf("oops; multiple OCSP files not supported\n"); + break; + } + gnutls_certificate_set_ocsp_status_request_function( + state->x509_cred, server_ocsp_stapling_cb, ofile); +# endif + + DEBUG(D_tls) debug_printf("OCSP response file = %s\n", ofile); + } + else + DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n"); +#endif + } } else { - if ((rc = tls_add_certfile(state, host, + if (0 < (rc = tls_add_certfile(state, host, state->exp_tls_certificate, state->exp_tls_privatekey, errstr))) return rc; DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); @@ -949,36 +1014,6 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) } /* tls_certificate */ -/* Set the OCSP stapling server info */ - -#ifndef DISABLE_OCSP -if ( !host /* server */ - && tls_ocsp_file - ) - { - if (gnutls_buggy_ocsp) - { - DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); - } - else - { - if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", - &state->exp_tls_ocsp_file, errstr)) - return DEFER; - - /* 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); - - DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file); - } - } -#endif - - /* Set the trusted CAs file if one is provided, and then add the CRL if one is provided. Experiment shows that, if the certificate file is empty, an unhelpful error message is provided. However, if we just refrain from setting anything up @@ -1071,7 +1106,7 @@ else if (cert_count < 0) { rc = cert_count; - exim_gnutls_err_check(US"setting certificate trust"); + exim_gnutls_err_check(rc, US"setting certificate trust"); } DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count); @@ -1084,7 +1119,7 @@ if (state->tls_crl && *state->tls_crl && if (cert_count < 0) { rc = cert_count; - exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file"); + exim_gnutls_err_check(rc, US"gnutls_certificate_set_x509_crl_file"); } DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count); } @@ -1135,7 +1170,7 @@ if (!state->host) /* Link the credentials to the session. */ rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred); -exim_gnutls_err_check(US"gnutls_credentials_set"); +exim_gnutls_err_check(rc, US"gnutls_credentials_set"); return OK; } @@ -1225,12 +1260,12 @@ if (!exim_gnutls_base_init_done) if (!gnutls_allow_auto_pkcs11) { rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); - exim_gnutls_err_check(US"gnutls_pkcs11_init"); + exim_gnutls_err_check(rc, US"gnutls_pkcs11_init"); } #endif rc = gnutls_global_init(); - exim_gnutls_err_check(US"gnutls_global_init"); + exim_gnutls_err_check(rc, US"gnutls_global_init"); #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 DEBUG(D_tls) @@ -1265,7 +1300,7 @@ else DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n"); rc = gnutls_init(&state->session, GNUTLS_SERVER); } -exim_gnutls_err_check(US"gnutls_init"); +exim_gnutls_err_check(rc, US"gnutls_init"); state->host = host; @@ -1300,7 +1335,7 @@ if (host) sz = Ustrlen(state->tlsp->sni); rc = gnutls_server_name_set(state->session, GNUTLS_NAME_DNS, state->tlsp->sni, sz); - exim_gnutls_err_check(US"gnutls_server_name_set"); + exim_gnutls_err_check(rc, US"gnutls_server_name_set"); } } else if (state->tls_sni) @@ -1340,12 +1375,12 @@ if (want_default_priorities) p = US exim_default_gnutls_priority; } -exim_gnutls_err_check(string_sprintf( +exim_gnutls_err_check(rc, string_sprintf( "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"", p, errpos - CS p, errpos)); rc = gnutls_priority_set(state->session, state->priority_cache); -exim_gnutls_err_check(US"gnutls_priority_set"); +exim_gnutls_err_check(rc, US"gnutls_priority_set"); gnutls_db_set_cache_expiration(state->session, ssl_session_timeout); @@ -1705,6 +1740,7 @@ server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, gnutls_datum_t * ocsp_response) { int ret; +DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr); if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0) { diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 9816f734f..7c7362bc7 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -933,7 +933,7 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX } supply_response: - cbinfo->u_ocsp.server.response = resp; + cbinfo->u_ocsp.server.response = resp; /*XXX stack?*/ return; bad: @@ -941,7 +941,7 @@ bad: { extern char ** environ; uschar ** p; - if (environ) for (p = USS environ; *p != NULL; p++) + if (environ) for (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"); @@ -1130,6 +1130,7 @@ else #ifndef DISABLE_OCSP if (cbinfo->is_server && cbinfo->u_ocsp.server.file) { + /*XXX stack*/ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr)) return DEFER; @@ -1267,9 +1268,15 @@ static int tls_server_stapling_cb(SSL *s, void *arg) { const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; -uschar *response_der; +uschar *response_der; /*XXX blob */ int response_der_len; +/*XXX stack: use SSL_get_certificate() to see which cert; from that work +out which ocsp blob to send. Unfortunately, SSL_get_certificate is known +buggy in current OpenSSL; it returns the last cert loaded always rather than +the one actually presented. So we can't support a stack of OCSP proofs at +this time. */ + DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response\n", cbinfo->u_ocsp.server.response ? "have" : "lack"); @@ -1279,7 +1286,7 @@ if (!cbinfo->u_ocsp.server.response) return SSL_TLSEXT_ERR_NOACK; response_der = NULL; -response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, +response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/ &response_der); if (response_der_len <= 0) return SSL_TLSEXT_ERR_NOACK; @@ -1471,7 +1478,7 @@ static int tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey, #ifndef DISABLE_OCSP - uschar *ocsp_file, + uschar *ocsp_file, /*XXX stack, in server*/ #endif address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr) { @@ -1941,7 +1948,7 @@ the error. */ rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, #ifndef DISABLE_OCSP - tls_ocsp_file, + tls_ocsp_file, /*XXX stack*/ #endif NULL, &server_static_cbinfo, errstr); if (rc != OK) return rc; diff --git a/test/confs/5652 b/test/confs/5652 new file mode 100644 index 000000000..7dce363c2 --- /dev/null +++ b/test/confs/5652 @@ -0,0 +1,82 @@ +# Exim test configuration 5652 +# OCSP stapling, server, multiple certs + +.include DIR/aux-var/tls_conf_prefix + +primary_hostname = server1.example.com + +# ----- Main settings ----- + +acl_smtp_mail = check_mail +acl_smtp_rcpt = check_recipient + +log_selector = +tls_peerdn + +queue_only +queue_run_in_order + +tls_advertise_hosts = * + +CADIR = DIR/aux-fixed/exim-ca +DRSA = CADIR/example.com +DECDSA = CADIR/example_ec.com + +tls_certificate = DRSA/server1.example.com/server1.example.com.pem \ + : DECDSA/server1.example_ec.com/server1.example_ec.com.pem +tls_privatekey = DRSA/server1.example.com/server1.example.com.unlocked.key \ + : DECDSA/server1.example_ec.com/server1.example_ec.com.unlocked.key +tls_ocsp_file = DRSA/server1.example.com/server1.example.com.ocsp.good.resp \ + : DECDSA/server1.example_ec.com/server1.example_ec.com.ocsp.good.resp + + + +# ------ ACL ------ + +begin acl + +check_mail: + accept logwrite = acl_mail: ocsp in status: $tls_in_ocsp \ + (${listextract {${eval:$tls_in_ocsp+1}} \ + {notreq:notresp:vfynotdone:failed:verified}}) + +check_recipient: + accept + + +# ----- Routers ----- + +begin routers + +client: + driver = manualroute + condition = ${if !eq {SERVER}{server}} + route_list = * 127.0.0.1 + self = send + transport = remote_delivery + errors_to = "" + +srvr: + driver = accept + retry_use_local_part + transport = local_delivery + + +# ----- Transports ----- + +begin transports + +remote_delivery: + driver = smtp + port = PORT_D + hosts_require_tls = * + tls_require_ciphers = OPT + hosts_require_ocsp = * + tls_verify_certificates = CERT + +local_delivery: + driver = appendfile + file = DIR/test-mail/$local_part + headers_add = TLS: cipher=$tls_cipher peerdn=$tls_peerdn + user = CALLER + +# End diff --git a/test/log/5652 b/test/log/5652 new file mode 100644 index 000000000..94946ea61 --- /dev/null +++ b/test/log/5652 @@ -0,0 +1,16 @@ +1999-03-02 09:44:33 1: Server sends good staple on request, to client requiring RSA auth +1999-03-02 09:44:33 10HmaX-0005vi-00 <= CALLER@server1.example.com U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaX-0005vi-00 => rsa.auth@test.ex R=client T=remote_delivery H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no DN="CN=server1.example.com" C="250 OK id=10HmaY-0005vi-00" +1999-03-02 09:44:33 10HmaX-0005vi-00 Completed +1999-03-02 09:44:33 2: Server sends good staple on request, to client preferring ECDSA auth +1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@server1.example.com U=CALLER P=local S=sss +1999-03-02 09:44:33 10HmaZ-0005vi-00 => ecdsa.auth@test.ex R=client T=remote_delivery H=127.0.0.1 [127.0.0.1] X=TLS1.x:ke_ECDSA_AES_256_CBC_SHAnnn:256 CV=no DN="CN=server1.example_ec.com" C="250 OK id=10HmbA-0005vi-00" +1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed + +******** SERVER ******** +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 acl_mail: ocsp in status: 2 (vfynotdone) +1999-03-02 09:44:33 10HmaY-0005vi-00 <= <> H=localhost (server1.example.com) [127.0.0.1] P=esmtps X=TLS1.x:ke_RSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmaX-0005vi-00@server1.example.com +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +1999-03-02 09:44:33 acl_mail: ocsp in status: 2 (vfynotdone) +1999-03-02 09:44:33 10HmbA-0005vi-00 <= <> H=localhost (server1.example.com) [127.0.0.1] P=esmtps X=TLS1.x:ke_ECDSA_AES_256_CBC_SHAnnn:256 CV=no S=sss id=E10HmaZ-0005vi-00@server1.example.com diff --git a/test/scripts/5650-OCSP-GnuTLS/5652 b/test/scripts/5650-OCSP-GnuTLS/5652 new file mode 100644 index 000000000..4a33ea862 --- /dev/null +++ b/test/scripts/5650-OCSP-GnuTLS/5652 @@ -0,0 +1,37 @@ +# OCSP stapling, server, multiple certs +# +# +# +exim -z '1: Server sends good staple on request, to client requiring RSA auth' +**** +# +exim -bd -oX PORT_D -DSERVER=server +**** +exim -odf \ + -DOPT=NONE:+SIGN-RSA-SHA256:+VERS-TLS-ALL:+ECDHE-RSA:+DHE-RSA:+RSA:+CIPHER-ALL:+MAC-ALL:+COMP-NULL:+CURVE-ALL:+CTYPE-X509 \ + -DCERT=DIR/aux-fixed/exim-ca/example.com/server1.example.com/ca_chain.pem \ + rsa.auth@test.ex +Subject: test + +. +**** +killdaemon +# +# +# +# +exim -z '2: Server sends good staple on request, to client preferring ECDSA auth' +**** +# +exim -bd -oX PORT_D -DSERVER=server +**** +exim -odf \ + -DOPT=NONE:+SIGN-ECDSA-SHA512:+VERS-TLS-ALL:+KX-ALL:+CIPHER-ALL:+MAC-ALL:+COMP-NULL:+CURVE-ALL:+CTYPE-X509 \ + -DCERT=DIR/aux-fixed/exim-ca/example_ec.com/server1.example_ec.com/ca_chain.pem \ + ecdsa.auth@test.ex +Subject: test + +. +**** +killdaemon +no_msglog_check