/* Proxytunnel - (C) 2001-2008 Jos Visser / Mark Janssen */ /* Contact: josv@osp.nl / maniac@maniac.nl */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* ptstream.c */ #include #include #include #include #include #include #include #include #include #include "proxytunnel.h" /* Open a stream for incoming and outgoing data with the specified fds */ PTSTREAM *stream_open(int incoming_fd, int outgoing_fd) { PTSTREAM *pts; /* Initialise the structure and store the file descriptor */ pts = malloc(sizeof(PTSTREAM)); pts->incoming_fd = incoming_fd; pts->outgoing_fd = outgoing_fd; pts->ssl = NULL; pts->ctx = NULL; /* Return a pointer to the structure */ return pts; } /* Close a stream */ int stream_close(PTSTREAM *pts) { #ifdef USE_SSL /* Destroy the SSL context */ if (pts->ssl) { SSL_shutdown (pts->ssl); SSL_free (pts->ssl); SSL_CTX_free (pts->ctx); } #endif /* USE_SSL */ /* Close the incoming fd */ close(pts->incoming_fd); /* Close the outgoing fd */ close(pts->outgoing_fd); /* Free the structure */ free(pts); return 1; } /* Read from a stream */ int stream_read(PTSTREAM *pts, void *buf, size_t len) { /* Read up to the specified number of bytes into the buffer */ int bytes_read; if (!pts->ssl) { /* For a non-SSL stream... */ bytes_read = read(pts->incoming_fd, buf, len); } else { #ifdef USE_SSL /* For an SSL stream... */ bytes_read = SSL_read(pts->ssl, buf, len); #else /* No SSL support, so must use a non-SSL stream */ bytes_read = read(pts->incoming_fd, buf, len); #endif /* USE_SSL */ } return bytes_read; } /* Write to a stream */ int stream_write(PTSTREAM *pts, void *buf, size_t len) { /* Write the specified number of bytes from the buffer */ int bytes_written; int total_bytes_written = 0; while (total_bytes_written < len) { if (!pts->ssl) { /* For a non-SSL stream... */ bytes_written = write(pts->outgoing_fd, buf + total_bytes_written, len - total_bytes_written); } else { #ifdef USE_SSL /* For an SSL stream... */ bytes_written = SSL_write(pts->ssl, buf + total_bytes_written, len - total_bytes_written); #else /* No SSL support, so must use a non-SSL stream */ bytes_written = write(pts->outgoing_fd, buf + total_bytes_written, len - total_bytes_written); #endif /* USE_SSL */ } if (bytes_written <= 0) { break; } total_bytes_written += bytes_written; } return total_bytes_written; } /* * Copy a block of data from one stream to another. A true * return code signifies EOF on the from socket descriptor. */ int stream_copy(PTSTREAM *pts_from, PTSTREAM *pts_to) { char buf[SIZE]; int n; /* Read a buffer from the source socket */ if ( ( n = stream_read( pts_from, buf, SIZE ) ) < 0 ) { my_perror( "Socket read error" ); exit( 1 ); } /* If we have read 0 bytes, there is an EOF on src */ if( n==0 ) return 1; /* Write the buffer to the destination socket */ if ( stream_write( pts_to, buf, n ) != n ) { my_perror( "Socket write error" ); exit( 1 ); } /* We're not yet at EOF */ return 0; } /* 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 OPENSSL_VERSION_NUMBER >= 0x10100000L if (check_cert_valid_host((char*)ASN1_STRING_get0_data(gn->d.ia5), peer_host)) { #else if (check_cert_valid_host((char*)ASN1_STRING_data(gn->d.ia5), peer_host)) { #endif 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, 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; #ifndef DEFAULT_CA_FILE const char *ca_file = NULL; #else const char *ca_file = DEFAULT_CA_FILE; /* Default cert file from Makefile */ #endif /* !DEFAULT_CA_FILE */ #ifndef DEFAULT_CA_DIR const char *ca_dir = "/etc/ssl/certs/"; /* Default cert directory if none given */ #else const char *ca_dir = DEFAULT_CA_DIR; /* Default cert directory from Makefile */ #endif /* !DEFAULT_CA_DIR */ long vresult; const char *peer_arg = NULL; size_t peer_arg_len; char peer_arg_fmt[32]; char *peer_host = NULL; /* Initialise the connection */ SSLeay_add_ssl_algorithms(); #if OPENSSL_VERSION_NUMBER >= 0x10100000L meth = TLS_client_method(); #else meth = SSLv23_client_method(); #endif SSL_load_error_strings(); ctx = SSL_CTX_new (meth); 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; } } /* If given, load client certificate (chain) and key */ if ( args_info.clientcert_given && args_info.clientkey_given ) { if ( 1 != SSL_CTX_use_certificate_chain_file(ctx, args_info.clientcert_arg) ) { message("Error loading client certificate (chain) from %s\n", args_info.clientcert_arg); goto fail; } if ( 1 != SSL_CTX_use_PrivateKey_file(ctx, args_info.clientkey_arg, SSL_FILETYPE_PEM) ) { message("Error loading client key from %s, or key does not match certificate\n", args_info.clientkey_arg); goto fail; } } ssl = SSL_new (ctx); if ( ssl == NULL ) { message("SSL_new failed\n"); goto fail; } SSL_set_rfd (ssl, stream_get_incoming_fd(pts)); SSL_set_wfd (ssl, stream_get_outgoing_fd(pts)); /* Determine the host name we are connecting to */ peer_arg = args_info.host_given ? args_info.host_arg : proxy_arg; peer_arg_len = strlen(peer_arg); peer_host = alloca(peer_arg_len + 1); snprintf( peer_arg_fmt, sizeof(peer_arg_fmt), peer_arg[0] == '[' ? "[%%%zu[^]]]" : "%%%zu[^:]", peer_arg_len); if ( sscanf( peer_arg, peer_arg_fmt, peer_host ) != 1 ) { goto fail; } /* SNI support */ if ( args_info.verbose_flag ) { message( "Set SNI hostname to %s\n", peer_host); } res = SSL_set_tlsext_host_name(ssl, peer_host); if ( res != 1 ) { message( "SSL_set_tlsext_host_name() failed for host name '%s'. " "TLS SNI error, giving up\n", peer_host); goto fail; } if ( SSL_connect (ssl) <= 0) { message( "SSL_connect failed\n"); goto fail; } 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; } /* Verify the certificate name matches the host we are connecting to */ if (!check_cert_names(cert, peer_host)) { goto fail; } X509_free(cert); } /* Store ssl and ctx parameters */ pts->ssl = ssl; pts->ctx = ctx; #else message("Warning: stream_open(): SSL stream requested but no SSL support available; using unencrypted connection"); #endif /* USE_SSL */ return 1; fail: #ifdef USE_SSL if (cert != NULL) { X509_free(cert); } #endif /* USE_SSL */ exit(1); } /* Return the incoming_fd for a given stream */ int stream_get_incoming_fd(PTSTREAM *pts) { if (!pts->ssl) return pts->incoming_fd; else #ifdef USE_SSL return SSL_get_rfd(pts->ssl); #else return pts->incoming_fd; #endif /* USE_SSL */ } /* Return the outgoing_fd for a given stream */ int stream_get_outgoing_fd(PTSTREAM *pts) { if (!pts->ssl) return pts->outgoing_fd; else #ifdef USE_SSL return SSL_get_wfd(pts->ssl); #else return pts->outgoing_fd; #endif /* USE_SSL */ } // vim:noexpandtab:ts=4