CERTs: added checking of SAN(s) when CN does not match; fix CN wildcard handling
authorFrank W. Bergmann <loudmouth@tuxad.com>
Sun, 08 Feb 2015 01:10:26 +0100
changeset 661 22492003e3f7
parent 660 1da5ff42dc1f
child 662 9600dc32c15c
CERTs: added checking of SAN(s) when CN does not match; fix CN wildcard handling
loudmouth/lm-ssl-openssl.c
--- a/loudmouth/lm-ssl-openssl.c	Sun Feb 08 01:09:47 2015 +0100
+++ b/loudmouth/lm-ssl-openssl.c	Sun Feb 08 01:10:26 2015 +0100
@@ -37,6 +37,9 @@
 
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/x509v3.h>
+#include <openssl/asn1.h>
+#include <openssl/safestack.h>
 
 #define LM_SSL_CN_MAX       63
 
@@ -207,6 +210,7 @@
     crt_subj = X509_get_subject_name(srv_crt);
     cn = (gchar *) g_malloc0(LM_SSL_CN_MAX + 1);
 
+    /* FWB: deprecated call, can only get first entry */
     if (X509_NAME_get_text_by_NID(crt_subj, NID_commonName, cn, LM_SSL_CN_MAX) > 0) {
         gchar *domain = cn;
 
@@ -214,11 +218,53 @@
                "%s: server = '%s', cn = '%s'\n",
                __FILE__, server, cn);
 
-        if ((cn[0] == '*') && (cn[1] == '.')) {
-            domain = strstr (cn, server);
-        }
+        if (domain != NULL) {
 
-        if ((domain == NULL) || (strncasecmp (server, domain, LM_SSL_CN_MAX) != 0)) {
+            if ((cn[0] == '*') && (cn[1] == '.')) {
+                /* 
+                 * FWB: huh? ever tested?
+                 * server="sub.domain.tld";
+                 * cn="*.domain.tld";
+                 * domain=strstr(cn, server); ???
+                 */
+                /* domain = strstr (cn, server); */
+                server = strchr(server, '.') + 1;
+                domain = cn + 2;
+            }
+    
+            if (strncasecmp (server, domain, LM_SSL_CN_MAX) != 0) {
+                /* FWB: CN doesn't match, try SANs */
+                int subject_alt_names_nb = -1;
+                int san_result = 0;
+                int san_counter;
+                STACK_OF(GENERAL_NAME) *subject_alt_names = NULL;
+    
+                /* g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: CN does not match server name\n", __FILE__); */
+                // Try to extract the names within the SAN extension from the certificate
+                subject_alt_names = X509_get_ext_d2i((X509 *) srv_crt, NID_subject_alt_name, NULL, NULL);
+                if (subject_alt_names != NULL) {
+    
+                    // Check each name within the extension
+                    subject_alt_names_nb = sk_GENERAL_NAME_num(subject_alt_names);
+                    for (san_counter=0; san_counter<subject_alt_names_nb; san_counter++) {
+                        const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(subject_alt_names, san_counter);
+                        if (current_name->type == GEN_DNS) {
+                            // Current name is a DNS name, let's check it, it's ASCII
+                            if (strcasecmp(server, (char *)current_name->d.dNSName->data) == 0) {
+                                g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: found SAN '%s' - MATCH\n", __FILE__, current_name->d.dNSName->data);
+                                san_result = 1; /* break; */
+                            } else {
+                                g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: found SAN '%s'\n", __FILE__, current_name->d.dNSName->data);
+                            }
+                        }
+                    }
+    
+                }
+                sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free);
+                if (!san_result) goto cn_and_san_mismatch;
+            } /* SAN */
+        } else {
+            cn_and_san_mismatch:
             if (base->func (ssl,
                             LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH,
                             base->func_data) != LM_SSL_RESPONSE_CONTINUE) {