From d7726f8a1366efcd09329ee20beefb7e8ece9078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim Rühsen?= Date: Wed, 16 Mar 2016 11:23:51 +0100 Subject: [PATCH] Fix SNI server names with trailing dot(s) * src/gnutls.c (ssl_connect_wget, ssl_check_certificate): Fix SNI server name * src/openssl.c (ssl_connect_wget, ssl_check_certificate): Fix SNI server name Fixes #47408 --- src/gnutls.c | 32 ++++++++++++++++++++++++++++---- src/openssl.c | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/gnutls.c b/src/gnutls.c index d39371f..3e1596a 100644 --- a/src/gnutls.c +++ b/src/gnutls.c @@ -36,6 +36,7 @@ as that of the covered work. */ #include #include #include +#include #include #include @@ -518,6 +519,22 @@ _do_handshake (gnutls_session_t session, int fd, double timeout) return err; } +static const char * +_sni_hostname(const char *hostname) +{ + size_t len = strlen(hostname); + + char *sni_hostname = xmemdup(hostname, len + 1); + + /* Remove trailing dot(s) to fix #47408. + * Regarding RFC 6066 (SNI): The hostname is represented as a byte + * string using ASCII encoding without a trailing dot. */ + while (len && sni_hostname[--len] == '.') + sni_hostname[len] = 0; + + return sni_hostname; +} + bool ssl_connect_wget (int fd, const char *hostname, int *continue_session) { @@ -530,8 +547,12 @@ ssl_connect_wget (int fd, const char *hostname, int *continue_session) /* We set the server name but only if it's not an IP address. */ if (! is_valid_ip_address (hostname)) { - gnutls_server_name_set (session, GNUTLS_NAME_DNS, hostname, - strlen (hostname)); + /* GnuTLS 3.4.x (x<) disrespects the length parameter, we have to construct a new string */ + /* see https://gitlab.com/gnutls/gnutls/issues/78 */ + const char *sni_hostname = _sni_hostname(hostname); + + gnutls_server_name_set (session, GNUTLS_NAME_DNS, sni_hostname, strlen(sni_hostname)); + xfree(sni_hostname); } gnutls_set_default_priority (session); @@ -719,6 +740,7 @@ ssl_check_certificate (int fd, const char *host) gnutls_x509_crt_t cert; const gnutls_datum_t *cert_list; unsigned int cert_list_size; + const char *sni_hostname; if ((err = gnutls_x509_crt_init (&cert)) < 0) { @@ -753,13 +775,15 @@ ssl_check_certificate (int fd, const char *host) logprintf (LOG_NOTQUIET, _("The certificate has expired\n")); success = false; } - if (!gnutls_x509_crt_check_hostname (cert, host)) + sni_hostname = _sni_hostname(host); + if (!gnutls_x509_crt_check_hostname (cert, sni_hostname)) { logprintf (LOG_NOTQUIET, _("The certificate's owner does not match hostname %s\n"), - quote (host)); + quote (sni_hostname)); success = false; } + xfree(sni_hostname); crt_deinit: gnutls_x509_crt_deinit (cert); } diff --git a/src/openssl.c b/src/openssl.c index 6701c0d..48eeadb 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -35,6 +35,7 @@ as that of the covered work. */ #include #include #include +#include #include #include @@ -506,6 +507,22 @@ ssl_connect_with_timeout_callback(void *arg) ctx->result = SSL_connect(ctx->ssl); } +static const char * +_sni_hostname(const char *hostname) +{ + size_t len = strlen(hostname); + + char *sni_hostname = xmemdup(hostname, len + 1); + + /* Remove trailing dot(s) to fix #47408. + * Regarding RFC 6066 (SNI): The hostname is represented as a byte + * string using ASCII encoding without a trailing dot. */ + while (len && sni_hostname[--len] == '.') + sni_hostname[len] = 0; + + return sni_hostname; +} + /* Perform the SSL handshake on file descriptor FD, which is assumed to be connected to an SSL server. The SSL handle provided by OpenSSL is registered with the file descriptor FD using @@ -532,7 +549,12 @@ ssl_connect_wget (int fd, const char *hostname, int *continue_session) then use it whenever we have a hostname. If not, don't, ever. */ if (! is_valid_ip_address (hostname)) { - if (! SSL_set_tlsext_host_name (conn, hostname)) + const char *sni_hostname = _sni_hostname(hostname); + + long rc = SSL_set_tlsext_host_name (conn, sni_hostname); + xfree(sni_hostname); + + if (rc == 0) { DEBUGP (("Failed to set TLS server-name indication.")); goto error; @@ -762,9 +784,12 @@ ssl_check_certificate (int fd, const char *host) { /* Test subject alternative names */ + /* SNI hostname must not have a trailing dot */ + const char *sni_hostname = _sni_hostname(host); + /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)? * Signal it by host_in_octet_string. */ - ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host); + ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (sni_hostname); int numaltnames = sk_GENERAL_NAME_num (subjectAltNames); int i; @@ -799,7 +824,7 @@ ssl_check_certificate (int fd, const char *host) if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName)) { /* Compare and check for NULL attack in ASN1_STRING */ - if (pattern_match ((char *)name_in_utf8, host) && + if (pattern_match ((char *)name_in_utf8, sni_hostname) && (strlen ((char *)name_in_utf8) = (size_t) ASN1_STRING_length (name->d.dNSName))) { @@ -820,9 +845,11 @@ ssl_check_certificate (int fd, const char *host) logprintf (LOG_NOTQUIET, _("%s: no certificate subject alternative name matches\n" "\trequested host name %s.\n"), - severity, quote_n (1, host)); + severity, quote_n (1, sni_hostname)); success = false; } + + xfree(sni_hostname); } if (alt_name_checked == false) -- 2.7.0