Research presented independently by Dan Kaminsky and Moxie Marlinspike at BlackHat09 demonstrated a vulnerability related to NUL bytes in X.509 certificate name fields. Attackers needs to go to a CA and acquire a certificate for an invalid name. To attack GnuTLS, the attacker needs to acquire such an invalid certificate for each TLS server to be attacked. The attacker is then able to use the certificate to become a man-in-the-middle on server authenticated channels. The client will receive the attacker's certificate, so the attack leaves traces with the client. The attack does not work for client-authenticated TLS channels.
To make client notice the attack and reject it, a few separate aspects needs to be resolved: 1) When displaying a certificate to the user, GnuTLS needs to be fixed so that it does not truncate strings in CN/SAN fields to the first NUL. 2) When comparing the CN/SAN fields to a hostname, it has to make sure it compares the entire string, and not only up until the first NUL. We recommend all users to upgrade to version 2.8.3 which solves these concerns. For people using older releases, or wants to read the minimal patch required to solve the problem, I have prepared patches that applies to version 2.8.1 -- the last stable releases that didn't have this vulnerability. To solve 1) apply PATCH 1 included below. The patches in git corresponding to this patch is (they have to be applied in order): http://git.savannah.gnu.org/cgit/gnutls.git/commit/?h=gnutls_2_8_x&id=a431be86124f900c4082e82d32917f86fcce461a http://git.savannah.gnu.org/cgit/gnutls.git/commit/?h=gnutls_2_8_x&id=74b6d92f9675ce4e03642c4d6ced4a3a614b07f6 http://git.savannah.gnu.org/cgit/gnutls.git/commit/?h=gnutls_2_8_x&id=40081594e3de518b998f3e5177ed5a9f7707f2e8 To solve 2) apply PATCH 2 included below. This patch was supplied by Tomas Hoger <[email protected]>. The patches in git corresponding to this patch is (they have to be applied in order): http://git.savannah.gnu.org/cgit/gnutls.git/patch/?id=5a58e9d33448235377afd5fbfcee1683dc70eae3 http://git.savannah.gnu.org/cgit/gnutls.git/patch/?id=1ea190d216767dd4ab93b87361cbcb9d4fb3aafc I have verified that the patches work for v2.6.6 as well if you manually resolve one failed hunk. /Simon PATCH 1 diff -ur gnutls-2.8.1.orig/lib/x509/common.c gnutls-2.8.1/lib/x509/common.c --- gnutls-2.8.1.orig/lib/x509/common.c 2009-06-02 20:59:32.000000000 +0200 +++ gnutls-2.8.1/lib/x509/common.c 2009-08-14 11:56:17.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation * * Author: Nikos Mavrogiannopoulos * @@ -242,6 +242,10 @@ { str[len] = 0; + /* Refuse to deal with strings containing NULs. */ + if (strlen (str) != len) + return GNUTLS_E_ASN1_DER_ERROR; + if (res) _gnutls_str_cpy (res, *res_size, str); *res_size = len; @@ -291,25 +295,27 @@ non_printable = 0; } - if (res) + if (non_printable == 0) { - if (non_printable == 0) - { - str[len] = 0; - _gnutls_str_cpy (res, *res_size, str); - *res_size = len; - } - else + str[len] = 0; + + /* Refuse to deal with strings containing NULs. */ + if (strlen (str) != len) + return GNUTLS_E_ASN1_DER_ERROR; + + if (res) + _gnutls_str_cpy (res, *res_size, str); + *res_size = len; + } + else + { + result = _gnutls_x509_data2hex (str, len, res, res_size); + if (result < 0) { - result = _gnutls_x509_data2hex (str, len, res, res_size); - if (result < 0) - { - gnutls_assert (); - return result; - } + gnutls_assert (); + return result; } } - } return 0; diff -ur gnutls-2.8.1.orig/lib/x509/output.c gnutls-2.8.1/lib/x509/output.c --- gnutls-2.8.1.orig/lib/x509/output.c 2009-06-02 20:59:32.000000000 +0200 +++ gnutls-2.8.1/lib/x509/output.c 2009-08-14 11:56:17.000000000 +0200 @@ -354,6 +354,17 @@ return; } + if ((err == GNUTLS_SAN_DNSNAME + || err == GNUTLS_SAN_RFC822NAME + || err == GNUTLS_SAN_URI) && + strlen (buffer) != size) + { + adds (str, _("warning: distributionPoint contains an embedded NUL, " + "replacing with '!'\n")); + while (strlen (buffer) < size) + buffer[strlen (buffer)] = '!'; + } + switch (err) { case GNUTLS_SAN_DNSNAME: @@ -552,6 +563,17 @@ return; } + if ((err == GNUTLS_SAN_DNSNAME + || err == GNUTLS_SAN_RFC822NAME + || err == GNUTLS_SAN_URI) && + strlen (buffer) != size) + { + adds (str, _("warning: SAN contains an embedded NUL, " + "replacing with '!'\n")); + while (strlen (buffer) < size) + buffer[strlen (buffer)] = '!'; + } + switch (err) { case GNUTLS_SAN_DNSNAME: @@ -623,8 +645,18 @@ } if (err == GNUTLS_SAN_OTHERNAME_XMPP) - addf (str, _("%s\t\t\tXMPP Address: %.*s\n"), prefix, - (int) size, buffer); + { + if (strlen (buffer) != size) + { + adds (str, _("warning: SAN contains an embedded NUL, " + "replacing with '!'\n")); + while (strlen (buffer) < size) + buffer[strlen (buffer)] = '!'; + } + + addf (str, _("%s\t\t\tXMPP Address: %.*s\n"), prefix, + (int) size, buffer); + } else { addf (str, _("%s\t\t\totherName OID: %.*s\n"), prefix, PATCH 2 diff -ur gnutls-2.8.1.orig/lib/gnutls_str.c gnutls-2.8.1/lib/gnutls_str.c --- gnutls-2.8.1.orig/lib/gnutls_str.c 2009-06-02 20:59:32.000000000 +0200 +++ gnutls-2.8.1/lib/gnutls_str.c 2009-08-14 11:40:45.000000000 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2002, 2004, 2005, 2007, 2008 Free Software Foundation + * Copyright (C) 2002, 2004, 2005, 2007, 2008, 2009 Free Software Foundation * * Author: Nikos Mavrogiannopoulos * @@ -363,17 +363,22 @@ /* compare hostname against certificate, taking account of wildcards * return 1 on success or 0 on error + * + * note: certnamesize is required as X509 certs can contain embedded NULs in + * the strings such as CN or subjectAltName */ int -_gnutls_hostname_compare (const char *certname, const char *hostname) +_gnutls_hostname_compare (const char *certname, + size_t certnamesize, + const char *hostname) { /* find the first different character */ for (; *certname && *hostname && toupper (*certname) == toupper (*hostname); - certname++, hostname++) + certname++, hostname++, certnamesize--) ; /* the strings are the same */ - if (strlen (certname) == 0 && strlen (hostname) == 0) + if (certnamesize == 0 && *hostname == '\0') return 1; if (*certname == '*') @@ -381,15 +386,16 @@ /* a wildcard certificate */ certname++; + certnamesize--; while (1) { /* Use a recursive call to allow multiple wildcards */ - if (_gnutls_hostname_compare (certname, hostname)) - { - return 1; - } - /* wildcards are only allowed to match a single domain component or component fragment */ + if (_gnutls_hostname_compare (certname, certnamesize, hostname)) + return 1; + + /* wildcards are only allowed to match a single domain + component or component fragment */ if (*hostname == '\0' || *hostname == '.') break; hostname++; diff -ur gnutls-2.8.1.orig/lib/gnutls_str.h gnutls-2.8.1/lib/gnutls_str.h --- gnutls-2.8.1.orig/lib/gnutls_str.h 2009-06-02 20:59:32.000000000 +0200 +++ gnutls-2.8.1/lib/gnutls_str.h 2009-08-14 11:40:45.000000000 +0200 @@ -79,7 +79,7 @@ int _gnutls_hex2bin (const opaque * hex_data, int hex_size, opaque * bin_data, size_t * bin_size); -int _gnutls_hostname_compare (const char *certname, const char *hostname); +int _gnutls_hostname_compare (const char *certname, size_t certnamesize, const char *hostname); #define MAX_CN 256 #endif diff -ur gnutls-2.8.1.orig/lib/openpgp/pgp.c gnutls-2.8.1/lib/openpgp/pgp.c --- gnutls-2.8.1.orig/lib/openpgp/pgp.c 2009-06-02 20:59:32.000000000 +0200 +++ gnutls-2.8.1/lib/openpgp/pgp.c 2009-08-14 11:40:45.000000000 +0200 @@ -570,7 +570,7 @@ if (ret == 0) { - if (_gnutls_hostname_compare (dnsname, hostname)) + if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname)) return 1; } } diff -ur gnutls-2.8.1.orig/lib/x509/rfc2818_hostname.c gnutls-2.8.1/lib/x509/rfc2818_hostname.c --- gnutls-2.8.1.orig/lib/x509/rfc2818_hostname.c 2009-06-02 20:59:32.000000000 +0200 +++ gnutls-2.8.1/lib/x509/rfc2818_hostname.c 2009-08-14 11:40:45.000000000 +0200 @@ -74,7 +74,7 @@ if (ret == GNUTLS_SAN_DNSNAME) { found_dnsname = 1; - if (_gnutls_hostname_compare (dnsname, hostname)) + if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname)) { return 1; } @@ -84,7 +84,7 @@ found_dnsname = 1; /* RFC 2818 is unclear whether the CN should be compared for IP addresses too, but we won't do it. */ - if (_gnutls_hostname_compare (dnsname, hostname)) + if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname)) { return 1; } @@ -104,7 +104,7 @@ return 0; } - if (_gnutls_hostname_compare (dnsname, hostname)) + if (_gnutls_hostname_compare (dnsname, dnsnamesize, hostname)) { return 1; }
pgpFIJCmqlk9W.pgp
Description: PGP signature
_______________________________________________ Help-gnutls mailing list [email protected] http://lists.gnu.org/mailman/listinfo/help-gnutls
