./ct_report/coverage/cyrsasl_external.COVER.html

1 %%%=============================================================================
2 %%% @copyright (C) 1999-2020, Erlang Solutions Ltd
3 %%% @doc SASL EXTERNAL implementation (XEP178)
4 %%%
5 %%% SASL EXTERNAL mechanism requires client's SSL certificate. the purpose of
6 %%% this module is to parse the certificate and get authorization identity (if
7 %%% any provided by the client). this module doesn't make authorization, it
8 %%% only prepares all the data and provides it to auth. backend.
9 %%%
10 %%% the next extra fields are added to the mongoose_credentials record by this
11 %%% module:
12 %%% * pem_cert - certificate in PEM format
13 %%% * der_cert - certificate in DER format
14 %%% * common_name - CN field (bitstring) of the client's cert (if available)
15 %%% * xmpp_addresses - list of provided "xmppAddr" fields (bare jids) provided in
16 %%% the client's certificate (empty list if not available)
17 %%% * auth_id - authorization identity (bare jid, if provided by the client)
18 %%%
19 %%% @end
20 %%%=============================================================================
21 -module(cyrsasl_external).
22
23 -xep([{xep, 178}, {version, "1.2"}, {status, partial}]).
24
25 -include("mongoose.hrl").
26 -include("jlib.hrl").
27
28 -export([mechanism/0, mech_new/3, mech_step/2]).
29
30 -ignore_xref([mech_new/3, behaviour_info/1]).
31
32 -behaviour(cyrsasl).
33
34 -callback verify_creds(Creds :: mongoose_credentials:t()) ->
35 {ok, Username :: binary()} | {error, Error :: binary()}.
36
37 -record(state, {creds :: mongoose_credentials:t()}).
38 -type sasl_external_state() :: #state{}.
39
40 -spec mechanism() -> cyrsasl:mechanism().
41 mechanism() ->
42
:-(
<<"EXTERNAL">>.
43
44 -spec mech_new(Host :: jid:server(),
45 Creds :: mongoose_credentials:t(),
46 Socket :: term()) -> {ok, sasl_external_state()}.
47 mech_new(_Host, Creds, #{socket := Socket, listener_opts := LOpts}) ->
48
:-(
Cert = case mongoose_c2s_socket:get_peer_certificate(Socket, LOpts) of
49
:-(
{ok, C} -> C;
50
:-(
_ -> no_cert
51 end,
52
:-(
maybe_extract_certs(Cert, Creds).
53
54 maybe_extract_certs(no_cert, Creds) ->
55
:-(
{ok, #state{creds = mongoose_credentials:extend(Creds, [{cert_file, false}])}};
56 maybe_extract_certs(Cert, Creds) ->
57
:-(
DerCert = public_key:pkix_encode('Certificate', Cert, plain),
58
:-(
PemCert = public_key:pem_encode([{'Certificate', DerCert, not_encrypted}]),
59
:-(
CertFields = get_common_name(Cert) ++ get_xmpp_addresses(Cert),
60
:-(
SaslExternalCredentials = [{cert_file, true}, {pem_cert, PemCert}, {der_cert, DerCert} | CertFields],
61
:-(
{ok, #state{creds = mongoose_credentials:extend(Creds, SaslExternalCredentials)}}.
62
63 -spec mech_step(State :: sasl_external_state(),
64 ClientIn :: binary()) -> {ok, mongoose_credentials:t()} | {error, binary()}.
65 mech_step(#state{creds = Creds}, User) ->
66
:-(
case mongoose_credentials:get(Creds, cert_file) of
67 false ->
68
:-(
{error, <<"not-authorized">>};
69 true ->
70
:-(
NewCreds = maybe_add_auth_id(Creds, User),
71
:-(
do_mech_step(NewCreds)
72 end.
73
74 %%%=============================================================================
75 %%% local functions
76 %%%=============================================================================
77 get_common_name(Cert) ->
78
:-(
case cert_utils:get_common_name(Cert) of
79
:-(
error -> [];
80
:-(
CN -> [{common_name, CN}]
81 end.
82
83 get_xmpp_addresses(Cert) ->
84
:-(
XmmpAddresses = cert_utils:get_xmpp_addresses(Cert),
85
:-(
[{xmpp_addresses, XmmpAddresses}].
86
87 maybe_add_auth_id(Creds, <<"">>) ->
88
:-(
Creds;
89 maybe_add_auth_id(Creds, User) ->
90
:-(
mongoose_credentials:set(Creds, auth_id, User).
91
92 do_mech_step(Creds) ->
93
:-(
VerificationList = get_verification_list(Creds),
94
:-(
case verification_loop(VerificationList, Creds) of
95 {error, Error} ->
96
:-(
{error, Error};
97 {ok, Name} ->
98
:-(
NewCreds = mongoose_credentials:extend(Creds, [{username, Name}]),
99
:-(
authorize(NewCreds)
100 end.
101
102 authorize(Creds) ->
103
:-(
case ejabberd_auth:authorize(Creds) of
104
:-(
{ok, NewCreds} -> {ok, NewCreds};
105
:-(
{error, not_authorized} -> {error, <<"not-authorized">>}
106 end.
107
108 get_verification_list(Creds) ->
109
:-(
HostType = mongoose_credentials:host_type(Creds),
110
:-(
mongoose_config:get_opt([{auth, HostType}, sasl_external]).
111
112 verification_loop([VerificationFn | T], Creds) ->
113
:-(
case verify_creds(VerificationFn, Creds) of
114 {error, Error} when T =:= [] ->
115
:-(
{error, Error};
116 {error, _} ->
117
:-(
verification_loop(T, Creds);
118 {ok, Name} ->
119
:-(
{ok, Name}
120 end.
121
122 verify_creds(standard, Creds) ->
123
:-(
standard_verification(Creds);
124 verify_creds(common_name, Creds) ->
125
:-(
common_name_verification(Creds);
126 verify_creds(auth_id, Creds) ->
127
:-(
auth_id_verification(Creds);
128 verify_creds({mod, Mod}, Creds) ->
129
:-(
custom_verification(Mod, Creds).
130
131
132 standard_verification(Creds) ->
133
:-(
Server = mongoose_credentials:lserver(Creds),
134
:-(
XmppAddrs = get_credentials(Creds, xmpp_addresses),
135
:-(
AuthId = get_credentials(Creds, auth_id),
136
:-(
XmppAddr = case {XmppAddrs, AuthId} of
137 {[OneXmppAddr], undefined} ->
138
:-(
OneXmppAddr;
139 {[_], _} ->
140
:-(
{error, <<"invalid-authzid">>};
141 {[_, _ | _], undefined} ->
142
:-(
{error, <<"invalid-authzid">>};
143 _ ->
144
:-(
case lists:member(AuthId, XmppAddrs) of
145 true ->
146
:-(
AuthId;
147 _ ->
148
:-(
{error, <<"not-authorized">>}
149 end
150 end,
151
:-(
verify_server(XmppAddr, Server).
152
153 common_name_verification(Creds) ->
154
:-(
Server = mongoose_credentials:lserver(Creds),
155
:-(
AuthId = get_credentials(Creds, auth_id),
156
:-(
CommonName = get_credentials(Creds, common_name),
157
:-(
case AuthId of
158 undefined when is_binary(CommonName) ->
159
:-(
{ok, CommonName};
160 _ ->
161
:-(
case verify_server(AuthId, Server) of
162
:-(
{ok, CommonName} -> {ok, CommonName};
163
:-(
_ -> {error, <<"invalid-authzid">>}
164 end
165 end.
166
167 auth_id_verification(Creds) ->
168
:-(
Server = mongoose_credentials:lserver(Creds),
169
:-(
AuthId = get_credentials(Creds, auth_id),
170
:-(
verify_server(AuthId, Server).
171
172 custom_verification(Module, Creds) ->
173
:-(
try
174
:-(
case apply(Module, verify_creds, [Creds]) of
175 {ok, Username} when is_binary(Username) ->
176
:-(
{ok, Username};
177 {error, Error} when is_binary(Error) ->
178
:-(
{error, Error};
179 InvalidReturnValue ->
180
:-(
?LOG_ERROR(#{what => sasl_external_failed, sasl_module => Module,
181
:-(
reason => invalid_return_value, return_value => InvalidReturnValue}),
182
:-(
{error, <<"not-authorized">>}
183 end
184 catch
185 Class:Exception ->
186
:-(
?LOG_ERROR(#{what => sasl_external_failed, sasl_module => Module,
187
:-(
class => Class, reason => Exception}),
188
:-(
{error, <<"not-authorized">>}
189 end.
190
191 get_credentials(Cred, Key) ->
192
:-(
mongoose_credentials:get(Cred, Key, undefined).
193
194 verify_server(undefined, _Server) ->
195
:-(
{error, <<"not-authorized">>};
196 verify_server({error, Error}, _Server) ->
197
:-(
{error, Error};
198 verify_server(Jid, Server) ->
199
:-(
JidRecord = jid:binary_to_bare(Jid),
200
:-(
case JidRecord#jid.lserver of
201 Server ->
202
:-(
{ok, JidRecord#jid.luser};
203 _ ->
204
:-(
{error, <<"not-authorized">>}
205 end.
Line Hits Source