./ct_report/coverage/just_tls.COVER.html

1 %%%=============================================================================
2 %%% @copyright (C) 1999-2018, Erlang Solutions Ltd
3 %%% @author Denys Gonchar <denys.gonchar@erlang-solutions.com>
4 %%% @doc TLS backend based on standard Erlang's SSL application
5 %%% @end
6 %%%=============================================================================
7 -module(just_tls).
8 -copyright("2018, Erlang Solutions Ltd.").
9 -author('denys.gonchar@erlang-solutions.com').
10
11 -behaviour(mongoose_tls).
12
13 -include_lib("public_key/include/public_key.hrl").
14
15 -record(tls_socket, {verify_results = [],
16 ssl_socket
17 }).
18
19 -type tls_socket() :: #tls_socket{}.
20 -export_type([tls_socket/0]).
21
22 % mongoose_tls behaviour
23 -export([tcp_to_tls/2,
24 send/2,
25 recv_data/2,
26 controlling_process/2,
27 sockname/1,
28 peername/1,
29 setopts/2,
30 get_peer_certificate/1,
31 close/1]).
32
33 % API
34 -export([make_ssl_opts/1]).
35
36 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37 %% APIs
38 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
39
40 -spec tcp_to_tls(inet:socket(), mongoose_tls:options()) ->
41 {ok, mongoose_tls:tls_socket()} | {error, any()}.
42 tcp_to_tls(TCPSocket, Options) ->
43 364 inet:setopts(TCPSocket, [{active, false}]),
44 364 {Ref, SSLOpts} = format_opts_with_ref(Options),
45 364 Ret = case Options of
46 #{connect := true} ->
47 % Currently unused as ejabberd_s2s_out uses fast_tls,
48 % and outgoing pools use Erlang SSL directly
49
:-(
ssl:connect(TCPSocket, SSLOpts);
50 #{} ->
51 364 ssl:handshake(TCPSocket, SSLOpts, 5000)
52 end,
53 364 VerifyResults = receive_verify_results(Ref),
54 364 case Ret of
55 {ok, SSLSocket} ->
56 77 {ok, #tls_socket{ssl_socket = SSLSocket, verify_results = VerifyResults}};
57 287 _ -> Ret
58 end.
59
60 %% -callback send(tls_socket(), binary()) -> ok | {error, any()}.
61 263 send(#tls_socket{ssl_socket = SSLSocket}, Packet) -> ssl:send(SSLSocket, Packet).
62
63 %% -callback recv_data(tls_socket(), binary()) -> {ok, binary()} | {error, any()}.
64 recv_data(_, <<"">>) ->
65 %% such call is required for fast_tls to accomplish
66 %% tls handshake, for just_tls we can ignore it
67
:-(
{ok, <<"">>};
68 recv_data(#tls_socket{ssl_socket = SSLSocket}, Data1) ->
69
:-(
case ssl:recv(SSLSocket, 0, 0) of
70
:-(
{ok, Data2} -> {ok, <<Data1/binary, Data2/binary>>};
71
:-(
_ -> {ok, Data1}
72 end.
73
74 %% -callback controlling_process(tls_socket(), pid()) -> ok | {error, any()}.
75 controlling_process(#tls_socket{ssl_socket = SSLSocket}, Pid) ->
76
:-(
ssl:controlling_process(SSLSocket, Pid).
77
78
79 %% -callback sockname(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
80 %% {error, any()}.
81
:-(
sockname(#tls_socket{ssl_socket = SSLSocket}) -> ssl:sockname(SSLSocket).
82
83
84 %% -callback peername(tls_socket()) -> {ok, {inet:ip_address(), inet:port_number()}} |
85 %% {error, any()}.
86
:-(
peername(#tls_socket{ssl_socket = SSLSocket}) -> ssl:peername(SSLSocket).
87
88
89 %% -callback setopts(tls_socket(), Opts::list()) -> ok | {error, any()}.
90 287 setopts(#tls_socket{ssl_socket = SSLSocket}, Opts) -> ssl:setopts(SSLSocket, Opts).
91
92
93 %% -callback get_peer_certificate(tls_socket()) -> {ok, Cert::any()} |
94 %% {bad_cert, bitstring()} |
95 %% no_peer_cert.
96 get_peer_certificate(#tls_socket{verify_results = [], ssl_socket = SSLSocket}) ->
97 117 case ssl:peercert(SSLSocket) of
98 {ok, PeerCert} ->
99 111 Cert = public_key:pkix_decode_cert(PeerCert, plain),
100 111 {ok, Cert};
101 6 _ -> no_peer_cert
102 end;
103 get_peer_certificate(#tls_socket{verify_results = [Err | _]}) ->
104 3 {bad_cert, error_to_list(Err)}.
105
106 %% -callback close(tls_socket()) -> ok.
107 77 close(#tls_socket{ssl_socket = SSLSocket}) -> ssl:close(SSLSocket).
108
109 %% @doc Prepare SSL options for direct use of ssl:connect/2 or ssl:handshake/2
110 %% The `disconnect_on_failure' option is not supported
111 -spec make_ssl_opts(mongoose_tls:options()) -> [ssl:tls_option()].
112 make_ssl_opts(Opts) ->
113 735 {dummy_ref, SSLOpts} = format_opts_with_ref(Opts),
114 735 SSLOpts.
115
116 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
117 %% local functions
118 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
119
120 format_opts_with_ref(Opts) ->
121 1099 Verify = verify_opt(Opts),
122 1099 {Ref, VerifyFun} = verify_fun_opt(Opts),
123 1099 SNIOpts = sni_opts(Opts),
124 1099 SSLOpts = maps:to_list(maps:with(ssl_option_keys(), Opts)),
125 1099 {Ref, [{fail_if_no_peer_cert, false}, {verify, Verify}, {verify_fun, VerifyFun}] ++
126 SNIOpts ++ SSLOpts}.
127
128 ssl_option_keys() ->
129 1099 [certfile, cacertfile, ciphers, keyfile, password, versions, dhfile].
130
131 sni_opts(#{server_name_indication := SNIOpts}) ->
132 521 process_sni_opts(SNIOpts);
133 sni_opts(#{}) ->
134 578 [].
135
136 process_sni_opts(#{enabled := false}) ->
137 520 [{server_name_indication, disable}];
138 process_sni_opts(#{enabled := true, host := SNIHost, protocol := https}) ->
139
:-(
[{server_name_indication, SNIHost},
140 {customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]}];
141 process_sni_opts(#{enabled := true, host := SNIHost, protocol := default}) ->
142
:-(
[{server_name_indication, SNIHost}];
143 process_sni_opts(#{enabled := true}) ->
144 1 [].
145
146 error_to_list(_Error) ->
147 %TODO: implement later if needed
148 3 "verify_fun failed".
149
150 179 verify_opt(#{verify_mode := none}) -> verify_none;
151 920 verify_opt(#{}) -> verify_peer.
152
153 %% This function translates TLS options to the function
154 %% which will later be used when TCP socket is upgraded to TLS
155 %% `verify_mode` is one of the following:
156 %% none - no validation of the clients certificate - any cert is accepted.
157 %% peer - standard verification of the certificate.
158 %% selfsigned_peer - the same as peer but also accepts self-signed certificates
159 %% `disconnect_on_failure` is a boolean parameter:
160 %% true - drop connection if certificate verification failed
161 %% false - connect anyway, but later return {bad_cert,Error}
162 %% on certificate verification (the same as fast_tls do).
163 verify_fun_opt(#{verify_mode := Mode, disconnect_on_failure := false}) ->
164 20 Ref = erlang:make_ref(),
165 20 {Ref, verify_fun(Ref, Mode)};
166 verify_fun_opt(#{verify_mode := Mode}) ->
167 1079 {dummy_ref, verify_fun(Mode)}.
168
169 verify_fun(Ref, Mode) when is_reference(Ref) ->
170 20 {Fun, State} = verify_fun(Mode),
171 20 {verify_fun_wrapper(Ref, Fun), State}.
172
173 verify_fun_wrapper(Ref, Fun) when is_reference(Ref), is_function(Fun, 3) ->
174 20 Pid = self(),
175 20 fun(Cert, Event, UserState) ->
176 91 Ret = Fun(Cert, Event, UserState),
177 91 case {Ret, Event} of
178 18 {{valid, _}, _} -> Ret;
179 {{unknown, NewState}, {extension, #'Extension'{critical = true}}} ->
180
:-(
send_verification_failure(Pid, Ref, unknown_critical_extension),
181
:-(
{valid, NewState};
182 72 {{unknown, _}, {extension, _}} -> Ret;
183 {_, _} -> %% {fail,Reason} = Ret
184 1 send_verification_failure(Pid, Ref, Ret),
185 1 {valid, UserState} %return the last valid user state
186 end
187 end.
188
189 verify_fun(peer) ->
190 888 {fun
191 9 (_, {bad_cert, _} = R, _) -> {fail, R};
192 2288 (_, {extension, _}, S) -> {unknown, S};
193
:-(
(_, valid, S) -> {valid, S};
194 572 (_, valid_peer, S) -> {valid, S}
195 end, []};
196 verify_fun(selfsigned_peer) ->
197 32 {fun
198 20 (_, {bad_cert, selfsigned_peer}, S) -> {valid, S};
199
:-(
(_, {bad_cert, _} = R, _) -> {fail, R};
200 8 (_, {extension, _}, S) -> {unknown, S};
201
:-(
(_, valid, S) -> {valid, S};
202 2 (_, valid_peer, S) -> {valid, S}
203 end, []};
204 verify_fun(none) ->
205 179 {fun(_, _, S) -> {valid, S} end, []}.
206
207
208 send_verification_failure(Pid, Ref, Reason) ->
209 1 Pid ! {cert_verification_failure, Ref, Reason}.
210
211 344 receive_verify_results(dummy_ref) -> [];
212 20 receive_verify_results(Ref) -> receive_verify_results(Ref, []).
213
214 receive_verify_results(Ref, Acc) ->
215 21 receive
216 {cert_verification_failure, Ref, Reason} ->
217 1 receive_verify_results(Ref, [Reason | Acc])
218 after 0 ->
219 20 lists:reverse(Acc)
220 end.
Line Hits Source