Package: libapache2-mod-gnutls
Version: 0.5.10-1.1
Severity: wishlist
Tags: patch

In general mod_gnutls supports virtual hosting of https sites using a single IP 
address by supporting Server Name Indication.
The problem with the current implementation in mod_gnutls is, that only one 
domain name in the certificate will be used when checking if the certificate is 
the right one for the incoming request. But certificates can contain multiple 
domain names they work for using the dnsName extension.
When I get certificates from StartSSL, they have the common name set to 
"www.example.com", and a dnsName extension set to "example.com". I cannot get a 
certificate just for "example.com" there. But my site is mostly accessed using 
just "example.com". So in my case virtual hosting fails, as the current 
implementation will only use the certificate if the incoming request is for 
"www.example.com".
I wrote a patch do mod_gnutls, that will use all domains of a certificate, when 
checking which is the right certificate for an incoming request.

BTW: The problem is also described at 
http://jan-krueger.net/development/mod_gnutls-and-startssl-level-1-certificates-the-problem-and-solution
 by Jan Krüger. But while his patch only supports up to 4 domains in a 
certificate, mine has support for an unlimited number of domains.

I would like to get multi-domain support integrated in the Debian package as 
well as in uplink. This would help me, because I would not have to patch all 
future versions of the package again ;-))


Regards,
Matthias


-- System Information:
Debian Release: wheezy/sid
  APT prefers testing
  APT policy: (500, 'testing')
Architecture: amd64 (x86_64)

Kernel: Linux 3.2.0-2-amd64 (SMP w/1 CPU core)
Locale: LANG=de_DE.UTF-8, LC_CTYPE=de_DE.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash

Versions of packages libapache2-mod-gnutls depends on:
ii  libapr-memcache0  0.7.0-1
ii  libc6             2.13-33
ii  libgnutls26       2.12.19-1

libapache2-mod-gnutls recommends no packages.

libapache2-mod-gnutls suggests no packages.

-- no debconf information
--- mod-gnutls-0.5.10.orig/src/gnutls_hooks.c
+++ mod-gnutls-0.5.10/src/gnutls_hooks.c
@@ -243,75 +243,95 @@ const char static_dh_params[] = "-----BE
  * Returns negative on error.
  */
 static int read_crt_cn(server_rec * s, apr_pool_t * p,
-		       gnutls_x509_crt_t cert, char **cert_cn)
+		       gnutls_x509_crt_t cert, char ***cert_cn)
 {
 	int rv = 0, i;
 	size_t data_len;
+	int number_names;
+	int copied_names;
+	char *buffer;
 
 
 	_gnutls_log(debug_log_fp, "%s: %d\n", __func__, __LINE__);
-	*cert_cn = NULL;
 
+	// find number of alt names
+	number_names = 0;
 	data_len = 0;
+	buffer = NULL;
+	do {
+		rv = gnutls_x509_crt_get_subject_alt_name(cert, number_names, buffer, &data_len, NULL);
+
+		// the first buffer we allocate, or we need bigger buffer
+		if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER && data_len > 1) {
+			// be prepared for longer names coming later without need to reallocate memory
+			data_len *= 2;
+			buffer = apr_palloc(p, data_len);
+		}
+
+		// one more name available, the last one we count is a fail we can use to store the CN
+		number_names++;
+	} while (rv >= 0);
+
+	// allocate memory of the list of names, plus one for the terminating NULL
+	*cert_cn = apr_palloc(p, sizeof(char*) * (number_names + 1));
+
+	// first name we try to get is CN
+	copied_names = 0;
 	rv = gnutls_x509_crt_get_dn_by_oid(cert,
 					   GNUTLS_OID_X520_COMMON_NAME,
-					   0, 0, NULL, &data_len);
+					   0, 0, buffer, &data_len);
 
 	if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER && data_len > 1) {
-		*cert_cn = apr_palloc(p, data_len);
+		buffer = apr_palloc(p, data_len);
 		rv = gnutls_x509_crt_get_dn_by_oid(cert,
 						   GNUTLS_OID_X520_COMMON_NAME,
-						   0, 0, *cert_cn,
+						   0, 0, buffer,
 						   &data_len);
-	} else {		/* No CN return subject alternative name */
-		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-			     "No common name found in certificate for '%s:%d'. Looking for subject alternative name...",
-			     s->server_hostname, s->port);
-		rv = 0;
-		/* read subject alternative name */
-		for (i = 0; !(rv < 0); i++) {
-			data_len = 0;
-			rv = gnutls_x509_crt_get_subject_alt_name(cert, i,
-								  NULL,
-								  &data_len,
-								  NULL);
-
-			if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER
-			    && data_len > 1) {
-				/* FIXME: not very efficient. What if we have several alt names
-				 * before DNSName?
-				 */
-				*cert_cn = apr_palloc(p, data_len + 1);
-
-				rv = gnutls_x509_crt_get_subject_alt_name
-				    (cert, i, *cert_cn, &data_len, NULL);
-				(*cert_cn)[data_len] = 0;
+	}
 
-				if (rv == GNUTLS_SAN_DNSNAME)
-					break;
-			}
+	if (rv >= 0) {
+		(*cert_cn)[0] = apr_palloc(p, strlen(buffer) + 1);
+		apr_cpystrn((*cert_cn)[0], buffer, strlen(buffer) + 1);
+		copied_names++;
+	}
+
+	// no we have to find subject alternative names
+	for (i = 0; i < number_names - 1 && rv != GNUTLS_E_SHORT_MEMORY_BUFFER; i++) {
+		rv = gnutls_x509_crt_get_subject_alt_name(cert, i, buffer, &data_len, NULL);
+
+		// we only ever increased buffer size, should get no memory problem anymore
+
+		// on success, copy result in the list
+		if (rv == GNUTLS_SAN_DNSNAME) {
+			(*cert_cn)[++copied_names] = apr_palloc(p, strlen(buffer) + 1);
+			apr_cpystrn((*cert_cn)[copied_names], buffer, strlen(buffer) + 1);
 		}
 	}
 
-	return rv;
+	// terminate list
+	(*cert_cn)[copied_names + 1] = NULL;
+
+	return copied_names >= 1 ? 0 : -1;
 }
 
 static int read_pgpcrt_cn(server_rec * s, apr_pool_t * p,
-			  gnutls_openpgp_crt_t cert, char **cert_cn)
+			  gnutls_openpgp_crt_t cert, char ***cert_cn)
 {
 	int rv = 0;
 	size_t data_len;
 
 
 	_gnutls_log(debug_log_fp, "%s: %d\n", __func__, __LINE__);
-	*cert_cn = NULL;
+	*cert_cn = apr_palloc(p, sizeof(char*) * 2);
+	(*cert_cn)[0] = NULL;
+	(*cert_cn)[1] = NULL;
 
 	data_len = 0;
 	rv = gnutls_openpgp_crt_get_name(cert, 0, NULL, &data_len);
 
 	if (rv == GNUTLS_E_SHORT_MEMORY_BUFFER && data_len > 1) {
-		*cert_cn = apr_palloc(p, data_len);
-		rv = gnutls_openpgp_crt_get_name(cert, 0, *cert_cn,
+		(*cert_cn)[0] = apr_palloc(p, data_len);
+		rv = gnutls_openpgp_crt_get_name(cert, 0, (*cert_cn)[0],
 						 &data_len);
 	} else {		/* No CN return subject alternative name */
 		ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
@@ -558,6 +578,7 @@ typedef struct {
 
 static int vhost_cb(void *baton, conn_rec * conn, server_rec * s)
 {
+	int tried_name = 0;
 	mgs_srvconf_rec *tsc;
 	vhost_cb_rec *x = baton;
 
@@ -569,31 +590,35 @@ static int vhost_cb(void *baton, conn_re
 		return 0;
 	}
 
-	/* The CN can contain a * -- this will match those too. */
-	if (ap_strcasecmp_match(x->sni_name, tsc->cert_cn) == 0) {
-		/* found a match */
+	/* try all names of the host */
+	for (tried_name = 0; tsc->cert_cn[tried_name] != NULL; tried_name++) {
+
+		/* The CN can contain a * -- this will match those too. */
+		if (ap_strcasecmp_match(x->sni_name, tsc->cert_cn[tried_name]) == 0) {
+			/* found a match */
 #if MOD_GNUTLS_DEBUG
-		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
-			     x->ctxt->c->base_server,
-			     "GnuTLS: Virtual Host CB: "
-			     "'%s' == '%s'", tsc->cert_cn, x->sni_name);
+			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+				     x->ctxt->c->base_server,
+				     "GnuTLS: Virtual Host CB: "
+				     "'%s' == '%s'", tsc->cert_cn[tried_name], x->sni_name);
 #endif
-		/* Because we actually change the server used here, we need to reset
-		 * things like ClientVerify.
-		 */
-		x->sc = tsc;
-		/* Shit. Crap. Dammit. We *really* should rehandshake here, as our
-		 * certificate structure *should* change when the server changes. 
-		 * acccckkkkkk. 
-		 */
-		return 1;
-	} else {
+			/* Because we actually change the server used here, we need to reset
+			 * things like ClientVerify.
+			 */
+			x->sc = tsc;
+			/* Shit. Crap. Dammit. We *really* should rehandshake here, as our
+			 * certificate structure *should* change when the server changes. 
+			 * acccckkkkkk. 
+			 */
+			return 1;
+		} else {
 #if MOD_GNUTLS_DEBUG
-		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
-			     x->ctxt->c->base_server,
-			     "GnuTLS: Virtual Host CB: "
-			     "'%s' != '%s'", tsc->cert_cn, x->sni_name);
+			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+				     x->ctxt->c->base_server,
+				     "GnuTLS: Virtual Host CB: "
+				     "'%s' != '%s'", tsc->cert_cn[tried_name], x->sni_name);
 #endif
+		}
 
 	}
 	return 0;
@@ -602,6 +627,7 @@ static int vhost_cb(void *baton, conn_re
 
 mgs_srvconf_rec *mgs_find_sni_server(gnutls_session_t session)
 {
+	int i;
 	int rv;
 	unsigned int sni_type;
 	size_t data_len = MAX_HOST_LEN;
@@ -661,22 +687,24 @@ mgs_srvconf_rec *mgs_find_sni_server(gnu
 		ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
 			     ctxt->c->base_server,
 			     "GnuTLS: sni-x509 cn: %s/%d pk: %s s: 0x%08X s->n: 0x%08X  sc: 0x%08X",
-			     tsc->cert_cn, rv,
+			     tsc->cert_cn[0], rv,
 			     gnutls_pk_algorithm_get_name
 			     (gnutls_x509_privkey_get_pk_algorithm
 			      (ctxt->sc->privkey_x509)), (unsigned int) s,
 			     (unsigned int) s->next, (unsigned int) tsc);
 #endif
 		/* The CN can contain a * -- this will match those too. */
-		if (ap_strcasecmp_match(sni_name, tsc->cert_cn) == 0) {
+		for (i = 0; tsc->cert_cn[i] != NULL; i++) [
+			if (ap_strcasecmp_match(sni_name, tsc->cert_cn[i]) == 0) {
 #if MOD_GNUTLS_DEBUG
-			ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
-				     ctxt->c->base_server,
-				     "GnuTLS: Virtual Host: "
-				     "'%s' == '%s'", tsc->cert_cn,
-				     sni_name);
+				ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+					     ctxt->c->base_server,
+					     "GnuTLS: Virtual Host: "
+					     "'%s' == '%s'", tsc->cert_cn[i],
+					     sni_name);
 #endif
-			return tsc;
+				return tsc;
+			}
 		}
 	}
 #endif
--- mod-gnutls-0.5.10.orig/include/mod_gnutls.h.in
+++ mod-gnutls-0.5.10/include/mod_gnutls.h.in
@@ -86,7 +86,7 @@ typedef struct
     gnutls_certificate_credentials_t certs;
     gnutls_srp_server_credentials_t srp_creds;
     gnutls_anon_server_credentials_t anon_creds;
-    char* cert_cn;
+    char** cert_cn;
     gnutls_x509_crt_t certs_x509[MAX_CHAIN_SIZE]; /* A certificate chain */
     unsigned int certs_x509_num;
     gnutls_x509_privkey_t privkey_x509;

Reply via email to