diff --git a/main.c b/main.c index cf2a043..b4db01f 100644 --- a/main.c +++ b/main.c @@ -115,10 +115,10 @@ main_usage(void) " -k pemfile use CA key (and cert) from pemfile to sign forged certs\n" " -C pemfile use CA chain from pemfile (intermediate and root CA certs)\n" " -K pemfile use key from pemfile for leaf certs (default: generate)\n" -" -w gendir write generated key/cert pairs to gendir\n" -" -W gendir same as -w but also write the original cert\n" " -t certdir use cert+chain+key PEM files from certdir to target all sites\n" " matching the common names (non-matching: generate if CA)\n" +" -w gendir write leaf key and only generated certificates to gendir\n" +" -W gendir write leaf key and all certificates to gendir\n" " -O deny all OCSP requests on all proxyspecs\n" " -P passthrough SSL connections if they cannot be split because of\n" " client cert auth or no matching cert and no CA (default: drop)\n" @@ -618,7 +618,7 @@ main(int argc, char *argv[]) free(rhs); break; case 'W': - opts->writeorig = 1; + opts->certgen_writeall = 1; if (opts->certgendir) free(opts->certgendir); opts->certgendir = strdup(optarg); @@ -626,7 +626,7 @@ main(int argc, char *argv[]) oom_die(argv0); break; case 'w': - opts->writeorig = 0; + opts->certgen_writeall = 0; if (opts->certgendir) free(opts->certgendir); opts->certgendir = strdup(optarg); @@ -669,11 +669,6 @@ main(int argc, char *argv[]) argv0); exit(EXIT_FAILURE); } - if (opts->certgendir && opts->key) { - fprintf(stderr, "%s: -K and -w are mutually exclusive.\n", - argv0); - exit(EXIT_FAILURE); - } if (!opts->spec) { fprintf(stderr, "%s: no proxyspec specified.\n", argv0); exit(EXIT_FAILURE); @@ -771,27 +766,37 @@ main(int argc, char *argv[]) } if (opts->certgendir) { - unsigned char *keyfpr = malloc(SSL_KEY_IDSZ); - if(ssl_key_identifier_sha1(opts->key, keyfpr)) { - fprintf(stderr, "%s: error generating RSA fingerprint\n", argv0); + char *keyid, *keyfn; + int prv; + FILE *keyf; + + keyid = ssl_key_identifier(opts->key, 0); + if (!keyid) { + fprintf(stderr, "%s: error generating key id\n", argv0); exit(EXIT_FAILURE); } - char *keyfn; - asprintf(&keyfn, "%s/%02X%02X%02X%02X%02X%02X%02X%02X%02X" - "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X.key", - opts->certgendir, - keyfpr[0], keyfpr[1], keyfpr[2], keyfpr[3], keyfpr[4], - keyfpr[5], keyfpr[6], keyfpr[7], keyfpr[8], keyfpr[9], - keyfpr[10], keyfpr[11], keyfpr[12], keyfpr[13], keyfpr[14], - keyfpr[15], keyfpr[16], keyfpr[17], keyfpr[18], keyfpr[19]); - FILE *keyfd = fopen(keyfn,"w"); - if (!keyfd) { - log_err_printf("Failed to open '%s' for writing: %s\n", - keyfn, strerror(errno)); - } else { - PEM_write_PrivateKey(keyfd, opts->key, NULL, 0, 0, NULL, NULL); - fclose(keyfd); + + prv = asprintf(&keyfn, "%s/%s.key", opts->certgendir, keyid); + if (prv == -1) { + fprintf(stderr, "%s: %s (%i)\n", argv0, + strerror(errno), errno); + exit(EXIT_FAILURE); } + + if (!(keyf = fopen(keyfn, "w"))) { + fprintf(stderr, "%s: Failed to open '%s' for writing: " + "%s (%i)\n", argv0, keyfn, + strerror(errno), errno); + exit(EXIT_FAILURE); + } + if (!PEM_write_PrivateKey(keyf, opts->key, NULL, 0, 0, + NULL, NULL)) { + fprintf(stderr, "%s: Failed to write key to '%s': " + "%s (%i)\n", argv0, keyfn, + strerror(errno), errno); + exit(EXIT_FAILURE); + } + fclose(keyf); } /* usage checks after defaults */ diff --git a/opts.h b/opts.h index 0279376..af6e215 100644 --- a/opts.h +++ b/opts.h @@ -80,7 +80,9 @@ typedef struct opts { #ifdef HAVE_LOCAL_PROCINFO unsigned int lprocinfo : 1; #endif /* HAVE_LOCAL_PROCINFO */ + unsigned int certgen_writeall: 1; char *ciphers; + char *certgendir; char *tgcrtdir; char *dropuser; char *dropgroup; @@ -101,8 +103,6 @@ typedef struct opts { char *ecdhcurve; #endif /* !OPENSSL_NO_ECDH */ proxyspec_t *spec; - char *certgendir; - unsigned int writeorig: 1; } opts_t; opts_t *opts_new(void) MALLOC; diff --git a/pxyconn.c b/pxyconn.c index 91e5c7f..c49ed73 100644 --- a/pxyconn.c +++ b/pxyconn.c @@ -40,6 +40,7 @@ #include "attrib.h" #include "proc.h" +#include #include #include #include @@ -120,6 +121,7 @@ typedef struct pxy_conn_ctx { /* status flags */ unsigned int immutable_cert : 1; /* 1 if the cert cannot be changed */ + unsigned int generated_cert : 1; /* 1 if we generated a new cert */ unsigned int connected : 1; /* 0 until both ends are connected */ unsigned int seen_req_header : 1; /* 0 until request header complete */ unsigned int seen_resp_header : 1; /* 0 until response hdr complete */ @@ -147,8 +149,10 @@ typedef struct pxy_conn_ctx { char *http_status_text; char *http_content_length; - /* log strings from SSL context */ + /* log strings related to SSL */ char *ssl_names; + char *origcrtfpr; + char *usedcrtfpr; #ifdef HAVE_LOCAL_PROCINFO /* local process information */ @@ -167,8 +171,6 @@ typedef struct pxy_conn_ctx { socklen_t addrlen; int af; X509 *origcrt; - char *origfpr[SSL_X509_FPRSZ*2+1]; - char *newfpr[SSL_X509_FPRSZ*2+1]; /* references to event base and configuration */ struct event_base *evbase; @@ -211,9 +213,7 @@ pxy_conn_ctx_new(proxyspec_t *spec, opts_t *opts, return ctx; } -static void -pxy_conn_ctx_free(pxy_conn_ctx_t *ctx) NONNULL(1); -static void +static void NONNULL(1) pxy_conn_ctx_free(pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY @@ -253,6 +253,12 @@ pxy_conn_ctx_free(pxy_conn_ctx_t *ctx) if (ctx->ssl_names) { free(ctx->ssl_names); } + if (ctx->origcrtfpr) { + free(ctx->origcrtfpr); + } + if (ctx->usedcrtfpr) { + free(ctx->usedcrtfpr); + } #ifdef HAVE_LOCAL_PROCINFO if (ctx->lproc.exec_path) { free(ctx->lproc.exec_path); @@ -314,17 +320,11 @@ pxy_debug_crt(X509 *crt) free(names); } - unsigned char fpr[SSL_X509_FPRSZ]; - if (ssl_x509_fingerprint_sha1(crt, fpr) == -1) { + char *fpr; + if (!(fpr = ssl_x509_fingerprint(crt, 1))) { log_err_printf("Warning: Error generating X509 fingerprint\n"); } else { - log_dbg_printf("Fingerprint: " "%02x:%02x:%02x:%02x:" - "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:" - "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", - fpr[0], fpr[1], fpr[2], fpr[3], fpr[4], - fpr[5], fpr[6], fpr[7], fpr[8], fpr[9], - fpr[10], fpr[11], fpr[12], fpr[13], fpr[14], - fpr[15], fpr[16], fpr[17], fpr[18], fpr[19]); + log_dbg_printf("Fingerprint: %s\n", fpr); } #ifdef DEBUG_CERTIFICATE @@ -375,11 +375,12 @@ pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) } else { rv = asprintf(&msg, "ssl %s %s " "sni:%s names:%s " - "sproto:%s:%s dproto:%s:%s" + "sproto:%s:%s dproto:%s:%s " + "origcrt:%s usedcrt:%s" #ifdef HAVE_LOCAL_PROCINFO " %s" #endif /* HAVE_LOCAL_PROCINFO */ - " %s %s\n", + "\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->sni), @@ -387,13 +388,13 @@ pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) SSL_get_version(ctx->src.ssl), SSL_get_cipher(ctx->src.ssl), SSL_get_version(ctx->dst.ssl), - SSL_get_cipher(ctx->dst.ssl) + SSL_get_cipher(ctx->dst.ssl), + STRORDASH(ctx->origcrtfpr), + STRORDASH(ctx->usedcrtfpr) #ifdef HAVE_LOCAL_PROCINFO , lpi #endif /* HAVE_LOCAL_PROCINFO */ - , - *ctx->origfpr, - *ctx->newfpr); + ); } if ((rv < 0) || !msg) { ctx->enomem = 1; @@ -455,7 +456,7 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO " %s" #endif /* HAVE_LOCAL_PROCINFO */ - "%s %s %s\n", + "%s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->http_host), @@ -466,17 +467,16 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO lpi, #endif /* HAVE_LOCAL_PROCINFO */ - ctx->ocsp_denied ? " ocsp:denied" : "", - *ctx->origfpr, - *ctx->newfpr); + ctx->ocsp_denied ? " ocsp:denied" : ""); } else { rv = asprintf(&msg, "https %s %s %s %s %s %s %s " "sni:%s names:%s " - "sproto:%s:%s dproto:%s:%s" + "sproto:%s:%s dproto:%s:%s " + "origcrt:%s usedcrt:%s" #ifdef HAVE_LOCAL_PROCINFO " %s" #endif /* HAVE_LOCAL_PROCINFO */ - "%s %s %s\n", + "%s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->http_host), @@ -490,12 +490,12 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) SSL_get_cipher(ctx->src.ssl), SSL_get_version(ctx->dst.ssl), SSL_get_cipher(ctx->dst.ssl), + STRORDASH(ctx->origcrtfpr), + STRORDASH(ctx->usedcrtfpr), #ifdef HAVE_LOCAL_PROCINFO lpi, #endif /* HAVE_LOCAL_PROCINFO */ - ctx->ocsp_denied ? " ocsp:denied" : "", - *ctx->origfpr, - *ctx->newfpr); + ctx->ocsp_denied ? " ocsp:denied" : ""); } if ((rv < 0 ) || !msg) { ctx->enomem = 1; @@ -732,6 +732,66 @@ pxy_srcsslctx_create(pxy_conn_ctx_t *ctx, X509 *crt, STACK_OF(X509) *chain, return sslctx; } +static int +pxy_srccert_write_to_gendir(pxy_conn_ctx_t *ctx, X509 *crt, int is_orig) +{ + char *fn; + int rv; + struct stat sb; + FILE *f; + + if (!ctx->origcrtfpr) + return -1; + if (is_orig) { + rv = asprintf(&fn, "%s/%s.crt", ctx->opts->certgendir, + ctx->origcrtfpr); + } else { + if (!ctx->usedcrtfpr) + return -1; + rv = asprintf(&fn, "%s/%s-%s.crt", ctx->opts->certgendir, + ctx->origcrtfpr, ctx->usedcrtfpr); + } + if (rv == -1) { + ctx->enomem = 1; + return -1; + } + if (stat(fn, &sb) == 0) { + free(fn); + return 0; + } + if (!(f = fopen(fn, "w"))) { + log_err_printf("Failed to open '%s' for writing: %s (%i)\n", + fn, strerror(errno), errno); + free(fn); + return -1; + } + if (!PEM_write_X509(f, crt)) { + log_err_printf("Failed to write certificate to '%s'\n", fn); + fclose(f); + free(fn); + return -1; + } + fclose(f); + free(fn); + return 0; +} + +static void +pxy_srccert_write(pxy_conn_ctx_t *ctx) +{ + if (ctx->opts->certgen_writeall || ctx->generated_cert) { + if (pxy_srccert_write_to_gendir(ctx, + SSL_get_certificate(ctx->src.ssl), 0) == -1) { + log_err_printf("Failed to write used certificate\n"); + } + } + if (ctx->opts->certgen_writeall) { + if (pxy_srccert_write_to_gendir(ctx, ctx->origcrt, 1) == -1) { + log_err_printf("Failed to write orig certificate\n"); + } + } +} + static cert_t * pxy_srccert_create(pxy_conn_ctx_t *ctx) { @@ -804,48 +864,19 @@ pxy_srccert_create(pxy_conn_ctx_t *ctx) } cert_set_key(cert, ctx->opts->key); cert_set_chain(cert, ctx->opts->chain); + ctx->generated_cert = 1; } - unsigned char origfpr[SSL_X509_FPRSZ], newfpr[SSL_X509_FPRSZ]; - ssl_x509_fingerprint_sha1(ctx->origcrt, origfpr); - ssl_x509_fingerprint_sha1(cert->crt, newfpr); - asprintf(ctx->origfpr,"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X" - "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", - origfpr[0], origfpr[1], origfpr[2], origfpr[3], origfpr[4], - origfpr[5], origfpr[6], origfpr[7], origfpr[8], origfpr[9], - origfpr[10], origfpr[11], origfpr[12], origfpr[13], origfpr[14], - origfpr[15], origfpr[16], origfpr[17], origfpr[18], origfpr[19]); - asprintf(ctx->newfpr,"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X" - "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", - newfpr[0], newfpr[1], newfpr[2], newfpr[3], newfpr[4], - newfpr[5], newfpr[6], newfpr[7], newfpr[8], newfpr[9], - newfpr[10], newfpr[11], newfpr[12], newfpr[13], newfpr[14], - newfpr[15], newfpr[16], newfpr[17], newfpr[18], newfpr[19]); - - if (ctx->opts->certgendir) { - char *crtfn; - asprintf(&crtfn, "%s/%s-%s.crt", ctx->opts->certgendir, *ctx->origfpr, *ctx->newfpr); - FILE *crtfd; - crtfd = fopen(crtfn, "w"); - if (crtfd) { - PEM_write_X509(crtfd, cert->crt); - fclose(crtfd); - } else { - log_err_printf("Failed to open '%s' for writing: %s\n", - crtfn, strerror(errno)); - } - if (ctx->opts->writeorig) { - char *origfn; - asprintf(&origfn, "%s/%s.crt", ctx->opts->certgendir, *ctx->origfpr); - FILE *origfd = fopen(origfn, "w"); - if (origfd) { - PEM_write_X509(origfd, ctx->origcrt); - fclose(origfd); - } else { - log_err_printf("Failed to open '%s' for writing: %s\n", - origfn, strerror(errno)); - } - } + if ((WANT_CONNECT_LOG(ctx) || ctx->opts->certgendir) && ctx->origcrt) { + ctx->origcrtfpr = ssl_x509_fingerprint(ctx->origcrt, 0); + if (!ctx->origcrtfpr) + ctx->enomem = 1; + } + if ((WANT_CONNECT_LOG(ctx) || ctx->opts->certgen_writeall) && + cert && cert->crt) { + ctx->usedcrtfpr = ssl_x509_fingerprint(cert->crt, 0); + if (!ctx->usedcrtfpr) + ctx->enomem = 1; } return cert; @@ -969,6 +1000,7 @@ pxy_ossl_servername_cb(SSL *ssl, UNUSED int *al, void *arg) return SSL_TLSEXT_ERR_NOACK; } cachemgr_fkcrt_set(ctx->origcrt, newcrt); + ctx->generated_cert = 1; if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("===> Updated forged server " "certificate:\n"); @@ -983,6 +1015,16 @@ pxy_ossl_servername_cb(SSL *ssl, UNUSED int *al, void *arg) ctx->enomem = 1; } } + if (WANT_CONNECT_LOG(ctx) || ctx->opts->certgendir) { + if (ctx->usedcrtfpr) { + free(ctx->usedcrtfpr); + } + ctx->usedcrtfpr = ssl_x509_fingerprint(newcrt, 0); + if (!ctx->usedcrtfpr) { + ctx->enomem = 1; + } + } + newsslctx = pxy_srcsslctx_create(ctx, newcrt, ctx->opts->chain, ctx->opts->key); if (!newsslctx) { @@ -1810,6 +1852,12 @@ connected: pxy_log_connect_nonhttp(ctx); } + /* write SSL certificates to gendir */ + if (this->ssl && (bev == ctx->src.bev) && + ctx->opts->certgendir) { + pxy_srccert_write(ctx); + } + if (OPTS_DEBUG(ctx->opts)) { if (this->ssl) { /* for SSL, we get two connect events */ diff --git a/sslsplit.1 b/sslsplit.1 index 649bc47..172d857 100644 --- a/sslsplit.1 +++ b/sslsplit.1 @@ -181,12 +181,6 @@ no matching certificate in the provided certificate directory. Use private key from \fIpemfile\fP for certificates forged on-the-fly. If \fB-K\fP is not given, SSLsplit will generate a random 1024-bit RSA key. .TP -.B \-w \fIgendir\fP -Write generated keys and certificates to individual files in \fIgendir\fP. -.TP -.B \-W \fIgendir\fP -Same as -w, but also write original certificates -.TP .B \-l \fIlogfile\fP Log connections to \fIlogfile\fP in a single line per connection format, including addresses and ports and some HTTP and SSL information, if available. @@ -297,6 +291,19 @@ Mac OS X. .B \-V Display version and compiled features information and exit. .TP +.B \-w \fIgendir\fP +Write generated keys and certificates to individual files in \fIgendir\fP. +For keys, the key identifier is used as filename, which consists of the SHA-1 +hash of the ASN.1 bit string of the public key, as referenced by the +subjectKeyIdentifier extension in certificates. +For certificates, the SHA-1 fingerprints of the original and the used (forged) +certificate are combined to form the filename. +Note that only newly generated certificates are written to disk. +.TP +.B \-W \fIgendir\fP +Same as \fB-w\fP, but also write original certificates and certificates not +newly generated, such as those loaded from \fB-t\fP. +.TP .B \-Z Disable SSL/TLS compression on all connections. This is useful if your limiting factor is CPU, not network bandwidth.