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 |
2095 |
lists:flatmap(fun({K, V}) -> fast_tls_opt(K, V) end, maps:to_list(Opts)); |
127 |
|
prepare_options(_Module, Opts) -> |
128 |
:-( |
Opts. |
129 |
|
|
130 |
754 |
fast_tls_opt(connect, true) -> [connect]; |
131 |
22 |
fast_tls_opt(connect, false) -> []; |
132 |
561 |
fast_tls_opt(mode, _) -> []; |
133 |
2063 |
fast_tls_opt(verify_mode, peer) -> []; |
134 |
32 |
fast_tls_opt(verify_mode, none) -> [verify_none]; |
135 |
2001 |
fast_tls_opt(cacertfile, File) -> [{cafile, File}]; |
136 |
398 |
fast_tls_opt(dhfile, File) -> [{dhfile, File}]; |
137 |
2095 |
fast_tls_opt(certfile, File) -> [{certfile, File}]; |
138 |
2095 |
fast_tls_opt(ciphers, Ciphers) -> [{ciphers, Ciphers}]; |
139 |
2078 |
fast_tls_opt(protocol_options, ProtoOpts) -> [{protocol_options, string:join(ProtoOpts, "|")}]. |
140 |
|
|
141 |
|
default_ciphers() -> |
142 |
250 |
"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 |
256 |
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 |
256 |
peername(#mongoose_tls_socket{tls_module = M, tls_socket = S}) -> M:peername(S). |
159 |
|
|
160 |
|
-spec setopts(socket(), Opts::list()) -> ok | {error, any()}. |
161 |
256 |
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)}. |