[stunnel-users] Patch: Hostname validation (against 4.15)

Magnus Therning magnus+stunnel at therning.org
Wed Mar 17 13:42:17 CET 2010


For reasons of insanity (not on my part) I've written the patch against 4.15,
if there's interest I'll be happy to port it to the latest version.

At the moment the patch is under GPL-2 (just like the rest of stunnel).  If
there's interest I'll work on getting my employer to release it under an
acceptable license (2- or 3-clause BSD).

/M

Index: stunnel-4.15/src/client.c
===================================================================
--- stunnel-4.15.orig/src/client.c
+++ stunnel-4.15/src/client.c
@@ -272,6 +272,169 @@ static void init_remote(CLI *c) {
         longjmp(c->err, 1);
 }

+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 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];
+    char ip2[NI_MAXHOST];
+    char name[NI_MAXHOST];
+    struct addrinfo hints, *ai, *aitop;
+
+    if(0 != getnameinfo(from, from_len, ip, sizeof(ip), 0, 0,
NI_NUMERICHOST)) {
+        s_log(LOG_ERR, "Post check: getnameinfo NI_NUMERICHOST failed");
+        return(0);
+    }
+
+    if(0 != getnameinfo(from, from_len, name, sizeof(name), 0, 0, NI_NAMEREQD))
+        return(0); // name lookup failed
+
+    // check if the name looks like a numeric address, if so someone's
+    // tricking us with PTR record like:
+    //   1.1.1.10.in-addr.arpa.  IN PTR  2.3.4.5
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_socktype = SOCK_DGRAM;
+    hints.ai_flags = AI_NUMERICHOST;
+    if(0 == getaddrinfo(name, 0, &hints, &ai)) {
+        s_log(LOG_WARNING, "Post check: Nasty PTR record '%s' is set
up for '%s', ignoring",
+            name, ip);
+        freeaddrinfo(ai);
+        return(0);
+    }
+
+    // map the name back to the IP and verify that the given address really is
+    // an addres of the host.
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_INET;
+    hints.ai_socktype = SOCK_STREAM;
+    if(0 != getaddrinfo(name, 0, &hints, &aitop)) {
+        s_log(LOG_WARNING, "Post check: Reverse mapping checking
getaddrinfo for %s [%s] "
+            "failed - breakin attempt?", name, ip);
+        return(0);
+    }
+    for(ai = aitop; ai; ai = ai->ai_next) {
+        if((0 == getnameinfo(ai->ai_addr, ai->ai_addrlen, ip2, sizeof(ip2),
+            0, 0, NI_NUMERICHOST)) && (0 == strcmp(ip2, ip)))
+            break;
+    }
+    freeaddrinfo(aitop);
+    if(!ai) {
+        // we reached the end of the list -> the name wasn't there
+        s_log(LOG_WARNING, "Post check: Address %s maps to %s, but
this does not map back "
+            "to the address - breakin attempt?", ip, name);
+        return(0);
+    }
+
+    return(strdup(name));
+}
+
+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: 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;
+    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;
+    }
+
+    rem_ip = get_remote_ip(c);
+    rem_name = get_remote_name(c);
+
+    s_log(LOG_NOTICE, "Post check: Remote IP: %s", rem_ip);
+    s_log(LOG_NOTICE, "Post check: Remote name: %s", rem_name);
+
+    if(rem_ip && verify_cert_hostname(cert, rem_ip))
+        ok = 1;
+    if(!ok && rem_name && verify_cert_hostname(cert, rem_name))
+        ok = 1;
+
+    if(rem_ip) free(rem_ip);
+    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;
@@ -320,8 +483,13 @@ static void init_ssl(CLI *c) {
         else
             i=SSL_accept(c->ssl);
         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_zero(&c->fds);
             s_poll_add(&c->fds, c->ssl_rfd->fd,
Index: stunnel-4.15/src/common.h
===================================================================
--- stunnel-4.15.orig/src/common.h
+++ stunnel-4.15/src/common.h
@@ -293,6 +293,8 @@ extern char *sys_errlist[];
 #include <openssl/err.h>
 #include <openssl/crypto.h> /* for CRYPTO_* and SSLeay_version */
 #include <openssl/rand.h>
+#include <openssl/conf.h>
+#include <openssl/x509v3.h>
 #ifdef HAVE_OSSL_ENGINE_H
 #include <openssl/engine.h>
 #endif

-- 
Magnus Therning                        (OpenPGP: 0xAB4DFBA4)
magnus@therning.org          Jabber: magnus@therning.org
http://therning.org/magnus         identi.ca|twitter: magthe
-------------- next part --------------
A non-text attachment was scrubbed...
Name: validate-name.patch
Type: text/x-patch
Size: 7045 bytes
Desc: not available
URL: <http://www.stunnel.org/pipermail/stunnel-users/attachments/20100317/2d1169ec/attachment.bin>


More information about the stunnel-users mailing list