diff options
-rw-r--r-- | INSTALL.md | 3 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | configure.ac | 56 | ||||
-rw-r--r-- | lib/Makefile.am | 4 | ||||
-rw-r--r-- | lib/str-unicode.c | 117 | ||||
-rw-r--r-- | lib/str.h | 2 | ||||
-rw-r--r-- | src/socket.c | 25 | ||||
-rw-r--r-- | tests/str-idna.c | 14 |
8 files changed, 195 insertions, 29 deletions
diff --git a/INSTALL.md b/INSTALL.md index 0bf841994a..2016e91e0c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -45,7 +45,8 @@ Optionally it may use the following libraries: * libtspi: for Trusted Platform Module (TPM) support, http://trousers.sourceforge.net/ * libunbound: For DNSSEC/DANE support, http://unbound.net/ * libz: For compression support, http://www.zlib.net/ -* libidn: For supporting internationalized DNS names, http://www.gnu.org/software/libidn/ +* libidn: For supporting internationalized DNS names (IDNA 2003), http://www.gnu.org/software/libidn/ +* libidn2: For supporting internationalized DNS names (IDNA 2008), https://www.gnu.org/software/libidn/#libidn2 To configure libnettle for installation and use by GnuTLS, a typical command sequence would be: @@ -37,7 +37,8 @@ We require several tools to check out and build the software, including: * [p11-kit](http://p11-glue.freedesktop.org/p11-kit.html) * [gperf](http://www.gnu.org/software/gperf/) * [libtasn1](https://www.gnu.org/software/libtasn1/) (optional) -* [Libidn](http://www.gnu.org/software/libidn/) (optional, for internationalization of DNS) +* [Libidn](http://www.gnu.org/software/libidn/) (optional, for internationalization of DNS, IDNA 2003) +* [Libidn2](https://www.gnu.org/software/libidn/#libidn2) (optional, for internationalization of DNS, IDNA 2008) * [Libunistring](http://www.gnu.org/software/libunistring/) (optional, for internationalization) * [AWK](http://www.gnu.org/software/awk/) (for make dist, pmccabe2html) * [git2cl](http://savannah.nongnu.org/projects/git2cl/) (for make dist, ChangeLog) diff --git a/configure.ac b/configure.ac index 13e745f5e4..c8ee67be4a 100644 --- a/configure.ac +++ b/configure.ac @@ -475,31 +475,49 @@ if ! $PKG_CONFIG --atleast-version=3.3 nettle; then fi AM_CONDITIONAL(WITH_OLD_NETTLE, test "$with_old_nettle" != "no") +idna_support=no +with_libidn2=no +with_libidn=no if test "$try_libidn" = yes;then -PKG_CHECK_MODULES(LIBIDN, libidn >= 0.5.6, [with_libidn=yes], [with_libidn=no]) -if test "$with_libidn" != "no";then - if ! $PKG_CONFIG --atleast-version=1.31 libidn; then - with_buggy_libidn=yes - fi + AC_SEARCH_LIBS(idn2_lookup_u8, idn2, [ + with_libidn2=yes; + idna_support="IDNA 2008" + AC_DEFINE([HAVE_LIBIDN2], 1, [Define if IDNA 2008 support is enabled.]) + AC_SUBST([LIBIDN_LIBS], [-lidn2]) + if test "x$GNUTLS_REQUIRES_PRIVATE" = "x"; then + GNUTLS_REQUIRES_PRIVATE="Requires.private: libidn2" + else + GNUTLS_REQUIRES_PRIVATE="${GNUTLS_REQUIRES_PRIVATE}, libidn2" + fi + ],[ + with_libidn2=no; + AC_MSG_WARN(*** LIBIDN2 was not found. You will not be able to use IDN2008 support) + ]) + + if test "$with_libidn2" = "no"; then + PKG_CHECK_MODULES(LIBIDN, libidn >= 0.5.6, [with_libidn=yes], [with_libidn=no]) + if test "$with_libidn" != "no";then + idna_support="IDNA 2003" + if ! $PKG_CONFIG --atleast-version=1.31 libidn; then + with_buggy_libidn=yes + fi - AC_DEFINE([HAVE_LIBIDN], 1, [Build IDNA support]) - if test "x$GNUTLS_REQUIRES_PRIVATE" = "x"; then - GNUTLS_REQUIRES_PRIVATE="Requires.private: libidn" - else - GNUTLS_REQUIRES_PRIVATE="${GNUTLS_REQUIRES_PRIVATE}, libidn" - fi -else - with_libidn=no - AC_MSG_WARN([[ + AC_DEFINE([HAVE_LIBIDN], 1, [Build IDNA support]) + if test "x$GNUTLS_REQUIRES_PRIVATE" = "x"; then + GNUTLS_REQUIRES_PRIVATE="Requires.private: libidn" + else + GNUTLS_REQUIRES_PRIVATE="${GNUTLS_REQUIRES_PRIVATE}, libidn" + fi + else + AC_MSG_WARN([[ *** *** libidn was not found. IDNA support will be disabled. *** ]]) + fi + fi fi -else - with_libidn=no -fi - +AM_CONDITIONAL(HAVE_LIBIDN2, test "$with_libidn2" != "no") AM_CONDITIONAL(HAVE_LIBIDN, test "$with_libidn" != "no") AM_CONDITIONAL(HAVE_BUGGY_LIBIDN, test "$with_buggy_libidn" = "yes") @@ -1034,7 +1052,7 @@ if features are disabled) ECDHE support: $ac_enable_ecdhe Anon auth support: $ac_enable_anon Heartbeat support: $ac_enable_heartbeat - IDNA support: $with_libidn + IDNA support: $idna_support Self checks: $enable_self_checks Non-SuiteB curves: $enable_non_suiteb FIPS140 mode: $enable_fips diff --git a/lib/Makefile.am b/lib/Makefile.am index 9c7425a91d..70ced9ab67 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -138,6 +138,10 @@ libgnutls_la_LIBADD = ../gl/libgnu.la x509/libgnutls_x509.la \ thirdparty_libadd = $(LTLIBZ) $(LTLIBINTL) $(LIBSOCKET) $(LTLIBNSL) \ $(P11_KIT_LIBS) $(LIB_SELECT) +if HAVE_LIBIDN2 +thirdparty_libadd += -lidn2 +endif + if HAVE_LIBIDN thirdparty_libadd += $(LIBIDN_LIBS) endif diff --git a/lib/str-unicode.c b/lib/str-unicode.c index 35c87cf5ac..bd5373303e 100644 --- a/lib/str-unicode.c +++ b/lib/str-unicode.c @@ -26,7 +26,9 @@ #include <uninorm.h> #include <unistr.h> #include <unictype.h> -#ifdef HAVE_LIBIDN +#ifdef HAVE_LIBIDN2 +# include <idn2.h> +#elif defined HAVE_LIBIDN # include <idna.h> # include <idn-free.h> #endif @@ -292,7 +294,7 @@ int gnutls_utf8_password_normalize(const unsigned char *password, unsigned plen, return ret; } -#ifdef HAVE_LIBIDN +#if defined HAVE_LIBIDN2 || defined HAVE_LIBIDN /*- * _gnutls_idna_map: * @input: contain the UTF-8 formatted domain name @@ -334,6 +336,27 @@ int _gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsi return ret; } +#ifdef HAVE_LIBIDN2 +#if IDN2_VERSION_NUMBER >= 0x00140000 + /* IDN2_NONTRANSITIONAL automatically converts to lowercase + * IDN2_NFC_INPUT converts to NFC before toASCII conversion + * + * Since IDN2_NONTRANSITIONAL implicitely does NFC conversion, we don't need + * the additional IDN2_NFC_INPUT. But just for the unlikely case that the linked + * library is not matching the headers when building and it doesn't support TR46, + * we provide IDN2_NFC_INPUT. */ + + rc = idn2_lookup_u8((uint8_t *)istr.data, (uint8_t **)&idna, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); +#else + rc = idn2_lookup_u8((uint8_t *)istr.data, (uint8_t **)&idna, IDN2_NFC_INPUT); +#endif + if (rc != IDN2_OK) { + gnutls_assert(); + _gnutls_debug_log("unable to convert name '%s' to IDNA format: %s\n", istr.data, idn2_strerror(rc)); + ret = GNUTLS_E_INVALID_UTF8_STRING; + goto fail; + } +#else rc = idna_to_ascii_8z((char*)istr.data, &idna, 0); if (rc != IDNA_SUCCESS) { gnutls_assert(); @@ -341,6 +364,7 @@ int _gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsi ret = GNUTLS_E_INVALID_UTF8_STRING; goto fail; } +#endif if (gnutls_malloc != malloc) { ret = _gnutls_set_strdatum(out, idna, strlen(idna)); @@ -351,11 +375,85 @@ int _gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsi ret = 0; } fail: +#ifdef HAVE_LIBIDN2 + idn2_free(idna); +#else idn_free(idna); +#endif gnutls_free(istr.data); return ret; } +#ifdef HAVE_LIBIDN2 +int _idn2_punycode_decode( + size_t input_length, + const char input[], + size_t *output_length, + uint32_t output[], + unsigned char case_flags[]); + +static int _idn2_to_unicode_8z8z(const char *src, char **dst) +{ + int rc, run; + size_t out_len = 0; + const char *e, *s; + char *p = NULL; + + for (run = 0; run < 2; run++) { + if (run) { + p = malloc(out_len + 1); + if (!p) + return IDN2_MALLOC; + *dst = p; + } + + out_len = 0; + for (e = s = src; *e; s = e) { + while (*e && *e != '.') + e++; + + if (e - s > 4 && s[0] == 'x' && s[1] == 'n' && s[2] == '-' && s[3] == '-') { + size_t u32len = IDN2_LABEL_MAX_LENGTH * 4; + uint32_t u32[IDN2_LABEL_MAX_LENGTH * 4]; + uint8_t u8[IDN2_LABEL_MAX_LENGTH + 1]; + size_t u8len; + + rc = _idn2_punycode_decode(e - s - 4, s + 4, &u32len, u32, NULL); + if (rc != IDN2_OK) + return rc; + + if (rc != IDN2_OK) + return rc; + + u8len = sizeof(u8); + if (u32_to_u8(u32, u32len, u8, &u8len) == NULL) + return IDN2_ENCODING_ERROR; + u8[u8len] = '\0'; + + if (run) + memcpy(*dst + out_len, u8, u8len); + out_len += u8len; + } else { + if (run) + memcpy(*dst + out_len, s, e - s); + out_len += e - s; + } + + if (*e) { + e++; + if (run) + (*dst)[out_len] = '.'; + out_len++; + } + } + } + + (*dst)[out_len] = 0; + + return IDN2_OK; +} +#endif + /*- * _gnutls_idna_reverse_map: * @input: contain the ACE (IDNA) formatted domain name @@ -392,6 +490,16 @@ int _gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *o return ret; } +#ifdef HAVE_LIBIDN2 + /* currently libidn2 just converts single labels, thus a wrapper function */ + rc = _idn2_to_unicode_8z8z((char*)istr.data, &u8); + if (rc != IDN2_OK) { + gnutls_assert(); + _gnutls_debug_log("unable to convert ACE name '%s' to UTF-8 format: %s\n", istr.data, idn2_strerror(rc)); + ret = GNUTLS_E_INVALID_UTF8_STRING; + goto fail; + } +#else rc = idna_to_unicode_8z8z((char*)istr.data, &u8, IDNA_ALLOW_UNASSIGNED); if (rc != IDNA_SUCCESS) { gnutls_assert(); @@ -399,6 +507,7 @@ int _gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *o ret = GNUTLS_E_INVALID_UTF8_STRING; goto fail; } +#endif if (gnutls_malloc != malloc) { ret = _gnutls_set_strdatum(out, u8, strlen(u8)); @@ -409,7 +518,11 @@ int _gnutls_idna_reverse_map(const char *input, unsigned ilen, gnutls_datum_t *o ret = 0; } fail: +#ifdef HAVE_LIBIDN2 + idn2_free(u8); +#else idn_free(u8); +#endif gnutls_free(istr.data); return ret; } @@ -48,7 +48,7 @@ int gnutls_utf8_password_normalize(const uint8_t *password, unsigned password_le int _gnutls_idna_email_map(const char *input, unsigned ilen, gnutls_datum_t *output); -#ifndef HAVE_LIBIDN +#if !defined HAVE_LIBIDN2 && !defined HAVE_LIBIDN inline static int __gnutls_idna_map(const char *input, unsigned ilen, gnutls_datum_t *out, unsigned flags) { diff --git a/src/socket.c b/src/socket.c index f60479f5cc..2c7e9009c1 100644 --- a/src/socket.c +++ b/src/socket.c @@ -43,7 +43,9 @@ #include <c-ctype.h> #include "sockets.h" -#ifdef HAVE_LIBIDN +#ifdef HAVE_LIBIDN2 +#include <idn2.h> +#elif defined HAVE_LIBIDN #include <idna.h> #include <idn-free.h> #endif @@ -398,7 +400,26 @@ socket_open(socket_st * hd, const char *hostname, const char *service, hd->rdata.size = rdata->size; } -#ifdef HAVE_LIBIDN +#ifdef HAVE_LIBIDN2 +#if IDN2_VERSION_NUMBER >= 0x00140000 + /* IDN2_NONTRANSITIONAL automatically converts to lowercase + * IDN2_NFC_INPUT converts to NFC before toASCII conversion + * + * Since IDN2_NONTRANSITIONAL implicitely does NFC conversion, we don't need + * the additional IDN2_NFC_INPUT. But just for the unlikely case that the linked + * library is not matching the headers when building and it doesn't support TR46, + * we provide IDN2_NFC_INPUT. */ + + err = idn2_lookup_u8((uint8_t *)hostname, (uint8_t **)&a_hostname, IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL); +#else + err = idn2_lookup_u8((uint8_t *)hostname, (uint8_t **)&a_hostname, IDN2_NFC_INPUT); +#endif + if (err != IDN2_OK) { + fprintf(stderr, "Cannot convert %s to IDNA: %s\n", hostname, + idn2_strerror(err)); + exit(1); + } +#elif defined HAVE_LIBIDN err = idna_to_ascii_8z(hostname, &a_hostname, IDNA_ALLOW_UNASSIGNED); if (err != IDNA_SUCCESS) { fprintf(stderr, "Cannot convert %s to IDNA: %s\n", hostname, diff --git a/tests/str-idna.c b/tests/str-idna.c index 20d4b46731..8ce08c6dcb 100644 --- a/tests/str-idna.c +++ b/tests/str-idna.c @@ -70,17 +70,25 @@ static void fname(void **glob_state) \ MATCH_FUNC(test_ascii, "localhost", "localhost", 1); MATCH_FUNC(test_ascii_caps, "LOCALHOST", "LOCALHOST", 1); MATCH_FUNC(test_greek1, "βόλοσ.com", "xn--nxasmq6b.com", 1); -MATCH_FUNC(test_greek2, "βόλος.com", "xn--nxasmq6b.com", 0); MATCH_FUNC(test_cap_greek3, "ΒΌΛΟΣ.com", "xn--nxasmq6b.com", 0); MATCH_FUNC(test_mix, "简体中文.εξτρα.com", "xn--fiqu1az03c18t.xn--mxah1amo.com", 1); -MATCH_FUNC(test_german1, "faß.de", "fass.de", 0); -MATCH_FUNC(test_caps_german2, "Faß.de", "fass.de", 0); MATCH_FUNC(test_caps_german3, "Ü.ü", "xn--tda.xn--tda", 0); MATCH_FUNC(test_caps_german4, "Bücher.de", "xn--bcher-kva.de", 0); MATCH_FUNC(test_german4, "bücher.de", "xn--bcher-kva.de", 1); MATCH_FUNC(test_u1, "夡夞夜夙", "xn--bssffl", 1); MATCH_FUNC(test_jp2, "日本語.jp", "xn--wgv71a119e.jp", 1); MATCH_FUNC(test_dots, "a.b.c。d。", "a.b.c.d.", 0); +#ifdef HAVE_LIBIDN2 +MATCH_FUNC(test_greek2, "βόλος.com", "xn--nxasmm1c.com", 0); +MATCH_FUNC(test_german1, "faß.de", "xn--fa-hia.de", 0); +#if IDN2_VERSION_NUMBER >= 0x00140000 +MATCH_FUNC(test_caps_german2, "Faß.de", "xn--fa-hia.de", 0); +#endif +#else +MATCH_FUNC(test_greek2, "βόλος.com", "xn--nxasmq6b.com", 0); +MATCH_FUNC(test_german1, "faß.de", "fass.de", 0); +MATCH_FUNC(test_caps_german2, "Faß.de", "fass.de", 0); +#endif int main(void) { |