% Evil cert patch: proceed elsewhere when certificate cannot be verified % ====================================================================== % % Current version: stunnel-4.35 % % This patch brings in the two following directives: % - evilconnect % - evilexec/evilexecargs % % When stunnel works in server mode and is asked to verify the client's % certificate, if the latter happens to be invalid the connection will % be normally shut down. When one of these options is used and the % certificate cannot be verified, stunnel redirects the "evil" % connection to another destination. % % The purpose of this feature is to cover up a suspicious SSL tunnel % aimed to evade a dictatorial proxy. % % How to apply this patch? Execute the following command in the top % stunnel directory: % patch -p0 < evil.patch % % Written by Jeremie LE HEN, jeremie at le-hen org, Jul 2009. % Updated to version 4.35 by Olivier LARDIT, Feb 2011. % Index: doc/stunnel.pod =================================================================== RCS file: /home/cvs/tataz/src/stunnel/doc/stunnel.pod,v retrieving revision 1.1.1.3 retrieving revision 1.4 diff -u -p -u -p -r1.1.1.3 -r1.4 --- doc/stunnel.pod 22 Feb 2011 08:21:15 -0000 1.1.1.3 +++ doc/stunnel.pod 22 Feb 2011 14:29:54 -0000 1.4 @@ -394,6 +394,29 @@ select engine number to read private key The engines are numbered starting from 1. +=item B = [host:]port + +connect to a remote host:port when the client's certificate cannot +be verified + +This is only meaningful in server mode when I and I are used. +Otherwise it has the same properties as the I option. + +=item B = executable_path (Unix only) + +execute local inetd-type program when the client's certificate cannot +be verified + +This is only meaningful in server mode when I and I are used. +Otherwise it has the same properties as the I option. + +=item B = $0 $1 $2 ... (Unix only) + +arguments for I including program name ($0) + +Quoting is currently not supported. +Arguments are separated with arbitrary number of whitespaces. + =item B = executable_path execute local inetd-type program Index: src/client.c =================================================================== RCS file: /home/cvs/tataz/src/stunnel/src/client.c,v retrieving revision 1.1.1.3 retrieving revision 1.4 diff -u -p -u -p -r1.1.1.3 -r1.4 --- src/client.c 22 Feb 2011 08:21:15 -0000 1.1.1.3 +++ src/client.c 22 Feb 2011 14:29:55 -0000 1.4 @@ -129,6 +129,7 @@ static void run_client(CLI *c) { c->fd=-1; c->ssl=NULL; c->sock_bytes=c->ssl_bytes=0; + c->evil_cert=0; error=setjmp(c->err); if(!error) @@ -261,6 +262,7 @@ static void init_remote(CLI *c) { static void init_ssl(CLI *c) { int i, err; SSL_SESSION *old_session; + X509 *peer; if(!(c->ssl=SSL_new(c->opt->ctx))) { sslerror("SSL_new"); @@ -314,8 +316,26 @@ static void init_ssl(CLI *c) { leave_critical_section(CRIT_SSL); #endif /* OpenSSL version < 1.0.0b */ err=SSL_get_error(c->ssl, i); - if(err==SSL_ERROR_NONE) - break; /* ok -> done */ + if(err==SSL_ERROR_NONE) { + if(c->opt->option.client) + break; /* ok -> done */ + if(!c->opt->option.evilremote && !c->opt->option.evilprogram) + break; /* ok -> done */ + peer=SSL_get_peer_certificate(c->ssl); + if (!peer) { + if (c->opt->verify_level!=SSL_VERIFY_NONE) { + s_log(LOG_ERR, "No cert, proceed to evil cert endpoint"); + c->evil_cert=1; + } + break; + } + if(SSL_get_verify_result(c->ssl)==X509_V_OK) + break; + sslerror("SSL_accept"); + s_log(LOG_ERR, "Proceed to evil cert endpoint"); + c->evil_cert=1; + break; + } if(err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) { s_poll_init(&c->fds); s_poll_add(&c->fds, c->ssl_rfd->fd, @@ -918,7 +938,10 @@ static int connect_local(CLI *c) { /* sp sigemptyset(&newmask); sigprocmask(SIG_SETMASK, &newmask, NULL); #endif - execvp(c->opt->execname, c->opt->execargs); + if (c->evil_cert) + execvp(c->opt->evilexecname, c->opt->evilexecargs); + else + execvp(c->opt->execname, c->opt->execargs); ioerror(c->opt->execname); /* execv failed */ _exit(1); default: /* parent */ @@ -981,13 +1004,15 @@ static int connect_remote(CLI *c) { /* c if(c->opt->option.delayed_lookup) { resolved_list.num=0; if(!name2addrlist(&resolved_list, - c->opt->remote_address, DEFAULT_LOOPBACK)) { + c->evil_cert ? c->opt->evilremote_address : + c->opt->remote_address, DEFAULT_LOOPBACK)) { s_log(LOG_ERR, "No host resolved"); longjmp(c->err, 1); } address_list=&resolved_list; } else /* use pre-resolved addresses */ - address_list=&c->opt->remote_addr; + address_list=c->evil_cert ? &c->opt->evilremote_addr : + &c->opt->remote_addr; /* try to connect each host from the list */ for(ind_try=0; ind_trynum; ind_try++) { Index: src/options.c =================================================================== RCS file: /home/cvs/tataz/src/stunnel/src/options.c,v retrieving revision 1.1.1.3 retrieving revision 1.5 diff -u -p -u -p -r1.1.1.3 -r1.5 --- src/options.c 22 Feb 2011 08:21:15 -0000 1.1.1.3 +++ src/options.c 22 Feb 2011 22:57:46 -0000 1.5 @@ -818,6 +818,75 @@ static char *parse_service_option(CMD cm } #endif + /* evilconnect */ + switch(cmd) { + case CMD_INIT: + section->option.evilremote=0; + section->evilremote_address=NULL; + section->evilremote_addr.num=0; + break; + case CMD_EXEC: + if(strcasecmp(opt, "evilconnect")) + break; + section->option.evilremote=1; + section->evilremote_address=stralloc(arg); + if(!section->option.delayed_lookup && + !name2addrlist(§ion->evilremote_addr, arg, + DEFAULT_LOOPBACK)) { + s_log(LOG_NOTICE, "Cannot resolve '%s' - delaying DNS lookup", arg); + section->option.delayed_lookup=1; + } + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + s_log(LOG_NOTICE, "%-15s = [host:]port connect remote host:port " + "on evil client cert", "evilconnect"); + break; + } + + /* evilexec */ +#ifndef USE_WIN32 + switch(cmd) { + case CMD_INIT: + section->option.evilprogram=0; + section->evilexecname=NULL; + break; + case CMD_EXEC: + if(strcasecmp(opt, "evilexec")) + break; + section->option.evilprogram=1; + section->evilexecname=stralloc(arg); + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + s_log(LOG_NOTICE, "%-15s = file execute local inetd-type program " + "on evil client cert", "evilexec"); + break; + } +#endif + + /* evilexecargs */ +#ifndef USE_WIN32 + switch(cmd) { + case CMD_INIT: + section->evilexecargs=NULL; + break; + case CMD_EXEC: + if(strcasecmp(opt, "evilexecargs")) + break; + section->evilexecargs=argalloc(arg); + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + s_log(LOG_NOTICE, "%-15s = arguments for 'evilexec' (including $0)", + "evilexecargs"); + break; + } +#endif + /* exec */ switch(cmd) { case CMD_INIT: @@ -1726,6 +1795,19 @@ static int section_init(int last_line, S "Each service must define two endpoints"); return 0; } + if((section->option.evilremote || section->option.evilprogram) && + section->option.client) + section_error(last_line, section->servname, + "Evil cert actions have sense only in server mode"); + if((section->option.evilremote || section->option.evilprogram) && + section->verify_level==SSL_VERIFY_NONE) + section_error(last_line, section->servname, + "Evil cert actions are only meaningful when verify level > 0"); + if((section->option.evilremote && !section->option.remote) || + (section->option.evilprogram && !section->option.program)) + section_error(last_line, section->servname, + "Evil cert actions can be only specified with their " + "program or remote node counterpart"); } return 1; /* all tests passed -- continue program execution */ } Index: src/prototypes.h =================================================================== RCS file: /home/cvs/tataz/src/stunnel/src/prototypes.h,v retrieving revision 1.1.1.3 retrieving revision 1.4 diff -u -p -u -p -r1.1.1.3 -r1.4 --- src/prototypes.h 22 Feb 2011 08:21:15 -0000 1.1.1.3 +++ src/prototypes.h 22 Feb 2011 14:29:55 -0000 1.4 @@ -146,14 +146,17 @@ typedef struct service_options_struct { /* service-specific data for client.c */ int fd; /* file descriptor accepting connections for this service */ char *execname; /* program name for local mode */ + char *evilexecname; /* same, with bad certificate */ #ifdef USE_WIN32 char *execargs; /* program arguments for local mode */ + char *evilexecargs; /* same, with bad certificate */ #else char **execargs; /* program arguments for local mode */ + char **evilexecargs; /* same, with bad certificate */ #endif - SOCKADDR_LIST local_addr, remote_addr, source_addr; + SOCKADDR_LIST local_addr, remote_addr, source_addr, evilremote_addr; char *username; - char *remote_address; + char *remote_address, *evilremote_address; int timeout_busy; /* maximum waiting for data time */ int timeout_close; /* maximum close_notify time */ int timeout_connect; /* maximum connect() time */ @@ -173,9 +176,11 @@ typedef struct service_options_struct { unsigned int delayed_lookup:1; unsigned int accept:1; unsigned int remote:1; + unsigned int evilremote:1; unsigned int retry:1; /* loop remote+program */ unsigned int sessiond:1; unsigned int program:1; + unsigned int evilprogram:1; #ifndef USE_WIN32 unsigned int pty:1; unsigned int transparent_src:1; @@ -343,6 +348,7 @@ typedef struct { unsigned long pid; /* PID of local process */ int fd; /* temporary file descriptor */ jmp_buf err; + int evil_cert; /* Certificate couldn't be verified */ char sock_buff[BUFFSIZE]; /* socket read buffer */ char ssl_buff[BUFFSIZE]; /* SSL read buffer */ Index: src/verify.c =================================================================== RCS file: /home/cvs/tataz/src/stunnel/src/verify.c,v retrieving revision 1.1.1.3 retrieving revision 1.4 diff -u -p -u -p -r1.1.1.3 -r1.4 --- src/verify.c 22 Feb 2011 08:21:15 -0000 1.1.1.3 +++ src/verify.c 22 Feb 2011 14:29:55 -0000 1.4 @@ -158,6 +158,7 @@ static int verify_callback(int preverify SSL *ssl; CLI *c; char subject_name[STRLEN]; + int evil_cert_ok; /* retrieve application specific data */ ssl=X509_STORE_CTX_get_ex_data(callback_ctx, @@ -169,22 +170,23 @@ static int verify_callback(int preverify subject_name, STRLEN); safestring(subject_name); + evil_cert_ok = c->opt->option.evilremote | c->opt->option.evilprogram; s_log(LOG_DEBUG, "Starting certificate verification: depth=%d, %s", callback_ctx->error_depth, subject_name); if(!cert_check(c, callback_ctx, preverify_ok)) { s_log(LOG_WARNING, "Certificate check failed: depth=%d, %s", callback_ctx->error_depth, subject_name); - return 0; /* reject connection */ + return evil_cert_ok; /* reject connection */ } if(!crl_check(c, callback_ctx)) { s_log(LOG_WARNING, "CRL check failed: depth=%d, %s", callback_ctx->error_depth, subject_name); - return 0; /* reject connection */ + return evil_cert_ok; /* reject connection */ } if(c->opt->option.ocsp && !ocsp_check(c, callback_ctx)) { s_log(LOG_WARNING, "OCSP check failed: depth=%d, %s", callback_ctx->error_depth, subject_name); - return 0; /* reject connection */ + return evil_cert_ok; /* reject connection */ } /* errnum=X509_STORE_CTX_get_error(ctx); */ s_log(LOG_NOTICE, "Certificate accepted: depth=%d, %s",