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