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); }