./ct_report/coverage/mongoose_tls.COVER.html

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