From d348bd7f1fb901d5632f0063dcd2c362af817661 Mon Sep 17 00:00:00 2001 From: Dan Gudmundsson Date: Tue, 19 Dec 2023 15:52:31 +0100 Subject: [PATCH] ssl: Handle ip-address as string correctly If the host was specified as string ip-address, server_name_indication didn't work. Convert "ip-address" to IP if needed, as was done for upgraded sockets previously. Also while at it cleanup handling when hostname is undefined. Fixes #7968 --- lib/ssl/doc/src/ssl.xml | 8 -------- lib/ssl/src/ssl.erl | 10 ++++------ lib/ssl/src/ssl_certificate.erl | 34 ++++++++++----------------------- lib/ssl/src/ssl_handshake.erl | 13 ++++++++----- lib/ssl/test/ssl_sni_SUITE.erl | 14 ++++++++++---- 5 files changed, 32 insertions(+), 47 deletions(-) diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index f719c55c32f3..31e86737b2f9 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -124,14 +124,6 @@ - - - - - - - - diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index f21a0fe9b3ed..73d388e785f1 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -160,9 +160,7 @@ ClosedTag::atom(), ErrTag::atom()}} | {cb_info, {CallbackModule::atom(), DataTag::atom(), ClosedTag::atom(), ErrTag::atom(), PassiveTag::atom()}}. --type host() :: hostname() | ip_address(). % exported --type hostname() :: string(). --type ip_address() :: inet:ip_address(). +-type host() :: inet:hostname() | inet:ip_address(). % exported -type session_id() :: binary(). % exported -type protocol_version() :: tls_version() | dtls_version(). % exported -type tls_version() :: 'tlsv1.2' | 'tlsv1.3' | tls_legacy_version(). @@ -450,7 +448,7 @@ -type client_psk_identity() :: psk_identity(). -type client_srp_identity() :: srp_identity(). -type customize_hostname_check() :: list(). --type sni() :: HostName :: hostname() | disable. +-type sni() :: inet:hostname() | disable. -type max_fragment_length() :: undefined | 512 | 1024 | 2048 | 4096. -type fallback() :: boolean(). -type ssl_imp() :: new | old. @@ -495,7 +493,7 @@ -type fail_if_no_peer_cert() :: boolean(). -type server_reuse_session() :: fun(). -type server_reuse_sessions() :: boolean(). --type sni_hosts() :: [{hostname(), [server_option() | common_option()]}]. +-type sni_hosts() :: [{inet:hostname(), [server_option() | common_option()]}]. -type sni_fun() :: fun((string()) -> [] | undefined). -type honor_cipher_order() :: boolean(). -type honor_ecc_order() :: boolean(). @@ -513,7 +511,7 @@ max_frag_enum => 1..4, ec_point_formats => [0..2], elliptic_curves => [public_key:oid()], - sni => hostname()}. % exported + sni => inet:hostname()}. % exported %% ------------------------------------------------------------------------------------------------------- -type connection_info() :: [common_info() | curve_info() | ssl_options_info() | security_info()]. -type common_info() :: {protocol, protocol_version()} | diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 34e4e8b0aee5..505eb10f79fa 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -553,33 +553,19 @@ other_issuer(#cert{otp=OtpCert}=Cert, CertDbHandle, CertDbRef) -> end end. -verify_hostname({fallback, Hostname}, Customize, Cert, UserState) when is_list(Hostname) -> - case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of - true -> - {valid, UserState}; - false -> - case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}], Customize) of - true -> - {valid, UserState}; - false -> - {fail, {bad_cert, hostname_check_failed}} - end - end; - -verify_hostname({fallback, Hostname}, Customize, Cert, UserState) -> +verify_hostname(Hostname, Customize, Cert, UserState) when is_tuple(Hostname) -> case public_key:pkix_verify_hostname(Cert, [{ip, Hostname}], Customize) of - true -> - {valid, UserState}; - false -> - {fail, {bad_cert, hostname_check_failed}} + true -> {valid, UserState}; + false -> {fail, {bad_cert, hostname_check_failed}} end; - verify_hostname(Hostname, Customize, Cert, UserState) -> - case public_key:pkix_verify_hostname(Cert, [{dns_id, Hostname}], Customize) of - true -> - {valid, UserState}; - false -> - {fail, {bad_cert, hostname_check_failed}} + HostId = case inet:parse_strict_address(Hostname) of + {ok, IP} -> {ip, IP}; + _ -> {dns_id, Hostname} + end, + case public_key:pkix_verify_hostname(Cert, [HostId], Customize) of + true -> {valid, UserState}; + false -> {fail, {bad_cert, hostname_check_failed}} end. verify_cert_extensions(Cert, #{cert_ext := CertExts} = UserState) -> diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 05a084a9f268..0bd2b1bd80aa 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -84,7 +84,6 @@ -export([get_cert_params/1, select_own_cert/1, - server_name/3, path_validation/10, validation_fun_and_state/4, path_validation_alert/1]). @@ -3554,12 +3553,16 @@ server_name(_, _, server) -> undefined; %% Not interesting to check your own name. server_name(SSLOpts, Host, client) -> case maps:get(server_name_indication, SSLOpts, undefined) of - undefined -> - {fallback, Host}; %% Fallback to Host argument to connect - SNI -> - SNI %% If Server Name Indication is available + disable -> disable; + undefined -> convert_hostname(Host); %% Fallback to Host argument to connect + UserSNI -> convert_hostname(UserSNI) %% If Server Name Indication is available end. +convert_hostname(SNI) when is_atom(SNI) -> + atom_to_list(SNI); +convert_hostname(SNI) -> + SNI. + client_ecc_extensions(SupportedECCs) -> CryptoSupport = proplists:get_value(public_keys, crypto:supports()), case proplists:get_bool(ecdh, CryptoSupport) of diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl index 9f37fe5535bd..8489c59a82c1 100644 --- a/lib/ssl/test/ssl_sni_SUITE.erl +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -226,7 +226,8 @@ dns_name(Config) -> ip_fallback(Config) -> Hostname = net_adm:localhost(), {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()), - IPStr = tuple_to_list(IP), + IPList = tuple_to_list(IP), + IPStr = lists:flatten(integer_to_list(hd(IPList)) ++ [io_lib:format(".~w", [I]) || I <- tl(IPList)]), #{server_config := ServerOpts0, client_config := ClientOpts0} = public_key:pkix_test_data(#{server_chain => @@ -235,7 +236,7 @@ ip_fallback(Config) -> peer => [{extensions, [#'Extension'{extnID = ?'id-ce-subjectAltName', extnValue = [{dNSName, Hostname}, - {iPAddress, IPStr}], + {iPAddress, IPList}], critical = false}]}, {key, ssl_test_lib:hardcode_rsa_key(3)}]}, client_chain => @@ -246,11 +247,15 @@ ip_fallback(Config) -> ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0, ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0, successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), - successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). + successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config), + successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IPStr, Config), + successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], list_to_atom(Hostname), Config). no_ip_fallback(Config) -> Hostname = net_adm:localhost(), {ok, #hostent{h_addr_list = [IP |_]}} = inet:gethostbyname(net_adm:localhost()), + IPList = tuple_to_list(IP), + IPStr = lists:flatten(integer_to_list(hd(IPList)) ++ [io_lib:format(".~w", [I]) || I <- tl(IPList)]), #{server_config := ServerOpts0, client_config := ClientOpts0} = public_key:pkix_test_data(#{server_chain => @@ -270,7 +275,8 @@ no_ip_fallback(Config) -> ServerConf = ssl_test_lib:sig_algs(rsa, Version) ++ ServerOpts0, ClientConf = ssl_test_lib:sig_algs(rsa, Version) ++ ClientOpts0, successfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], Hostname, Config), - unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config). + unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IP, Config), + unsuccessfull_connect(ServerConf, [{verify, verify_peer} | ClientConf], IPStr, Config). dns_name_reuse(Config) -> SNIHostname = "OTP.test.server",