Hi,

This mail includes a quite detailed explanation of the attached diff
that adds support for SSL Interception ("SSL-MITM") to relayd.  If you
don't want to read the story, just skip to the configuration example
and diff below.

As you probably know, relayd already supports running as a transparent
non-caching HTTP proxy that can be used for URL filtering and policy
enforcements.  It also supports running as an SSL server and can be
used to terminate SSL connections and to forward them as plain TCP.
The marketing term for this is "SSL acceleration" and is typically
used to provide HTTPS on the load balancer for HTTP-only webservers in
the pool.

Some time ago, I also added support for running relayd as an SSL
client to terminate plain TCP connections and transparently tunnel
them through SSL.  I don't know if there is an actual marketing term
for it, but let's call it some kind of SSL VPN ... whatever SSL VPN
means.

When combining both modes, SSL server and client, you end up with a
man-in-the-middle that can filter between two SSL connections.  This
is actually already possible with the current version of relayd.
However, for an effective SSL-MITM, or "SSL Interception", you need to
do some trickery with the certificates.  An SSL client, especially web
browsers for HTTPS, should verify the certificate and check if it is
a) valid, b) signed by a trusted Certificate Authority and c) it is
matching some additional security policies, most importantly having a
matching domain name in the certificate.

Some existing solutions and firewall vendors support SSL interception
(e.g. for URL filtering on secured connections) by generating client
certificates on-the-fly with a local CA that is accepted by the
clients.  This way, secured HTTPS connections from web browsers behind
a corporate gateway can be URL-filtered and "intercepted".  The
remaining problem is that the web browsers normally don't recognize
the local CA, but having a corporate infrastructure allows you to
deploy the custom CA certificate on your internal clients.  Another
solution is to obtain an official CA with private key or to get an
intermediate CA - a local CA signed by an official CA.  Getting an an
official CA or intermediate CA for SSL Interception is normally only
possible for governmental authorities (e.g. TURKTRUST in Turkey), or
people who have access to a possibly compromised CA (e.g. DigiNotar in
the Netherlands).  Regarding the large number of official CAs that are
accepted by modern browsers, I suppose that this is already a fairly
common practice that keeps on questioning the CA model itself.

The attached diff adds experimental support for SSL Interception to
relayd.  When configured for SSL Interception, relayd will listen for
incoming connections that have been diverted to the local socket by
PF.  Before accepting and negotiating the incoming SSL connection as a
server, it will look up the the original destination address on the
diverted socket, and pre-connect to the target server as an SSL client
to obtain the remote SSL certificate.  It will update or "patch" the
obtained SSL certificate by replacing the included public key with its
local server key because it doesn't have the private key of the remote
server certificate.  It also updates the X.509 issuer name to the
local CA subject name and signs the certificate with its local CA key.
This way it keeps all the other X.509 attributes that are already
present in the server certificate, including the "green bar" extended
validation attributes.  Now it finally accepts the SSL connection from
the diverted client using the updated certificate and continues to
handle the connection and to connect to the remote server.

You just need to generate a CA and configure relayd and PF to
run it.  Please also make sure that you apply it to the very latest
code from -current to include a fix for Chunked Transfer Encoding that
unbreaks Chrome.  I also warn you that you should make sure that you
legally created or obtained the CA certificate and that your privacy
policy actually allows to intercept encrypted HTTPS/SSL connections.

There are some known limitations: Safari on OSX uses the system key
chain that does not seem to allow importing custom CA Root
certificates and keeps on warning about the intercepted SSL
connections.  Additionally, Google is able to monitor the certificates
when accessing its servers with Chrome allowing them to detect
interception attempts.  This shouldn't be a problem if you're
intercepting users behind a corporate gateway with a local CA - but it
allowed Google to detect the misused CA certificate that was issued by
TURKTRUST to a related governmental authority in Turkey.  There is
probably a way to bypass the restriction on OSX and my upcoming filter
rewrite might allow to selectively disable SSL interception for
selected domains; you can already filter Chrome or well-known
Google-related domains.

Configuration Example:

1. Your CA key and certificate
# openssl req -x509 -days 365 -newkey rsa:2048 \
        -keyout /etc/ssl/private/ca.key -out /etc/ssl/ca.crt

2. You will also need an SSL server key and cert for 127.0.0.1, see
"listen on" in the RELAYS section of relayd.conf(5) and ssl(8) for
more details).

3. /etc/pf.conf:
# Divert incoming HTTPS traffic to relayd
pass in on vlan1 inet proto tcp to port 443 divert-to localhost port 8443

4. /etc/relayd.conf:
http protocol httpfilter {
        return error

        label "Get back to work!"
        request url filter "facebook.com/"

        # New configuration directives for SSL Interception
        ssl ca key "/etc/ssl/private/ca.key" password "humppa"
        ssl ca cert "/etc/ssl/ca.crt"
}
relay sslmitm {
        listen on 127.0.0.1 port 8443 ssl
        protocol httpfilter
        forward with ssl to destination
}

Reyk

Index: config.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/config.c,v
retrieving revision 1.8
diff -u -p -r1.8 config.c
--- config.c    18 Dec 2012 15:57:16 -0000      1.8
+++ config.c    22 Jan 2013 15:33:03 -0000
@@ -177,6 +177,8 @@ config_purge(struct relayd *env, u_int r
                        purge_tree(&proto->response_tree);
                        if (proto->style != NULL)
                                free(proto->style);
+                       if (proto->sslcapass != NULL)
+                               free(proto->sslcapass);
                        free(proto);
                }
                env->sc_protocount = 0;
@@ -830,6 +832,14 @@ config_setrelay(struct relayd *env, stru
                        iov[c].iov_base = rlay->rl_ssl_ca;
                        iov[c++].iov_len = rlay->rl_conf.ssl_ca_len;
                }
+               if (rlay->rl_conf.ssl_cacert_len) {
+                       iov[c].iov_base = rlay->rl_ssl_cacert;
+                       iov[c++].iov_len = rlay->rl_conf.ssl_cacert_len;
+               }
+               if (rlay->rl_conf.ssl_cakey_len) {
+                       iov[c].iov_base = rlay->rl_ssl_cakey;
+                       iov[c++].iov_len = rlay->rl_conf.ssl_cakey_len;
+               }
 
                if (id == PROC_RELAY) {
                        /* XXX imsg code will close the fd after 1st call */
@@ -898,7 +908,9 @@ config_getrelay(struct relayd *env, stru
        if ((u_int)(IMSG_DATA_SIZE(imsg) - s) <
            (rlay->rl_conf.ssl_cert_len +
            rlay->rl_conf.ssl_key_len +
-           rlay->rl_conf.ssl_ca_len)) {
+           rlay->rl_conf.ssl_ca_len +
+           rlay->rl_conf.ssl_cacert_len +
+           rlay->rl_conf.ssl_cakey_len)) {
                log_debug("%s: invalid message length", __func__);
                goto fail;
        }
@@ -920,6 +932,18 @@ config_getrelay(struct relayd *env, stru
                    rlay->rl_conf.ssl_ca_len)) == NULL)
                        goto fail;
                s += rlay->rl_conf.ssl_ca_len;
+       }
+       if (rlay->rl_conf.ssl_cacert_len) {
+               if ((rlay->rl_ssl_cacert = get_data(p + s,
+                   rlay->rl_conf.ssl_cacert_len)) == NULL)
+                       goto fail;
+               s += rlay->rl_conf.ssl_cacert_len;
+       }
+       if (rlay->rl_conf.ssl_cakey_len) {
+               if ((rlay->rl_ssl_cakey = get_data(p + s,
+                   rlay->rl_conf.ssl_cakey_len)) == NULL)
+                       goto fail;
+               s += rlay->rl_conf.ssl_cakey_len;
        }
 
        TAILQ_INIT(&rlay->rl_tables);
Index: parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/parse.y,v
retrieving revision 1.168
diff -u -p -r1.168 parse.y
--- parse.y     19 Oct 2012 16:49:50 -0000      1.168
+++ parse.y     22 Jan 2013 15:33:04 -0000
@@ -158,7 +158,7 @@ typedef struct {
 %token RETURN ROUNDROBIN ROUTE SACK SCRIPT SEND SESSION SOCKET SPLICE
 %token SSL STICKYADDR STYLE TABLE TAG TCP TIMEOUT TO ROUTER RTLABEL
 %token TRANSPARENT TRAP UPDATES URL VIRTUAL WITH TTL RTABLE MATCH
-%token RANDOM LEASTSTATES SRCHASH
+%token RANDOM LEASTSTATES SRCHASH KEY CERTIFICATE PASSWORD
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.string>      hostname interface table
@@ -975,6 +975,34 @@ sslflags   : SESSION CACHE sslcache        { prot
                        }
                        free($3);
                }
+               | CA KEY STRING PASSWORD STRING {
+                       if (strlcpy(proto->sslcakey, $3,
+                           sizeof(proto->sslcakey)) >=
+                           sizeof(proto->sslcakey)) {
+                               yyerror("sslcakey truncated");
+                               free($3);
+                               free($5);
+                               YYERROR;
+                       }
+                       if ((proto->sslcapass = strdup($5)) == NULL) {
+                               yyerror("sslcapass");
+                               free($3);
+                               free($5);
+                               YYERROR;
+                       }
+                       free($3);
+                       free($5);
+               }
+               | CA CERTIFICATE STRING         {
+                       if (strlcpy(proto->sslcacert, $3,
+                           sizeof(proto->sslcacert)) >=
+                           sizeof(proto->sslcacert)) {
+                               yyerror("sslcacert truncated");
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+               }
                | NO flag                       { proto->sslflags &= ~($2); }
                | flag                          { proto->sslflags |= $1; }
                ;
@@ -1788,6 +1816,7 @@ lookup(char *s)
                { "buffer",             BUFFER },
                { "ca",                 CA },
                { "cache",              CACHE },
+               { "cert",               CERTIFICATE },
                { "change",             CHANGE },
                { "check",              CHECK },
                { "ciphers",            CIPHERS },
@@ -1814,6 +1843,7 @@ lookup(char *s)
                { "interface",          INTERFACE },
                { "interval",           INTERVAL },
                { "ip",                 IP },
+               { "key",                KEY },
                { "label",              LABEL },
                { "least-states",       LEASTSTATES },
                { "listen",             LISTEN },
@@ -1830,6 +1860,7 @@ lookup(char *s)
                { "nothing",            NOTHING },
                { "on",                 ON },
                { "parent",             PARENT },
+               { "password",           PASSWORD },
                { "path",               PATH },
                { "port",               PORT },
                { "prefork",            PREFORK },
Index: relay.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relay.c,v
retrieving revision 1.161
diff -u -p -r1.161 relay.c
--- relay.c     17 Jan 2013 20:34:18 -0000      1.161
+++ relay.c     22 Jan 2013 15:33:05 -0000
@@ -1,7 +1,7 @@
 /*     $OpenBSD: relay.c,v 1.161 2013/01/17 20:34:18 bluhm Exp $       */
 
 /*
- * Copyright (c) 2006 - 2012 Reyk Floeter <r...@openbsd.org>
+ * Copyright (c) 2006 - 2013 Reyk Floeter <r...@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -1063,6 +1063,11 @@ relay_accept(int fd, short event, void *
                return;
        }
 
+       if (rlay->rl_conf.flags & F_SSLINTERCEPT) {
+               relay_preconnect(con);
+               return;
+       }
+
        relay_session(con);
        return;
  err:
@@ -1380,12 +1385,30 @@ relay_connect_retry(int fd, short sig, v
 }
 
 int
+relay_preconnect(struct rsession *con)
+{
+       log_debug("%s: session %d: process %d", __func__,
+           con->se_id, privsep_process);
+       return (relay_connect(con));
+}
+
+int
 relay_connect(struct rsession *con)
 {
        struct relay    *rlay = con->se_relay;
        struct timeval   evtpause = { 1, 0 };
        int              bnds = -1, ret;
 
+       /* Connection is already established but session not active */
+       if ((rlay->rl_conf.flags & F_SSLINTERCEPT) && con->se_out.s != -1) {
+               if (con->se_out.ssl == NULL) {
+                       log_debug("%s: ssl connect failed", __func__);
+                       return (-1);
+               }
+               relay_connected(con->se_out.s, EV_WRITE, con);
+               return (0);
+       }
+
        if (relay_inflight < 1)
                fatalx("relay_connect: no connection in flight");
 
@@ -1510,6 +1533,8 @@ relay_close(struct rsession *con, const 
                        SSL_shutdown(con->se_in.ssl);
                SSL_free(con->se_in.ssl);
        }
+       if (con->se_in.sslcert != NULL)
+               X509_free(con->se_in.sslcert);
        if (con->se_in.s != -1) {
                close(con->se_in.s);
                if (con->se_out.s == -1) {
@@ -1539,6 +1564,8 @@ relay_close(struct rsession *con, const 
                        SSL_shutdown(con->se_out.ssl);
                SSL_free(con->se_out.ssl);
        }
+       if (con->se_out.sslcert != NULL)
+               X509_free(con->se_out.sslcert);
        if (con->se_out.s != -1) {
                close(con->se_out.s);
 
@@ -1839,6 +1866,10 @@ relay_ssl_transaction(struct rsession *c
                cb = relay_ssl_accept;
                method = SSLv23_server_method();
                flag = EV_READ;
+
+               /* Use session-specific certificate for SSL interception. */
+               if (cre->sslcert != NULL)
+                       SSL_use_certificate(ssl, cre->sslcert);
        } else {
                cb = relay_ssl_connect;
                method = SSLv23_client_method();
@@ -1936,6 +1967,7 @@ relay_ssl_connect(int fd, short event, v
        int              retry_flag = 0;
        int              ssl_err = 0;
        int              ret;
+       X509            *servercert;
 
        if (event == EV_TIMEOUT) {
                relay_close(con, "SSL connect timeout");
@@ -1972,9 +2004,24 @@ relay_ssl_connect(int fd, short event, v
 #else
        log_debug(
 #endif
-           "relay %s, session %d connected (%d active)",
+           "relay %s, ssl session %d connected (%d active)",
            rlay->rl_conf.name, con->se_id, relay_sessions);
 
+       if (rlay->rl_conf.flags & F_SSLINTERCEPT) {
+               if ((servercert =
+                   SSL_get_peer_certificate(con->se_out.ssl)) == NULL ||
+                   (con->se_in.sslcert = ssl_update_certificate(servercert,
+                   rlay->rl_ssl_key, rlay->rl_conf.ssl_key_len,
+                   rlay->rl_ssl_cakey, rlay->rl_conf.ssl_cakey_len,
+                   rlay->rl_ssl_cacert, rlay->rl_conf.ssl_cacert_len))
+                   == NULL) {
+                       relay_close(con, "could not create certificate");
+                       return;
+               }
+               relay_session(con);
+               return;
+       }
+
        relay_connected(fd, EV_WRITE, con);
        return;
 
@@ -2296,11 +2343,31 @@ relay_load_certfiles(struct relay *rlay)
        struct protocol *proto = rlay->rl_proto;
        int      useport = htons(rlay->rl_conf.port);
 
-       if ((rlay->rl_conf.flags & F_SSLCLIENT) && strlen(proto->sslca)) {
-               if ((rlay->rl_ssl_ca = relay_load_file(proto->sslca,
-                   &rlay->rl_conf.ssl_ca_len)) == NULL)
-                       return (-1);
-               log_debug("%s: using ca %s", __func__, proto->sslca);
+       if (rlay->rl_conf.flags & F_SSLCLIENT) {
+               if (strlen(proto->sslca)) {
+                       if ((rlay->rl_ssl_ca =
+                           relay_load_file(proto->sslca,
+                           &rlay->rl_conf.ssl_ca_len)) == NULL)
+                               return (-1);
+                       log_debug("%s: using ca %s", __func__, proto->sslca);
+               }
+               if (strlen(proto->sslcacert)) {
+                       if ((rlay->rl_ssl_cacert =
+                           relay_load_file(proto->sslcacert,
+                           &rlay->rl_conf.ssl_cacert_len)) == NULL)
+                               return (-1);
+                       log_debug("%s: using ca certificate %s", __func__,
+                           proto->sslcacert);
+               }
+               if (strlen(proto->sslcakey) && proto->sslcapass != NULL) {
+                       if ((rlay->rl_ssl_cakey =
+                           ssl_load_key(env, proto->sslcakey,
+                           &rlay->rl_conf.ssl_cakey_len,
+                           proto->sslcapass)) == NULL)
+                               return (-1);
+                       log_debug("%s: using ca key %s", __func__,
+                           proto->sslcakey);
+               }
        }
 
        if ((rlay->rl_conf.flags & F_SSL) == 0)
@@ -2333,8 +2400,8 @@ relay_load_certfiles(struct relay *rlay)
                    "/etc/ssl/private/%s.key", hbuf) == -1)
                        return -1;
        }
-       if ((rlay->rl_ssl_key = relay_load_file(certfile,
-           &rlay->rl_conf.ssl_key_len)) == NULL)
+       if ((rlay->rl_ssl_key = ssl_load_key(env, certfile,
+           &rlay->rl_conf.ssl_key_len, NULL)) == NULL)
                return (-1);
        log_debug("%s: using private key %s", __func__, certfile);
 
Index: relayd.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.c,v
retrieving revision 1.115
diff -u -p -r1.115 relayd.c
--- relayd.c    17 Jan 2013 20:34:18 -0000      1.115
+++ relayd.c    22 Jan 2013 15:33:06 -0000
@@ -290,8 +290,16 @@ parent_configure(struct relayd *env)
                config_setrt(env, rt);
        TAILQ_FOREACH(proto, env->sc_protos, entry)
                config_setproto(env, proto);
-       TAILQ_FOREACH(rlay, env->sc_relays, rl_entry)
+       TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
+               /* Check for SSL Interception */
+               if ((rlay->rl_conf.flags & (F_SSL|F_SSLCLIENT)) ==
+                   (F_SSL|F_SSLCLIENT) &&
+                   rlay->rl_conf.ssl_cacert_len &&
+                   rlay->rl_conf.ssl_cakey_len)
+                       rlay->rl_conf.flags |= F_SSLINTERCEPT;
+
                config_setrelay(env, rlay);
+       }
 
        /* HCE, PFE and the preforked relays need to reload their config. */
        env->sc_reload = 2 + env->sc_prefork_relay;
Index: relayd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.conf.5,v
retrieving revision 1.132
diff -u -p -r1.132 relayd.conf.5
--- relayd.conf.5       29 Nov 2012 01:01:53 -0000      1.132
+++ relayd.conf.5       22 Jan 2013 15:33:06 -0000
@@ -1006,6 +1006,11 @@ Set the SSL options and session settings
 This is only used if SSL is enabled in the relay.
 Valid options are:
 .Bl -tag -width Ds
+.It Ic ca cert Ar path
+Specify a CA certificate for SSL interception.
+For more information, see the
+.Ic ca key
+option below.
 .It Ic ca file Ar path
 This option enables CA verification in SSL client mode.
 The daemon will load the CA (Certificate Authority) certificates from
@@ -1013,6 +1018,35 @@ the specified path to verify the server 
 .Ox
 provides a default CA bundle in
 .Pa /etc/ssl/cert.pem .
+.It Ic ca key Ar path Ic password Ar password
+Specify a CA key for SSL interception.
+The
+.Ar password
+argument will specify the password to decrypt the CA key
+(typically an RSA key).
+This option will enable SSL interception if the following conditions
+are true:
+.Pp
+.Bl -bullet -compact -offset indent
+.It
+SSL client mode is enabled by the
+.Ic listen
+directive:
+.Ic listen on ... ssl .
+.It
+SSL server mode and divert lookups are enabled by the
+.Ic forward
+directive:
+.Ic forward with ssl to destination .
+.It
+The
+.Ic ca cert
+option is specified.
+.It
+The
+.Ic ca key
+option is specified.
+.El
 .It Ic ciphers Ar string
 Set the string defining the SSL cipher suite.
 If not specified, the default value
Index: relayd.h
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/relayd.h,v
retrieving revision 1.163
diff -u -p -r1.163 relayd.h
--- relayd.h    27 Nov 2012 05:00:28 -0000      1.163
+++ relayd.h    22 Jan 2013 15:33:06 -0000
@@ -47,6 +47,7 @@
 #define SRV_NAME_SIZE          64
 #define MAX_NAME_SIZE          64
 #define SRV_MAX_VIRTS          16
+#define SSL_NAME_SIZE          512
 
 #define FD_RESERVE             5
 
@@ -77,6 +78,7 @@
 
 #if DEBUG > 1
 #define DPRINTF                log_debug
+#define DEBUG_CERT     1
 #else
 #define DPRINTF(x...)  do {} while(0)
 #endif
@@ -177,6 +179,7 @@ struct ctl_relay_event {
        struct ctl_relay_event  *dst;
        struct rsession         *con;
        SSL                     *ssl;
+       X509                    *sslcert;
        u_int8_t                *nodes;
        struct proto_tree       *tree;
 
@@ -275,6 +278,7 @@ TAILQ_HEAD(addresslist, address);
 #define F_MATCH                        0x00800000
 #define F_DIVERT               0x01000000
 #define F_SCRIPT               0x02000000
+#define F_SSLINTERCEPT         0x04000000
 
 #define F_BITS                                                         \
        "\10\01DISABLE\02BACKUP\03USED\04DOWN\05ADD\06DEL\07CHANGED"    \
@@ -448,6 +452,7 @@ struct rsession {
        int                              se_done;
        int                              se_retry;
        int                              se_retrycount;
+       int                              se_connectcount;
        u_int16_t                        se_mark;
        struct evbuffer                 *se_log;
        struct relay                    *se_relay;
@@ -569,6 +574,9 @@ struct protocol {
        u_int8_t                 sslflags;
        char                     sslciphers[768];
        char                     sslca[MAXPATHLEN];
+       char                     sslcacert[MAXPATHLEN];
+       char                     sslcakey[MAXPATHLEN];
+       char                    *sslcapass;
        char                     name[MAX_NAME_SIZE];
        int                      cache;
        enum prototype           type;
@@ -618,6 +626,8 @@ struct relay_config {
        off_t                    ssl_cert_len;
        off_t                    ssl_key_len;
        off_t                    ssl_ca_len;
+       off_t                    ssl_cacert_len;
+       off_t                    ssl_cakey_len;
 };
 
 struct relay {
@@ -641,6 +651,8 @@ struct relay {
        char                    *rl_ssl_cert;
        char                    *rl_ssl_key;
        char                    *rl_ssl_ca;
+       char                    *rl_ssl_cacert;
+       char                    *rl_ssl_cakey;
 
        struct ctl_stats         rl_stats[RELAY_MAXPROC + 1];
 
@@ -991,6 +1003,7 @@ void        relay_read(struct bufferevent *, v
 void    relay_error(struct bufferevent *, short, void *);
 int     relay_lognode(struct rsession *,
            struct protonode *, struct protonode *, char *, size_t);
+int     relay_preconnect(struct rsession *);
 int     relay_connect(struct rsession *);
 void    relay_connected(int, short, void *);
 void    relay_bindanyreq(struct rsession *, in_port_t, int);
@@ -1038,6 +1051,9 @@ void       ssl_init(struct relayd *);
 void    ssl_transaction(struct ctl_tcp_event *);
 SSL_CTX        *ssl_ctx_create(struct relayd *);
 void    ssl_error(const char *, const char *);
+char   *ssl_load_key(struct relayd *, const char *, off_t *, char *);
+X509   *ssl_update_certificate(X509 *, char *, off_t,
+           char *, off_t, char *, off_t);
 
 /* ssl_privsep.c */
 int     ssl_ctx_use_private_key(SSL_CTX *, char *, off_t);
Index: ssl.c
===================================================================
RCS file: /cvs/src/usr.sbin/relayd/ssl.c,v
retrieving revision 1.17
diff -u -p -r1.17 ssl.c
--- ssl.c       18 Dec 2012 15:34:07 -0000      1.17
+++ ssl.c       22 Jan 2013 15:33:06 -0000
@@ -1,6 +1,7 @@
 /*     $OpenBSD: ssl.c,v 1.17 2012/12/18 15:34:07 reyk Exp $   */
 
 /*
+ * Copyright (c) 2007-2013 Reyk Floeter <r...@openbsd.org>
  * Copyright (c) 2006 Pierre-Yves Ritschard <p...@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
@@ -41,6 +42,7 @@ void  ssl_read(int, short, void *);
 void   ssl_write(int, short, void *);
 void   ssl_connect(int, short, void *);
 void   ssl_cleanup(struct ctl_tcp_event *);
+int    ssl_password_cb(char *, int, int, void *);
 
 void
 ssl_read(int s, short event, void *arg)
@@ -240,12 +242,19 @@ ssl_error(const char *where, const char 
 void
 ssl_init(struct relayd *env)
 {
+       static int       inited = 0;
+
+       if (inited)
+               return;
+
        SSL_library_init();
        SSL_load_error_strings();
 
        /* Init hardware crypto engines. */
        ENGINE_load_builtin_engines();
        ENGINE_register_all_complete();
+
+       inited = 1;
 }
 
 void
@@ -283,4 +292,141 @@ ssl_ctx_create(struct relayd *env)
                fatal("could not create SSL context");
        }
        return (ctx);
+}
+
+int
+ssl_password_cb(char *buf, int size, int rwflag, void *u)
+{
+       size_t  len;
+       if (u == NULL) {
+               bzero(buf, size);
+               return (0);
+       }
+       if ((len = strlcpy(buf, u, size)) >= (size_t)size)
+               return (0);
+       return (len);
+}
+
+char *
+ssl_load_key(struct relayd *env, const char *name, off_t *len, char *pass)
+{
+       FILE            *fp;
+       EVP_PKEY        *key = NULL;
+       BIO             *bio = NULL;
+       long             size;
+       char            *data, *buf = NULL;
+
+       /* Initialize SSL library once */
+       ssl_init(env);
+
+       /*
+        * Read (possibly) encrypted key from file
+        */
+       if ((fp = fopen(name, "r")) == NULL)
+               return (NULL);
+
+       key = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, pass);
+       fclose(fp);
+       if (key == NULL)
+               goto fail;
+
+       /*
+        * Write unencrypted key to memory buffer
+        */
+       if ((bio = BIO_new(BIO_s_mem())) == NULL)
+               goto fail;
+       if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL))
+               goto fail;
+       if ((size = BIO_get_mem_data(bio, &data)) <= 0)
+               goto fail;
+       if ((buf = calloc(1, size)) == NULL)
+               goto fail;
+       memcpy(buf, data, size);
+
+       BIO_free_all(bio);
+       *len = (off_t)size;
+       return (buf);
+
+ fail:
+       ssl_error(__func__, name);
+
+       free(buf);
+       if (bio != NULL)
+               BIO_free_all(bio);
+       return (NULL);
+}
+
+X509 *
+ssl_update_certificate(X509 *oldcert, char *keystr, off_t keylen,
+    char *cakeystr, off_t cakeylen, char *cacertstr, off_t cacertlen)
+{
+       char             name[2][SSL_NAME_SIZE];
+       X509            *cert = NULL, *cacert = NULL;
+       EVP_PKEY        *key = NULL, *cakey = NULL;
+       BIO             *bio = NULL;
+
+       name[0][0] = name[1][0] = '\0';
+       if (!X509_NAME_oneline(X509_get_subject_name(oldcert),
+           name[0], sizeof(name[0])) ||
+           !X509_NAME_oneline(X509_get_issuer_name(oldcert),
+           name[1], sizeof(name[1])))
+               goto done;
+
+       /* Get SSL key */
+       if ((bio = BIO_new_mem_buf(keystr, keylen)) == NULL)
+               goto done;
+       if ((key = PEM_read_bio_PrivateKey(bio, &key,
+           ssl_password_cb, NULL)) == NULL)
+               goto done;
+
+       /* Get CA key */
+       BIO_free_all(bio);
+       if ((bio = BIO_new_mem_buf(cakeystr, cakeylen)) == NULL)
+               goto done;
+       if ((cakey = PEM_read_bio_PrivateKey(bio, &cakey,
+           ssl_password_cb, NULL)) == NULL)
+               goto done;
+
+       /* Get CA certificate */
+       BIO_free_all(bio);
+       if ((bio = BIO_new_mem_buf(cacertstr, cacertlen)) == NULL)
+               goto done;
+       if ((cacert = PEM_read_bio_X509(bio, &cacert,
+           ssl_password_cb, NULL)) == NULL)
+               goto done;
+
+       if ((cert = X509_dup(oldcert)) == NULL)
+               goto done;
+
+       /* Update certificate key and use our CA as the issuer */
+       X509_set_pubkey(cert, key);
+       X509_set_issuer_name(cert, X509_get_subject_name(cacert));
+
+       /* Sign with our CA */
+       if (!X509_sign(cert, cakey, EVP_sha1())) {
+               X509_free(cert);
+               cert = NULL;
+       }
+
+#if DEBUG_CERT
+       log_debug("%s: subject %s", __func__, name[0]);
+       log_debug("%s: issuer %s", __func__, name[1]);
+#if DEBUG > 2
+       X509_print_fp(stdout, cert);
+#endif
+#endif
+
+ done:
+       if (cert == NULL)
+               ssl_error(__func__, name[0]);
+       if (bio != NULL)
+               BIO_free_all(bio);
+       if (key != NULL)
+               EVP_PKEY_free(key);
+       if (cacert != NULL)
+               X509_free(cacert);
+       if (cakey != NULL)
+               EVP_PKEY_free(cakey);
+
+       return (cert);
 }

Reply via email to