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(&param, "HTTPS", "on", clt) == -1) {
                        errstr = "failed to encode param";
                        goto fail;
                }
+               if (srv_conf->tls_flags != 0 && fcgi_add_param(&param,
+                   "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(&param, "REMOTE_ADDR", hbuf, clt) == -1) {

Reply via email to