--- ./stunnel-4.43.orig/src/client.c 2011-10-21 12:03:53.000000000 +0500 +++ ./stunnel-4.43/src/client.c 2011-10-21 12:36:46.000000000 +0500 @@ -261,6 +261,230 @@ longjmp(c->err, 1); } + +static char *get_peer_ip(CLI *c) { +char *peer_ip = 0; + peer_ip = strtok(c->accepted_address, ":"); + return(strdup(peer_ip)); +} + + + + +static char *get_peer_name(CLI *c) { + +struct addrinfo *result; + struct addrinfo *res; + int error; + char hostname[NI_MAXHOST] = ""; + char *peer_ip = 0; + peer_ip = strtok(c->accepted_address, ":"); + /* resolve the domain name into a list of addresses */ + + error = getaddrinfo(peer_ip, NULL, NULL, &result); + if (error != 0) + { + s_log(LOG_ERR, "Post check: getaddr failed '%s'",gai_strerror(error)); + return(0); + } + + /* loop over all returned results and do inverse lookup */ + for (res = result; res != NULL; res = res->ai_next) + { + + error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0); + if (error != 0) + { + s_log(LOG_ERR, "Post check: getnameinfo failed '%s'",gai_strerror(error)); + return(0); + } + if (*hostname != '\0') + s_log(LOG_NOTICE, "Post Peer Name: '%s'", hostname); + } + + freeaddrinfo(result); + + return(strdup(hostname)); + +} + +static char *get_remote_ip(CLI *c) { + struct sockaddr *from = &(c->opt->remote_addr.addr[c->opt->remote_addr.cur].sa); + socklen_t from_len = sizeof(struct sockaddr_in); // only INET is of interest, no INET6 support + char ip[NI_MAXHOST]; + + if(0 != getnameinfo(from, from_len, ip, sizeof(ip), 0, 0, NI_NUMERICHOST)) { + s_log(LOG_ERR, "getnameinfo NI_NUMERICHOST failed"); + return(NULL); + } + + return(strdup(ip)); +} + + +static char *get_remote_name(CLI *c) { + struct addrinfo *result; + struct addrinfo *res; + int error; + char hostname[NI_MAXHOST] = ""; + char *ip = 0; + ip = get_remote_ip(c); + + error = getaddrinfo(ip, NULL, NULL, &result); + if (error != 0) + { + s_log(LOG_ERR, "Post check: getaddr failed '%s'",gai_strerror(error)); + return(0); + } + /* loop over all returned results and do inverse lookup */ + for (res = result; res != NULL; res = res->ai_next) + { + error = getnameinfo(res->ai_addr, res->ai_addrlen, hostname, NI_MAXHOST, NULL, 0, 0); + if (error != 0) + { + s_log(LOG_ERR, "Post check: getnameinfo failed '%s'",gai_strerror(error)); + return(0); + } + if (*hostname != '\0') + s_log(LOG_NOTICE, "Post Peer Name: '%s'", hostname); + } + + freeaddrinfo(result); + + return(strdup(hostname)); + +} + + + +static int verify_cert_hostname(X509 *cert, const char *hostname) { + int ok = 0; + + { // check against subjectAltName + GENERAL_NAMES *subjectAltNames; + int i; + + if(subjectAltNames = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0)) { + for(i = 0; !ok && i < sk_GENERAL_NAME_num(subjectAltNames); ++i) { + GENERAL_NAME *name; + unsigned char *dns; + int dnsLen; + + name = sk_GENERAL_NAME_value(subjectAltNames, i); + if(name->type != GEN_DNS) continue; + dnsLen = ASN1_STRING_to_UTF8(&dns, name->d.dNSName); + + s_log(LOG_NOTICE, "Post check: subjectAltName: \"%s\"," + "length: %d, strlen: %d", dns, dnsLen, strlen(dns)); + + if(strlen(hostname) == dnsLen) + if(!strcasecmp(dns, hostname)) ok = 1; + + OPENSSL_free(dns); + } + } + } + + { // check against CommonName + X509_NAME *subj; + int cnIdx; + + if(!ok + && (subj = X509_get_subject_name(cert)) + && (-1 != (cnIdx = X509_NAME_get_index_by_NID(subj, NID_commonName, -1)))) { + X509_NAME_ENTRY *cnEntry; + ASN1_STRING *cnASN1; + int cnLen; + unsigned char *cn; + + cnEntry = X509_NAME_get_entry(subj, cnIdx); + cnASN1 = X509_NAME_ENTRY_get_data(cnEntry); + cnLen = ASN1_STRING_to_UTF8(&cn, cnASN1); + s_log(LOG_NOTICE, "Post check: Cert CommonName: \"%s\"," + "length: %d, strlen: %d", cn, cnLen, strlen(cn)); + + /* only if the lengths are equal do we need to perform a comparison, + * also, if there are embedded NULLs in cn then cnLen > strlen(cn) */ + if(strlen(hostname) == cnLen) + if(!strcasecmp(cn, hostname)) ok = 1; + OPENSSL_free(cn); + } + + return ok; + } +} + +static int post_connection_check(CLI *c) { + int ok = 0; + char *rem_ip = 0; + char *rem_name = 0; + char *peer_ip = 0; + char *peer_name = 0; + X509 *cert = 0; + + if(c->opt->verify_level <= SSL_VERIFY_PEER) { + s_log(LOG_NOTICE, "Post check: verification level is low, skipping check"); + return X509_V_OK; + } + + if(!(cert = SSL_get_peer_certificate(c->ssl))) { + s_log(LOG_NOTICE, "Post check: No peer certificate!"); + return X509_V_ERR_APPLICATION_VERIFICATION; + } + + if(c->opt->option.verify_peer_name){ + peer_name = get_peer_name(c); + if(peer_name && verify_cert_hostname(cert, peer_name)) + ok = 1; + s_log(LOG_NOTICE, "Post check CN: By Client Name: %s", peer_name); + if(peer_name) free(peer_name); + X509_free(cert); + + + if(ok) + return SSL_get_verify_result(c->ssl); + else + return X509_V_ERR_APPLICATION_VERIFICATION; + } + + else if(c->opt->option.verify_peer_ip){ + peer_ip = get_peer_ip(c); + if(peer_ip && verify_cert_hostname(cert, peer_ip)) + ok = 1; + s_log(LOG_NOTICE, "Post check CN: By Client IP: %s", peer_ip); + if(peer_ip) free(peer_ip); + X509_free(cert); + if(ok) + return SSL_get_verify_result(c->ssl); + else + return X509_V_ERR_APPLICATION_VERIFICATION; + } + else if(c->opt->option.verify_server_ip){ + rem_ip = get_remote_ip(c); + if(rem_ip && verify_cert_hostname(cert, rem_ip)) + ok = 1; + s_log(LOG_NOTICE, "Post check CN: By Remote IP: %s", rem_ip); + if(rem_ip) free(rem_ip); + X509_free(cert); + if(ok) + return SSL_get_verify_result(c->ssl); + else + return X509_V_ERR_APPLICATION_VERIFICATION; + } + else if(c->opt->option.verify_server_name){ + rem_name = get_remote_name(c); + if(rem_name && verify_cert_hostname(cert, rem_name)) + ok = 1; + s_log(LOG_NOTICE, "Post check CN: By Remote Name: %s", rem_name); + if(rem_name) free(rem_name); + X509_free(cert); + if(ok) + return SSL_get_verify_result(c->ssl); + else + return X509_V_ERR_APPLICATION_VERIFICATION; + } + +} static void init_ssl(CLI *c) { int i, err; SSL_SESSION *old_session; @@ -326,8 +550,13 @@ leave_critical_section(CRIT_SSL); #endif /* OpenSSL version < 1.0.0b */ err=SSL_get_error(c->ssl, i); - if(err==SSL_ERROR_NONE) + if(err==SSL_ERROR_NONE) { + if(post_connection_check(c) != X509_V_OK) { + s_log(LOG_NOTICE, "Post connection cert verification failed"); + longjmp(c->err, 1); // bail + } break; /* ok -> done */ + } 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, --- ./stunnel-4.43.orig/src/options.c 2011-10-21 12:03:53.000000000 +0500 +++ ./stunnel-4.43/src/options.c 2011-10-21 13:06:30.000000000 +0500 @@ -1516,6 +1516,45 @@ break; } + /* verify_peer*/ + switch(cmd) { + case CMD_INIT: + section->option.verify_peer_name=0; + section->option.verify_peer_ip=0; + section->option.verify_server_name=0; + section->option.verify_server_ip=0; + break; + case CMD_EXEC: + if(strcasecmp(opt, "verify_peer")) + break; + if(!strcasecmp(arg, "client_name")) + section->option.verify_peer_name=1; + else if(!strcasecmp(arg, "client_ip")) + section->option.verify_peer_ip=1; + else if(!strcasecmp(arg, "remote_name")) + section->option.verify_server_name=1; + else if(!strcasecmp(arg, "remote_ip")) + section->option.verify_server_ip=1; + else if(!strcasecmp(arg, "no")) { + section->option.verify_peer_name=0; + section->option.verify_peer_ip=0; + section->option.verify_server_name=0; + section->option.verify_server_ip=0; + } + else + return "Argument should be 'client_name' 'client_ip' 'remote_name' 'remote_ip 'or 'no'"; + return NULL; /* OK */ + case CMD_DEFAULT: + break; + case CMD_HELP: + s_log(LOG_NOTICE, "%-15s = client_name - verify CN by Client Name", "verify_peer"); + s_log(LOG_NOTICE, "%18sclient_ip - verify CN by Client IP", ""); + s_log(LOG_NOTICE, "%18sremote_name - verify CN by Server Name", ""); + s_log(LOG_NOTICE, "%18sremote_ip - verify CN by Server IP", ""); + s_log(LOG_NOTICE, "%18sno - Disable", ""); + break; + } + if(cmd==CMD_EXEC) return option_not_found; return NULL; /* OK */ --- ./stunnel-4.43.orig/src/prototypes.h 2011-10-21 12:03:53.000000000 +0500 +++ ./stunnel-4.43/src/prototypes.h 2011-10-21 13:08:18.000000000 +0500 @@ -197,6 +197,11 @@ unsigned int transparent_dst:1; /* endpoint: transparent destination */ #endif unsigned int ocsp:1; + /* verify_peer */ + unsigned int verify_peer_name:1; + unsigned int verify_peer_ip:1; + unsigned int verify_server_name:1; + unsigned int verify_server_ip:1; } option; } SERVICE_OPTIONS;