Merge pull request #17 from ExtraHop/master

New features (Add IPv6 and SSL cert verification support, make SSLv3 optional)
This commit is contained in:
Mark Janssen 2016-11-08 15:36:32 +01:00 committed by GitHub
commit cb8e792052
7 changed files with 276 additions and 43 deletions

View file

@ -60,9 +60,14 @@ 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"
#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"
" -R, --remproxyauth=STRING Remote proxy auth credentials user:pass combination\n"
@ -136,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() \
@ -160,9 +166,12 @@ 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; \
args_info->no_check_cert_flag = 0; \
args_info->cacert_arg = NULL; \
}
clear_args();
@ -206,12 +215,15 @@ 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' },
{ "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:", 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:" );
c = getopt( argc, argv, "hVia:u:s:t:F:p:P:r:R:d:H:x:nvNeEXqLo:TzC:" );
#endif
if (c == -1)
@ -390,6 +402,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) {
@ -423,6 +440,22 @@ 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 '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. */
@ -503,12 +536,23 @@ 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 ) {
/* 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;

View file

@ -47,9 +47,12 @@ 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 */
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. */
@ -75,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 );

View file

@ -49,6 +49,20 @@ 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)
*-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.
*-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

2
http.c
View file

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

View file

@ -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;
@ -266,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 */
@ -275,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
@ -388,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 */
@ -397,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

View file

@ -19,12 +19,15 @@
/* ptstream.c */
#include <arpa/inet.h>
#include <openssl/x509v3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include "proxytunnel.h"
@ -142,14 +145,112 @@ 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;
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;
char proxy_arg_fmt[32];
size_t proxy_arg_len;
/* Initialise the connection */
SSLeay_add_ssl_algorithms();
if (args_info.enforcetls1_flag) {
@ -160,6 +261,30 @@ 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);
if ( !args_info.no_check_cert_flag ) {
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;
}
}
ssl = SSL_new (ctx);
SSL_set_rfd (ssl, stream_get_incoming_fd(pts));
@ -177,6 +302,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;
@ -185,6 +346,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);
}

View file

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