Now that libtls supports CRL checking (thanks jsing@ & beck@ for your
help to get that in), let's have another go at adding tls client
verification to httpd.

This diff does just that, with options to verify tls client certs,
optionally with full chain CRL checks.

All the same info is passed through to fastcgi as in httpd diff I
posted here in late May (see also the two related threads on tech@ in
late March / early April), plus a new TLS_VERIFY_MODE to let the fastcgi
responders know how much (if any) client cert checking httpd has done
already (for much the same reason as httpd passes through HTTPS to
fastcgi).

With this diff, httpd is capable of looking after all aspects of tls
client authentication.

Any *authorisation* based on tls client certs beyond a simple "holder
of any trusted, authentic, non-revoked cert can access anything" remains
the province of the fastcgi responders, presumably based on the contents
of TLS_PEER_* (again, see the discussion here in April).

Thoughts?

Index: usr.sbin/httpd/config.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/config.c,v
retrieving revision 1.52
diff -u -p -r1.52 config.c
--- usr.sbin/httpd/config.c     28 May 2017 10:37:26 -0000      1.52
+++ usr.sbin/httpd/config.c     7 Jul 2017 08:24:01 -0000
@@ -326,6 +326,80 @@ config_settls(struct httpd *env, struct 
                }
        }
 
+       if (srv_conf->tls_ca_len != 0 &&
+           config_settls_chunk(env, srv, IMSG_CFG_TLS_CA) != 0)
+               return (-1);
+
+       if (srv_conf->tls_crl_len != 0)
+               return config_settls_chunk(env, srv, IMSG_CFG_TLS_CRL);
+
+       return (0);
+}
+
+int
+config_settls_chunk(struct httpd *env, struct server *srv, int cmd)
+{
+       struct privsep           *ps = env->sc_ps;
+       struct server_config     *srv_conf = &srv->srv_conf;
+       struct tls_config         tls;
+       struct iovec              iov[2];
+       size_t                    c, chunk_len, i, sent;
+       size_t                   *src_len;
+       uint8_t                 **src;
+       const char *              type;
+
+       if ((srv_conf->flags & SRVFLAG_TLS) == 0)
+               return (0);
+       if (cmd == IMSG_CFG_TLS_CA) {
+               if ((srv_conf->tls_client_flags & TLSCLIENTFLAG_VERIFY) == 0)
+                       return (0);
+               src_len = &srv_conf->tls_ca_len;
+               src = &srv_conf->tls_ca;
+               type = "ca cert(s)";
+       } else if (cmd == IMSG_CFG_TLS_CRL) {
+               if ((srv_conf->tls_client_flags & TLSCLIENTFLAG_CRL) == 0)
+                       return (0);
+               src_len = &srv_conf->tls_crl_len;
+               src = &srv_conf->tls_crl;
+               type = "crl(s)";
+       } else {
+               DPRINTF("%s: server \"%s[%u]\": invalid cmd: %d", __func__,
+                   srv_conf->name, srv_conf->id, cmd);
+               return (-1);
+       }
+
+       log_debug("%s: configuring tls %s for %s", __func__, type,
+           srv_conf->name);
+
+       if (*src_len != 0) {
+               DPRINTF("%s: sending %s for \"%s[%u]\" to %s fd %d", __func__,
+                   type, srv_conf->name, srv_conf->id,
+                   ps->ps_title[PROC_SERVER], srv->srv_s);
+               memset(&tls, 0, sizeof(tls));
+               tls.id = srv_conf->id;
+               tls.tls_src_len = *src_len;
+
+               chunk_len = MAX_IMSGSIZE - (ssize_t)IMSG_HEADER_SIZE - 
sizeof(tls);
+               for (sent = 0, i = 1; sent < tls.tls_src_len;
+                   sent += chunk_len, i++) {
+                       if (tls.tls_src_len - sent < chunk_len)
+                               chunk_len = tls.tls_src_len - sent;
+                       tls.tls_chunk_off = sent;
+                       c = 0;
+                       iov[c].iov_base = &tls;
+                       iov[c++].iov_len = sizeof(tls);
+                       iov[c].iov_base = *src + sent;
+                       iov[c++].iov_len = chunk_len;
+
+                       if (proc_composev(ps, PROC_SERVER, cmd, iov, c) != 0) {
+                               log_warn("%s: failed to compose chunk %lu of "
+                                   "%s imsg for `%s'", __func__, i, type,
+                                   srv_conf->name);
+                               return (-1);
+                       }
+               }
+       }
+
        return (0);
 }
 
@@ -644,6 +718,79 @@ config_gettls(struct httpd *env, struct 
                    tls_conf.tls_ocsp_staple_len)) == NULL)
                        goto fail;
                s += tls_conf.tls_ocsp_staple_len;
+       }
+
+       return (0);
+
+ fail:
+       return (-1);
+}
+
+int
+config_gettls_chunk(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+       struct privsep           *ps = env->sc_ps;
+#endif
+       struct server_config     *srv_conf = NULL;
+       struct tls_config         tls_conf;
+       uint8_t                  *p = imsg->data;
+       uint8_t                 **dst;
+       size_t                   *dst_len;
+       size_t                    s, chunk_len, min_chunk_len;
+       const char *              type;
+
+       IMSG_SIZE_CHECK(imsg, &tls_conf);
+       memcpy(&tls_conf, p, sizeof(tls_conf));
+       s = sizeof(tls_conf);
+       chunk_len = MAX_IMSGSIZE - IMSG_HEADER_SIZE - s;
+       if (tls_conf.tls_src_len - tls_conf.tls_chunk_off > chunk_len)
+           min_chunk_len = chunk_len;
+       else
+           min_chunk_len = tls_conf.tls_src_len - tls_conf.tls_chunk_off;
+
+       if ((IMSG_DATA_SIZE(imsg) - s) < min_chunk_len ||
+           tls_conf.tls_chunk_off >= tls_conf.tls_src_len) {
+               log_debug("%s: invalid message length", __func__);
+               goto fail;
+       }
+
+       if ((srv_conf = serverconfig_byid(tls_conf.id)) == NULL) {
+               log_debug("%s: server not found", __func__);
+               goto fail;
+       }
+
+       if (imsg->hdr.type == IMSG_CFG_TLS_CA) {
+               dst_len = &srv_conf->tls_ca_len;
+               dst = &srv_conf->tls_ca;
+               type = "ca cert(s)";
+       } else if (imsg->hdr.type == IMSG_CFG_TLS_CRL) {
+               dst_len = &srv_conf->tls_crl_len;
+               dst = &srv_conf->tls_crl;
+               type = "crl(s)";
+       } else {
+               DPRINTF("%s: server \"%s[%u]\": invalid cmd: %d", __func__,
+                   srv_conf->name, srv_conf->id, imsg->hdr.type);
+               goto fail;
+       }
+
+       DPRINTF("%s: %s %d tls %s configuration \"%s[%u]\" chunk %lu",
+           __func__, ps->ps_title[privsep_process], ps->ps_instance, type,
+           srv_conf->name, srv_conf->id,
+           (tls_conf.tls_src_len - tls_conf.tls_chunk_off) / chunk_len);
+
+       if (*dst_len != 0) {
+               if (tls_conf.tls_chunk_off == 0) {
+                       free(*dst);
+                       if ((*dst = calloc(tls_conf.tls_src_len + 1,
+                           sizeof(**dst))) == NULL)
+                               goto fail;
+               }
+               if (tls_conf.tls_src_len - tls_conf.tls_chunk_off < chunk_len)
+                       chunk_len = tls_conf.tls_src_len -
+                           tls_conf.tls_chunk_off;
+               *dst_len = tls_conf.tls_src_len;
+               memcpy(*dst + tls_conf.tls_chunk_off, p + s, chunk_len);
        }
 
        return (0);
Index: usr.sbin/httpd/httpd.8
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.8,v
retrieving revision 1.53
diff -u -p -r1.53 httpd.8
--- usr.sbin/httpd/httpd.8      15 Sep 2016 20:57:07 -0000      1.53
+++ usr.sbin/httpd/httpd.8      7 Jul 2017 08:24:01 -0000
@@ -71,6 +71,8 @@ options increase the verbosity.
 .Bl -tag -width "/etc/ssl/private/server.key" -compact
 .It Pa /etc/httpd.conf
 Default configuration file.
+.It Pa /etc/ssl/ca.crt
+Default SSL/TLS client CA certificate.
 .It Pa /etc/ssl/private/server.key
 Default SSL/TLS server key.
 .It Pa /etc/ssl/server.crt
Index: usr.sbin/httpd/httpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v
retrieving revision 1.82
diff -u -p -r1.82 httpd.conf.5
--- usr.sbin/httpd/httpd.conf.5 9 Apr 2017 09:13:28 -0000       1.82
+++ usr.sbin/httpd/httpd.conf.5 7 Jul 2017 08:24:02 -0000
@@ -342,6 +342,25 @@ The revision of the HTTP specification u
 .It Ic SERVER_SOFTWARE
 The server software name of
 .Xr httpd 8 .
+.It Ic TLS_PEER_CHAIN
+The TLS client certificate chain, if any, PEM encoded, with
+newlines translated to tabs.
+.It Ic TLS_PEER_EXPIRES
+The time at which the TLS client certificate, if any, expires,
+in seconds since the epoch.
+.It Ic TLS_PEER_HASH
+A hash of the TLS client certificate, if any.
+See
+.Xr tls_peer_cert_hash 3 .
+.It Ic TLS_PEER_ISSUER
+The issuer of the TLS client certificate, if any.
+.It Ic TLS_PEER_OCSP_URL
+The OCSP URL from the TLS client certificate, if any.
+.It Ic TLS_PEER_SUBJECT
+The subject of the TLS client certificate, if any.
+.It Ic TLS_VERIFY_MODE
+A variable that is set to a comma separated list of TLS client verification
+features in use.
 .El
 .It Ic hsts Oo Ar option Oc
 Enable HTTP Strict Transport Security.
@@ -526,6 +545,32 @@ 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 file
+Specify the CA certificate(s) for this server to use when verifying client
+certificates.
+The
+.Ar file
+should contain one or more PEM encoded CA certificates.
+The default is
+.Pa /etc/ssl/ca.crt .
+.It Ic client crl Ar file
+Specify the CRL(s) for this server to use when verifying client certificate
+revocation status.
+The
+.Ar file
+should contain one or more PEM encoded CRLs.
+.It Ic client verify Op Ic crl
+Requires an authentic TLS client certificate chain (checked using the CA
+certificate(s) specified by the
+.Ic tls client ca
+option) for a TLS connection to proceed beyond the handshake.
+With
+.Ic crl
+specified, also checks the revocation status of the entire client certificate
+chain against the CRL(s) specified by the
+.Ic tls client crl
+option and refuses to proceed beyond the handshake if any certificate in the
+chain is found to be revoked.
 .It Ic dhe Ar params
 Specify the DHE parameters to use for DHE cipher suites.
 Valid parameter values are none, legacy and auto.
@@ -688,6 +733,7 @@ server "www.example.com" {
 .Ed
 .Sh SEE ALSO
 .Xr htpasswd 1 ,
+.Xr tls_peer_cert_hash 3 ,
 .Xr patterns 7 ,
 .Xr httpd 8 ,
 .Xr ocspcheck 8 ,
Index: usr.sbin/httpd/httpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v
retrieving revision 1.132
diff -u -p -r1.132 httpd.h
--- usr.sbin/httpd/httpd.h      28 May 2017 10:37:26 -0000      1.132
+++ usr.sbin/httpd/httpd.h      7 Jul 2017 08:24:02 -0000
@@ -55,6 +55,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      "compat"
@@ -218,6 +219,8 @@ enum imsg_type {
        IMSG_CTL_REOPEN,
        IMSG_CFG_SERVER,
        IMSG_CFG_TLS,
+       IMSG_CFG_TLS_CA,
+       IMSG_CFG_TLS_CRL,
        IMSG_CFG_MEDIA,
        IMSG_CFG_AUTH,
        IMSG_CFG_DONE,
@@ -323,8 +326,8 @@ struct range_data {
 struct client {
        uint32_t                 clt_id;
        pid_t                    clt_pid;
-       void                    *clt_srv;
-       void                    *clt_srv_conf;
+       struct server           *clt_srv;
+       struct server_config    *clt_srv_conf;
        uint32_t                 clt_srv_id;
        struct sockaddr_storage  clt_srv_ss;
        struct str_match         clt_srv_match;
@@ -455,6 +458,10 @@ struct server_tls_ticket {
        unsigned char   tt_key[TLS_TICKET_KEY_SIZE];
 };
 
+#define TLSCLIENTFLAG_VERIFY   0x01
+#define TLSCLIENTFLAG_CRL      0x02
+#define TLSCLIENTFLAG_BITS     "\10\01VERIFY\02CRL"
+
 struct server_config {
        uint32_t                 id;
        uint32_t                 parent_id;
@@ -475,10 +482,17 @@ 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;
+       uint8_t                 *tls_crl;
+       char                    *tls_crl_file;
+       size_t                   tls_crl_len;
        char                     tls_ciphers[NAME_MAX];
+       uint8_t                  tls_client_flags;
        char                     tls_dhe_params[NAME_MAX];
        char                     tls_ecdhe_curve[NAME_MAX];
        uint8_t                 *tls_key;
@@ -521,9 +535,14 @@ TAILQ_HEAD(serverhosts, server_config);
 struct tls_config {
        uint32_t                 id;
 
+       size_t                   tls_ca_len;
        size_t                   tls_cert_len;
+       size_t                   tls_crl_len;
        size_t                   tls_key_len;
        size_t                   tls_ocsp_staple_len;
+
+       size_t                   tls_chunk_off;
+       size_t                   tls_src_len;
 };
 
 struct server {
@@ -586,6 +605,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 *);
@@ -787,8 +808,10 @@ int         config_getreset(struct httpd *, str
 int     config_getcfg(struct httpd *, struct imsg *);
 int     config_setserver(struct httpd *, struct server *);
 int     config_settls(struct httpd *, struct server *);
+int     config_settls_chunk(struct httpd *, struct server *, int cmd);
 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: usr.sbin/httpd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/parse.y,v
retrieving revision 1.90
diff -u -p -r1.90 parse.y
--- usr.sbin/httpd/parse.y      25 Mar 2017 17:25:34 -0000      1.90
+++ usr.sbin/httpd/parse.y      7 Jul 2017 08:24:02 -0000
@@ -129,11 +129,13 @@ typedef struct {
 
 %}
 
-%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON
-%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LIFETIME
-%token LISTEN LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY OCSP ON PORT PREFORK
-%token PROTOCOLS REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TICKET
-%token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST
+%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CA CERTIFICATE CHECK CHROOT
+%token CIPHERS CLIENT COMMON COMBINED CONNECTION CRL DHE DIRECTORY ECDHE ERR
+%token FCGI INDEX IP KEY LIFETIME LISTEN LOCATION LOG LOGDIR MATCH MAXIMUM NO
+%token NODELAY OCSP ON PORT PREFORK PROTOCOLS REQUESTS ROOT SACK SERVER SOCKET
+%token STRIP STYLE SYSLOG TCP TICKET TIMEOUT TLS TYPE TYPES HSTS MAXAGE
+%token SUBDOMAINS VERIFY
+%token DEFAULT PRELOAD REQUEST
 %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
@@ -254,6 +256,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");
@@ -344,6 +349,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);
@@ -737,6 +758,7 @@ tlsopts             : CERTIFICATE STRING            {
                        }
                        free($2);
                }
+               | CLIENT tlsclientopt
                | DHE STRING                    {
                        if (strlcpy(srv_conf->tls_dhe_params, $2,
                            sizeof(srv_conf->tls_dhe_params)) >=
@@ -785,6 +807,26 @@ tlsopts            : CERTIFICATE STRING            {
                }
                ;
 
+tlsclientopt   : CA STRING     {
+                       free(srv_conf->tls_ca_file);
+                       if ((srv_conf->tls_ca_file = strdup($2)) == NULL)
+                               fatal("out of memory");
+                       free($2);
+               }
+               | CRL STRING    {
+                       free(srv_conf->tls_crl_file);
+                       if ((srv_conf->tls_crl_file = strdup($2)) == NULL)
+                               fatal("out of memory");
+               }
+               | VERIFY verifyopt {
+                       srv_conf->tls_client_flags |= TLSCLIENTFLAG_VERIFY;
+               }
+
+verifyopt      : /* empty */
+               | CRL {
+                       srv_conf->tls_client_flags |= TLSCLIENTFLAG_CRL;
+               }
+
 root           : ROOT rootflags
                | ROOT '{' optnl rootflags_l '}'
                ;
@@ -1217,12 +1259,16 @@ lookup(char *s)
                { "block",              BLOCK },
                { "body",               BODY },
                { "buffer",             BUFFER },
+               { "ca",                 CA },
                { "certificate",        CERTIFICATE },
+               { "check",              CHECK },
                { "chroot",             CHROOT },
                { "ciphers",            CIPHERS },
+               { "client",             CLIENT },
                { "combined",           COMBINED },
                { "common",             COMMON },
                { "connection",         CONNECTION },
+               { "crl",                CRL },
                { "default",            DEFAULT },
                { "dhe",                DHE },
                { "directory",          DIRECTORY },
@@ -1269,6 +1315,7 @@ lookup(char *s)
                { "tls",                TLS },
                { "type",               TYPE },
                { "types",              TYPES },
+               { "verify",             VERIFY },
                { "with",               WITH }
        };
        const struct keywords   *p;
@@ -2043,9 +2090,15 @@ 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_crl_file =
+           strdup(src->srv_conf.tls_crl_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");
@@ -2080,6 +2133,7 @@ server_inherit(struct server *src, struc
                dst->srv_conf.flags |= SRVFLAG_TLS;
        else
                dst->srv_conf.flags &= ~SRVFLAG_TLS;
+       dst->srv_conf.tls_client_flags = src->srv_conf.tls_client_flags;
 
        /* Don't inherit the "match" option, use it from the alias */
        dst->srv_conf.flags &= ~SRVFLAG_SERVER_MATCH;
@@ -2087,6 +2141,22 @@ server_inherit(struct server *src, struc
 
        if (server_tls_load_keypair(dst) == -1) {
                yyerror("failed to load public/private keys "
+                   "for server %s", dst->srv_conf.name);
+               serverconfig_free(&dst->srv_conf);
+               free(dst);
+               return (NULL);
+       }
+
+       if (server_tls_load_ca(dst) == -1) {
+               yyerror("failed to load ca cert(s) "
+                   "for server %s", dst->srv_conf.name);
+               serverconfig_free(&dst->srv_conf);
+               free(dst);
+               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);
Index: usr.sbin/httpd/server.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server.c,v
retrieving revision 1.109
diff -u -p -r1.109 server.c
--- usr.sbin/httpd/server.c     17 Apr 2017 21:58:27 -0000      1.109
+++ usr.sbin/httpd/server.c     7 Jul 2017 08:24:03 -0000
@@ -197,6 +197,46 @@ server_tls_load_ocsp(struct server *srv)
 }
 
 int
+server_tls_load_ca(struct server *srv)
+{
+       if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0 ||
+           srv->srv_conf.tls_client_flags == 0)
+               return (0);
+
+       if (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.flags & SRVFLAG_TLS) == 0 ||
+           (srv->srv_conf.tls_client_flags & TLSCLIENTFLAG_CRL) == 0)
+               return (0);
+
+       if (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;
@@ -254,6 +294,26 @@ server_tls_init(struct server *srv)
                return (-1);
        }
 
+       if (srv->srv_conf.tls_client_flags != 0) {
+               if (srv->srv_conf.tls_ca != NULL &&
+                   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);
+               }
+               tls_config_verify_client(srv->srv_tls_config);
+
+               if ((srv->srv_conf.tls_client_flags & TLSCLIENTFLAG_CRL) &&
+                   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;
@@ -267,6 +327,30 @@ server_tls_init(struct server *srv)
                        log_warnx("%s: failed to add tls keypair", __func__);
                        return (-1);
                }
+
+               if (srv_conf->tls_client_flags == 0 ||
+                   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_conf->tls_client_flags & TLSCLIENTFLAG_CRL) == 0 ||
+                   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) {
+                       log_warnx("%s: failed to add crl(s)", __func__);
+                       return (-1);
+               }
        }
 
        /* set common session ID among all processes */
@@ -302,11 +386,17 @@ server_tls_init(struct server *srv)
 
        /* We're now done with the public/private key... */
        tls_config_clear_keys(srv->srv_tls_config);
+       freezero(srv->srv_conf.tls_ca, srv->srv_conf.tls_ca_len);
        freezero(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len);
+       freezero(srv->srv_conf.tls_crl, srv->srv_conf.tls_crl_len);
        freezero(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len);
+       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);
@@ -412,7 +502,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);
@@ -425,8 +519,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;
@@ -1091,7 +1189,7 @@ void
 server_tls_handshake(int fd, short event, void *arg)
 {
        struct client *clt = (struct client *)arg;
-       struct server *srv = (struct server *)clt->clt_srv;
+       struct server *srv = clt->clt_srv;
        int ret;
 
        if (event == EV_TIMEOUT) {
@@ -1281,6 +1379,10 @@ server_dispatch_parent(int fd, struct pr
                break;
        case IMSG_CFG_TLS:
                config_gettls(httpd_env, imsg);
+               break;
+       case IMSG_CFG_TLS_CA:
+       case IMSG_CFG_TLS_CRL:
+               config_gettls_chunk(httpd_env, imsg);
                break;
        case IMSG_CFG_DONE:
                config_getcfg(httpd_env, imsg);
Index: usr.sbin/httpd/server_fcgi.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v
retrieving revision 1.74
diff -u -p -r1.74 server_fcgi.c
--- usr.sbin/httpd/server_fcgi.c        21 Jan 2017 11:32:04 -0000      1.74
+++ usr.sbin/httpd/server_fcgi.c        7 Jul 2017 08:24:03 -0000
@@ -93,11 +93,12 @@ server_fcgi(struct httpd *env, struct cl
        struct fcgi_record_header       *h;
        struct fcgi_begin_request_body  *begin;
        char                             hbuf[HOST_NAME_MAX+1];
-       size_t                           scriptlen;
+       size_t                           chainlen, scriptlen;
        int                              pathlen;
        int                              fd = -1, ret;
        const char                      *stripped, *p, *alias, *errstr = NULL;
-       char                            *str, *script = NULL;
+       char                            *chainc, *chainp, *str, *script = NULL;
+       const uint8_t                   *chain;
 
        if (srv_conf->socket[0] == ':') {
                struct sockaddr_storage  ss;
@@ -282,11 +283,55 @@ 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_client_flags) {
+                       if (fcgi_add_param(&param, "TLS_VERIFY_MODE",
+                           printb_flags(srv_conf->tls_client_flags,
+                           TLSCLIENTFLAG_BITS), clt) == -1
+                           || fcgi_add_param(&param, "TLS_PEER_SUBJECT",
+                           tls_peer_cert_subject(clt->clt_tls_ctx), clt) == -1
+                           || fcgi_add_param(&param, "TLS_PEER_ISSUER",
+                           tls_peer_cert_issuer(clt->clt_tls_ctx), clt) == -1
+                           || fcgi_add_param(&param, "TLS_PEER_HASH",
+                           tls_peer_cert_hash(clt->clt_tls_ctx), clt) == -1) {
+                               errstr = "failed to encode param";
+                               goto fail;
+                       }
+                       chain = tls_peer_cert_chain_pem(clt->clt_tls_ctx,
+                           &chainlen);
+                       if ((chainc = strndup(chain, chainlen)) == NULL)
+                               goto fail;
+                       chainp = chainc;
+                       while ((chainp = strchr(chainp, '\n')) != NULL)
+                               *chainp = '\t';
+                       ret = fcgi_add_param(&param, "TLS_PEER_CHAIN", chainc,
+                           clt);
+                       free(chainc);
+                       if (ret == -1) {
+                               errstr = "failed to encode param";
+                               goto fail;
+                       }
+                       if (asprintf(&str, "%lld",
+                           tls_peer_cert_notafter(clt->clt_tls_ctx)) == -1
+                           || fcgi_add_param(&param, "TLS_PEER_EXPIRES", str,
+                           clt) == -1) {
+                               errstr = "failed to encode param";
+                               goto fail;
+                       }
+                       free(str);
+                       str = NULL;
+                       if (tls_peer_ocsp_url(clt->clt_tls_ctx) != NULL
+                           && fcgi_add_param(&param, "TLS_PEER_OCSP_URL",
+                           tls_peer_ocsp_url(clt->clt_tls_ctx), 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) {
Index: regress/usr.sbin/httpd/tests/Client.pm
===================================================================
RCS file: /cvs/src/regress/usr.sbin/httpd/tests/Client.pm,v
retrieving revision 1.1
diff -u -p -r1.1 Client.pm
--- regress/usr.sbin/httpd/tests/Client.pm      16 Jul 2015 16:35:57 -0000      
1.1
+++ regress/usr.sbin/httpd/tests/Client.pm      7 Jul 2017 08:24:03 -0000
@@ -59,6 +59,11 @@ sub child {
            PeerAddr            => $self->{connectaddr},
            PeerPort            => $self->{connectport},
            SSL_verify_mode     => SSL_VERIFY_NONE,
+           SSL_use_cert        => $self->{offertlscert} ? 1 : 0,
+           SSL_cert_file       => $self->{offertlscert} ?
+                                       $self->{chroot}."/client.crt" : "",
+           SSL_key_file        => $self->{offertlscert} ?
+                                       $self->{chroot}."/client.key" : "",
        ) or die ref($self), " $iosocket socket connect failed: $!,$SSL_ERROR";
        print STDERR "connect sock: ",$cs->sockhost()," ",$cs->sockport(),"\n";
        print STDERR "connect peer: ",$cs->peerhost()," ",$cs->peerport(),"\n";
Index: regress/usr.sbin/httpd/tests/Httpd.pm
===================================================================
RCS file: /cvs/src/regress/usr.sbin/httpd/tests/Httpd.pm,v
retrieving revision 1.2
diff -u -p -r1.2 Httpd.pm
--- regress/usr.sbin/httpd/tests/Httpd.pm       30 Jan 2017 21:18:24 -0000      
1.2
+++ regress/usr.sbin/httpd/tests/Httpd.pm       7 Jul 2017 08:24:03 -0000
@@ -72,6 +72,10 @@ sub new {
            print $fh "\n";
            print $fh "\ttls certificate \"".$args{chroot}."/server.crt\"\n";
            print $fh "\ttls key \"".$args{chroot}."/server.key\"";
+           if ($self->{verifytls}) {
+               print $fh "\ntls client ca \"".$args{chroot}."/ca.crt\"";
+               print $fh "\ntls client verify";
+           }
        }
        print $fh "\n\troot \"/\"";
        print $fh "\n\tlog style combined";
Index: regress/usr.sbin/httpd/tests/Makefile
===================================================================
RCS file: /cvs/src/regress/usr.sbin/httpd/tests/Makefile,v
retrieving revision 1.7
diff -u -p -r1.7 Makefile
--- regress/usr.sbin/httpd/tests/Makefile       30 Jan 2017 21:18:24 -0000      
1.7
+++ regress/usr.sbin/httpd/tests/Makefile       7 Jul 2017 08:24:03 -0000
@@ -80,10 +80,16 @@ ca.crt:
 server.req:
        openssl req -batch -new -subj 
/L=OpenBSD/O=httpd-regress/OU=server/CN=localhost/ -nodes -newkey rsa -keyout 
server.key -out server.req
 
+client.req:
+       openssl req -batch -new -subj 
/L=OpenBSD/O=httpd-regress/OU=client/CN=localhost/ -nodes -newkey rsa -keyout 
client.key -out $@
+
 server.crt: ca.crt server.req
        openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in 
server.req -out server.crt
 
-${REGRESS_TARGETS:M*tls*} ${REGRESS_TARGETS:M*https*}: server.crt
+client.crt: ca.crt client.req
+       openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in 
client.req -out $@
+
+${REGRESS_TARGETS:M*tls*} ${REGRESS_TARGETS:M*https*}: server.crt client.crt
 
 # make perl syntax check for all args files
 
Index: regress/usr.sbin/httpd/tests/args-tls-verify.pl
===================================================================
RCS file: regress/usr.sbin/httpd/tests/args-tls-verify.pl
diff -N regress/usr.sbin/httpd/tests/args-tls-verify.pl
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ regress/usr.sbin/httpd/tests/args-tls-verify.pl     7 Jul 2017 08:24:03 
-0000
@@ -0,0 +1,20 @@
+# test https connection, verifying client cert
+
+use strict;
+use warnings;
+
+our %args = (
+    client => {
+       tls => 1,
+       offertlscert => 1,
+       loggrep => 'Issuer.*/OU=ca/',
+    },
+    httpd => {
+       listentls => 1,
+       verifytls => 1,
+    },
+    len => 512,
+    md5 => path_md5("512")
+);
+
+1;

Reply via email to