diff options
Diffstat (limited to 'lib/ssl')
-rw-r--r-- | lib/ssl/doc/src/ssl.xml | 12 | ||||
-rw-r--r-- | lib/ssl/src/inet_tls_dist.erl | 286 | ||||
-rw-r--r-- | lib/ssl/src/ssl.erl | 12 | ||||
-rw-r--r-- | lib/ssl/src/ssl_cipher.erl | 28 | ||||
-rw-r--r-- | lib/ssl/test/ssl_basic_SUITE.erl | 25 | ||||
-rw-r--r-- | lib/ssl/test/ssl_dist_bench_SUITE.erl | 15 |
6 files changed, 178 insertions, 200 deletions
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 825bf46459..adf4fb9ba4 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -1501,6 +1501,18 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> + <name>suite_to_str(CipherSuite) -> String</name> + <fsummary>Returns the string representation of a cipher suite.</fsummary> + <type> + <v>CipherSuite = erl_cipher_suite()</v> + <v>String = string()</v> + </type> + <desc> + <p>Returns the string representation of a cipher suite.</p> + </desc> + </func> + + <func> <name>transport_accept(ListenSocket) -></name> <name>transport_accept(ListenSocket, Timeout) -> {ok, NewSocket} | {error, Reason}</name> diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index a6ceff25cb..aa3d7e3f72 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -31,7 +31,7 @@ -export([nodelay/0]). --export([verify_client/3, verify_server/3, cert_nodes/1]). +-export([verify_client/3, cert_nodes/1]). -export([dbg/0]). % Debug @@ -236,12 +236,10 @@ accept_loop(Driver, Listen, Kernel) -> end. accept_loop(Driver, Listen, Kernel, Socket) -> - {Opts,CertNodesFun} = - setup_verify_client( - Driver, Socket, get_ssl_options(server)), + Opts = setup_verify_client(Socket, get_ssl_options(server)), wait_for_code_server(), case - ssl:ssl_accept( + ssl:handshake( Socket, trace([{active, false},{packet, 4}|Opts]), net_kernel:connecttime()) @@ -255,7 +253,7 @@ accept_loop(Driver, Listen, Kernel, Socket) -> {Kernel, controller, Pid} -> ok = ssl:controlling_process(SslSocket, Pid), trace( - Pid ! {self(), controller, CertNodesFun}); + Pid ! {self(), controller}); {Kernel, unsupported_protocol} -> exit(trace(unsupported_protocol)) end, @@ -278,48 +276,59 @@ accept_loop(Driver, Listen, Kernel, Socket) -> %% as a configuration marker that verify_client/3 shall be used. %% %% Replace the State in the first occurence of -%% {verify_fun,{fun ?MODULE:verify_client/3,State}} and remove the rest. +%% {verify_fun,{fun ?MODULE:verify_client/3,State}} +%% and remove the rest. %% The inserted state is not accesible from a configuration file %% since it is dynamic and connection dependent. %% -setup_verify_client(Driver, Socket, Opts) -> - setup_verify_client(Driver, Socket, Opts, undefined, []). +setup_verify_client(Socket, Opts) -> + setup_verify_client(Socket, Opts, true, []). %% -setup_verify_client(_Driver, _Socket, [], CertNodesFun, OptsR) -> - {lists:reverse(OptsR),CertNodesFun}; -setup_verify_client(Driver, Socket, [Opt|Opts], CertNodesFun, OptsR) -> +setup_verify_client(_Socket, [], _, OptsR) -> + lists:reverse(OptsR); +setup_verify_client(Socket, [Opt|Opts], First, OptsR) -> case Opt of - {verify_fun,{Fun,NewCertNodesFun}} -> + {verify_fun,{Fun,_}} -> case Fun =:= fun ?MODULE:verify_client/3 of - true when is_function(NewCertNodesFun, 1) -> + true -> if - CertNodesFun =:= undefined -> + First -> case inet:peername(Socket) of {ok,{PeerIP,_Port}} -> + {ok,Allowed} = net_kernel:allowed(), + AllowedHosts = allowed_hosts(Allowed), setup_verify_client( - Driver, Socket, Opts, NewCertNodesFun, + Socket, Opts, false, [{verify_fun, - {Fun, - {NewCertNodesFun,Driver,PeerIP}}} + {Fun, {AllowedHosts,PeerIP}}} |OptsR]); {error,Reason} -> exit(trace({no_peername,Reason})) end; true -> setup_verify_client( - Driver, Socket, Opts, CertNodesFun, OptsR) + Socket, Opts, First, OptsR) end; - true -> - exit( - trace( - {verify_client_bad_argument,CertNodesFun})); false -> setup_verify_client( - Driver, Socket, Opts, CertNodesFun, [Opt|OptsR]) + Socket, Opts, First, [Opt|OptsR]) end; _ -> - setup_verify_client( - Driver, Socket, Opts, CertNodesFun, [Opt|OptsR]) + setup_verify_client(Socket, Opts, First, [Opt|OptsR]) + end. + +allowed_hosts(Allowed) -> + lists:usort(allowed_node_hosts(Allowed)). +%% +allowed_node_hosts([]) -> []; +allowed_node_hosts([Node|Allowed]) -> + case dist_util:split_node(Node) of + {node,_,Host} -> + [Host|allowed_node_hosts(Allowed)]; + {host,Host} -> + [Host|allowed_node_hosts(Allowed)]; + _ -> + allowed_node_hosts(Allowed) end. %% Same as verify_peer but check cert host names for @@ -330,48 +339,19 @@ verify_client(_, {extension,_}, S) -> {unknown,S}; verify_client(_, valid, S) -> {valid,S}; -verify_client(PeerCert, valid_peer, {CertNodesFun,Driver,PeerIP} = S) -> - %% - %% Parse out all node names from the peer's certificate - %% - case CertNodesFun(PeerCert) of - undefined -> - %% Certificate allows all nodes +verify_client(_, valid_peer, {[],_} = S) -> + %% Allow all hosts + {valid,S}; +verify_client(PeerCert, valid_peer, {AllowedHosts,PeerIP} = S) -> + case + public_key:pkix_verify_hostname( + PeerCert, + [{ip,PeerIP}|[{dns_id,Host} || Host <- AllowedHosts]]) + of + true -> {valid,S}; - [] -> - %% Certificate allows no nodes - {fail,cert_missing_node_name}; - CertNodes -> - %% Check if the IP address of one of the nodes - %% in the peer certificate has the peer's IP address - case filter_nodes_by_ip(CertNodes, Driver, PeerIP) of - [] -> - {fail,cert_no_host_with_peer_ip}; - _ -> - {valid,S} - end - end. - -%% Filter out the nodes that has got the given IP address -%% -filter_nodes_by_ip(Nodes, Driver, IP) -> - [Node || - Node <- Nodes, - case dist_util:split_node(Node) of - {node,_,Host} -> - filter_host_by_ip(Host, Driver, IP); - {host,Host} -> - filter_host_by_ip(Host, Driver, IP); - {name,_Name} -> - true - end]. - -filter_host_by_ip(Host, Driver, IP) -> - case Driver:getaddr(Host) of - {ok,IP} -> - true; - _ -> - false + false -> + {fail,cert_no_hostname_nor_ip_match} end. @@ -417,19 +397,18 @@ gen_accept_connection( spawn_opt( fun() -> do_accept( - Driver, Kernel, AcceptPid, DistCtrl, - MyNode, Allowed, SetupTime) + Driver, AcceptPid, DistCtrl, + MyNode, Allowed, SetupTime, Kernel) end, [link, {priority, max}])). -do_accept(Driver, Kernel, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) -> +do_accept( + _Driver, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime, Kernel) -> SslSocket = ssl_connection:get_sslsocket(DistCtrl), receive - {AcceptPid, controller, CertNodesFun} -> + {AcceptPid, controller} -> Timer = dist_util:start_timer(SetupTime), - NewAllowed = - allowed_nodes( - Driver, CertNodesFun, SslSocket, Allowed), + NewAllowed = allowed_nodes(SslSocket, Allowed), HSData0 = hs_data_common(SslSocket), HSData = HSData0#hs_data{ @@ -443,65 +422,67 @@ do_accept(Driver, Kernel, AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) -> dist_util:handshake_other_started(trace(HSData)) end. -%% Return a list of allowed nodes according to -%% the given Allowed list that matches the peer certificate -%% -allowed_nodes(_Driver, undefined, _SslSocket, Allowed) -> - Allowed; -allowed_nodes(Driver, CertNodesFun, SslSocket, Allowed) -> +allowed_nodes(_SslSocket, []) -> + %% Allow all + []; +allowed_nodes(SslSocket, Allowed) -> case ssl:peercert(SslSocket) of {ok,PeerCertDER} -> case ssl:peername(SslSocket) of {ok,{PeerIP,_Port}} -> - PeerCert = public_key:pkix_decode_cert(PeerCertDER, otp), - %% - %% Parse out all node names from the peer's certificate - %% - case CertNodesFun(PeerCert) of - undefined -> - %% Certificate allows all nodes - Allowed; + PeerCert = + public_key:pkix_decode_cert(PeerCertDER, otp), + case + allowed_nodes( + PeerCert, allowed_hosts(Allowed), PeerIP) + of [] -> - %% Certificate allows no nodes - ?shutdown(cert_missing_node_name); - CertNodes -> - %% Filter out all nodes in the - %% allowed list that is in peer - %% certificate and that has got - %% the same IP address as the peer - allowed( - filter_nodes_by_ip( - CertNodes, Driver, PeerIP), - Allowed) + error_logger:error_msg( + "** Connection attempt from " + "disallowed node(s) ~p ** ~n", [PeerIP]), + ?shutdown2( + PeerIP, trace({is_allowed, not_allowed})); + AllowedNodes -> + AllowedNodes end; Error1 -> ?shutdown2(no_peer_ip, trace(Error1)) end; + {error,no_peercert} -> + Allowed; Error2 -> ?shutdown2(no_peer_cert, trace(Error2)) end. -allowed(CertNodes, []) -> - %% Empty allowed list means all allowed - %% -> allow only certificate nodes - CertNodes; -allowed(CertNodes, Allowed) -> - %% Find the intersection of the allowed list and certificate nodes - case - [CertNode || - CertNode <- CertNodes, - dist_util:is_allowed(CertNode, Allowed)] - of - [] -> - error_logger:error_msg( - "** Connection attempt from " - "disallowed node(s) ~p ** ~n", [CertNodes]), - ?shutdown2(CertNodes, trace({is_allowed, not_allowed})); - NewAllowed -> - NewAllowed +allowed_nodes(PeerCert, [], PeerIP) -> + case public_key:pkix_verify_hostname(PeerCert, [{ip,PeerIP}]) of + true -> + Host = inet:ntoa(PeerIP), + true = is_list(Host), + [Host]; + false -> + [] + end; +allowed_nodes(PeerCert, [Node|Allowed], PeerIP) -> + case dist_util:split_node(Node) of + {node,_,Host} -> + allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host); + {host,Host} -> + allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host); + _ -> + allowed_nodes(PeerCert, Allowed, PeerIP) + end. + +allowed_nodes(PeerCert, Allowed, PeerIP, Node, Host) -> + case public_key:pkix_verify_hostname(PeerCert, [{dns_id,Host}]) of + true -> + [Node|allowed_nodes(PeerCert, Allowed, PeerIP)]; + false -> + allowed_nodes(PeerCert, Allowed, PeerIP) end. + setup(Node, Type, MyNode, LongOrShortNames, SetupTime) -> gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames, SetupTime). @@ -541,15 +522,7 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> end. do_setup_connect(Driver, Kernel, Node, Address, Ip, TcpPort, Version, Type, MyNode, Timer) -> - Opts = - trace( - connect_options( - %% - %% Use verify_server/3 to verify that - %% the server's certificate is for Node - %% - setup_verify_server( - get_ssl_options(client), Node))), + Opts = trace(connect_options(get_ssl_options(client))), dist_util:reset_timer(Timer), case ssl:connect( Address, TcpPort, @@ -587,67 +560,6 @@ close(Socket) -> gen_close(Driver, Socket) -> trace(Driver:close(Socket)). -%% {verify_fun,{fun ?MODULE:verify_server/3,_}} is used -%% as a configuration marker that verify_server/3 shall be used. -%% -%% Replace the State in the first occurence of -%% {verify_fun,{fun ?MODULE:verify_server/3,State}} and remove the rest. -%% The inserted state is not accesible from a configuration file -%% since it is dynamic and connection dependent. -%% -setup_verify_server(Opts, Node) -> - setup_verify_server(Opts, Node, true). -%% -setup_verify_server([], _Node, _) -> - []; -setup_verify_server([Opt|Opts], Node, Once) -> - case Opt of - {verify_fun,{Fun,CertNodesFun}} -> - case Fun =:= fun ?MODULE:verify_server/3 of - true when not is_function(CertNodesFun, 1) -> - ?shutdown2( - Node, - {verify_server_bad_argument,CertNodesFun}); - true when Once -> - [{verify_fun,{Fun,{CertNodesFun,Node}}} - |setup_verify_server(Opts, Node, false)]; - true -> - setup_verify_server(Opts, Node, Once); - false -> - [Opt|setup_verify_server(Opts, Node, Once)] - end; - _ -> - [Opt|setup_verify_server(Opts, Node, Once)] - end. - -verify_server(_, {bad_cert,_} = Reason, _) -> - {fail,Reason}; -verify_server(_, {extension,_}, S) -> - {unknown,S}; -verify_server(_, valid, S) -> - {valid,S}; -verify_server(PeerCert, valid_peer, {CertNodesFun,Node} = S) -> - %% - %% Parse out all node names from the peer's certificate - %% - case CertNodesFun(PeerCert) of - undefined -> - %% Certificate allows all nodes - {valid,S}; - [] -> - %% Certificate allows no nodes - {fail,cert_missing_node_name}; - CertNodes -> - %% Check that the node we are connecting to - %% is in the peer certificate - case dist_util:is_allowed(Node, CertNodes) of - true -> - {valid,S}; - false -> - {fail,wrong_nodes_in_cert} - end - end. - %% ------------------------------------------------------------ %% Determine if EPMD module supports address resolving. Default diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 565cc9e1bc..eb5b351dd3 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -55,7 +55,7 @@ format_error/1, renegotiate/1, prf/5, negotiated_protocol/1, connection_information/1, connection_information/2]). %% Misc --export([handle_options/2, tls_version/1, new_ssl_options/3]). +-export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1]). -deprecated({ssl_accept, 1, eventually}). -deprecated({ssl_accept, 2, eventually}). @@ -772,6 +772,16 @@ tls_version({3, _} = Version) -> tls_version({254, _} = Version) -> dtls_v1:corresponding_tls_version(Version). + +%%-------------------------------------------------------------------- +-spec suite_to_str(ssl_cipher:erl_cipher_suite()) -> string(). +%% +%% Description: Return the string representation of a cipher suite. +%%-------------------------------------------------------------------- +suite_to_str(Cipher) -> + ssl_cipher:suite_to_str(Cipher). + + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 3f8b9a8a9b..c5b5b76f05 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -43,7 +43,7 @@ filter/3, filter_suites/1, filter_suites/2, hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, random_bytes/1, calc_mac_hash/4, - is_stream_ciphersuite/1]). + is_stream_ciphersuite/1, suite_to_str/1]). -export_type([cipher_suite/0, erl_cipher_suite/0, old_erl_cipher_suite/0, openssl_cipher_suite/0, @@ -1877,6 +1877,32 @@ suite(#{key_exchange := dhe_rsa, prf := sha256}) -> ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256. + +%%-------------------------------------------------------------------- +-spec suite_to_str(erl_cipher_suite()) -> string(). +%% +%% Description: Return the string representation of a cipher suite. +%%-------------------------------------------------------------------- +suite_to_str(#{key_exchange := null, + cipher := null, + mac := null, + prf := null}) -> + "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"; +suite_to_str(#{key_exchange := Kex, + cipher := Cipher, + mac := aead, + prf := PRF}) -> + "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ + "_" ++ string:to_upper(atom_to_list(PRF)); +suite_to_str(#{key_exchange := Kex, + cipher := Cipher, + mac := Mac}) -> + "TLS_" ++ string:to_upper(atom_to_list(Kex)) ++ + "_WITH_" ++ string:to_upper(atom_to_list(Cipher)) ++ + "_" ++ string:to_upper(atom_to_list(Mac)). + + %%-------------------------------------------------------------------- -spec openssl_suite(openssl_cipher_suite()) -> cipher_suite(). %% diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 162c63850f..e8fb25e0e4 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -108,7 +108,8 @@ basic_tests() -> clear_pem_cache, defaults, fallback, - cipher_format + cipher_format, + suite_to_str ]. basic_tests_tls() -> @@ -1227,7 +1228,27 @@ cipher_format(Config) when is_list(Config) -> ssl:close(Socket1), {ok, Socket2} = ssl:listen(0, [{ciphers, ssl:cipher_suites(openssl)}]), ssl:close(Socket2). - + +%%-------------------------------------------------------------------- +suite_to_str() -> + [{doc, "Test that the suite_to_str API works"}]. +suite_to_str(Config) when is_list(Config) -> + "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" = + ssl:suite_to_str(#{key_exchange => null, + cipher => null, + mac => null, + prf => null}), + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" = + ssl:suite_to_str(#{key_exchange => ecdhe_ecdsa, + cipher => aes_128_gcm, + mac => aead, + prf => sha256}), + "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" = + ssl:suite_to_str(#{key_exchange => ecdh_rsa, + cipher => aes_128_cbc, + mac => sha256, + prf => sha256}). + %%-------------------------------------------------------------------- peername() -> diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl index f827ea12bb..3c7904cf24 100644 --- a/lib/ssl/test/ssl_dist_bench_SUITE.erl +++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl @@ -117,19 +117,14 @@ init_per_suite(Config) -> ?MODULE_STRING ++ " ROOT CA", CertOptions), SSLConf = [{verify, verify_peer}, - {fail_if_no_peer_cert, true}, {versions, [TLSVersion]}, {ciphers, [TLSCipher]}], ServerConf = - [{verify_fun, - {fun inet_tls_dist:verify_client/3, - fun inet_tls_dist:cert_nodes/1}} - | SSLConf], - ClientConf = - [{verify_fun, - {fun inet_tls_dist:verify_server/3, - fun inet_tls_dist:cert_nodes/1}} + [{fail_if_no_peer_cert, true}, + {verify_fun, + {fun inet_tls_dist:verify_client/3,[]}} | SSLConf], + ClientConf = SSLConf, %% write_node_conf( NodeAConfFile, NodeA, ServerConf, ClientConf, @@ -291,6 +286,8 @@ roundtrip(A, B, Prefix, HA, HB) -> Rounds = 40000, [] = ssl_apply(HA, erlang, nodes, []), [] = ssl_apply(HB, erlang, nodes, []), + ok = ssl_apply(HA, net_kernel, allow, [[B]]), + ok = ssl_apply(HB, net_kernel, allow, [[A]]), Time = ssl_apply(HA, fun () -> roundtrip_runner(A, B, Rounds) end), [B] = ssl_apply(HA, erlang, nodes, []), [A] = ssl_apply(HB, erlang, nodes, []), |