My attempts to get this accepted last year stalled. As best I recall, the main stumbling block was agreeing on how much / exactly which client cert data to pass through to fastcgi (my view was that passing the whole client cert chain would be ideal).
So, here's a stripped down version that passes *no* client cert data through to fastcgi at all (but still passes the relevant status flags in TLS_PEER_VERIFY). This diff provides everything necessary for tls client *authentication*, but *none* of what's required to use tls client certs for authorisation or accounting. I figured that if we can agree on this much, so httpd can be used for the authentication-only case (which is all non-fastcgi sites would want) straight away, that's be a good first step -- then we can come back and argue the toss over how much client cert data is necessary/sufficient to pass through for authorisation/accounting purposes. There's also a trivial regression test (unchanged from last year), which I'll post again separately next. Index: config.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/config.c,v retrieving revision 1.53 diff -u -p -r1.53 config.c --- config.c 19 Jul 2017 17:36:25 -0000 1.53 +++ config.c 16 May 2018 07:59:10 -0000 @@ -304,10 +304,18 @@ config_setserver_tls(struct httpd *env, log_debug("%s: configuring tls for %s", __func__, srv_conf->name); + if (config_settls(env, srv, TLS_CFG_CA, "ca", srv_conf->tls_ca, + srv_conf->tls_ca_len) != 0) + return (-1); + if (config_settls(env, srv, TLS_CFG_CERT, "cert", srv_conf->tls_cert, srv_conf->tls_cert_len) != 0) return (-1); + if (config_settls(env, srv, TLS_CFG_CRL, "crl", srv_conf->tls_crl, + srv_conf->tls_crl_len) != 0) + return (-1); + if (config_settls(env, srv, TLS_CFG_KEY, "key", srv_conf->tls_key, srv_conf->tls_key_len) != 0) return (-1); @@ -431,6 +439,7 @@ config_getserver_config(struct httpd *en f = SRVFLAG_TLS; srv_conf->flags |= parent->flags & f; + srv_conf->tls_flags = parent->tls_flags; f = SRVFLAG_ACCESS_LOG; if ((srv_conf->flags & f) == 0) { @@ -655,9 +664,21 @@ config_getserver_tls(struct httpd *env, } switch (tls_conf.tls_type) { + case TLS_CFG_CA: + if (config_gettls(env, srv_conf, &tls_conf, "ca", p, len, + &srv_conf->tls_ca, &srv_conf->tls_ca_len) != 0) + goto fail; + break; + case TLS_CFG_CERT: if (config_gettls(env, srv_conf, &tls_conf, "cert", p, len, &srv_conf->tls_cert, &srv_conf->tls_cert_len) != 0) + goto fail; + break; + + case TLS_CFG_CRL: + if (config_gettls(env, srv_conf, &tls_conf, "crl", p, len, + &srv_conf->tls_crl, &srv_conf->tls_crl_len) != 0) goto fail; break; Index: httpd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v retrieving revision 1.90 diff -u -p -r1.90 httpd.conf.5 --- httpd.conf.5 11 Apr 2018 15:50:46 -0000 1.90 +++ httpd.conf.5 16 May 2018 07:59:10 -0000 @@ -342,6 +342,10 @@ The revision of the HTTP specification u .It Ic SERVER_SOFTWARE The server software name of .Xr httpd 8 . +.It Ic TLS_PEER_VERIFY +A variable that is set to a comma separated list of TLS client verification +features in use +.Pq omitted when TLS client verification is not in use . .El .It Ic hsts Oo Ar option Oc Enable HTTP Strict Transport Security. @@ -526,6 +530,23 @@ will be used (strong crypto cipher suite See the CIPHERS section of .Xr openssl 1 for information about SSL/TLS cipher suites and preference lists. +.It Ic client ca Ar cafile Oo Ic crl Ar crlfile Oc Op Ic optional +Require +.Po +or, if +.Ic optional +is specified, request but do not require +.Pc +TLS client certificates whose authenticity can be verified +against the CA certificate(s) in +.Ar cafile +in order to proceed beyond the TLS handshake. +With +.Ic crl +specified, additionally require that no certificate in the client chain be +listed as revoked in the CRL(s) in +.Ar crlfile . +CA certificates and CRLs should be PEM encoded. .It Ic dhe Ar params Specify the DHE parameters to use for DHE cipher suites. Valid parameter values are none, legacy and auto. Index: httpd.h =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v retrieving revision 1.136 diff -u -p -r1.136 httpd.h --- httpd.h 11 Apr 2018 15:50:46 -0000 1.136 +++ httpd.h 16 May 2018 07:59:10 -0000 @@ -424,6 +424,11 @@ SPLAY_HEAD(client_tree, client); #define HSTSFLAG_PRELOAD 0x02 #define HSTSFLAG_BITS "\10\01SUBDOMAINS\02PRELOAD" +#define TLSFLAG_CA 0x01 +#define TLSFLAG_CRL 0x02 +#define TLSFLAG_OPTIONAL 0x04 +#define TLSFLAG_BITS "\10\01CA\02CRL\3OPTIONAL" + enum log_format { LOG_FORMAT_COMMON, LOG_FORMAT_COMBINED, @@ -480,12 +485,19 @@ struct server_config { uint32_t maxrequests; size_t maxrequestbody; + uint8_t *tls_ca; + char *tls_ca_file; + size_t tls_ca_len; uint8_t *tls_cert; size_t tls_cert_len; char *tls_cert_file; char tls_ciphers[HTTPD_TLS_CONFIG_MAX]; + uint8_t *tls_crl; + char *tls_crl_file; + size_t tls_crl_len; char tls_dhe_params[HTTPD_TLS_CONFIG_MAX]; char tls_ecdhe_curves[HTTPD_TLS_CONFIG_MAX]; + uint8_t tls_flags; uint8_t *tls_key; size_t tls_key_len; char *tls_key_file; @@ -524,7 +536,9 @@ struct server_config { TAILQ_HEAD(serverhosts, server_config); enum tls_config_type { + TLS_CFG_CA, TLS_CFG_CERT, + TLS_CFG_CRL, TLS_CFG_KEY, TLS_CFG_OCSP_STAPLE, }; @@ -598,6 +612,8 @@ int cmdline_symset(char *); /* server.c */ void server(struct privsep *, struct privsep_proc *); int server_tls_cmp(struct server *, struct server *, int); +int server_tls_load_ca(struct server *); +int server_tls_load_crl(struct server *); int server_tls_load_keypair(struct server *); int server_tls_load_ocsp(struct server *); void server_generate_ticket_key(struct server_config *); Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/httpd/parse.y,v retrieving revision 1.94 diff -u -p -r1.94 parse.y --- parse.y 26 Apr 2018 14:12:19 -0000 1.94 +++ parse.y 16 May 2018 07:59:11 -0000 @@ -134,6 +134,7 @@ typedef struct { %token PROTOCOLS REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TICKET %token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS +%token CA CLIENT CRL OPTIONAL %token <v.string> STRING %token <v.number> NUMBER %type <v.port> port @@ -345,6 +346,22 @@ server : SERVER optmatch STRING { YYERROR; } + if (server_tls_load_ca(srv) == -1) { + yyerror("server \"%s\": failed to load " + "ca cert(s)", srv->srv_conf.name); + serverconfig_free(srv_conf); + free(srv); + YYERROR; + } + + if (server_tls_load_crl(srv) == -1) { + yyerror("server \"%s\": failed to load crl(s)", + srv->srv_conf.name); + serverconfig_free(srv_conf); + free(srv); + YYERROR; + } + if (server_tls_load_ocsp(srv) == -1) { yyerror("server \"%s\": failed to load " "ocsp staple", srv->srv_conf.name); @@ -587,6 +604,7 @@ serveroptsl : LISTEN ON STRING opttls po sizeof(s->srv_conf.ss)); s->srv_conf.port = srv->srv_conf.port; s->srv_conf.prefixlen = srv->srv_conf.prefixlen; + s->srv_conf.tls_flags = srv->srv_conf.tls_flags; if (last_server_id == INT_MAX) { yyerror("too many servers/locations defined"); @@ -760,6 +778,13 @@ tlsopts : CERTIFICATE STRING { } free($2); } + | CLIENT CA STRING tlsclientopt { + srv_conf->tls_flags |= TLSFLAG_CA; + free(srv_conf->tls_ca_file); + if ((srv_conf->tls_ca_file = strdup($3)) == NULL) + fatal("out of memory"); + free($3); + } | DHE STRING { if (strlcpy(srv_conf->tls_dhe_params, $2, sizeof(srv_conf->tls_dhe_params)) >= @@ -808,6 +833,18 @@ tlsopts : CERTIFICATE STRING { } ; +tlsclientopt : /* empty */ + | tlsclientopt CRL STRING { + srv_conf->tls_flags = TLSFLAG_CRL; + free(srv_conf->tls_crl_file); + if ((srv_conf->tls_crl_file = strdup($3)) == NULL) + fatal("out of memory"); + free($3); + } + | tlsclientopt OPTIONAL { + srv_conf->tls_flags |= TLSFLAG_OPTIONAL; + } + ; root : ROOT rootflags | ROOT '{' optnl rootflags_l '}' ; @@ -1240,12 +1277,15 @@ lookup(char *s) { "block", BLOCK }, { "body", BODY }, { "buffer", BUFFER }, + { "ca", CA }, { "certificate", CERTIFICATE }, { "chroot", CHROOT }, { "ciphers", CIPHERS }, + { "client", CLIENT }, { "combined", COMBINED }, { "common", COMMON }, { "connection", CONNECTION }, + { "crl", CRL }, { "default", DEFAULT }, { "dhe", DHE }, { "directory", DIRECTORY }, @@ -1270,6 +1310,7 @@ lookup(char *s) { "nodelay", NODELAY }, { "ocsp", OCSP }, { "on", ON }, + { "optional", OPTIONAL }, { "pass", PASS }, { "port", PORT }, { "prefork", PREFORK }, @@ -2100,6 +2141,21 @@ server_inherit(struct server *src, struc serverconfig_free(&dst->srv_conf); free(dst); return (NULL); + } + + if (server_tls_load_ca(dst) == -1) { + yyerror("falied to load ca cert(s) for server %s", + dst->srv_conf.name); + serverconfig_free(&dst->srv_conf); + return NULL; + } + + if (server_tls_load_crl(dst) == -1) { + yyerror("failed to load crl(s) for server %s", + dst->srv_conf.name); + serverconfig_free(&dst->srv_conf); + free(dst); + return NULL; } if (server_tls_load_ocsp(dst) == -1) { Index: server.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server.c,v retrieving revision 1.113 diff -u -p -r1.113 server.c --- server.c 29 Nov 2017 16:55:08 -0000 1.113 +++ server.c 16 May 2018 07:59:11 -0000 @@ -134,6 +134,8 @@ server_tls_cmp(struct server *s1, struct sc1 = &s1->srv_conf; sc2 = &s2->srv_conf; + if (sc1->tls_flags != sc2->tls_flags) + return (-1); if (sc1->tls_protocols != sc2->tls_protocols) return (-1); if (sc1->tls_ticket_lifetime != sc2->tls_ticket_lifetime) @@ -207,6 +209,40 @@ server_tls_load_ocsp(struct server *srv) } int +server_tls_load_ca(struct server *srv) +{ + if ((srv->srv_conf.tls_flags & TLSFLAG_CA) == 0 || + srv->srv_conf.tls_ca_file == NULL) + 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 ca cert(s) from %s", __func__, + srv->srv_conf.tls_ca_file); + + return (0); +} + +int +server_tls_load_crl(struct server *srv) +{ + if ((srv->srv_conf.tls_flags & TLSFLAG_CA) == 0 || + srv->srv_conf.tls_crl_file == NULL) + return (0); + + if ((srv->srv_conf.tls_crl = tls_load_file( + srv->srv_conf.tls_crl_file, + &srv->srv_conf.tls_crl_len, NULL)) == NULL) + return (-1); + log_debug("%s: using crl(s) from %s", __func__, + srv->srv_conf.tls_crl_file); + + return (0); +} + +int server_tls_init(struct server *srv) { struct server_config *srv_conf; @@ -264,6 +300,27 @@ server_tls_init(struct server *srv) return (-1); } + if (srv->srv_conf.tls_ca != NULL) { + if (tls_config_set_ca_mem(srv->srv_tls_config, + srv->srv_conf.tls_ca, srv->srv_conf.tls_ca_len) != 0) { + log_warnx("%s: failed to add ca cert(s)", __func__); + return (-1); + } + if (srv->srv_conf.tls_flags & TLSFLAG_OPTIONAL) + tls_config_verify_client_optional(srv->srv_tls_config); + else + tls_config_verify_client(srv->srv_tls_config); + + if (srv->srv_conf.tls_crl != NULL) { + if (tls_config_set_crl_mem(srv->srv_tls_config, + srv->srv_conf.tls_crl, + srv->srv_conf.tls_crl_len) != 0) { + log_warnx("%s: failed to add crl(s)", __func__); + return (-1); + } + } + } + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { if (srv_conf->tls_cert == NULL || srv_conf->tls_key == NULL) continue; @@ -277,6 +334,26 @@ server_tls_init(struct server *srv) log_warnx("%s: failed to add tls keypair", __func__); return (-1); } + + if (srv->srv_conf.tls_ca == NULL) + continue; + log_debug("%s: adding ca cert(s) for server %s", __func__, + srv->srv_conf.name); + if (tls_config_set_ca_mem(srv->srv_tls_config, + srv_conf->tls_ca, srv_conf->tls_ca_len) != 0) { + log_warnx("%s: failed to add ca cert(s)", __func__); + return (-1); + } + + if (srv->srv_conf.tls_crl == NULL) + continue; + + log_debug("%s: adding crl(s) for server %s", __func__, + srv->srv_conf.name); + if (tls_config_set_crl_mem(srv->srv_tls_config, + srv_conf->tls_crl, srv_conf->tls_crl_len) != 0) { + return (-1); + } } /* set common session ID among all processes */ @@ -310,13 +387,19 @@ server_tls_init(struct server *srv) return (-1); } - /* We're now done with the public/private key... */ + /* We're now done with the public/private key & ca/crl... */ tls_config_clear_keys(srv->srv_tls_config); freezero(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len); freezero(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len); + free(srv->srv_conf.tls_ca); + free(srv->srv_conf.tls_crl); + srv->srv_conf.tls_ca = NULL; srv->srv_conf.tls_cert = NULL; + srv->srv_conf.tls_crl = 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_crl_len = 0; srv->srv_conf.tls_key_len = 0; return (0); @@ -422,7 +505,11 @@ void serverconfig_free(struct server_config *srv_conf) { free(srv_conf->return_uri); + free(srv_conf->tls_ca_file); + free(srv_conf->tls_ca); free(srv_conf->tls_cert_file); + free(srv_conf->tls_crl_file); + free(srv_conf->tls_crl); free(srv_conf->tls_key_file); free(srv_conf->tls_ocsp_staple_file); free(srv_conf->tls_ocsp_staple); @@ -435,8 +522,12 @@ 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_crl = NULL; + srv_conf->tls_crl_file = NULL; srv_conf->tls_key = NULL; srv_conf->tls_key_file = NULL; srv_conf->tls_ocsp_staple = NULL; Index: server_fcgi.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v retrieving revision 1.75 diff -u -p -r1.75 server_fcgi.c --- server_fcgi.c 31 Jul 2017 08:02:49 -0000 1.75 +++ server_fcgi.c 16 May 2018 07:59:12 -0000 @@ -282,11 +282,18 @@ server_fcgi(struct httpd *env, struct cl goto fail; } - if (srv_conf->flags & SRVFLAG_TLS) + if (srv_conf->flags & SRVFLAG_TLS) { if (fcgi_add_param(¶m, "HTTPS", "on", clt) == -1) { errstr = "failed to encode param"; goto fail; } + if (srv_conf->tls_flags != 0 && fcgi_add_param(¶m, + "TLS_PEER_VERIFY", printb_flags(srv_conf->tls_flags, + TLSFLAG_BITS), clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + } (void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf)); if (fcgi_add_param(¶m, "REMOTE_ADDR", hbuf, clt) == -1) {