1 |
|
%%%============================================================================= |
2 |
|
%%% @copyright (C) 1999-2018, Erlang Solutions Ltd |
3 |
|
%%% @author Denys Gonchar <denys.gonchar@erlang-solutions.com> |
4 |
|
%%% @doc this module provides general TLS interface for MongooseIM. |
5 |
|
%%% |
6 |
|
%%% by default tls_module is set to fast_tls, alternatively it can be any |
7 |
|
%%% module that implements mongoose_tls behaviour |
8 |
|
%%% @end |
9 |
|
%%%============================================================================= |
10 |
|
-module(mongoose_tls). |
11 |
|
-copyright("2018, Erlang Solutions Ltd."). |
12 |
|
-author('denys.gonchar@erlang-solutions.com'). |
13 |
|
|
14 |
|
%% tls interfaces required by ejabberd_socket & ejabberd_receiver modules. |
15 |
|
-export([prepare_options/2, |
16 |
|
tcp_to_tls/2, |
17 |
|
default_ciphers/0, |
18 |
|
send/2, |
19 |
|
recv_data/2, |
20 |
|
controlling_process/2, |
21 |
|
sockname/1, |
22 |
|
peername/1, |
23 |
|
setopts/2, |
24 |
|
get_peer_certificate/1, |
25 |
|
get_tls_last_message/1, |
26 |
|
close/1]). |
27 |
|
|
28 |
|
-export([get_sockmod/1]). |
29 |
|
|
30 |
|
-ignore_xref([behaviour_info/1, close/1, controlling_process/2, peername/1, |
31 |
|
send/2, setopts/2, sockname/1]). |
32 |
|
|
33 |
|
-type tls_socket() :: any(). |
34 |
|
-type cert() :: {ok, Cert::any()} | {bad_cert, bitstring()} | no_peer_cert. |
35 |
|
|
36 |
|
%% Options used for client-side and server-side TLS connections. |
37 |
|
%% All modules implementing this behaviour have to support the mandatory 'verify_mode' option. |
38 |
|
%% Other options should be supported if the implementing module supports it. |
39 |
|
-type options() :: #{module => module(), % fast_tls by default |
40 |
|
connect => boolean(), % set to 'true' for a client-side call to tcp_to_tls/2 |
41 |
|
verify_mode := peer | selfsigned_peer | none, |
42 |
|
certfile => string(), |
43 |
|
cacertfile => string(), |
44 |
|
ciphers => string(), |
45 |
|
dhfile => string(), % server-only |
46 |
|
|
47 |
|
%% only for just_tls |
48 |
|
disconnect_on_failure => boolean(), |
49 |
|
keyfile => string(), |
50 |
|
password => string(), |
51 |
|
versions => [atom()], |
52 |
|
server_name_indication => sni_options(), % client-only |
53 |
|
|
54 |
|
% only for fast_tls |
55 |
|
protocol_options => [string()]}. |
56 |
|
|
57 |
|
-type sni_options() :: #{enabled := boolean, |
58 |
|
protocol := default | https, |
59 |
|
host => string()}. |
60 |
|
|
61 |
|
-export_type([tls_socket/0]). |
62 |
|
|
63 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
64 |
|
%% behaviour definition |
65 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
66 |
|
-callback tcp_to_tls(inet:socket(), options()) -> {ok, tls_socket()} | {error, any()}. |
67 |
|
|
68 |
|
-callback send(tls_socket(), binary()) -> ok | {error , any()}. |
69 |
|
|
70 |
|
-callback recv_data(tls_socket(), binary()) -> {ok, binary()} | {error, any()}. |
71 |
|
|
72 |
|
-callback controlling_process(tls_socket(), pid()) -> ok | {error, any()}. |
73 |
|
|
74 |
|
-callback sockname(tls_socket()) -> {ok, mongoose_transport:peer()} | |
75 |
|
{error, any()}. |
76 |
|
|
77 |
|
-callback peername(tls_socket()) -> {ok, mongoose_transport:peer()} | |
78 |
|
{error, any()}. |
79 |
|
|
80 |
|
-callback setopts(tls_socket(), Opts::list()) -> ok | {error, any()}. |
81 |
|
|
82 |
|
-callback get_peer_certificate(tls_socket()) -> cert(). |
83 |
|
|
84 |
|
-callback close(tls_socket()) -> ok. |
85 |
|
|
86 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
87 |
|
%% socket type definition |
88 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
89 |
|
|
90 |
|
-record(mongoose_tls_socket, {tls_module :: module(), |
91 |
|
tcp_socket :: inet:socket(), |
92 |
|
tls_socket :: tls_socket(), |
93 |
|
tls_opts :: options(), |
94 |
|
has_cert :: boolean() |
95 |
|
}). |
96 |
|
|
97 |
|
-type socket() :: #mongoose_tls_socket{}. |
98 |
|
|
99 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
100 |
|
%% APIs |
101 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
102 |
|
|
103 |
|
-spec tcp_to_tls(inet:socket(), options()) -> {ok, socket()} | {error, any()}. |
104 |
|
tcp_to_tls(TCPSocket, Opts) -> |
105 |
1118 |
Module = maps:get(module, Opts, fast_tls), |
106 |
1118 |
PreparedOpts = prepare_options(Module, maps:remove(module, Opts)), |
107 |
1118 |
case Module:tcp_to_tls(TCPSocket, PreparedOpts) of |
108 |
|
{ok, TLSSocket} -> |
109 |
960 |
HasCert = has_peer_cert(Opts), |
110 |
960 |
{ok, #mongoose_tls_socket{tls_module = Module, |
111 |
|
tcp_socket = TCPSocket, |
112 |
|
tls_socket = TLSSocket, |
113 |
|
tls_opts = Opts, |
114 |
|
has_cert = HasCert}}; |
115 |
158 |
Error -> Error |
116 |
|
end. |
117 |
|
|
118 |
|
-spec prepare_options(module(), options()) -> any(). |
119 |
|
prepare_options(fast_tls, Opts) -> |
120 |
|
%% fast_tls is an external library and its API cannot use Opts directly |
121 |
4064 |
lists:flatmap(fun({K, V}) -> fast_tls_opt(K, V) end, maps:to_list(Opts)); |
122 |
|
prepare_options(_Module, Opts) -> |
123 |
199 |
Opts. |
124 |
|
|
125 |
1579 |
fast_tls_opt(connect, true) -> [connect]; |
126 |
4033 |
fast_tls_opt(verify_mode, peer) -> []; |
127 |
31 |
fast_tls_opt(verify_mode, none) -> [verify_none]; |
128 |
3172 |
fast_tls_opt(cacertfile, File) -> [{cafile, File}]; |
129 |
743 |
fast_tls_opt(dhfile, File) -> [{dhfile, File}]; |
130 |
4064 |
fast_tls_opt(certfile, File) -> [{certfile, File}]; |
131 |
4064 |
fast_tls_opt(ciphers, Ciphers) -> [{ciphers, Ciphers}]; |
132 |
4047 |
fast_tls_opt(protocol_options, ProtoOpts) -> [{protocol_options, string:join(ProtoOpts, "|")}]. |
133 |
|
|
134 |
|
default_ciphers() -> |
135 |
362 |
"TLSv1.2:TLSv1.3". |
136 |
|
|
137 |
|
-spec send(socket(), binary()) -> ok | {error, any()}. |
138 |
907 |
send(#mongoose_tls_socket{tls_module = M, tls_socket = S}, B) -> M:send(S, B). |
139 |
|
|
140 |
|
|
141 |
|
-spec recv_data(socket(), binary()) -> {ok, binary()} | {error, any()}. |
142 |
3108 |
recv_data(#mongoose_tls_socket{tls_module = M, tls_socket = S}, B) -> M:recv_data(S, B). |
143 |
|
|
144 |
|
|
145 |
|
-spec controlling_process(socket(), pid()) -> ok | {error, any()}. |
146 |
|
controlling_process(#mongoose_tls_socket{tls_module = M, tls_socket = S}, Pid) -> |
147 |
:-( |
M:controlling_process(S, Pid). |
148 |
|
|
149 |
|
|
150 |
|
-spec sockname(tls_socket()) -> {ok, mongoose_transport:peer()} | {error, any()}. |
151 |
:-( |
sockname(#mongoose_tls_socket{tls_module = M, tls_socket = S}) -> M:sockname(S). |
152 |
|
|
153 |
|
|
154 |
|
-spec peername(tls_socket()) -> {ok, mongoose_transport:peer()} | {error, any()}. |
155 |
2442 |
peername(#mongoose_tls_socket{tls_module = M, tls_socket = S}) -> M:peername(S). |
156 |
|
|
157 |
|
|
158 |
|
-spec setopts(socket(), Opts::list()) -> ok | {error, any()}. |
159 |
2442 |
setopts(#mongoose_tls_socket{tls_module = M, tls_socket = S}, Opts) -> M:setopts(S, Opts). |
160 |
|
|
161 |
|
|
162 |
|
-spec get_peer_certificate(socket()) -> cert(). |
163 |
|
get_peer_certificate(#mongoose_tls_socket{has_cert = false}) -> |
164 |
31 |
no_peer_cert; |
165 |
|
get_peer_certificate(#mongoose_tls_socket{tls_module = just_tls, tls_socket = S}) -> |
166 |
120 |
just_tls:get_peer_certificate(S); |
167 |
|
get_peer_certificate(#mongoose_tls_socket{tls_module = fast_tls, tls_socket = S, |
168 |
|
tls_opts = TLSOpts}) -> |
169 |
109 |
case {fast_tls:get_verify_result(S), fast_tls:get_peer_certificate(S)} of |
170 |
64 |
{0, {ok, Cert}} -> {ok, Cert}; |
171 |
|
{Error, {ok, Cert}} -> |
172 |
18 |
maybe_allow_selfsigned(Error, Cert, TLSOpts); |
173 |
27 |
{_, error} -> no_peer_cert |
174 |
|
end. |
175 |
|
|
176 |
|
-spec close(socket()) -> ok. |
177 |
960 |
close(#mongoose_tls_socket{tls_module = M, tls_socket = S}) -> M:close(S). |
178 |
|
|
179 |
|
-spec get_sockmod(socket()) -> module(). |
180 |
294 |
get_sockmod(#mongoose_tls_socket{tls_module = Module}) -> Module. |
181 |
|
|
182 |
|
-spec get_tls_last_message(ejabberd_socket:socket()) -> {ok, binary()} | {error, term()}. |
183 |
|
get_tls_last_message(#mongoose_tls_socket{} = Socket) -> |
184 |
20 |
case get_sockmod(Socket) of |
185 |
|
fast_tls -> |
186 |
20 |
fast_tls:get_tls_last_message(peer, Socket#mongoose_tls_socket.tls_socket); |
187 |
|
_ -> |
188 |
:-( |
{error, undefined} |
189 |
|
end; |
190 |
|
get_tls_last_message(_) -> |
191 |
:-( |
{error, undefined}. |
192 |
|
|
193 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
194 |
|
%% local functions |
195 |
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
196 |
|
-spec has_peer_cert(options()) -> boolean(). |
197 |
|
has_peer_cert(#{connect := true}) -> |
198 |
17 |
true; % server always provides cert |
199 |
|
has_peer_cert(#{verify_mode := VerifyMode}) -> |
200 |
943 |
VerifyMode =/= none. % client provides cert only when requested |
201 |
|
|
202 |
|
%% 18 is OpenSSL's and fast_tls's error code for self-signed certs |
203 |
|
maybe_allow_selfsigned(18, Cert, #{verify_mode := selfsigned_peer}) -> |
204 |
:-( |
{ok, Cert}; |
205 |
|
maybe_allow_selfsigned(Error, Cert, _SSLOpts) -> |
206 |
18 |
cert_verification_error(Error, Cert). |
207 |
|
|
208 |
|
cert_verification_error(Error, Cert) -> |
209 |
18 |
{bad_cert, fast_tls:get_cert_verify_string(Error, Cert)}. |