Commit 0a19572d authored by Tim Rühsen's avatar Tim Rühsen 🛠 Committed by Nikos Mavrogiannopoulos
Browse files

Add support for libidn2 (IDNA 2008 + TR46)


Signed-off-by: Tim Rühsen's avatarTim Rühsen <tim.ruehsen@gmx.de>
parent a3dadb29
......@@ -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)
......
......@@ -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
......
......@@ -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
......
......@@ -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)
{
......
......@@ -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,
......
......@@ -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)
{
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment