Add SSL certificate verification support and enable it by default

Disable SSL certificate verification with -z/--no-check-certificate
This commit is contained in:
Stephen Kent 2016-08-18 10:24:11 -07:00
parent 4554d4b801
commit 5a7692f28f
7 changed files with 179 additions and 10 deletions

View file

@ -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. */

View file

@ -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. */

View file

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

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

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

View file

@ -19,6 +19,8 @@
/* ptstream.c */
#include <arpa/inet.h>
#include <openssl/x509v3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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);
}

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