Hi, this diff adds client side certificate checks to httpd. Most parts are straight forward. But, to transfer the whole certificate authority store to the server process through the imsg infrastructure I had to change this in an chunked transfer. Documentation of this feature in httpd.conf.5 is included.
I tested this diff with certificates created by this script: https://github.com/younix/ucspi/blob/master/tests.mk and with firefox, chromium and 'openssl s_client...'. If anything is wrong with this diff, just notify me, I will fix it. Bye, Jan Index: config.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/config.c,v retrieving revision 1.44 diff -u -p -r1.44 config.c --- config.c 2 Dec 2015 15:13:00 -0000 1.44 +++ config.c 15 Jan 2016 11:15:11 -0000 @@ -235,55 +235,57 @@ config_settls(struct httpd *env, struct struct privsep *ps = env->sc_ps; struct tls_config tls; struct iovec iov[2]; - size_t c; + size_t n; + char *p; if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0) return (0); - log_debug("%s: configuring TLS for %s", __func__, srv->srv_conf.name); - - if (srv->srv_conf.tls_cert_len != 0) { - DPRINTF("%s: sending TLS cert \"%s[%u]\" to %s fd %d", __func__, - srv->srv_conf.name, srv->srv_conf.id, - ps->ps_title[PROC_SERVER], srv->srv_s); - - memset(&tls, 0, sizeof(tls)); - tls.id = srv->srv_conf.id; - tls.port = srv->srv_conf.port; - memcpy(&tls.ss, &srv->srv_conf.ss, sizeof(tls.ss)); - tls.tls_cert_len = srv->srv_conf.tls_cert_len; - - c = 0; - iov[c].iov_base = &tls; - iov[c++].iov_len = sizeof(tls); - iov[c].iov_base = srv->srv_conf.tls_cert; - iov[c++].iov_len = srv->srv_conf.tls_cert_len; + if ((p = malloc(srv->srv_conf.tls_ca_len + srv->srv_conf.tls_cert_len + + srv->srv_conf.tls_key_len)) == NULL) { + log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " + "`%s'", __func__, srv->srv_conf.name); + return (-1); + } - if (proc_composev(ps, PROC_SERVER, IMSG_CFG_TLS, iov, c) != 0) { - log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " - "`%s'", __func__, srv->srv_conf.name); - return (-1); - } + memcpy(p, srv->srv_conf.tls_ca, srv->srv_conf.tls_ca_len); + n = srv->srv_conf.tls_ca_len; + memcpy(p + n, srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len); + n += srv->srv_conf.tls_cert_len; + memcpy(p + n, srv->srv_conf.tls_key, srv->srv_conf.tls_key_len); + n += srv->srv_conf.tls_key_len; + + /* send tls_config structure */ + memset(&tls, 0, sizeof(tls)); + tls.id = srv->srv_conf.id; + tls.port = srv->srv_conf.port; + memcpy(&tls.ss, &srv->srv_conf.ss, sizeof(tls.ss)); + tls.tls_ca_optional = srv->srv_conf.tls_ca_optional; + tls.tls_ca_len = srv->srv_conf.tls_ca_len; + tls.tls_cert_len = srv->srv_conf.tls_cert_len; + tls.tls_key_len = srv->srv_conf.tls_key_len; + + if (proc_compose(ps, PROC_SERVER, IMSG_CFG_TLS, &tls, sizeof(tls)) != 0) { + log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " + "`%s'", __func__, srv->srv_conf.name); + return (-1); } - if (srv->srv_conf.tls_key_len != 0) { - DPRINTF("%s: sending TLS key \"%s[%u]\" to %s fd %d", __func__, - srv->srv_conf.name, srv->srv_conf.id, - ps->ps_title[PROC_SERVER], srv->srv_s); - - memset(&tls, 0, sizeof(tls)); - tls.id = srv->srv_conf.id; - tls.port = srv->srv_conf.port; - memcpy(&tls.ss, &srv->srv_conf.ss, sizeof(tls.ss)); - tls.tls_key_len = srv->srv_conf.tls_key_len; - - c = 0; - iov[c].iov_base = &tls; - iov[c++].iov_len = sizeof(tls); - iov[c].iov_base = srv->srv_conf.tls_key; - iov[c++].iov_len = srv->srv_conf.tls_key_len; + iov[0].iov_base = &tls; + iov[0].iov_len = sizeof(tls); + +#define MSG_CHUNK_SIZE (MAX_IMSGSIZE - (ssize_t)IMSG_HEADER_SIZE - iov[0].iov_len) + + /* transfer in chunks */ + for (; n > 0; p += MSG_CHUNK_SIZE) { + size_t len = n > MSG_CHUNK_SIZE ? MSG_CHUNK_SIZE : n; + n -= len; - if (proc_composev(ps, PROC_SERVER, IMSG_CFG_TLS, iov, c) != 0) { + iov[1].iov_base = p; + iov[1].iov_len = len; + + if (proc_composev(ps, PROC_SERVER, IMSG_CFG_TLS_CHNK, iov, 2) + != 0) { log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " "`%s'", __func__, srv->srv_conf.name); return (-1); @@ -558,6 +560,54 @@ config_getserver(struct httpd *env, stru } int +config_gettls_chunk(struct httpd *env, struct imsg *imsg) +{ + struct server *srv = NULL; + struct tls_config tls_conf; + uint8_t *p = imsg->data; + size_t s = 0; + + IMSG_SIZE_CHECK(imsg, &tls_conf); + memcpy(&tls_conf, p, sizeof(tls_conf)); + s = sizeof(tls_conf); + + /* Find server with matching listening socket. */ + if ((srv = server_byaddr((struct sockaddr *) + &tls_conf.ss, tls_conf.port)) == NULL) { + log_debug("%s: server not found", __func__); + goto fail; + } + +#define CHUNK_CPY(dst, dst_len, dst_size, src, src_size, src_off) \ + do { \ + if (dst_len < dst_size) { \ + size_t n = dst_size - dst_len; \ + if (n > src_size - src_off) \ + n = src_size - src_off; \ + memcpy(dst + dst_len, src + src_off, n); \ + dst_len += n; \ + src_off += n; \ + } \ + } while(0) + + CHUNK_CPY(srv->srv_conf.tls_ca, srv->srv_conf.tls_ca_len, + srv->srv_conf.tls_ca_size, p, IMSG_DATA_SIZE(imsg), s); + + CHUNK_CPY(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len, + srv->srv_conf.tls_cert_size, p, IMSG_DATA_SIZE(imsg), s); + + CHUNK_CPY(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len, + srv->srv_conf.tls_key_size, p, IMSG_DATA_SIZE(imsg), s); + +#undef CHUNK_CPY + + return (0); + + fail: + return (-1); +} + +int config_gettls(struct httpd *env, struct imsg *imsg) { #ifdef DEBUG @@ -572,12 +622,6 @@ config_gettls(struct httpd *env, struct memcpy(&tls_conf, p, sizeof(tls_conf)); s = sizeof(tls_conf); - if ((IMSG_DATA_SIZE(imsg) - s) < - (tls_conf.tls_cert_len + tls_conf.tls_key_len)) { - log_debug("%s: invalid message length", __func__); - goto fail; - } - /* Find server with matching listening socket. */ if ((srv = server_byaddr((struct sockaddr *) &tls_conf.ss, tls_conf.port)) == NULL) { @@ -589,20 +633,25 @@ config_gettls(struct httpd *env, struct ps->ps_title[privsep_process], ps->ps_instance, srv->srv_conf.name, srv->srv_conf.id); - if (tls_conf.tls_cert_len != 0) { - srv->srv_conf.tls_cert_len = tls_conf.tls_cert_len; - if ((srv->srv_conf.tls_cert = get_data(p + s, - tls_conf.tls_cert_len)) == NULL) - goto fail; - s += tls_conf.tls_cert_len; - } - if (tls_conf.tls_key_len != 0) { - srv->srv_conf.tls_key_len = tls_conf.tls_key_len; - if ((srv->srv_conf.tls_key = get_data(p + s, - tls_conf.tls_key_len)) == NULL) - goto fail; - s += tls_conf.tls_key_len; - } + srv->srv_conf.tls_ca_optional = tls_conf.tls_ca_optional; + srv->srv_conf.tls_ca_len = 0; + srv->srv_conf.tls_cert_len = 0; + srv->srv_conf.tls_key_len = 0; + + srv->srv_conf.tls_ca_size = tls_conf.tls_ca_len; + srv->srv_conf.tls_cert_size = tls_conf.tls_cert_len; + srv->srv_conf.tls_key_size = tls_conf.tls_key_len; + + free(srv->srv_conf.tls_ca); + free(srv->srv_conf.tls_cert); + free(srv->srv_conf.tls_key); + + if ((srv->srv_conf.tls_ca = malloc(tls_conf.tls_ca_len)) == NULL) + goto fail; + if ((srv->srv_conf.tls_cert = malloc(tls_conf.tls_cert_len)) == NULL) + goto fail; + if ((srv->srv_conf.tls_key = malloc(tls_conf.tls_key_len)) == NULL) + goto fail; return (0); Index: httpd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v retrieving revision 1.68 diff -u -p -r1.68 httpd.conf.5 --- httpd.conf.5 19 Jul 2015 05:17:27 -0000 1.68 +++ httpd.conf.5 15 Jan 2016 11:04:23 -0000 @@ -434,6 +434,13 @@ Set the TLS configuration for the server These options are only used if TLS has been enabled via the listen directive. Valid options are: .Bl -tag -width Ds +.It Ic ca Ar file Oo Ic optional Oc +Specify a certificate authority for verifying client side certificates. +.Ar file +should contain a PEM encoded certificate of the certificate authority. +The parameter +.Ic optional +does not requiring the client to send a certificate. .It Ic certificate Ar file Specify the certificate to use for this server. The Index: httpd.h =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v retrieving revision 1.102 diff -u -p -r1.102 httpd.h --- httpd.h 2 Dec 2015 15:13:00 -0000 1.102 +++ httpd.h 15 Jan 2016 11:20:19 -0000 @@ -50,6 +50,7 @@ #define HTTPD_ERROR_LOG "error.log" #define HTTPD_DEFAULT_TYPE { "bin", "application", "octet-stream", NULL } #define HTTPD_LOGVIS VIS_NL|VIS_TAB|VIS_CSTYLE +#define HTTPD_TLS_CA "/etc/ssl/ca.crt" #define HTTPD_TLS_CERT "/etc/ssl/server.crt" #define HTTPD_TLS_KEY "/etc/ssl/private/server.key" #define HTTPD_TLS_CIPHERS "HIGH:!aNULL" @@ -203,6 +204,7 @@ enum imsg_type { IMSG_CTL_REOPEN, IMSG_CFG_SERVER, IMSG_CFG_TLS, + IMSG_CFG_TLS_CHNK, IMSG_CFG_MEDIA, IMSG_CFG_AUTH, IMSG_CFG_DONE, @@ -424,14 +426,21 @@ struct server_config { uint32_t maxrequests; size_t maxrequestbody; + uint8_t *tls_ca; + size_t tls_ca_len; + size_t tls_ca_size; + char *tls_ca_file; + uint8_t tls_ca_optional; uint8_t *tls_cert; size_t tls_cert_len; + size_t tls_cert_size; char *tls_cert_file; char tls_ciphers[NAME_MAX]; char tls_dhe_params[NAME_MAX]; char tls_ecdhe_curve[NAME_MAX]; uint8_t *tls_key; size_t tls_key_len; + size_t tls_key_size; char *tls_key_file; uint32_t tls_protocols; @@ -468,8 +477,12 @@ struct tls_config { in_port_t port; struct sockaddr_storage ss; + int tls_ca_optional; + size_t tls_ca_len; size_t tls_cert_len; size_t tls_key_len; + + size_t chunk_size; }; struct server { @@ -723,6 +736,7 @@ int config_setserver(struct httpd *, st int config_settls(struct httpd *, struct server *); int config_getserver(struct httpd *, struct imsg *); int config_gettls(struct httpd *, struct imsg *); +int config_gettls_chunk(struct httpd *, struct imsg *); int config_setmedia(struct httpd *, struct media_type *); int config_getmedia(struct httpd *, struct imsg *); int config_setauth(struct httpd *, struct auth *); Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/httpd/parse.y,v retrieving revision 1.77 diff -u -p -r1.77 parse.y --- parse.y 22 Nov 2015 13:27:13 -0000 1.77 +++ parse.y 15 Jan 2016 11:31:09 -0000 @@ -129,12 +129,12 @@ typedef struct { %} -%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON -%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN -%token LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS -%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT -%token TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD -%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS +%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CA CERTIFICATE CHROOT CIPHERS +%token COMMON COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY +%token LISTEN LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY ON PORT PREFORK +%token PROTOCOLS REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG +%token TCP TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD +%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS OPTIONAL %token <v.string> STRING %token <v.number> NUMBER %type <v.port> port @@ -244,6 +244,9 @@ server : SERVER optmatch STRING { s->srv_conf.flags |= SRVFLAG_SERVER_MATCH; s->srv_conf.logformat = LOG_FORMAT_COMMON; s->srv_conf.tls_protocols = TLS_PROTOCOLS_DEFAULT; + if ((s->srv_conf.tls_ca_file = + strdup(HTTPD_TLS_CA)) == NULL) + fatal("out of memory"); if ((s->srv_conf.tls_cert_file = strdup(HTTPD_TLS_CERT)) == NULL) fatal("out of memory"); @@ -663,7 +666,21 @@ tlsopts_l : tlsopts optcommanl tlsopts_l | tlsopts optnl ; -tlsopts : CERTIFICATE STRING { +tlsopts : CA STRING { + srv_conf->tls_ca_optional = 0; + free(srv_conf->tls_ca_file); + if ((srv_conf->tls_ca_file = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + | CA STRING OPTIONAL { + srv_conf->tls_ca_optional = 1; + free(srv_conf->tls_ca_file); + if ((srv_conf->tls_ca_file = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + | CERTIFICATE STRING { free(srv_conf->tls_cert_file); if ((srv_conf->tls_cert_file = strdup($2)) == NULL) fatal("out of memory"); @@ -1148,6 +1165,7 @@ lookup(char *s) { "block", BLOCK }, { "body", BODY }, { "buffer", BUFFER }, + { "ca", CA}, { "certificate", CERTIFICATE }, { "chroot", CHROOT }, { "ciphers", CIPHERS }, @@ -1176,6 +1194,7 @@ lookup(char *s) { "no", NO }, { "nodelay", NODELAY }, { "on", ON }, + { "optional", OPTIONAL }, { "pass", PASS }, { "port", PORT }, { "prefork", PREFORK }, @@ -1970,16 +1989,24 @@ server_inherit(struct server *src, struc /* Copy the source server and assign a new Id */ memcpy(&dst->srv_conf, &src->srv_conf, sizeof(dst->srv_conf)); + if ((dst->srv_conf.tls_ca_file = + strdup(src->srv_conf.tls_ca_file)) == NULL) + fatal("out of memory"); if ((dst->srv_conf.tls_cert_file = strdup(src->srv_conf.tls_cert_file)) == NULL) fatal("out of memory"); if ((dst->srv_conf.tls_key_file = strdup(src->srv_conf.tls_key_file)) == NULL) fatal("out of memory"); + dst->srv_conf.tls_ca = NULL; dst->srv_conf.tls_cert = NULL; dst->srv_conf.tls_key = NULL; + dst->srv_conf.tls_ca_len = 0; dst->srv_conf.tls_cert_len = 0; dst->srv_conf.tls_key_len = 0; + dst->srv_conf.tls_ca_size = 0; + dst->srv_conf.tls_cert_size = 0; + dst->srv_conf.tls_key_size = 0; if (src->srv_conf.return_uri != NULL && (dst->srv_conf.return_uri = Index: server.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server.c,v retrieving revision 1.83 diff -u -p -r1.83 server.c --- server.c 2 Dec 2015 15:13:00 -0000 1.83 +++ server.c 15 Jan 2016 02:40:24 -0000 @@ -138,6 +138,13 @@ server_tls_load_keypair(struct server *s if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0) return (0); + if ((srv->srv_conf.tls_ca = tls_load_file( + srv->srv_conf.tls_ca_file, &srv->srv_conf.tls_ca_len, + NULL)) == NULL) + return (-1); + log_debug("%s: using certificate authority %s", __func__, + srv->srv_conf.tls_ca_file); + if ((srv->srv_conf.tls_cert = tls_load_file( srv->srv_conf.tls_cert_file, &srv->srv_conf.tls_cert_len, NULL)) == NULL) @@ -196,6 +203,17 @@ server_tls_init(struct server *srv) return (-1); } + if (srv->srv_conf.tls_ca_len > 0) { + if (tls_config_set_ca_mem(srv->srv_tls_config, + srv->srv_conf.tls_ca, srv->srv_conf.tls_ca_len) != 0) { + log_warn("%s: failed to set tls ca", __func__); + return (-1); + } + if (srv->srv_conf.tls_ca_optional) + tls_config_verify_client_optional(srv->srv_tls_config); + else + tls_config_verify_client(srv->srv_tls_config); + } if (tls_config_set_cert_mem(srv->srv_tls_config, srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len) != 0) { log_warn("%s: failed to set tls cert", __func__); @@ -215,12 +233,16 @@ server_tls_init(struct server *srv) /* We're now done with the public/private key... */ tls_config_clear_keys(srv->srv_tls_config); + explicit_bzero(srv->srv_conf.tls_ca, srv->srv_conf.tls_ca_len); explicit_bzero(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len); explicit_bzero(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len); + free(srv->srv_conf.tls_ca); free(srv->srv_conf.tls_cert); free(srv->srv_conf.tls_key); + srv->srv_conf.tls_ca = NULL; srv->srv_conf.tls_cert = NULL; srv->srv_conf.tls_key = NULL; + srv->srv_conf.tls_ca_len = 0; srv->srv_conf.tls_cert_len = 0; srv->srv_conf.tls_key_len = 0; @@ -317,9 +339,15 @@ void serverconfig_free(struct server_config *srv_conf) { free(srv_conf->return_uri); + free(srv_conf->tls_ca_file); free(srv_conf->tls_cert_file); free(srv_conf->tls_key_file); + if (srv_conf->tls_ca != NULL) { + explicit_bzero(srv_conf->tls_ca, srv_conf->tls_ca_len); + free(srv_conf->tls_ca); + } + if (srv_conf->tls_cert != NULL) { explicit_bzero(srv_conf->tls_cert, srv_conf->tls_cert_len); free(srv_conf->tls_cert); @@ -336,6 +364,8 @@ serverconfig_reset(struct server_config { srv_conf->auth = NULL; srv_conf->return_uri = NULL; + srv_conf->tls_ca = NULL; + srv_conf->tls_ca_file = NULL; srv_conf->tls_cert = NULL; srv_conf->tls_cert_file = NULL; srv_conf->tls_key = NULL; @@ -1132,6 +1162,9 @@ server_dispatch_parent(int fd, struct pr break; case IMSG_CFG_TLS: config_gettls(env, imsg); + break; + case IMSG_CFG_TLS_CHNK: + config_gettls_chunk(env, imsg); break; case IMSG_CFG_DONE: config_getcfg(env, imsg);
