X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Ftls-openssl.c;h=e37b1add5001ad6ba8b6a2f02f07dee08f8b34df;hb=e5cccda9bbf169ea7dc97fa3859735523dd4cec0;hp=1d6b91470aa87d94eb5fe951470a500471afbc2e;hpb=e51c7be22dfccad376659a1a46cee93c9979bbf7;p=exim.git diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 1d6b91470..e37b1add5 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -22,19 +22,24 @@ functions from the OpenSSL library. */ #include #include #include -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP # include #endif -#ifdef EXPERIMENTAL_OCSP -#define EXIM_OCSP_SKEW_SECONDS (300L) -#define EXIM_OCSP_MAX_AGE (-1L) +#ifndef DISABLE_OCSP +# define EXIM_OCSP_SKEW_SECONDS (300L) +# define EXIM_OCSP_MAX_AGE (-1L) #endif #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT) # define EXIM_HAVE_OPENSSL_TLSEXT #endif +#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP) +# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile" +# define DISABLE_OCSP +#endif + /* Structure for collecting random data for seeding. */ typedef struct randstuff { @@ -88,7 +93,7 @@ static BOOL reexpand_tls_files_for_sni = FALSE; typedef struct tls_ext_ctx_cb { uschar *certificate; uschar *privatekey; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP BOOL is_server; union { struct { @@ -127,7 +132,7 @@ setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL opt #ifdef EXIM_HAVE_OPENSSL_TLSEXT static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); #endif -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP static int tls_server_stapling_cb(SSL *s, void *arg); #endif @@ -213,7 +218,7 @@ return rsa_key; /* Extreme debug -#if defined(EXPERIMENTAL_OCSP) +#ifndef DISABLE_OCSP void x509_store_dump_cert_s_names(X509_STORE * store) { @@ -295,7 +300,7 @@ else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0) { DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", X509_STORE_CTX_get_error_depth(x509ctx), txt); -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store) { /* client, wanting stapling */ /* Add the server cert's signing chain as the one @@ -322,13 +327,25 @@ else /* client, wanting hostname check */ # if OPENSSL_VERSION_NUMBER >= 0x010100000L || OPENSSL_VERSION_NUMBER >= 0x010002000L +# ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS +# define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0 +# endif { int sep = 0; uschar * list = verify_cert_hostnames; uschar * name; - while (name = string_nextinlist(&list, &sep, NULL, 0)) - if (X509_check_host(cert, name, 0, 0)) + int rc; + while ((name = string_nextinlist(&list, &sep, NULL, 0))) + if ((rc = X509_check_host(cert, name, 0, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS))) + { + if (rc < 0) + { + log_write(0, LOG_MAIN, "SSL verify error: internal error\n"); + name = NULL; + } break; + } if (!name) { log_write(0, LOG_MAIN, @@ -344,7 +361,7 @@ else return 0; /* reject */ } # endif -#endif +#endif /*EXPERIMENTAL_CERTNAMES*/ DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n", *calledp ? "" : " authenticated", txt); @@ -368,6 +385,28 @@ return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called, } +#ifdef EXPERIMENTAL_DANE +/* This gets called *by* the dane library verify callback, which interposes +itself. +*/ +static int +verify_callback_client_dane(int state, X509_STORE_CTX * x509ctx) +{ +X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx); +static uschar txt[256]; + +X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt)); + +DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", txt); +tls_out.peerdn = txt; +tls_out.peercert = X509_dup(cert); + +if (state == 1) + tls_out.certificate_verified = TRUE; +return 1; +} +#endif + /************************************************* * Information callback * @@ -419,14 +458,11 @@ const char *pem; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded)) return FALSE; -if (dhexpanded == NULL || *dhexpanded == '\0') - { +if (!dhexpanded || !*dhexpanded) bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); - } else if (dhexpanded[0] == '/') { - bio = BIO_new_file(CS dhexpanded, "r"); - if (bio == NULL) + if (!(bio = BIO_new_file(CS dhexpanded, "r"))) { tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), host, US strerror(errno)); @@ -441,8 +477,7 @@ else return TRUE; } - pem = std_dh_prime_named(dhexpanded); - if (!pem) + if (!(pem = std_dh_prime_named(dhexpanded))) { tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), host, US strerror(errno)); @@ -451,8 +486,7 @@ else bio = BIO_new_mem_buf(CS pem, -1); } -dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); -if (dh == NULL) +if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) { BIO_free(bio); tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), @@ -486,7 +520,7 @@ return TRUE; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /************************************************* * Load OCSP information into state * *************************************************/ @@ -620,7 +654,7 @@ bad: } return; } -#endif /*EXPERIMENTAL_OCSP*/ +#endif /*!DISABLE_OCSP*/ @@ -682,7 +716,7 @@ if (expanded != NULL && *expanded != 0) "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL); } -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (cbinfo->is_server && cbinfo->u_ocsp.server.file != NULL) { if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded)) @@ -753,8 +787,7 @@ if (!reexpand_tls_files_for_sni) not confident that memcpy wouldn't break some internal reference counting. Especially since there's a references struct member, which would be off. */ -server_sni = SSL_CTX_new(SSLv23_server_method()); -if (!server_sni) +if (!(server_sni = SSL_CTX_new(SSLv23_server_method()))) { ERR_error_string(ERR_get_error(), ssl_errstring); DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); @@ -772,7 +805,7 @@ SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); if (cbinfo->server_cipher_list) SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list); -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if (cbinfo->u_ocsp.server.file) { SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb); @@ -788,8 +821,8 @@ OCSP information. */ rc = tls_expand_session_files(server_sni, cbinfo); if (rc != OK) return SSL_TLSEXT_ERR_NOACK; -rc = init_dh(server_sni, cbinfo->dhparam, NULL); -if (rc != OK) return SSL_TLSEXT_ERR_NOACK; +if (!init_dh(server_sni, cbinfo->dhparam, NULL)) + return SSL_TLSEXT_ERR_NOACK; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); SSL_set_SSL_CTX(s, server_sni); @@ -801,7 +834,7 @@ return SSL_TLSEXT_ERR_OK; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /************************************************* * Callback to handle OCSP Stapling * @@ -985,8 +1018,7 @@ if(!(bs = OCSP_response_get1_basic(rsp))) OCSP_RESPONSE_free(rsp); return i; } -#endif /*EXPERIMENTAL_OCSP*/ - +#endif /*!DISABLE_OCSP*/ /************************************************* @@ -997,13 +1029,14 @@ return i; of the library. We allocate and return a context structure. Arguments: + ctxp returned SSL context host connected host, if client; NULL if server dhparam DH parameter file certificate certificate file privatekey private key ocsp_file file of stapling info (server); flag for require ocsp (client) addr address if client; NULL if server (for some randomness) - cbp place to put allocated context + cbp place to put allocated callback context Returns: OK/DEFER/FAIL */ @@ -1011,7 +1044,7 @@ Returns: OK/DEFER/FAIL static int tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP uschar *ocsp_file, #endif address_item *addr, tls_ext_ctx_cb ** cbp) @@ -1024,7 +1057,7 @@ tls_ext_ctx_cb *cbinfo; cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); cbinfo->certificate = certificate; cbinfo->privatekey = privatekey; -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP if ((cbinfo->is_server = host==NULL)) { cbinfo->u_ocsp.server.file = ocsp_file; @@ -1035,6 +1068,7 @@ else cbinfo->u_ocsp.client.verify_store = NULL; #endif cbinfo->dhparam = dhparam; +cbinfo->server_cipher_list = NULL; cbinfo->host = host; SSL_load_error_strings(); /* basic set up */ @@ -1126,7 +1160,7 @@ if (rc != OK) return rc; #ifdef EXIM_HAVE_OPENSSL_TLSEXT if (host == NULL) /* server */ { -# ifdef EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP /* We check u_ocsp.server.file, not server.response, because we care about if the option exists, not what the current expansion might be, as SNI might change the certificate and OCSP file in use between now and the time the @@ -1142,7 +1176,7 @@ if (host == NULL) /* server */ SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo); } -# ifdef EXPERIMENTAL_OCSP +# ifndef DISABLE_OCSP else /* client */ if(ocsp_file) /* wanting stapling */ { @@ -1379,7 +1413,7 @@ if (tls_in.active >= 0) the error. */ rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP tls_ocsp_file, #endif NULL, &server_static_cbinfo); @@ -1521,6 +1555,50 @@ return OK; +static int +tls_client_basic_ctx_init(SSL_CTX * ctx, + host_item * host, smtp_transport_options_block * ob +#ifdef EXPERIMENTAL_CERTNAMES + , tls_ext_ctx_cb * cbinfo +#endif + ) +{ +int rc; +/* stick to the old behaviour for compatibility if tls_verify_certificates is + set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only + the specified host patterns if one of them is defined */ + +if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) || + (verify_check_host(&ob->tls_verify_hosts) == OK)) + { + if ((rc = setup_certs(ctx, ob->tls_verify_certificates, + ob->tls_crl, host, FALSE, verify_callback_client)) != OK) + return rc; + client_verify_optional = FALSE; + +#ifdef EXPERIMENTAL_CERTNAMES + if (ob->tls_verify_cert_hostnames) + { + if (!expand_check(ob->tls_verify_cert_hostnames, + US"tls_verify_cert_hostnames", + &cbinfo->verify_cert_hostnames)) + return FAIL; + if (cbinfo->verify_cert_hostnames) + DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", + cbinfo->verify_cert_hostnames); + } +#endif + } +else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) + { + if ((rc = setup_certs(ctx, ob->tls_verify_certificates, + ob->tls_crl, host, TRUE, verify_callback_client)) != OK) + return rc; + client_verify_optional = TRUE; + } + +return OK; +} /************************************************* * Start a TLS session in a client * @@ -1545,21 +1623,89 @@ tls_client_start(int fd, host_item *host, address_item *addr, { smtp_transport_options_block * ob = v_ob; static uschar txt[256]; -uschar *expciphers; -X509* server_cert; +uschar * expciphers; +X509 * server_cert; int rc; static uschar cipherbuf[256]; -#ifdef EXPERIMENTAL_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; + +#ifndef DISABLE_OCSP +BOOL request_ocsp = FALSE; +BOOL require_ocsp = FALSE; +#endif +#ifdef EXPERIMENTAL_DANE +dns_answer tlsa_dnsa; +BOOL dane = FALSE; +BOOL dane_required; +#endif + +#ifdef EXPERIMENTAL_DANE +dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, + host->name, host->address, NULL) == OK; + +if (host->dnssec == DS_YES) + { + if( dane_required + || verify_check_this_host(&ob->hosts_try_dane, NULL, + host->name, host->address, NULL) == OK + ) + { + /* move this out to host.c given the similarity to dns_lookup() ? */ + uschar buffer[300]; + uschar * fullname = buffer; + + /* TLSA lookup string */ + (void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, + host->name); + + switch (rc = dns_lookup(&tlsa_dnsa, buffer, T_TLSA, &fullname)) + { + case DNS_AGAIN: + return DEFER; /* just defer this TLS'd conn */ + + default: + case DNS_FAIL: + if (dane_required) + { + log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed"); + return FAIL; + } + break; + + case DNS_SUCCEED: + if (!dns_is_secure(&tlsa_dnsa)) + { + log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); + return DEFER; + } + dane = TRUE; + break; + } + } + } +else if (dane_required) + { + /*XXX a shame we only find this after making tcp & smtp connection */ + /* move the test earlier? */ + log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC"); + return FAIL; + } + +if (!dane) /*XXX todo: enable ocsp with dane */ +#endif + +#ifndef DISABLE_OCSP + { + require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp, + NULL, host->name, host->address, NULL) == OK; + request_ocsp = require_ocsp ? TRUE + : verify_check_this_host(&ob->hosts_request_ocsp, + NULL, host->name, host->address, NULL) == OK; + } #endif rc = tls_init(&client_ctx, host, NULL, ob->tls_certificate, ob->tls_privatekey, -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP (void *)(long)request_ocsp, #endif addr, &client_static_cbinfo); @@ -1585,38 +1731,26 @@ if (expciphers != NULL) return tls_error(US"SSL_CTX_set_cipher_list", host, NULL); } -/* stick to the old behaviour for compatibility if tls_verify_certificates is - set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only - the specified host patterns if one of them is defined */ - -if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) || - (verify_check_host(&ob->tls_verify_hosts) == OK)) +#ifdef EXPERIMENTAL_DANE +if (dane) { - if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates, - ob->tls_crl, host, FALSE, verify_callback_client)) != OK) - return rc; - client_verify_optional = FALSE; + SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback_client_dane); + + if (!DANESSL_library_init()) + return tls_error(US"library init", host, NULL); + if (DANESSL_CTX_init(client_ctx) <= 0) + return tls_error(US"context init", host, NULL); + } +else + +#endif + if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob #ifdef EXPERIMENTAL_CERTNAMES - if (ob->tls_verify_cert_hostnames) - { - if (!expand_check(ob->tls_verify_cert_hostnames, - US"tls_verify_cert_hostnames", - &client_static_cbinfo->verify_cert_hostnames)) - return FAIL; - if (client_static_cbinfo->verify_cert_hostnames) - DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", - client_static_cbinfo->verify_cert_hostnames); - } + , client_static_cbinfo #endif - } -else if (verify_check_host(&ob->tls_try_verify_hosts) == OK) - { - if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates, - ob->tls_crl, host, TRUE, verify_callback_client)) != OK) + )) != OK) return rc; - client_verify_optional = TRUE; - } if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL); @@ -1647,7 +1781,7 @@ if (ob->tls_sni) } } -#ifdef EXPERIMENTAL_OCSP +#ifndef DISABLE_OCSP /* Request certificate status at connection-time. If the server does OCSP stapling we will get the callback (set in tls_init()) */ if (request_ocsp) @@ -1658,6 +1792,51 @@ if (request_ocsp) } #endif +#ifdef EXPERIMENTAL_DANE +if (dane) + { + dns_record * rr; + dns_scan dnss; + uschar * hostnames[2] = { host->name, NULL }; + + if (DANESSL_init(client_ssl, NULL, hostnames) != 1) + return tls_error(US"hostnames load", host, NULL); + + for (rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_NEXT) + ) if (rr->type == T_TLSA) + { + uschar * p = rr->data; + int usage, selector, mtype; + const char * mdname; + + GETSHORT(usage, p); + GETSHORT(selector, p); + GETSHORT(mtype, p); + + switch (mtype) + { + default: /* log bad */ return FAIL; + case 0: mdname = NULL; break; + case 1: mdname = "sha256"; break; + case 2: mdname = "sha512"; break; + } + + switch (DANESSL_add_tlsa(client_ssl, + (uint8_t) usage, (uint8_t) selector, + mdname, p, rr->size - (p - rr->data))) + { + default: + case 0: /* action not taken */ + return tls_error(US"tlsa load", host, NULL); + case 1: break; + } + } + } +#endif + + /* There doesn't seem to be a built-in timeout on connection. */ DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); @@ -1666,6 +1845,11 @@ alarm(ob->command_timeout); rc = SSL_connect(client_ssl); alarm(0); +#ifdef EXPERIMENTAL_DANE +if (dane) + DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */ +#endif + if (rc <= 0) return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);