diff --git a/main.c b/main.c index b8d8718..cf2a043 100644 --- a/main.c +++ b/main.c @@ -115,6 +115,8 @@ 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" " -O deny all OCSP requests on all proxyspecs\n" @@ -274,7 +276,7 @@ main(int argc, char *argv[]) } while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z OPT_i "k:c:C:K:t:" - "OPs:r:R:e:Eu:m:j:p:l:L:S:F:dDVh")) != -1) { + "OPs:r:R:e:Eu:m:j:p:l:L:S:F:dDVhW:w:")) != -1) { switch (ch) { case 'c': if (opts->cacrt) @@ -615,6 +617,22 @@ main(int argc, char *argv[]) free(lhs); free(rhs); break; + case 'W': + opts->writeorig = 1; + if (opts->certgendir) + free(opts->certgendir); + opts->certgendir = strdup(optarg); + if (!opts->certgendir) + oom_die(argv0); + break; + case 'w': + opts->writeorig = 0; + if (opts->certgendir) + free(opts->certgendir); + opts->certgendir = strdup(optarg); + if (!opts->certgendir) + oom_die(argv0); + break; } #ifdef HAVE_LOCAL_PROCINFO case 'i': @@ -651,6 +669,11 @@ 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); @@ -747,6 +770,30 @@ 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); + 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); + } + } + /* usage checks after defaults */ if (opts->dropgroup && !opts->dropuser) { fprintf(stderr, "%s: -m depends on -u.\n", argv0); diff --git a/opts.c b/opts.c index 04daa91..ba66165 100644 --- a/opts.c +++ b/opts.c @@ -105,6 +105,9 @@ opts_free(opts_t *opts) if (opts->contentlog) { free(opts->contentlog); } + if (opts->certgendir) { + free(opts->certgendir); + } if (opts->contentlog_basedir) { free(opts->contentlog_basedir); } diff --git a/opts.h b/opts.h index c836efe..0279376 100644 --- a/opts.h +++ b/opts.h @@ -101,6 +101,8 @@ 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 3cefc66..91e5c7f 100644 --- a/pxyconn.c +++ b/pxyconn.c @@ -167,6 +167,8 @@ 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; @@ -377,7 +379,7 @@ pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO " %s" #endif /* HAVE_LOCAL_PROCINFO */ - "\n", + " %s %s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->sni), @@ -389,7 +391,9 @@ pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO , lpi #endif /* HAVE_LOCAL_PROCINFO */ - ); + , + *ctx->origfpr, + *ctx->newfpr); } if ((rv < 0) || !msg) { ctx->enomem = 1; @@ -451,7 +455,7 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO " %s" #endif /* HAVE_LOCAL_PROCINFO */ - "%s\n", + "%s %s %s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->http_host), @@ -462,7 +466,9 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO lpi, #endif /* HAVE_LOCAL_PROCINFO */ - ctx->ocsp_denied ? " ocsp:denied" : ""); + ctx->ocsp_denied ? " ocsp:denied" : "", + *ctx->origfpr, + *ctx->newfpr); } else { rv = asprintf(&msg, "https %s %s %s %s %s %s %s " "sni:%s names:%s " @@ -470,7 +476,7 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO " %s" #endif /* HAVE_LOCAL_PROCINFO */ - "%s\n", + "%s %s %s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->http_host), @@ -487,7 +493,9 @@ pxy_log_connect_http(pxy_conn_ctx_t *ctx) #ifdef HAVE_LOCAL_PROCINFO lpi, #endif /* HAVE_LOCAL_PROCINFO */ - ctx->ocsp_denied ? " ocsp:denied" : ""); + ctx->ocsp_denied ? " ocsp:denied" : "", + *ctx->origfpr, + *ctx->newfpr); } if ((rv < 0 ) || !msg) { ctx->enomem = 1; @@ -798,6 +806,48 @@ pxy_srccert_create(pxy_conn_ctx_t *ctx) cert_set_chain(cert, ctx->opts->chain); } + 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)); + } + } + } + return cert; } diff --git a/sslsplit.1 b/sslsplit.1 index abdf1b0..c17a63d 100644 --- a/sslsplit.1 +++ b/sslsplit.1 @@ -30,15 +30,15 @@ sslsplit \-\- transparent and scalable SSL/TLS interception .SH SYNOPSIS .na .B sslsplit -[\fB-kCKOPZdDgGsrReumjplLSFi\fP] \fB-c\fP \fIpem\fP +[\fB-kCKwWOPZdDgGsrReumjplLSFi\fP] \fB-c\fP \fIpem\fP \fIproxyspecs\fP [...] .br .B sslsplit -[\fB-kCKOPZdDgGsrReumjplLSFi\fP] \fB-c\fP \fIpem\fP \fB-t\fP \fIdir\fP +[\fB-kCKwWOPZdDgGsrReumjplLSFi\fP] \fB-c\fP \fIpem\fP \fB-t\fP \fIdir\fP \fIproxyspecs\fP [...] .br .B sslsplit -[\fB-OPZdDgGsrReumjplLSFi\fP] \fB-t\fP \fIdir\fP +[\fB-OPZwWdDgGsrReumjplLSFi\fP] \fB-t\fP \fIdir\fP \fIproxyspecs\fP [...] .br .B sslsplit -E @@ -181,6 +181,12 @@ 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.