diff options
Diffstat (limited to 'tcpd/libcouriertls.c')
| -rw-r--r-- | tcpd/libcouriertls.c | 385 |
1 files changed, 307 insertions, 78 deletions
diff --git a/tcpd/libcouriertls.c b/tcpd/libcouriertls.c index 199015e..1f5a40f 100644 --- a/tcpd/libcouriertls.c +++ b/tcpd/libcouriertls.c @@ -9,6 +9,7 @@ #include "libcouriertls.h" #include <openssl/rand.h> #include <openssl/x509.h> +#include <openssl/x509v3.h> #include "tlscache.h" #include "rfc1035/rfc1035.h" #include "soxwrap/soxwrap.h" @@ -21,6 +22,7 @@ #include <stdlib.h> #include <ctype.h> #include <netdb.h> +#include <idna.h> #if HAVE_DIRENT_H #include <dirent.h> #define NAMLEN(dirent) strlen((dirent)->d_name) @@ -140,38 +142,151 @@ static int ssl_verify_callback(int goodcert, X509_STORE_CTX *x509) return (1); } +#if HAVE_X509_VERIFY_PARAM_SET1_HOST static int verifypeer(const struct tls_info *info, SSL *ssl) { - X509 *x=NULL; - X509_NAME *subj=NULL; - int nentries, j; - char domain[256]; - char *p; - char errmsg[1000]; - if (!info->peer_verify_domain) return (1); - if (info->isserver) + return SSL_get_verify_result(ssl) == X509_V_OK; +} +#else + +static int hostmatch_utf8(const char *a, const char *b) +{ + while (*a || *b) { - x=SSL_get_peer_certificate(ssl); + char ca=*a; + char cb=*b; + + if (ca >= 'A' && ca <= 'Z') + ca += 'a'-'A'; + if (cb >= 'A' && cb <= 'Z') + cb += 'a'-'A'; + if (ca != cb) + return 0; - if (x) - subj=X509_get_subject_name(x); + ++a; + ++b; } - else + return 1; +} + +static int hostmatch(const struct tls_info *info, const char *domain) +{ + const char *p=domain; + const char *verify_domain=info->peer_verify_domain; + char *idn_domain1; + char *idn_domain2; + int rc; + + if (*p == '*') { - STACK_OF(X509) *peer_cert_chain=SSL_get_peer_cert_chain(ssl); + ++p; - if (peer_cert_chain && sk_X509_num(peer_cert_chain) > 0) + if (*p != '.') + return 0; + + while (*verify_domain) { - X509 *xx=sk_X509_value(peer_cert_chain, 0); + if (*verify_domain++ == '.') + break; + } + } + + if (idna_to_unicode_8z8z(verify_domain, &idn_domain1, 0) + != IDNA_SUCCESS) + idn_domain1=0; + + if (idna_to_unicode_8z8z(p, &idn_domain2, 0) + != IDNA_SUCCESS) + idn_domain2=0; + + rc=hostmatch_utf8(idn_domain1 ? idn_domain1:info->peer_verify_domain, + idn_domain2 ? idn_domain2:p); + + if (idn_domain1) + free(idn_domain1); + if (idn_domain2) + free(idn_domain2); + return rc; +} + +static int verifypeer1(const struct tls_info *info, X509 *x, + STACK_OF(GENERAL_NAME) *subject_alt_names); + +static int verifypeer(const struct tls_info *info, SSL *ssl) +{ + X509 *x=NULL; + int rc; + STACK_OF(GENERAL_NAME) *subject_alt_names; + + if (!info->peer_verify_domain) + return (1); + + if (SSL_get_verify_result(ssl) != X509_V_OK) + return (1); + + x=SSL_get_peer_certificate(ssl); - if (xx) - subj=X509_get_subject_name(xx); + if (!x) + return (0); + + subject_alt_names= + X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + + rc=verifypeer1(info, x, subject_alt_names); + + if (subject_alt_names) + GENERAL_NAMES_free(subject_alt_names); + + X509_free(x); + + return rc; +} + +static int verifypeer1(const struct tls_info *info, X509 *x, + STACK_OF(GENERAL_NAME) *subject_alt_names) +{ + X509_NAME *subj=NULL; + int nentries, j; + char domain[256]; +char errmsg[1000]; + + if(subject_alt_names) + { + int n=sk_GENERAL_NAME_num(subject_alt_names); + int i; + + for (i=0; i<n; i++) + { + const GENERAL_NAME *gn= + sk_GENERAL_NAME_value(subject_alt_names, i); + const char *str; + int l; + + if (gn->type != GEN_DNS) + continue; + +#ifdef HAVE_OPENSSL110 + str = (const char *)ASN1_STRING_get0_data(gn->d.ia5); +#else + str = (const char *)ASN1_STRING_data(gn->d.ia5); +#endif + l=ASN1_STRING_length(gn->d.ia5); + + if (l >= sizeof(domain)-1) + l=sizeof(domain)-1; + + memcpy(domain, str, l); + domain[l]=0; + + if (hostmatch(info, domain)) + return 1; } } + subj=X509_get_subject_name(x); nentries=0; if (subj) @@ -216,23 +331,8 @@ static int verifypeer(const struct tls_info *info, SSL *ssl) } } - if (x) - X509_free(x); - p=domain; - - if (*p == '*') - { - int pl, l; - - pl=strlen(++p); - l=strlen(info->peer_verify_domain); - - if (*p == '.' && pl <= l && - strcasecmp(info->peer_verify_domain+l-pl, p) == 0) - return (1); - } - else if (strcasecmp(info->peer_verify_domain, p) == 0) - return (1); + if (domain[0] && hostmatch(info, domain)) + return 1; strcpy(errmsg, "couriertls: Mismatched SSL certificate: CN="); strcat(errmsg, domain); @@ -242,11 +342,15 @@ static int verifypeer(const struct tls_info *info, SSL *ssl) (*info->tls_err_msg)(errmsg, info->app_data); return (0); } +#endif static void nonsslerror(const struct tls_info *info, const char *pfix) { char errmsg[256]; + if (errno == 0) + return; + strcpy(errmsg, "couriertls: "); strncat(errmsg, pfix, 200); strcat(errmsg, ": "); @@ -332,10 +436,83 @@ static void load_dh_params(SSL_CTX *ctx, const char *filename, sslerror(info, filename, -1); } +static int check_readable_file(const char *filename) +{ + return (access(filename, R_OK) == 0) ? 1 : 0; +} + +#ifdef HAVE_OPENSSL_SNI +static char *get_servername_concated_readable_file(const char *filename, + const char *servername, + struct tls_info *info) +{ + char *filename_buffer; + char *p; + + if (!filename || !servername) return NULL; + if (!*filename || !*servername) return NULL; + + filename_buffer=malloc(strlen(filename)+strlen(servername)+2); + if (!filename_buffer) + { + nonsslerror(info, "malloc"); + exit(1); + } + + strcat(strcpy(filename_buffer, filename), "."); + + p=filename_buffer + strlen(filename_buffer); + + while ((*p=*servername) != 0) + { + if (*p == '/') + *p='.'; /* Script kiddie check */ + ++p; + ++servername; + } + if (check_readable_file(filename_buffer)) { + return filename_buffer; + } + + free(filename_buffer); + return NULL; +} +#endif + +static char *get_ip_concated_readable_file(SSL_CTX *ctx, const char *filename, const char *ip) +{ + char *test_file; + const struct tls_info *info=SSL_CTX_get_app_data(ctx); + + if (!filename || !ip) return NULL; + if (!*filename || !*ip) return NULL; + + test_file= malloc(strlen(filename)+strlen(ip)+2); + if (!test_file) + { + nonsslerror(info, "malloc"); + exit(1); + } + + strcpy(test_file, filename); + strcat(test_file, "."); + strcat(test_file, ip); + + if (check_readable_file(test_file)) { + return test_file; + } + + free(test_file); + return NULL; +} + static int read_certfile(SSL_CTX *ctx, const char *filename, + const char *private_key_filename, int *cert_file_flags) { const struct tls_info *info=SSL_CTX_get_app_data(ctx); + const char *privatekey_file = (private_key_filename == NULL) + ? filename : private_key_filename; if(!SSL_CTX_use_certificate_chain_file(ctx, filename)) { @@ -345,7 +522,7 @@ static int read_certfile(SSL_CTX *ctx, const char *filename, load_dh_params(ctx, filename, cert_file_flags); - if(!SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM)) + if(!SSL_CTX_use_PrivateKey_file(ctx, privatekey_file, SSL_FILETYPE_PEM)) { sslerror(info, filename, -1); return (0); @@ -353,36 +530,39 @@ static int read_certfile(SSL_CTX *ctx, const char *filename, return (1); } -static int process_certfile(SSL_CTX *ctx, const char *certfile, const char *ip, +static int process_certfile(SSL_CTX *ctx, const char *certfile, + const char *private_key_file, + const char *ip, int (*func)(SSL_CTX *, const char *, + const char *, int *), int *cert_file_flags) { if (ip && *ip) { char *test_file; + char *test_private_key_file; if (strncmp(ip, "::ffff:", 7) == 0 && strchr(ip, '.')) - return (process_certfile(ctx, certfile, ip+7, func, cert_file_flags)); + return (process_certfile(ctx, certfile, private_key_file, ip+7, func, cert_file_flags)); - test_file= malloc(strlen(certfile)+strlen(ip)+2); - - strcpy(test_file, certfile); - strcat(test_file, "."); - strcat(test_file, ip); - - if (access(test_file, R_OK) == 0) + test_file= get_ip_concated_readable_file(ctx, certfile, ip); + test_private_key_file = get_ip_concated_readable_file(ctx, private_key_file, ip); + if (test_file != NULL) { - int rc= (*func)(ctx, test_file, + int rc= (*func)(ctx, test_file, test_private_key_file, cert_file_flags); free(test_file); + if (test_private_key_file) free(test_private_key_file); + return rc; } - free(test_file); + if (test_private_key_file) free(test_private_key_file); } - return (*func)(ctx, certfile, cert_file_flags); + private_key_file = check_readable_file(private_key_file) ? private_key_file : NULL; + return (*func)(ctx, certfile, private_key_file, cert_file_flags); } static int client_cert_cb(ssl_handle ssl, X509 **x509, EVP_PKEY **pkey) @@ -502,33 +682,17 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg) const char *servername=SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); const char *certfile=safe_getenv(info, "TLS_CERTFILE"); + const char *private_keyfile=safe_getenv(info, "TLS_PRIVATE_KEYFILE"); int cert_file_flags=0; - char *buffer; - char *p; + char *cert_file_buffer; + char *private_keyfile_buffer; if (!servername || !certfile) return SSL_TLSEXT_ERR_OK; - buffer=malloc(strlen(certfile)+strlen(servername)+2); - if (!buffer) - { - nonsslerror(info, "malloc"); - exit(1); - } - - strcat(strcpy(buffer, certfile), "."); - - p=buffer + strlen(buffer); - - while ((*p=*servername) != 0) - { - if (*p == '/') - *p='.'; /* Script kiddie check */ - ++p; - ++servername; - } - - if (access(buffer, R_OK) == 0) + cert_file_buffer = get_servername_concated_readable_file(certfile, servername, info); + private_keyfile_buffer = get_servername_concated_readable_file(private_keyfile, servername, info); + if (cert_file_buffer != NULL) { SSL_CTX *orig_ctx=SSL_get_SSL_CTX(ssl); SSL_CTX *temp_ctx=tls_create_int(1, info, 1); @@ -541,7 +705,7 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg) exit(1); } SSL_set_SSL_CTX(ssl, temp_ctx); - rc=read_certfile(orig_ctx, buffer, &cert_file_flags); + rc=read_certfile(orig_ctx, cert_file_buffer, private_keyfile_buffer, &cert_file_flags); SSL_set_SSL_CTX(ssl, orig_ctx); tls_destroy(temp_ctx); if (!rc) @@ -551,7 +715,8 @@ static int server_cert_cb(ssl_handle ssl, int *ad, void *arg) exit(1); } } - free(buffer); + if (cert_file_buffer) free(cert_file_buffer); + if (private_keyfile_buffer) free(private_keyfile_buffer); #endif return SSL_TLSEXT_ERR_OK; @@ -571,6 +736,7 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info, int session_timeout=atoi(safe_getenv(info, "TLS_TIMEOUT")); const char *dhparamsfile=safe_getenv(info, "TLS_DHPARAMS"); const char *certfile=safe_getenv(info, "TLS_CERTFILE"); + const char *private_keyfile=safe_getenv(info, "TLS_PRIVATE_KEYFILE"); const char *s; struct stat stat_buf; const char *peer_cert_dir=NULL; @@ -588,6 +754,9 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info, if (!*certfile) certfile=NULL; + if (!*private_keyfile) + private_keyfile=NULL; + if (!*dhparamsfile) dhparamsfile=NULL; @@ -697,7 +866,7 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info, if (dhparamsfile) load_dh_params(ctx, dhparamsfile, &cert_file_flags); - if (certfile && !process_certfile(ctx, certfile, s, + if (certfile && !process_certfile(ctx, certfile, private_keyfile, s, read_certfile, &cert_file_flags)) { @@ -765,13 +934,11 @@ SSL_CTX *tls_create_int(int isserver, const struct tls_info *info, "/"), de->d_name); fp=fopen(q, "r"); - if (!fp) - { - nonsslerror(info, q); - exit(1); - } free(q); + if (!fp) + continue; + while ((x=PEM_read_X509(fp, NULL, NULL, NULL))) { SSL_CTX_add_client_CA(ctx,x); @@ -1113,14 +1280,39 @@ SSL *tls_connect(SSL_CTX *ctx, int fd) } else { + char *idn_domain1=0; + + if (info->peer_verify_domain) + { + if (idna_to_unicode_8z8z(info->peer_verify_domain, + &idn_domain1, 0) + != IDNA_SUCCESS) + idn_domain1=0; + } SSL_set_connect_state(ssl); #ifdef HAVE_OPENSSL_SNI if (info->peer_verify_domain) { - SSL_set_tlsext_host_name(ssl, info->peer_verify_domain); + SSL_set_tlsext_host_name(ssl, + idn_domain1 ? idn_domain1 + : (char *) + info->peer_verify_domain); + } +#endif +#if HAVE_X509_VERIFY_PARAM_SET1_HOST + if (info->peer_verify_domain) + { + X509_VERIFY_PARAM *param = SSL_get0_param(ssl); + X509_VERIFY_PARAM_set1_host(param, + idn_domain1 ? + idn_domain1 : + info->peer_verify_domain, + 0); } #endif + if (idn_domain1) + free(idn_domain1); if ((rc=SSL_connect(ssl)) > 0) { @@ -1173,6 +1365,8 @@ int tls_transfer(struct tls_transfer_info *t, SSL *ssl, int fd, struct tls_info *info=SSL_get_app_data(ssl); int n; + errno=0; + if (info->connect_interrupted) { n=SSL_connect(ssl); @@ -1411,6 +1605,8 @@ static void dump_x509(X509 *x509, X509_NAME *subj=X509_get_subject_name(x509); int nentries, j; time_t timestamp; + STACK_OF(GENERAL_NAME) *subject_alt_names; + static const char gcc_shutup[]="%Y-%m-%d %H:%M:%S"; if (!subj) @@ -1457,6 +1653,39 @@ static void dump_x509(X509 *x509, } (*dump_func)("\n", 1, dump_arg); + subject_alt_names= + X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL); + + if (subject_alt_names) + { + int n=sk_GENERAL_NAME_num(subject_alt_names); + int i; + + for (i=0; i<n; i++) + { + const GENERAL_NAME *gn= + sk_GENERAL_NAME_value(subject_alt_names, i); + const char *domain; + int l; + + if (gn->type != GEN_DNS) + continue; + +#ifdef HAVE_OPENSSL110 + domain = (const char *)ASN1_STRING_get0_data(gn->d.ia5); +#else + domain = (const char *)ASN1_STRING_data(gn->d.ia5); +#endif + l=ASN1_STRING_length(gn->d.ia5); + + (*dump_func)("Subject-Alt-Name-DNS: ", -1, dump_arg); + (*dump_func)(domain, l, dump_arg); + (*dump_func)("\n", -1, dump_arg); + } + + GENERAL_NAMES_free(subject_alt_names); + } + timestamp=asn1toTime(X509_get_notBefore(x509)); if (timestamp) |
