From e771c3aaeede1c4980e587807017c40424d6b091 Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Thu, 18 Aug 2016 10:13:37 -0700 Subject: [PATCH 1/5] Read arbitrary hostname lengths on the command line --- cmdline.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmdline.c b/cmdline.c index 28d321f..c836723 100644 --- a/cmdline.c +++ b/cmdline.c @@ -503,12 +503,18 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar } if (args_info->proxy_given ) { + char proxy_arg_fmt[32]; + size_t proxy_arg_len; char * phost; int pport; - phost = malloc( 50+1 ); - - r = sscanf( args_info->proxy_arg, "%50[^:]:%5u", phost, &pport ); + proxy_arg_len = strlen( args_info->proxy_arg ); + if ( (phost = malloc( proxy_arg_len + 1 )) == NULL ) { + message( "Out of memory\n" ); + exit(1); + } + snprintf( proxy_arg_fmt, sizeof(proxy_arg_fmt), "%%%zu[^:]:%%5u", proxy_arg_len - 1 ); + r = sscanf( args_info->proxy_arg, proxy_arg_fmt, phost, &pport ); if ( r == 2 ) { args_info->proxyhost_arg = phost; args_info->proxyport_arg = pport; From dbffa1653e323c3f923a733b67de4987434e74ba Mon Sep 17 00:00:00 2001 From: Will Mortensen Date: Thu, 18 Aug 2016 10:15:33 -0700 Subject: [PATCH 2/5] Add IPv6 support --- cmdline.c | 5 +++++ proxytunnel.c | 59 +++++++++++++++++++++++++-------------------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/cmdline.c b/cmdline.c index c836723..6998d5f 100644 --- a/cmdline.c +++ b/cmdline.c @@ -515,6 +515,11 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar } snprintf( proxy_arg_fmt, sizeof(proxy_arg_fmt), "%%%zu[^:]:%%5u", proxy_arg_len - 1 ); r = sscanf( args_info->proxy_arg, proxy_arg_fmt, phost, &pport ); + if ( r != 2 ) { + /* try bracket-enclosed IPv6 literal */ + snprintf( proxy_arg_fmt, sizeof(proxy_arg_fmt), "[%%%zu[^]]]:%%5u", proxy_arg_len - 1 ); + r = sscanf( args_info->proxy_arg, proxy_arg_fmt, phost, &pport ); + } if ( r == 2 ) { args_info->proxyhost_arg = phost; args_info->proxyport_arg = pport; diff --git a/proxytunnel.c b/proxytunnel.c index 9844e3d..b0ab19d 100644 --- a/proxytunnel.c +++ b/proxytunnel.c @@ -66,45 +66,44 @@ void signal_handler( int signal ) { * the socket that is connected to the proxy */ int tunnel_connect() { - struct sockaddr_in sa; - struct hostent *he; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV, + .ai_protocol = 0 + }; + struct addrinfo * result, * rp; + int rc; + char service[6]; int sd; - /* Create the socket */ - if( ( sd = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) { - my_perror("Can not create socket"); + rc = snprintf( service, sizeof(service), "%d", args_info.proxyport_arg ); + if( ( rc < 0 ) || ( rc >= sizeof(service) ) ) { + /* this should never happen */ + message( "snprintf() failed" ); exit(1); } - - /* Lookup the IP address of the proxy */ - if( ! ( he = gethostbyname( args_info.proxyhost_arg ) ) ) { -// FIXME: my_perror("Local proxy %s could not be resolved", args_info.proxyhost_arg); - my_perror("Local proxy could not be resolved." ); + rc = getaddrinfo( args_info.proxyhost_arg, service, &hints, &result ); + if( rc != 0 ) { + message( "getaddrinfo() failed to resolve local proxy: %s\n", + gai_strerror( rc ) ); exit(1); } - - char ip[16]; - snprintf(ip, 16, "%d.%d.%d.%d", he->h_addr[0] & 255, he->h_addr[1] & 255, he->h_addr[2] & 255, he->h_addr[3] & 255); - if( args_info.verbose_flag && strcmp(args_info.proxyhost_arg, ip)) { - message( "Local proxy %s resolves to %d.%d.%d.%d\n", - args_info.proxyhost_arg, - he->h_addr[0] & 255, - he->h_addr[1] & 255, - he->h_addr[2] & 255, - he->h_addr[3] & 255 ); + for (rp = result; rp != NULL; rp = rp->ai_next) { + sd = socket( rp->ai_family, rp->ai_socktype, rp->ai_protocol ); + if( sd < 0 ) { + continue; + } + if( connect( sd, rp->ai_addr, rp->ai_addrlen ) == 0 ) { + break; + } + close(sd); } - - /* Set up the structure to connect to the proxy port of the proxy host */ - memset( &sa, '\0', sizeof( sa ) ); - sa.sin_family = AF_INET; - memcpy( &sa.sin_addr.s_addr, he->h_addr, 4); - sa.sin_port = htons( args_info.proxyport_arg ); - - /* Connect the socket */ - if( connect( sd, (struct sockaddr*) &sa, sizeof( sa ) ) < 0 ) { - my_perror("connect() failed"); + if( rp == NULL ) { + my_perror( "failed to connect to local proxy" ); exit(1); } + freeaddrinfo(result); /* Increase interactivity of tunnel, patch by Ingo Molnar */ int flag = 1; From 4554d4b801142600087a73512a707aa1bf6c990d Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Thu, 18 Aug 2016 10:21:48 -0700 Subject: [PATCH 3/5] Add an option to disable SSLv3 Disable SSLv3 with the option -T/--no-ssl3 --- cmdline.c | 12 ++++++++++-- cmdline.h | 1 + docs/proxytunnel.1.adoc | 3 +++ ptstream.c | 5 +++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cmdline.c b/cmdline.c index 6998d5f..6c20bdc 100644 --- a/cmdline.c +++ b/cmdline.c @@ -60,6 +60,7 @@ void cmdline_parser_print_help (void) { " -E, --encrypt-proxy SSL encrypt data between client and local proxy\n" " -X, --encrypt-remproxy SSL encrypt data between local and remote proxy\n" " -L (legacy) enforce TLSv1 connection\n" +" -T, --no-ssl3 Do not connect using SSLv3\n" #endif "\n" "Additional options for specific features:\n" @@ -160,6 +161,7 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar args_info->encrypt_flag = 0; \ args_info->encryptproxy_flag = 0; \ args_info->encryptremproxy_flag = 0; \ + args_info->no_ssl3_flag = 0; \ args_info->proctitle_arg = NULL; \ args_info->enforcetls1_flag = 0; \ args_info->host_arg = NULL; \ @@ -206,12 +208,13 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar { "encrypt", 0, NULL, 'e' }, { "encrypt-proxy", 0, NULL, 'E' }, { "encrypt-remproxy",0,NULL, 'X' }, + { "no-ssl3", 0, NULL, 'T' }, { NULL, 0, NULL, 0 } }; - c = getopt_long (argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:", long_options, &option_index); + c = getopt_long (argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:T", long_options, &option_index); #else - c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:" ); + c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:T" ); #endif if (c == -1) @@ -390,6 +393,11 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar message("SSL local to remote proxy enabled\n"); break; + case 'T': /* Turn off SSLv3 */ + args_info->no_ssl3_flag = !(args_info->no_ssl3_flag); + if( args_info->verbose_flag ) + message("SSLv3 disabled\n"); + break; case 'd': /* Destination host to built the tunnel to. */ if (args_info->dest_given) { diff --git a/cmdline.h b/cmdline.h index 01a92ad..f8620e0 100644 --- a/cmdline.h +++ b/cmdline.h @@ -47,6 +47,7 @@ struct gengetopt_args_info { int encrypt_flag; /* Turn on SSL encryption (default=off). */ int encryptproxy_flag; /* Turn on client to proxy SSL encryption (def=off).*/ int encryptremproxy_flag; /* Turn on local to remote proxy SSL encryption (def=off).*/ + int no_ssl3_flag; /* Turn off SSLv3 (default=on) */ char *proctitle_arg; /* Override process title (default=off). */ int enforcetls1_flag; /* Override default and enforce TLSv1 */ char *host_arg; /* Optional Host Header */ diff --git a/docs/proxytunnel.1.adoc b/docs/proxytunnel.1.adoc index 03fa427..9d60abe 100644 --- a/docs/proxytunnel.1.adoc +++ b/docs/proxytunnel.1.adoc @@ -49,6 +49,9 @@ also be used for other proxy-traversing purposes like proxy bouncing. == ADDITIONAL OPTIONS +*-T*, *--no-ssl3*:: + Prevent the use of SSLv3 in encrypted connections (default: enabled) + *-F*, *--passfile*=_filename_:: Use _filename_ for reading username and password for HTTPS proxy authentication, the file uses the same format as .wgetrc and can be shared diff --git a/ptstream.c b/ptstream.c index 4f16d40..6c9bc97 100644 --- a/ptstream.c +++ b/ptstream.c @@ -149,6 +149,7 @@ int stream_enable_ssl(PTSTREAM *pts) { SSL *ssl; SSL_CTX *ctx; long res = 1; + long ssl_options = 0; /* Initialise the connection */ SSLeay_add_ssl_algorithms(); @@ -160,6 +161,10 @@ int stream_enable_ssl(PTSTREAM *pts) { SSL_load_error_strings(); ctx = SSL_CTX_new (meth); + if (args_info.no_ssl3_flag) { + ssl_options |= SSL_OP_NO_SSLv3; + } + SSL_CTX_set_options (ctx, ssl_options); ssl = SSL_new (ctx); SSL_set_rfd (ssl, stream_get_incoming_fd(pts)); From 5a7692f28f536108acea5b5efc129e14c9c383ca Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Thu, 18 Aug 2016 10:24:11 -0700 Subject: [PATCH 4/5] Add SSL certificate verification support and enable it by default Disable SSL certificate verification with -z/--no-check-certificate --- cmdline.c | 15 +++- cmdline.h | 1 + docs/proxytunnel.1.adoc | 6 ++ http.c | 2 +- proxytunnel.c | 8 +-- ptstream.c | 155 +++++++++++++++++++++++++++++++++++++++- ptstream.h | 2 +- 7 files changed, 179 insertions(+), 10 deletions(-) diff --git a/cmdline.c b/cmdline.c index 6c20bdc..9d15e57 100644 --- a/cmdline.c +++ b/cmdline.c @@ -64,6 +64,9 @@ void cmdline_parser_print_help (void) { #endif "\n" "Additional options for specific features:\n" +#ifdef USE_SSL +" -z, --no-check-certficate Don't verify server SSL certificate\n" +#endif " -F, --passfile=STRING File with credentials for proxy authentication\n" " -P, --proxyauth=STRING Proxy auth credentials user:pass combination\n" " -R, --remproxyauth=STRING Remote proxy auth credentials user:pass combination\n" @@ -165,6 +168,7 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar args_info->proctitle_arg = NULL; \ args_info->enforcetls1_flag = 0; \ args_info->host_arg = NULL; \ + args_info->no_check_cert_flag = 0; \ } clear_args(); @@ -209,12 +213,13 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar { "encrypt-proxy", 0, NULL, 'E' }, { "encrypt-remproxy",0,NULL, 'X' }, { "no-ssl3", 0, NULL, 'T' }, + { "no-check-certificate",0,NULL,'z' }, { NULL, 0, NULL, 0 } }; - c = getopt_long (argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:T", long_options, &option_index); + c = getopt_long (argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:Tz", long_options, &option_index); #else - c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:T" ); + c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:Tz" ); #endif if (c == -1) @@ -431,6 +436,12 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar args_info->quiet_flag = !(args_info->quiet_flag); break; + case 'z': /* Don't verify server SSL certificate */ + args_info->no_check_cert_flag = 1; + if( args_info->verbose_flag ) + message("Server SSL certificate verification disabled\n"); + break; + case 0: /* Long option with no short option */ case '?': /* Invalid option. */ diff --git a/cmdline.h b/cmdline.h index f8620e0..c1eef43 100644 --- a/cmdline.h +++ b/cmdline.h @@ -51,6 +51,7 @@ struct gengetopt_args_info { char *proctitle_arg; /* Override process title (default=off). */ int enforcetls1_flag; /* Override default and enforce TLSv1 */ char *host_arg; /* Optional Host Header */ + int no_check_cert_flag; /* Turn off server SSL certificate verification (default=on) */ int help_given; /* Whether help was given. */ int version_given; /* Whether version was given. */ int user_given; /* Whether user was given. */ diff --git a/docs/proxytunnel.1.adoc b/docs/proxytunnel.1.adoc index 9d60abe..6740412 100644 --- a/docs/proxytunnel.1.adoc +++ b/docs/proxytunnel.1.adoc @@ -52,6 +52,12 @@ also be used for other proxy-traversing purposes like proxy bouncing. *-T*, *--no-ssl3*:: Prevent the use of SSLv3 in encrypted connections (default: enabled) +*-z*, *--no-check-certificate*:: + Do not verify server SSL certificate when establishing an SSL connection. + By default, the server SSL certficate is verified and the target host name + is checked against the server certificate's subject alternative names if + any are present, or common name if there are no subject alternative names. + *-F*, *--passfile*=_filename_:: Use _filename_ for reading username and password for HTTPS proxy authentication, the file uses the same format as .wgetrc and can be shared diff --git a/http.c b/http.c index b88f812..7f16ac9 100644 --- a/http.c +++ b/http.c @@ -159,7 +159,7 @@ void proxy_protocol(PTSTREAM *pts) { /* If --encrypt-remproxy is specified, connect to the remote proxy using SSL */ if ( args_info.encryptremproxy_flag ) - stream_enable_ssl(stunnel); + stream_enable_ssl(stunnel, args_info.remproxy_arg); if( args_info.verbose_flag ) message( "\nTunneling to %s (destination)\n", args_info.dest_arg ); diff --git a/proxytunnel.c b/proxytunnel.c index b0ab19d..18aa414 100644 --- a/proxytunnel.c +++ b/proxytunnel.c @@ -265,7 +265,7 @@ void do_daemon() #ifdef USE_SSL /* If --encrypt-proxy is specified, connect to the proxy using SSL */ if ( args_info.encryptproxy_flag ) - stream_enable_ssl(stunnel); + stream_enable_ssl(stunnel, args_info.proxy_arg); #endif /* USE_SSL */ /* Open the tunnel */ @@ -274,7 +274,7 @@ void do_daemon() #ifdef USE_SSL /* If --encrypt is specified, wrap all traffic after the proxy handoff in SSL */ if( args_info.encrypt_flag ) - stream_enable_ssl(stunnel); + stream_enable_ssl(stunnel, args_info.dest_arg); #endif /* USE_SSL */ #ifdef SETPROCTITLE @@ -387,7 +387,7 @@ int main( int argc, char *argv[] ) { /* If --encrypt-proxy is specified, connect to the proxy using SSL */ #ifdef USE_SSL if ( args_info.encryptproxy_flag ) - stream_enable_ssl(stunnel); + stream_enable_ssl(stunnel, args_info.proxy_arg); #endif /* USE_SSL */ /* Open the tunnel */ @@ -396,7 +396,7 @@ int main( int argc, char *argv[] ) { /* If --encrypt is specified, wrap all traffic after the proxy handoff in SSL */ #ifdef USE_SSL if( args_info.encrypt_flag ) - stream_enable_ssl(stunnel); + stream_enable_ssl(stunnel, args_info.dest_arg); #endif /* USE_SSL */ #ifdef SETPROCTITLE diff --git a/ptstream.c b/ptstream.c index 6c9bc97..491c562 100644 --- a/ptstream.c +++ b/ptstream.c @@ -19,6 +19,8 @@ /* ptstream.c */ +#include +#include #include #include #include @@ -142,15 +144,109 @@ int stream_copy(PTSTREAM *pts_from, PTSTREAM *pts_to) { } +/* Check the certificate host name against the expected host name */ +/* Return 1 if peer hostname is valid, any other value indicates failure */ +int check_cert_valid_host(const char *cert_host, const char *peer_host) { + if (cert_host == NULL || peer_host == NULL) { + return 0; + } + if (cert_host[0] == '*') { + if (strncmp(cert_host, "*.", 2) != 0) { + /* Invalid wildcard hostname */ + return 0; + } + /* Skip "*." */ + cert_host += 2; + /* Wildcards can only match the first subdomain component */ + while (*peer_host++ != '.' && *peer_host != '\0') + ;; + } + if (strlen(cert_host) == 0 || strlen(peer_host) == 0) { + return 0; + } + return strcmp(cert_host, peer_host) == 0; +} + + +int check_cert_valid_ip6(const unsigned char *cert_ip_data, const int cert_ip_len, const struct in6_addr *addr6) { + int i; + for (i = 0; i < cert_ip_len; i++) { + if (cert_ip_data[i] != addr6->s6_addr[i]) { + return 0; + } + } + return 1; +} + + +int check_cert_valid_ip(const unsigned char *cert_ip_data, const int cert_ip_len, const struct in_addr *addr) { + int i; + for (i = 0; i < cert_ip_len; i++) { + if (cert_ip_data[i] != ((addr->s_addr >> (i * 8)) & 0xFF)) { + return 0; + } + } + return 1; +} + + +int check_cert_names(X509 *cert, char *peer_host) { + char peer_cn[256]; + const GENERAL_NAME *gn; + STACK_OF(GENERAL_NAME) *gen_names; + struct in_addr addr; + struct in6_addr addr6; + int peer_host_is_ipv4 = 0, peer_host_is_ipv6 = 0; + int i, san_count; + + gen_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + san_count = sk_GENERAL_NAME_num(gen_names); + if (san_count > 0) { + peer_host_is_ipv4 = (inet_pton(AF_INET, peer_host, &addr) == 1); + peer_host_is_ipv6 = (peer_host_is_ipv4 ? 0 : inet_pton(AF_INET6, peer_host, &addr6) == 1); + for (i = 0; i < san_count; i++) { + gn = sk_GENERAL_NAME_value(gen_names, i); + if (gn->type == GEN_DNS && !(peer_host_is_ipv4 || peer_host_is_ipv6)) { + if (check_cert_valid_host((char*)ASN1_STRING_data(gn->d.ia5), peer_host)) { + return 1; + } + } else if (gn->type == GEN_IPADD) { + if (gn->d.ip->length == 4 && peer_host_is_ipv4) { + if (check_cert_valid_ip(gn->d.ip->data, gn->d.ip->length, &addr)) { + return 1; + } + } else if (gn->d.ip->length == 16 && peer_host_is_ipv6) { + if (check_cert_valid_ip6(gn->d.ip->data, gn->d.ip->length, &addr6)) { + return 1; + } + } + } + } + message("Host name %s does not match certificate subject alternative names\n", peer_host); + } else { + X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, peer_cn, sizeof(peer_cn)); + message("Host name %s does not match certificate common name %s or any subject alternative names\n", peer_host, peer_cn); + return check_cert_valid_host(peer_cn, peer_host); + } + return 0; +} + /* Initiate an SSL handshake on this stream and encrypt all subsequent data */ -int stream_enable_ssl(PTSTREAM *pts) { +int stream_enable_ssl(PTSTREAM *pts, const char *proxy_arg) { #ifdef USE_SSL const SSL_METHOD *meth; SSL *ssl; SSL_CTX *ctx; long res = 1; long ssl_options = 0; - + + X509* cert = NULL; + const char *ca_dir = "/etc/ssl/certs/"; /* Default cert directory if none given */ + long vresult; + char *peer_host = NULL; + char proxy_arg_fmt[32]; + size_t proxy_arg_len; + /* Initialise the connection */ SSLeay_add_ssl_algorithms(); if (args_info.enforcetls1_flag) { @@ -165,6 +261,14 @@ int stream_enable_ssl(PTSTREAM *pts) { ssl_options |= SSL_OP_NO_SSLv3; } SSL_CTX_set_options (ctx, ssl_options); + + if ( !args_info.no_check_cert_flag ) { + if (!SSL_CTX_load_verify_locations(ctx, NULL, ca_dir)) { + message("Error loading certificates from %s\n", ca_dir); + goto fail; + } + } + ssl = SSL_new (ctx); SSL_set_rfd (ssl, stream_get_incoming_fd(pts)); @@ -182,6 +286,42 @@ int stream_enable_ssl(PTSTREAM *pts) { SSL_connect (ssl); + if ( !args_info.no_check_cert_flag ) { + /* Make sure peer presented a certificate */ + cert = SSL_get_peer_certificate(ssl); + if (cert == NULL) { + message("No certificate presented\n"); + goto fail; + } + + /* Check that the certificate is valid */ + vresult = SSL_get_verify_result(ssl); + if (vresult != X509_V_OK) { + message("Certificate verification failed (%s)\n", + X509_verify_cert_error_string(vresult)); + goto fail; + } + + /* Determine the host name we are connecting to */ + proxy_arg_len = strlen(proxy_arg); + if ((peer_host = malloc(proxy_arg_len + 1)) == NULL) { + message("Out of memory\n"); + goto fail; + } + snprintf( proxy_arg_fmt, sizeof(proxy_arg_fmt), proxy_arg[0] == '[' ? "[%%%zu[^]]]" : "%%%zu[^:]", proxy_arg_len - 1 ); + if ( sscanf( proxy_arg, proxy_arg_fmt, peer_host ) != 1 ) { + goto fail; + } + + /* Verify the certificate name matches the host we are connecting to */ + if (!check_cert_names(cert, peer_host)) { + goto fail; + } + + free(peer_host); + X509_free(cert); + } + /* Store ssl and ctx parameters */ pts->ssl = ssl; pts->ctx = ctx; @@ -190,6 +330,17 @@ int stream_enable_ssl(PTSTREAM *pts) { #endif /* USE_SSL */ return 1; + +fail: +#ifdef USE_SSL + if (cert != NULL) { + X509_free(cert); + } + if (peer_host != NULL) { + free(peer_host); + } +#endif /* USE_SSL */ + exit(1); } diff --git a/ptstream.h b/ptstream.h index e7efd43..ee36af2 100644 --- a/ptstream.h +++ b/ptstream.h @@ -44,7 +44,7 @@ int stream_close(PTSTREAM *pts); int stream_read(PTSTREAM *pts, void *buf, size_t len); int stream_write(PTSTREAM *pts, void *buf, size_t len); int stream_copy(PTSTREAM *pts_from, PTSTREAM *pts_to); -int stream_enable_ssl(PTSTREAM *pts); +int stream_enable_ssl(PTSTREAM *pts, const char *proxy_arg); int stream_get_incoming_fd(PTSTREAM *pts); int stream_get_outgoing_fd(PTSTREAM *pts); From 655845108ca221b709c435b004a3cf8f2b3d78fd Mon Sep 17 00:00:00 2001 From: Stephen Kent Date: Thu, 18 Aug 2016 10:26:41 -0700 Subject: [PATCH 5/5] Add an option to provide a CA cert for server certificate verification Enable this option with -C/--cacert --- cmdline.c | 18 ++++++++++++++++-- cmdline.h | 2 ++ docs/proxytunnel.1.adoc | 5 +++++ ptstream.c | 20 ++++++++++++++++++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/cmdline.c b/cmdline.c index 9d15e57..518d216 100644 --- a/cmdline.c +++ b/cmdline.c @@ -66,6 +66,7 @@ void cmdline_parser_print_help (void) { "Additional options for specific features:\n" #ifdef USE_SSL " -z, --no-check-certficate Don't verify server SSL certificate\n" +" -C, --cacert=STRING Path to trusted CA certificate or directory\n" #endif " -F, --passfile=STRING File with credentials for proxy authentication\n" " -P, --proxyauth=STRING Proxy auth credentials user:pass combination\n" @@ -140,6 +141,7 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar args_info->proctitle_given = 0; args_info->enforcetls1_given = 0; args_info->host_given = 0; + args_info->cacert_given = 0; /* No... we can't make this a function... -- Maniac */ #define clear_args() \ @@ -169,6 +171,7 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar args_info->enforcetls1_flag = 0; \ args_info->host_arg = NULL; \ args_info->no_check_cert_flag = 0; \ + args_info->cacert_arg = NULL; \ } clear_args(); @@ -214,12 +217,13 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar { "encrypt-remproxy",0,NULL, 'X' }, { "no-ssl3", 0, NULL, 'T' }, { "no-check-certificate",0,NULL,'z' }, + { "cacert", 1, NULL, 'C' }, { NULL, 0, NULL, 0 } }; - c = getopt_long (argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:Tz", long_options, &option_index); + c = getopt_long (argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:TzC:", long_options, &option_index); #else - c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:Tz" ); + c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:TzC:" ); #endif if (c == -1) @@ -442,6 +446,16 @@ int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *ar message("Server SSL certificate verification disabled\n"); break; + case 'C': /* Trusted CA certificate (or directory) for server SSL certificate verification */ + if (args_info->cacert_given) { + fprintf (stderr, "%s: `--cacert' (`-C') option given more than once\n", PACKAGE); + clear_args (); + exit(1); + } + args_info->cacert_given = 1; + args_info->cacert_arg = gengetopt_strdup (optarg); + break; + case 0: /* Long option with no short option */ case '?': /* Invalid option. */ diff --git a/cmdline.h b/cmdline.h index c1eef43..cd85191 100644 --- a/cmdline.h +++ b/cmdline.h @@ -52,6 +52,7 @@ struct gengetopt_args_info { int enforcetls1_flag; /* Override default and enforce TLSv1 */ char *host_arg; /* Optional Host Header */ int no_check_cert_flag; /* Turn off server SSL certificate verification (default=on) */ + char *cacert_arg; /* Trusted CA certificate (or directory) for server SSL certificate verification */ int help_given; /* Whether help was given. */ int version_given; /* Whether version was given. */ int user_given; /* Whether user was given. */ @@ -77,6 +78,7 @@ struct gengetopt_args_info { int proctitle_given; /* Whether to override process title */ int enforcetls1_given; /* Wheter to enforce TLSv1 */ int host_given; /* Wheter we override the Host Header */ + int cacert_given; /* Whether cacert was given */ }; int cmdline_parser( int argc, char * const *argv, struct gengetopt_args_info *args_info ); diff --git a/docs/proxytunnel.1.adoc b/docs/proxytunnel.1.adoc index 6740412..a884207 100644 --- a/docs/proxytunnel.1.adoc +++ b/docs/proxytunnel.1.adoc @@ -58,6 +58,11 @@ also be used for other proxy-traversing purposes like proxy bouncing. is checked against the server certificate's subject alternative names if any are present, or common name if there are no subject alternative names. +*-C*, *--cacert*=_filename/directory_:: + Specify a CA certificate file (or directory containing CA certificate(s)) + to trust when verifying a server SSL certificate. If a directory is provided, + it must be prepared with OpenSSL's c_rehash tool. (default: /etc/ssl/certs) + *-F*, *--passfile*=_filename_:: Use _filename_ for reading username and password for HTTPS proxy authentication, the file uses the same format as .wgetrc and can be shared diff --git a/ptstream.c b/ptstream.c index 491c562..88d9e1f 100644 --- a/ptstream.c +++ b/ptstream.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "proxytunnel.h" @@ -241,6 +242,9 @@ int stream_enable_ssl(PTSTREAM *pts, const char *proxy_arg) { long ssl_options = 0; X509* cert = NULL; + int status; + struct stat st_buf; + const char *ca_file = NULL; const char *ca_dir = "/etc/ssl/certs/"; /* Default cert directory if none given */ long vresult; char *peer_host = NULL; @@ -263,8 +267,20 @@ int stream_enable_ssl(PTSTREAM *pts, const char *proxy_arg) { SSL_CTX_set_options (ctx, ssl_options); if ( !args_info.no_check_cert_flag ) { - if (!SSL_CTX_load_verify_locations(ctx, NULL, ca_dir)) { - message("Error loading certificates from %s\n", ca_dir); + if ( args_info.cacert_given ) { + if ((status = stat(args_info.cacert_arg, &st_buf)) != 0) { + message("Error reading certificate path %s\n", args_info.cacert_arg); + goto fail; + } + if (S_ISDIR(st_buf.st_mode)) { + ca_dir = args_info.cacert_arg; + } else { + ca_dir = NULL; + ca_file = args_info.cacert_arg; + } + } + if (!SSL_CTX_load_verify_locations(ctx, ca_file, ca_dir)) { + message("Error loading certificate(s) from %s\n", args_info.cacert_arg); goto fail; } }