diff options
author | Reyk Floeter <reyk@cvs.openbsd.org> | 2013-05-30 20:17:13 +0000 |
---|---|---|
committer | Reyk Floeter <reyk@cvs.openbsd.org> | 2013-05-30 20:17:13 +0000 |
commit | 0c2d8cd7deeae266c9f95b1c0cf9b50f5db07c8a (patch) | |
tree | d94ea7e5c9d6a8e00007e14c32c9c9bc993dc613 /usr.sbin/relayd | |
parent | 44440dacaf7f504a2a860b04576e7c1d91c85652 (diff) |
Support SSL inspection, the ability to transparently filter in SSL/TLS
connections (eg. HTTPS) by using a local CA that is accepted by the
clients. See the "SSL RELAYS" and "EXAMPLES" sections in the
relayd.conf(5) manpage for more details.
ok benno@, manpage bits jmc@
Diffstat (limited to 'usr.sbin/relayd')
-rw-r--r-- | usr.sbin/relayd/config.c | 29 | ||||
-rw-r--r-- | usr.sbin/relayd/parse.y | 35 | ||||
-rw-r--r-- | usr.sbin/relayd/relay.c | 91 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.c | 12 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.conf.5 | 161 | ||||
-rw-r--r-- | usr.sbin/relayd/relayd.h | 18 | ||||
-rw-r--r-- | usr.sbin/relayd/ssl.c | 148 |
7 files changed, 474 insertions, 20 deletions
diff --git a/usr.sbin/relayd/config.c b/usr.sbin/relayd/config.c index 73e6b856a05..ab87e2ff3eb 100644 --- a/usr.sbin/relayd/config.c +++ b/usr.sbin/relayd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.8 2012/12/18 15:57:16 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.9 2013/05/30 20:17:12 reyk Exp $ */ /* * Copyright (c) 2011 Reyk Floeter <reyk@openbsd.org> @@ -177,6 +177,8 @@ config_purge(struct relayd *env, u_int reset) 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; @@ -635,6 +637,7 @@ config_getproto(struct relayd *env, struct imsg *imsg) proto->request_nodes = 0; proto->response_nodes = 0; + proto->sslcapass = NULL; RB_INIT(&proto->request_tree); RB_INIT(&proto->response_tree); @@ -830,6 +833,14 @@ config_setrelay(struct relayd *env, struct relay *rlay) 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 +909,9 @@ config_getrelay(struct relayd *env, struct imsg *imsg) 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; } @@ -921,6 +934,18 @@ config_getrelay(struct relayd *env, struct imsg *imsg) 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); TAILQ_INSERT_TAIL(env->sc_relays, rlay, rl_entry); diff --git a/usr.sbin/relayd/parse.y b/usr.sbin/relayd/parse.y index 7735d99409b..3d401dd2414 100644 --- a/usr.sbin/relayd/parse.y +++ b/usr.sbin/relayd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.170 2013/04/27 16:39:30 benno Exp $ */ +/* $OpenBSD: parse.y,v 1.171 2013/05/30 20:17:12 reyk Exp $ */ /* * Copyright (c) 2007-2011 Reyk Floeter <reyk@openbsd.org> @@ -159,7 +159,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 @@ -980,6 +980,34 @@ sslflags : SESSION CACHE sslcache { proto->cache = $3; } } 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; } ; @@ -1799,6 +1827,7 @@ lookup(char *s) { "buffer", BUFFER }, { "ca", CA }, { "cache", CACHE }, + { "cert", CERTIFICATE }, { "change", CHANGE }, { "check", CHECK }, { "ciphers", CIPHERS }, @@ -1825,6 +1854,7 @@ lookup(char *s) { "interface", INTERFACE }, { "interval", INTERVAL }, { "ip", IP }, + { "key", KEY }, { "label", LABEL }, { "least-states", LEASTSTATES }, { "listen", LISTEN }, @@ -1841,6 +1871,7 @@ lookup(char *s) { "nothing", NOTHING }, { "on", ON }, { "parent", PARENT }, + { "password", PASSWORD }, { "path", PATH }, { "port", PORT }, { "prefork", PREFORK }, diff --git a/usr.sbin/relayd/relay.c b/usr.sbin/relayd/relay.c index ef276ab2a1b..ca30f51daac 100644 --- a/usr.sbin/relayd/relay.c +++ b/usr.sbin/relayd/relay.c @@ -1,7 +1,7 @@ -/* $OpenBSD: relay.c,v 1.165 2013/04/20 17:45:02 deraadt Exp $ */ +/* $OpenBSD: relay.c,v 1.166 2013/05/30 20:17:12 reyk Exp $ */ /* - * Copyright (c) 2006 - 2012 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006 - 2013 Reyk Floeter <reyk@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 @@ -1127,6 +1127,11 @@ relay_accept(int fd, short event, void *arg) return; } + if (rlay->rl_conf.flags & F_SSLINSPECT) { + relay_preconnect(con); + return; + } + relay_session(con); return; err: @@ -1444,12 +1449,30 @@ relay_connect_retry(int fd, short sig, void *arg) } 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_SSLINSPECT) && 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"); @@ -1573,6 +1596,8 @@ relay_close(struct rsession *con, const char *msg) 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) { @@ -1602,6 +1627,8 @@ relay_close(struct rsession *con, const char *msg) 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); @@ -1903,6 +1930,10 @@ relay_ssl_transaction(struct rsession *con, struct ctl_relay_event *cre) cb = relay_ssl_accept; method = SSLv23_server_method(); flag = EV_READ; + + /* Use session-specific certificate for SSL inspection. */ + if (cre->sslcert != NULL) + SSL_use_certificate(ssl, cre->sslcert); } else { cb = relay_ssl_connect; method = SSLv23_client_method(); @@ -2000,6 +2031,7 @@ relay_ssl_connect(int fd, short event, void *arg) int retry_flag = 0; int ssl_err = 0; int ret; + X509 *servercert = NULL; if (event == EV_TIMEOUT) { relay_close(con, "SSL connect timeout"); @@ -2036,9 +2068,28 @@ relay_ssl_connect(int fd, short event, void *arg) #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_SSLINSPECT) { + 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); + } else + con->se_in.sslcert = NULL; + if (servercert != NULL) + X509_free(servercert); + if (con->se_in.sslcert == NULL) + relay_close(con, "could not create certificate"); + else + relay_session(con); + return; + } + relay_connected(fd, EV_WRITE, con); return; @@ -2360,11 +2411,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) @@ -2397,8 +2468,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); diff --git a/usr.sbin/relayd/relayd.c b/usr.sbin/relayd/relayd.c index ce09304e7a1..8be99615419 100644 --- a/usr.sbin/relayd/relayd.c +++ b/usr.sbin/relayd/relayd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: relayd.c,v 1.116 2013/03/10 23:32:53 reyk Exp $ */ +/* $OpenBSD: relayd.c,v 1.117 2013/05/30 20:17:12 reyk Exp $ */ /* * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org> @@ -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 Inspection */ + 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_SSLINSPECT; + config_setrelay(env, rlay); + } /* HCE, PFE and the preforked relays need to reload their config. */ env->sc_reload = 2 + env->sc_prefork_relay; diff --git a/usr.sbin/relayd/relayd.conf.5 b/usr.sbin/relayd/relayd.conf.5 index 3f0cc4e9b6d..93956c91a96 100644 --- a/usr.sbin/relayd/relayd.conf.5 +++ b/usr.sbin/relayd/relayd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: relayd.conf.5,v 1.133 2013/04/27 16:39:30 benno Exp $ +.\" $OpenBSD: relayd.conf.5,v 1.134 2013/05/30 20:17:12 reyk Exp $ .\" .\" Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> .\" Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org> @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: April 27 2013 $ +.Dd $Mdocdate: May 30 2013 $ .Dt RELAYD.CONF 5 .Os .Sh NAME @@ -680,6 +680,83 @@ Specify the inactivity timeout in seconds for accepted sessions. The default timeout is 600 seconds (10 minutes). The maximum is 2147483647 seconds (68 years). .El +.Sh SSL RELAYS +In addition to plain TCP, +.Xr relayd 8 +supports the Secure Sockets Layer (SSL) and Transport Layer Security +(TLS) cryptographic protocols for authenticated and encrypted relays. +TLS is the successor of the original SSL protocol but the term SSL can +refer to either of the protocols in +.Nm . +.Xr relayd 8 +can operate as an SSL client or server to offer a variety of options +for different use cases related to SSL. +.Bl -tag -width Ds +.It Ic SSL client +When configuring the relay +.Ic forward +statements with the +.Ic with ssl +directive, +.Xr relayd 8 +will enable client-side SSL to connect to the remote host. +This is commonly used for SSL tunneling and transparent encapsulation +of plain TCP connections. +See the +.Ic forward to +description in the +.Sx RELAYS +section for more details. +.It Ic SSL server +When specifying the +.Ic ssl +keyword in the relay +.Ic listen +statements, +.Xr relayd 8 +will accept connections from clients as an SSL server. +This mode is also known as +.Dq SSL acceleration . +See the +.Ic listen on +description in the +.Sx RELAYS +section for more details. +.It Ic SSL client and server +When combining both modes, SSL server and client, +.Xr relayd 8 +can filter SSL connections as a man-in-the-middle. +This combined mode is also called +.Dq SSL inspection . +The configuration requires additional X.509 certificate settings; +see the +.Ic ca key +description in the +.Sx PROTOCOLS +section for more details. +.El +.Pp +When configured for +.Dq SSL inspection +mode, +.Xr relayd 8 +will listen for incoming connections which 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 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. .Sh PROTOCOLS Protocols are templates defining actions and settings for relays. They allow setting generic TCP options, SSL settings, and actions @@ -1008,6 +1085,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 inspection. +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 @@ -1015,6 +1097,35 @@ the specified path to verify the server certificates. .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 inspection. +The +.Ar password +argument will specify the password to decrypt the CA key +(typically an RSA key). +This option will enable SSL inspection 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 @@ -1270,6 +1381,52 @@ relay "sshforward" { } .Ed .Pp +The following relay example will configure +.Dq SSL inspection +as described in the +.Sx SSL RELAYS +section. +To start, first generate a new local CA key and certificate: +.Bd -literal -offset indent +# openssl req -x509 -days 365 -newkey rsa:2048 \e + -keyout /etc/ssl/private/ca.key -out /etc/ssl/ca.crt +.Ed +.Pp +An SSL server key and self-signed cert for 127.0.0.1 are also required; +see +.Ic listen on +in the +.Sx RELAYS +section for more details about certificate locations. +Configure the packet filter with a matching divert rule in +.Xr pf.conf 5 : +.Bd -literal -offset indent +# Divert incoming HTTPS traffic to relayd +pass in on vlan1 inet proto tcp to port 443 \e + divert-to localhost port 8443 +.Ed +.Pp +And finally configure the SSL inspection in +.Nm : +.Bd -literal -offset indent +http protocol httpfilter { + return error + + label "Prohibited!" + request url filter "social.network.example.com/" + + # New configuration directives for SSL Interception + ssl ca key "/etc/ssl/private/ca.key" password "password123" + ssl ca cert "/etc/ssl/ca.crt" +} + +relay sslinspect { + listen on 127.0.0.1 port 8443 ssl + protocol httpfilter + forward with ssl to destination +} +.Ed +.Pp The next simple router configuration example can be used to run redundant, health-checked WAN links: .Bd -literal -offset indent diff --git a/usr.sbin/relayd/relayd.h b/usr.sbin/relayd/relayd.h index 85934cb1b2e..92315134cbb 100644 --- a/usr.sbin/relayd/relayd.h +++ b/usr.sbin/relayd/relayd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: relayd.h,v 1.168 2013/04/27 16:39:30 benno Exp $ */ +/* $OpenBSD: relayd.h,v 1.169 2013/05/30 20:17:12 reyk Exp $ */ /* * Copyright (c) 2006 - 2012 Reyk Floeter <reyk@openbsd.org> @@ -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; @@ -282,6 +285,7 @@ TAILQ_HEAD(addresslist, address); #define F_MATCH 0x00800000 #define F_DIVERT 0x01000000 #define F_SCRIPT 0x02000000 +#define F_SSLINSPECT 0x04000000 #define F_BITS \ "\10\01DISABLE\02BACKUP\03USED\04DOWN\05ADD\06DEL\07CHANGED" \ @@ -455,6 +459,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; @@ -576,6 +581,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; @@ -625,6 +633,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 { @@ -648,6 +658,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]; @@ -1001,6 +1013,7 @@ int relay_spliceadjust(struct ctl_relay_event *); 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); @@ -1048,6 +1061,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); diff --git a/usr.sbin/relayd/ssl.c b/usr.sbin/relayd/ssl.c index b5bb33e1563..4a7770c3396 100644 --- a/usr.sbin/relayd/ssl.c +++ b/usr.sbin/relayd/ssl.c @@ -1,6 +1,7 @@ -/* $OpenBSD: ssl.c,v 1.17 2012/12/18 15:34:07 reyk Exp $ */ +/* $OpenBSD: ssl.c,v 1.18 2013/05/30 20:17:12 reyk Exp $ */ /* + * Copyright (c) 2007-2013 Reyk Floeter <reyk@openbsd.org> * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@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 *what) void ssl_init(struct relayd *env) { + static int initialized = 0; + + if (initialized) + return; + SSL_library_init(); SSL_load_error_strings(); /* Init hardware crypto engines. */ ENGINE_load_builtin_engines(); ENGINE_register_all_complete(); + + initialized = 1; } void @@ -284,3 +293,140 @@ ssl_ctx_create(struct relayd *env) } 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); +} |