./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.1"}, {comment, "partially implemented."}]).
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 258 <<"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) ->
48 66 Cert = mongoose_credentials:get(Creds, client_cert, no_cert),
49 66 maybe_extract_certs(Cert, Creds).
50
51 maybe_extract_certs(no_cert, Creds) ->
52 5 {ok, #state{creds = mongoose_credentials:extend(Creds, [{cert_file, false}])}};
53 maybe_extract_certs(Cert, Creds) ->
54 61 DerCert = public_key:pkix_encode('Certificate', Cert, plain),
55 61 PemCert = public_key:pem_encode([{'Certificate', DerCert, not_encrypted}]),
56 61 CertFields = get_common_name(Cert) ++ get_xmpp_addresses(Cert),
57 61 SaslExternalCredentials = [{cert_file, true}, {pem_cert, PemCert}, {der_cert, DerCert} | CertFields],
58 61 {ok, #state{creds = mongoose_credentials:extend(Creds, SaslExternalCredentials)}}.
59
60
61 -spec mech_step(State :: sasl_external_state(),
62 ClientIn :: binary()) -> {ok, mongoose_credentials:t()} | {error, binary()}.
63 mech_step(#state{creds = Creds}, User) ->
64 66 case mongoose_credentials:get(Creds, cert_file) of
65 false ->
66 5 {error, <<"not-authorized">>};
67 true ->
68 61 NewCreds = maybe_add_auth_id(Creds, User),
69 61 do_mech_step(NewCreds)
70 end.
71
72 %%%=============================================================================
73 %%% local functions
74 %%%=============================================================================
75 get_common_name(Cert) ->
76 61 case cert_utils:get_common_name(Cert) of
77
:-(
error -> [];
78 61 CN -> [{common_name, CN}]
79 end.
80
81 get_xmpp_addresses(Cert) ->
82 61 XmmpAddresses = cert_utils:get_xmpp_addresses(Cert),
83 61 [{xmpp_addresses, XmmpAddresses}].
84
85 maybe_add_auth_id(Creds, <<"">>) ->
86 34 Creds;
87 maybe_add_auth_id(Creds, User) ->
88 27 mongoose_credentials:set(Creds, auth_id, User).
89
90 do_mech_step(Creds) ->
91 61 VerificationList = get_verification_list(Creds),
92 61 case verification_loop(VerificationList, Creds) of
93 {error, Error} ->
94 27 {error, Error};
95 {ok, Name} ->
96 34 NewCreds = mongoose_credentials:extend(Creds, [{username, Name}]),
97 34 authorize(NewCreds)
98 end.
99
100 authorize(Creds) ->
101 34 case ejabberd_auth:authorize(Creds) of
102 34 {ok, NewCreds} -> {ok, NewCreds};
103
:-(
{error, not_authorized} -> {error, <<"not-authorized">>}
104 end.
105
106 get_verification_list(Creds) ->
107 61 HostType = mongoose_credentials:host_type(Creds),
108 61 mongoose_config:get_opt([{auth, HostType}, sasl_external]).
109
110 verification_loop([VerificationFn | T], Creds) ->
111 73 case verify_creds(VerificationFn, Creds) of
112 {error, Error} when T =:= [] ->
113 27 {error, Error};
114 {error, _} ->
115 12 verification_loop(T, Creds);
116 {ok, Name} ->
117 34 {ok, Name}
118 end.
119
120 verify_creds(standard, Creds) ->
121 43 standard_verification(Creds);
122 verify_creds(common_name, Creds) ->
123 9 common_name_verification(Creds);
124 verify_creds(auth_id, Creds) ->
125 3 auth_id_verification(Creds);
126 verify_creds({mod, Mod}, Creds) ->
127 18 custom_verification(Mod, Creds).
128
129
130 standard_verification(Creds) ->
131 43 Server = mongoose_credentials:lserver(Creds),
132 43 XmppAddrs = get_credentials(Creds, xmpp_addresses),
133 43 AuthId = get_credentials(Creds, auth_id),
134 43 XmppAddr = case {XmppAddrs, AuthId} of
135 {[OneXmppAddr], undefined} ->
136 10 OneXmppAddr;
137 {[_], _} ->
138 6 {error, <<"invalid-authzid">>};
139 {[_, _ | _], undefined} ->
140 3 {error, <<"invalid-authzid">>};
141 _ ->
142 24 case lists:member(AuthId, XmppAddrs) of
143 true ->
144 3 AuthId;
145 _ ->
146 21 {error, <<"not-authorized">>}
147 end
148 end,
149 43 verify_server(XmppAddr, Server).
150
151 common_name_verification(Creds) ->
152 9 Server = mongoose_credentials:lserver(Creds),
153 9 AuthId = get_credentials(Creds, auth_id),
154 9 CommonName = get_credentials(Creds, common_name),
155 9 case AuthId of
156 undefined when is_binary(CommonName) ->
157 3 {ok, CommonName};
158 _ ->
159 6 case verify_server(AuthId, Server) of
160 3 {ok, CommonName} -> {ok, CommonName};
161 3 _ -> {error, <<"invalid-authzid">>}
162 end
163 end.
164
165 auth_id_verification(Creds) ->
166 3 Server = mongoose_credentials:lserver(Creds),
167 3 AuthId = get_credentials(Creds, auth_id),
168 3 verify_server(AuthId, Server).
169
170 custom_verification(Module, Creds) ->
171 18 try
172 18 case apply(Module, verify_creds, [Creds]) of
173 {ok, Username} when is_binary(Username) ->
174 12 {ok, Username};
175 {error, Error} when is_binary(Error) ->
176 6 {error, Error};
177 InvalidReturnValue ->
178
:-(
?LOG_ERROR(#{what => sasl_external_failed, sasl_module => Module,
179
:-(
reason => invalid_return_value, return_value => InvalidReturnValue}),
180
:-(
{error, <<"not-authorized">>}
181 end
182 catch
183 Class:Exception ->
184
:-(
?LOG_ERROR(#{what => sasl_external_failed, sasl_module => Module,
185
:-(
class => Class, reason => Exception}),
186
:-(
{error, <<"not-authorized">>}
187 end.
188
189 get_credentials(Cred, Key) ->
190 107 mongoose_credentials:get(Cred, Key, undefined).
191
192 verify_server(undefined, _Server) ->
193
:-(
{error, <<"not-authorized">>};
194 verify_server({error, Error}, _Server) ->
195 30 {error, Error};
196 verify_server(Jid, Server) ->
197 22 JidRecord = jid:binary_to_bare(Jid),
198 22 case JidRecord#jid.lserver of
199 Server ->
200 22 {ok, JidRecord#jid.luser};
201 _ ->
202
:-(
{error, <<"not-authorized">>}
203 end.
Line Hits Source