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;