1%% Copyright (c) 2011-2021, Loïc Hoguin <essen@ninenines.eu> 2%% Copyright (c) 2020-2021, Jan Uhlig <juhlig@hnc-agency.org> 3%% Copyright (c) 2021, Maria Scott <maria-12648430@hnc-agency.org> 4%% 5%% Permission to use, copy, modify, and/or distribute this software for any 6%% purpose with or without fee is hereby granted, provided that the above 7%% copyright notice and this permission notice appear in all copies. 8%% 9%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 17-module(ranch_ssl). 18-behaviour(ranch_transport). 19 20-export([name/0]). 21-export([secure/0]). 22-export([messages/0]). 23-export([listen/1]). 24-export([disallowed_listen_options/0]). 25-export([accept/2]). 26-export([handshake/2]). 27-export([handshake/3]). 28-export([handshake_continue/2]). 29-export([handshake_continue/3]). 30-export([handshake_cancel/1]). 31-export([connect/3]). 32-export([connect/4]). 33-export([recv/3]). 34-export([recv_proxy_header/2]). 35-export([send/2]). 36-export([sendfile/2]). 37-export([sendfile/4]). 38-export([sendfile/5]). 39-export([setopts/2]). 40-export([getopts/2]). 41-export([getstat/1]). 42-export([getstat/2]). 43-export([controlling_process/2]). 44-export([peername/1]). 45-export([sockname/1]). 46-export([shutdown/2]). 47-export([close/1]). 48-export([cleanup/1]). 49 50-type ssl_opt() :: {alpn_preferred_protocols, [binary()]} 51 | {anti_replay, '10k' | '100k' | {integer(), integer(), integer()}} 52 | {beast_mitigation, one_n_minus_one | zero_n | disabled} 53 | {cacertfile, file:filename()} 54 | {cacerts, [public_key:der_encoded()]} 55 | {cert, public_key:der_encoded()} 56 | {certfile, file:filename()} 57 | {ciphers, ssl:ciphers()} 58 | {client_renegotiation, boolean()} 59 | {crl_cache, [any()]} 60 | {crl_check, boolean() | peer | best_effort} 61 | {depth, integer()} 62 | {dh, binary()} 63 | {dhfile, file:filename()} 64 | {eccs, [ssl:named_curve()]} 65 | {fail_if_no_peer_cert, boolean()} 66 | {handshake, hello | full} 67 | {hibernate_after, timeout()} 68 | {honor_cipher_order, boolean()} 69 | {honor_ecc_order, boolean()} 70 | {key, ssl:key()} 71 | {key_update_at, pos_integer()} 72 | {keyfile, file:filename()} 73 | {log_alert, boolean()} 74 | {log_level, logger:level()} 75 | {max_handshake_size, integer()} 76 | {middlebox_comp_mode, boolean()} 77 | {next_protocols_advertised, [binary()]} 78 | {padding_check, boolean()} 79 | {partial_chain, fun()} 80 | {password, string()} 81 | {protocol, tls | dtls} 82 | {psk_identity, string()} 83 | {reuse_session, fun()} 84 | {reuse_sessions, boolean()} 85 | {secure_renegotiate, boolean()} 86 | {session_tickets, disabled | stateful | stateless} 87 | {signature_algs, [{ssl:hash(), ssl:sign_algo()}]} 88 | {signature_algs_cert, [ssl:sign_scheme()]} 89 | {sni_fun, fun()} 90 | {sni_hosts, [{string(), ssl_opt()}]} 91 | {supported_groups, [ssl:group()]} 92 | {user_lookup_fun, {fun(), any()}} 93 | {verify, verify_none | verify_peer} 94 | {verify_fun, {fun(), any()}} 95 | {versions, [ssl:protocol_version()]}. 96-export_type([ssl_opt/0]). 97 98-type opt() :: ranch_tcp:opt() | ssl_opt(). 99-export_type([opt/0]). 100 101-type opts() :: [opt()]. 102-export_type([opts/0]). 103 104-spec name() -> ssl. 105name() -> ssl. 106 107-spec secure() -> boolean(). 108secure() -> 109 true. 110 111-spec messages() -> {ssl, ssl_closed, ssl_error, ssl_passive}. 112messages() -> {ssl, ssl_closed, ssl_error, ssl_passive}. 113 114-spec listen(ranch:transport_opts(opts())) -> {ok, ssl:sslsocket()} | {error, atom()}. 115listen(TransOpts) -> 116 ok = cleanup(TransOpts), 117 SocketOpts = maps:get(socket_opts, TransOpts, []), 118 case lists:keymember(cert, 1, SocketOpts) 119 orelse lists:keymember(certfile, 1, SocketOpts) 120 orelse lists:keymember(sni_fun, 1, SocketOpts) 121 orelse lists:keymember(sni_hosts, 1, SocketOpts) 122 orelse lists:keymember(user_lookup_fun, 1, SocketOpts) of 123 true -> 124 Logger = maps:get(logger, TransOpts, logger), 125 do_listen(SocketOpts, Logger); 126 false -> 127 {error, no_cert} 128 end. 129 130do_listen(SocketOpts0, Logger) -> 131 SocketOpts1 = ranch:set_option_default(SocketOpts0, backlog, 1024), 132 SocketOpts2 = ranch:set_option_default(SocketOpts1, nodelay, true), 133 SocketOpts3 = ranch:set_option_default(SocketOpts2, send_timeout, 30000), 134 SocketOpts = ranch:set_option_default(SocketOpts3, send_timeout_close, true), 135 DisallowedOpts0 = disallowed_listen_options(), 136 DisallowedOpts = unsupported_tls_options(SocketOpts) ++ DisallowedOpts0, 137 %% We set the port to 0 because it is given in the Opts directly. 138 %% The port in the options takes precedence over the one in the 139 %% first argument. 140 ssl:listen(0, ranch:filter_options(SocketOpts, DisallowedOpts, 141 [binary, {active, false}, {packet, raw}, {reuseaddr, true}], Logger)). 142 143%% 'binary' and 'list' are disallowed but they are handled 144%% specifically as they do not have 2-tuple equivalents. 145-spec disallowed_listen_options() -> [atom()]. 146disallowed_listen_options() -> 147 [alpn_advertised_protocols, client_preferred_next_protocols, 148 fallback, server_name_indication, srp_identity 149 |ranch_tcp:disallowed_listen_options()]. 150 151unsupported_tls_options(SocketOpts) -> 152 unsupported_tls_version_options(lists:usort(get_tls_versions(SocketOpts))). 153 154unsupported_tls_version_options([tlsv1|_]) -> 155 []; 156unsupported_tls_version_options(['tlsv1.1'|_]) -> 157 [beast_mitigation, padding_check]; 158unsupported_tls_version_options(['tlsv1.2'|_]) -> 159 [beast_mitigation, padding_check]; 160unsupported_tls_version_options(['tlsv1.3'|_]) -> 161 [beast_mitigation, client_renegotiation, next_protocols_advertised, 162 padding_check, psk_identity, reuse_session, reuse_sessions, 163 secure_renegotiate, user_lookup_fun]; 164unsupported_tls_version_options(_) -> 165 []. 166 167-spec accept(ssl:sslsocket(), timeout()) 168 -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. 169accept(LSocket, Timeout) -> 170 ssl:transport_accept(LSocket, Timeout). 171 172-spec handshake(inet:socket() | ssl:sslsocket(), timeout()) 173 -> {ok, ssl:sslsocket()} | {ok, ssl:sslsocket(), ssl:protocol_extensions()} | {error, any()}. 174handshake(CSocket, Timeout) -> 175 handshake(CSocket, [], Timeout). 176 177-spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout()) 178 -> {ok, ssl:sslsocket()} | {ok, ssl:sslsocket(), ssl:protocol_extensions()} | {error, any()}. 179handshake(CSocket, Opts, Timeout) -> 180 case ssl:handshake(CSocket, Opts, Timeout) of 181 OK = {ok, _} -> 182 OK; 183 OK = {ok, _, _} -> 184 OK; 185 Error = {error, _} -> 186 Error 187 end. 188 189-spec handshake_continue(ssl:sslsocket(), timeout()) 190 -> {ok, ssl:sslsocket()} | {error, any()}. 191handshake_continue(CSocket, Timeout) -> 192 handshake_continue(CSocket, [], Timeout). 193 194-spec handshake_continue(ssl:sslsocket(), [ssl:tls_server_option()], timeout()) 195 -> {ok, ssl:sslsocket()} | {error, any()}. 196handshake_continue(CSocket, Opts, Timeout) -> 197 case ssl:handshake_continue(CSocket, Opts, Timeout) of 198 OK = {ok, _} -> 199 OK; 200 Error = {error, _} -> 201 Error 202 end. 203 204-spec handshake_cancel(ssl:sslsocket()) -> ok. 205handshake_cancel(CSocket) -> 206 ok = ssl:handshake_cancel(CSocket). 207 208%% @todo Probably filter Opts? 209-spec connect(inet:ip_address() | inet:hostname(), 210 inet:port_number(), any()) 211 -> {ok, inet:socket()} | {error, atom()}. 212connect(Host, Port, Opts) when is_integer(Port) -> 213 ssl:connect(Host, Port, 214 Opts ++ [binary, {active, false}, {packet, raw}]). 215 216%% @todo Probably filter Opts? 217-spec connect(inet:ip_address() | inet:hostname(), 218 inet:port_number(), any(), timeout()) 219 -> {ok, inet:socket()} | {error, atom()}. 220connect(Host, Port, Opts, Timeout) when is_integer(Port) -> 221 ssl:connect(Host, Port, 222 Opts ++ [binary, {active, false}, {packet, raw}], 223 Timeout). 224 225-spec recv(ssl:sslsocket(), non_neg_integer(), timeout()) 226 -> {ok, any()} | {error, closed | atom()}. 227recv(Socket, Length, Timeout) -> 228 ssl:recv(Socket, Length, Timeout). 229 230-spec recv_proxy_header(ssl:sslsocket(), timeout()) 231 -> {ok, ranch_proxy_header:proxy_info()} 232 | {error, closed | atom()} 233 | {error, protocol_error, atom()}. 234recv_proxy_header(SSLSocket, Timeout) -> 235 %% There's currently no documented way to perform a TCP recv 236 %% on an sslsocket(), even before the TLS handshake. However 237 %% nothing prevents us from retrieving the TCP socket and using 238 %% it. Since it's an undocumented interface this may however 239 %% make forward-compatibility more difficult. 240 {sslsocket, {gen_tcp, TCPSocket, _, _}, _} = SSLSocket, 241 ranch_tcp:recv_proxy_header(TCPSocket, Timeout). 242 243-spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}. 244send(Socket, Packet) -> 245 ssl:send(Socket, Packet). 246 247-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd()) 248 -> {ok, non_neg_integer()} | {error, atom()}. 249sendfile(Socket, Filename) -> 250 sendfile(Socket, Filename, 0, 0, []). 251 252-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(), 253 non_neg_integer(), non_neg_integer()) 254 -> {ok, non_neg_integer()} | {error, atom()}. 255sendfile(Socket, File, Offset, Bytes) -> 256 sendfile(Socket, File, Offset, Bytes, []). 257 258%% Unlike with TCP, no syscall can be used here, so sending files 259%% through SSL will be much slower in comparison. Note that unlike 260%% file:sendfile/5 this function accepts either a file or a file name. 261-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(), 262 non_neg_integer(), non_neg_integer(), ranch_transport:sendfile_opts()) 263 -> {ok, non_neg_integer()} | {error, atom()}. 264sendfile(Socket, File, Offset, Bytes, Opts) -> 265 ranch_transport:sendfile(?MODULE, Socket, File, Offset, Bytes, Opts). 266 267%% @todo Probably filter Opts? 268-spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. 269setopts(Socket, Opts) -> 270 ssl:setopts(Socket, Opts). 271 272-spec getopts(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}. 273getopts(Socket, Opts) -> 274 ssl:getopts(Socket, Opts). 275 276-spec getstat(ssl:sslsocket()) -> {ok, list()} | {error, atom()}. 277getstat(Socket) -> 278 ssl:getstat(Socket). 279 280-spec getstat(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}. 281getstat(Socket, OptionNames) -> 282 ssl:getstat(Socket, OptionNames). 283 284-spec controlling_process(ssl:sslsocket(), pid()) 285 -> ok | {error, closed | not_owner | atom()}. 286controlling_process(Socket, Pid) -> 287 ssl:controlling_process(Socket, Pid). 288 289-spec peername(ssl:sslsocket()) 290 -> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}. 291peername(Socket) -> 292 ssl:peername(Socket). 293 294-spec sockname(ssl:sslsocket()) 295 -> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}. 296sockname(Socket) -> 297 ssl:sockname(Socket). 298 299-spec shutdown(ssl:sslsocket(), read | write | read_write) 300 -> ok | {error, atom()}. 301shutdown(Socket, How) -> 302 ssl:shutdown(Socket, How). 303 304-spec close(ssl:sslsocket()) -> ok. 305close(Socket) -> 306 ssl:close(Socket). 307 308-spec cleanup(ranch:transport_opts(opts())) -> ok. 309cleanup(#{socket_opts:=SocketOpts}) -> 310 case lists:keyfind(ip, 1, lists:reverse(SocketOpts)) of 311 {ip, {local, SockFile}} -> 312 _ = file:delete(SockFile), 313 ok; 314 _ -> 315 ok 316 end; 317cleanup(_) -> 318 ok. 319 320get_tls_versions(SocketOpts) -> 321 %% Socket options need to be reversed for keyfind because later options 322 %% take precedence when contained multiple times, but keyfind will return 323 %% the earliest occurence. 324 case lists:keyfind(versions, 1, lists:reverse(SocketOpts)) of 325 {versions, Versions} -> 326 Versions; 327 false -> 328 get_tls_versions_env() 329 end. 330 331get_tls_versions_env() -> 332 case application:get_env(ssl, protocol_version) of 333 {ok, Versions} -> 334 Versions; 335 undefined -> 336 get_tls_versions_app() 337 end. 338 339get_tls_versions_app() -> 340 {supported, Versions} = lists:keyfind(supported, 1, ssl:versions()), 341 Versions. 342