ranch_ssl.erl (8424B)
1 %% Copyright (c) 2011-2018, Loïc Hoguin <essen@ninenines.eu> 2 %% 3 %% Permission to use, copy, modify, and/or distribute this software for any 4 %% purpose with or without fee is hereby granted, provided that the above 5 %% copyright notice and this permission notice appear in all copies. 6 %% 7 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15 -module(ranch_ssl). 16 -behaviour(ranch_transport). 17 18 -export([name/0]). 19 -export([secure/0]). 20 -export([messages/0]). 21 -export([listen/1]). 22 -export([disallowed_listen_options/0]). 23 -export([accept/2]). 24 -export([accept_ack/2]). 25 -export([handshake/3]). 26 -export([connect/3]). 27 -export([connect/4]). 28 -export([recv/3]). 29 -export([recv_proxy_header/2]). 30 -export([send/2]). 31 -export([sendfile/2]). 32 -export([sendfile/4]). 33 -export([sendfile/5]). 34 -export([setopts/2]). 35 -export([getopts/2]). 36 -export([getstat/1]). 37 -export([getstat/2]). 38 -export([controlling_process/2]). 39 -export([peername/1]). 40 -export([sockname/1]). 41 -export([shutdown/2]). 42 -export([close/1]). 43 44 -type ssl_opt() :: {alpn_preferred_protocols, [binary()]} 45 | {beast_mitigation, one_n_minus_one | zero_n | disabled} 46 | {cacertfile, string()} 47 | {cacerts, [public_key:der_encoded()]} 48 | {cert, public_key:der_encoded()} 49 | {certfile, string()} 50 | {ciphers, [ssl_cipher:erl_cipher_suite()]} 51 | {client_renegotiation, boolean()} 52 | {crl_cache, {module(), {internal | any(), list()}}} 53 | {crl_check, boolean() | peer | best_effort} 54 | {depth, 0..255} 55 | {dh, public_key:der_encoded()} 56 | {dhfile, string()} 57 | {fail_if_no_peer_cert, boolean()} 58 | {hibernate_after, integer() | undefined} 59 | {honor_cipher_order, boolean()} 60 | {key, {'RSAPrivateKey' | 'DSAPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()}} 61 | {keyfile, string()} 62 | {log_alert, boolean()} 63 | {next_protocols_advertised, [binary()]} 64 | {padding_check, boolean()} 65 | {partial_chain, fun(([public_key:der_encoded()]) -> {trusted_ca, public_key:der_encoded()} | unknown_ca)} 66 | {password, string()} 67 | {psk_identity, string()} 68 | {reuse_session, fun()} 69 | {reuse_sessions, boolean()} 70 | {secure_renegotiate, boolean()} 71 | {signature_algs, [{atom(), atom()}]} 72 | {sni_fun, fun()} 73 | {sni_hosts, [{string(), ssl_opt()}]} 74 | {user_lookup_fun, {fun(), any()}} 75 | {v2_hello_compatible, boolean()} 76 | {verify, verify_none | verify_peer} 77 | {verify_fun, {fun(), any()}} 78 | {versions, [atom()]}. 79 -export_type([ssl_opt/0]). 80 81 -type opt() :: ranch_tcp:opt() | ssl_opt(). 82 -export_type([opt/0]). 83 84 -type opts() :: [opt()]. 85 -export_type([opts/0]). 86 87 name() -> ssl. 88 89 -spec secure() -> boolean(). 90 secure() -> 91 true. 92 93 messages() -> {ssl, ssl_closed, ssl_error}. 94 95 -spec listen(opts()) -> {ok, ssl:sslsocket()} | {error, atom()}. 96 listen(Opts) -> 97 case lists:keymember(cert, 1, Opts) 98 orelse lists:keymember(certfile, 1, Opts) 99 orelse lists:keymember(sni_fun, 1, Opts) 100 orelse lists:keymember(sni_hosts, 1, Opts) of 101 true -> 102 do_listen(Opts); 103 false -> 104 {error, no_cert} 105 end. 106 107 do_listen(Opts0) -> 108 Opts1 = ranch:set_option_default(Opts0, backlog, 1024), 109 Opts2 = ranch:set_option_default(Opts1, nodelay, true), 110 Opts3 = ranch:set_option_default(Opts2, send_timeout, 30000), 111 Opts = ranch:set_option_default(Opts3, send_timeout_close, true), 112 %% We set the port to 0 because it is given in the Opts directly. 113 %% The port in the options takes precedence over the one in the 114 %% first argument. 115 ssl:listen(0, ranch:filter_options(Opts, disallowed_listen_options(), 116 [binary, {active, false}, {packet, raw}, {reuseaddr, true}])). 117 118 %% 'binary' and 'list' are disallowed but they are handled 119 %% specifically as they do not have 2-tuple equivalents. 120 disallowed_listen_options() -> 121 [alpn_advertised_protocols, client_preferred_next_protocols, 122 fallback, server_name_indication, srp_identity 123 |ranch_tcp:disallowed_listen_options()]. 124 125 -spec accept(ssl:sslsocket(), timeout()) 126 -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}. 127 accept(LSocket, Timeout) -> 128 ssl:transport_accept(LSocket, Timeout). 129 130 -spec accept_ack(ssl:sslsocket(), timeout()) -> ok. 131 accept_ack(CSocket, Timeout) -> 132 {ok, _} = handshake(CSocket, [], Timeout), 133 ok. 134 135 -spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout()) 136 -> {ok, ssl:sslsocket()} | {error, any()}. 137 handshake(CSocket, Opts, Timeout) -> 138 case ssl:handshake(CSocket, Opts, Timeout) of 139 {ok, NewSocket} -> 140 {ok, NewSocket}; 141 Error = {error, _} -> 142 Error 143 end. 144 145 %% @todo Probably filter Opts? 146 -spec connect(inet:ip_address() | inet:hostname(), 147 inet:port_number(), any()) 148 -> {ok, inet:socket()} | {error, atom()}. 149 connect(Host, Port, Opts) when is_integer(Port) -> 150 ssl:connect(Host, Port, 151 Opts ++ [binary, {active, false}, {packet, raw}]). 152 153 %% @todo Probably filter Opts? 154 -spec connect(inet:ip_address() | inet:hostname(), 155 inet:port_number(), any(), timeout()) 156 -> {ok, inet:socket()} | {error, atom()}. 157 connect(Host, Port, Opts, Timeout) when is_integer(Port) -> 158 ssl:connect(Host, Port, 159 Opts ++ [binary, {active, false}, {packet, raw}], 160 Timeout). 161 162 -spec recv(ssl:sslsocket(), non_neg_integer(), timeout()) 163 -> {ok, any()} | {error, closed | atom()}. 164 recv(Socket, Length, Timeout) -> 165 ssl:recv(Socket, Length, Timeout). 166 167 -spec recv_proxy_header(ssl:sslsocket(), timeout()) 168 -> {ok, ranch_proxy_header:proxy_info()} 169 | {error, closed | atom()} 170 | {error, protocol_error, atom()}. 171 recv_proxy_header(SSLSocket, Timeout) -> 172 %% There's currently no documented way to perform a TCP recv 173 %% on an sslsocket(), even before the TLS handshake. However 174 %% nothing prevents us from retrieving the TCP socket and using 175 %% it. Since it's an undocumented interface this may however 176 %% make forward-compatibility more difficult. 177 {sslsocket, {gen_tcp, TCPSocket, _, _}, _} = SSLSocket, 178 ranch_tcp:recv_proxy_header(TCPSocket, Timeout). 179 180 -spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}. 181 send(Socket, Packet) -> 182 ssl:send(Socket, Packet). 183 184 -spec sendfile(ssl:sslsocket(), file:name_all() | file:fd()) 185 -> {ok, non_neg_integer()} | {error, atom()}. 186 sendfile(Socket, Filename) -> 187 sendfile(Socket, Filename, 0, 0, []). 188 189 -spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(), 190 non_neg_integer(), non_neg_integer()) 191 -> {ok, non_neg_integer()} | {error, atom()}. 192 sendfile(Socket, File, Offset, Bytes) -> 193 sendfile(Socket, File, Offset, Bytes, []). 194 195 %% Unlike with TCP, no syscall can be used here, so sending files 196 %% through SSL will be much slower in comparison. Note that unlike 197 %% file:sendfile/5 this function accepts either a file or a file name. 198 -spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(), 199 non_neg_integer(), non_neg_integer(), ranch_transport:sendfile_opts()) 200 -> {ok, non_neg_integer()} | {error, atom()}. 201 sendfile(Socket, File, Offset, Bytes, Opts) -> 202 ranch_transport:sendfile(?MODULE, Socket, File, Offset, Bytes, Opts). 203 204 %% @todo Probably filter Opts? 205 -spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}. 206 setopts(Socket, Opts) -> 207 ssl:setopts(Socket, Opts). 208 209 -spec getopts(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}. 210 getopts(Socket, Opts) -> 211 ssl:getopts(Socket, Opts). 212 213 -spec getstat(ssl:sslsocket()) -> {ok, list()} | {error, atom()}. 214 getstat(Socket) -> 215 ssl:getstat(Socket). 216 217 -spec getstat(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}. 218 getstat(Socket, OptionNames) -> 219 ssl:getstat(Socket, OptionNames). 220 221 -spec controlling_process(ssl:sslsocket(), pid()) 222 -> ok | {error, closed | not_owner | atom()}. 223 controlling_process(Socket, Pid) -> 224 ssl:controlling_process(Socket, Pid). 225 226 -spec peername(ssl:sslsocket()) 227 -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 228 peername(Socket) -> 229 ssl:peername(Socket). 230 231 -spec sockname(ssl:sslsocket()) 232 -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}. 233 sockname(Socket) -> 234 ssl:sockname(Socket). 235 236 -spec shutdown(ssl:sslsocket(), read | write | read_write) 237 -> ok | {error, atom()}. 238 shutdown(Socket, How) -> 239 ssl:shutdown(Socket, How). 240 241 -spec close(ssl:sslsocket()) -> ok. 242 close(Socket) -> 243 ssl:close(Socket).