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