From a616cc04cb11c784637b2589e721cad82315fb09 Mon Sep 17 00:00:00 2001 From: Evgeny Khramtsov Date: Sun, 22 Sep 2019 11:04:38 +0300 Subject: [PATCH] Support IDN hostnames in ACME requests --- rebar.config | 5 +++-- src/ejabberd_acme.erl | 43 +++++++++++++++++++++++++++++++------------ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/rebar.config b/rebar.config index e702cff51..fcf26fc3b 100644 --- a/rebar.config +++ b/rebar.config @@ -24,7 +24,8 @@ {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.1.2"}}}, {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.17"}}}, {fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.37"}}}, - {xmpp, ".*", {git, "https://github.com/processone/xmpp", "e3181a5"}}, + {idna, ".*", {git, "https://github.com/benoitc/erlang-idna", {tag, "6.0.0"}}}, + {xmpp, ".*", {git, "https://github.com/processone/xmpp", "984b3c3"}}, {fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.20"}}}, {yconf, ".*", {git, "https://github.com/processone/yconf", {tag, "1.0.0"}}}, {jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}}, @@ -33,7 +34,7 @@ {jose, ".*", {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.8.4"}}}, {eimp, ".*", {git, "https://github.com/processone/eimp", {tag, "1.0.12"}}}, {mqtree, ".*", {git, "https://github.com/processone/mqtree", {tag, "1.0.4"}}}, - {acme, ".*", {git, "https://github.com/processone/acme.git", "b4c2899"}}, + {acme, ".*", {git, "https://github.com/processone/acme.git", "b312547"}}, {if_var_true, stun, {stun, ".*", {git, "https://github.com/processone/stun", {tag, "1.0.29"}}}}, {if_var_true, sip, {esip, ".*", {git, "https://github.com/processone/esip", {tag, "1.0.30"}}}}, {if_var_true, mysql, {p1_mysql, ".*", {git, "https://github.com/processone/p1_mysql", diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl index 67a1e969f..9dbe22b58 100644 --- a/src/ejabberd_acme.erl +++ b/src/ejabberd_acme.erl @@ -46,7 +46,9 @@ -type cert() :: #'OTPCertificate'{}. -type cert_type() :: ec | rsa. -type io_error() :: file:posix(). --type issue_result() :: ok | acme:issue_return() | {error, {file, io_error()}}. +-type issue_result() :: ok | acme:issue_return() | + {error, {file, io_error()} | + {idna_failed, binary()}}. %%%=================================================================== %%% API @@ -191,17 +193,22 @@ unregister_challenge(Ref) -> %%%=================================================================== -spec issue_request(state(), [binary(),...]) -> {issue_result(), state()}. issue_request(State, Domains) -> - case read_account_key() of - {ok, AccKey} -> - Config = ejabberd_option:acme(), - DirURL = maps:get(ca_url, Config, default_directory_url()), - Contact = maps:get(contact, Config, []), - CertType = maps:get(cert_type, Config, rsa), - issue_request(State, DirURL, Domains, AccKey, CertType, Contact); - {error, Reason} = Err -> - ?ERROR_MSG("Failed to request certificate for ~s: ~s", - [misc:format_hosts_list(Domains), - format_error(Reason)]), + case check_idna(Domains) of + ok -> + case read_account_key() of + {ok, AccKey} -> + Config = ejabberd_option:acme(), + DirURL = maps:get(ca_url, Config, default_directory_url()), + Contact = maps:get(contact, Config, []), + CertType = maps:get(cert_type, Config, rsa), + issue_request(State, DirURL, Domains, AccKey, CertType, Contact); + {error, Reason} = Err -> + ?ERROR_MSG("Failed to request certificate for ~s: ~s", + [misc:format_hosts_list(Domains), + format_error(Reason)]), + {Err, State} + end; + {error, _} = Err -> {Err, State} end. @@ -620,6 +627,16 @@ have_acme_listener() -> false end, ejabberd_option:listen()). +-spec check_idna([binary()]) -> ok | {error, {idna_failed, binary()}}. +check_idna([Domain|Domains]) -> + try idna:to_ascii(binary_to_list(Domain)) of + _ -> check_idna(Domains) + catch _:_ -> + {error, {idna_failed, Domain}} + end; +check_idna([]) -> + ok. + -spec format_error(term()) -> string(). format_error({file, Reason}) -> "I/O error: " ++ file:format_error(Reason); @@ -631,6 +648,8 @@ format_error(invalid_argument) -> "Invalid argument"; format_error(unexpected_certfile) -> "The certificate file was not obtained using ACME"; +format_error({idna_failed, Domain}) -> + "Not an IDN hostname: " ++ binary_to_list(Domain); format_error({bad_cert, _, _} = Reason) -> "Malformed certificate file: " ++ pkix:format_error(Reason); format_error(Reason) ->