summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Rühsen <tim.ruehsen@gmx.de>2017-01-21 23:14:46 +0100
committerNikos Mavrogiannopoulos <nmav@redhat.com>2017-01-26 16:11:03 +0100
commit0d18eaea0bf9e29f27f6a6ec330c9f455d7472f6 (patch)
tree179be2ea41a86f7fe7266688cc34b541e825a4cf
parente173bec552096c7cecc918117a441921e964b8c8 (diff)
downloadgnutls-0d18eaea0bf9e29f27f6a6ec330c9f455d7472f6.tar.gz
Add support for libidn2 (IDNA 2008 + TR46)
Signed-off-by: Tim Rühsen <tim.ruehsen@gmx.de>
-rw-r--r--INSTALL.md3
-rw-r--r--README.md3
-rw-r--r--configure.ac56
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/str-unicode.c117
-rw-r--r--lib/str.h2
-rw-r--r--src/socket.c25
-rw-r--r--tests/str-idna.c14
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:
diff --git a/README.md b/README.md
index 52583f9cd0..9b21c21d3f 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,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 9dfe96dc71..1cca2406b3 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;
}
diff --git a/lib/str.h b/lib/str.h
index d341baa2ef..a294caed8b 100644
--- a/lib/str.h
+++ b/lib/str.h
@@ -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)
{