Merge pull request #174 from weiss/fix-s2s-in-auth
Fix certificate authentication for incoming s2s connections
This commit is contained in:
commit
e9d104ec47
|
@ -224,34 +224,55 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||||
not StateData#state.authenticated ->
|
not StateData#state.authenticated ->
|
||||||
send_text(StateData,
|
send_text(StateData,
|
||||||
?STREAM_HEADER(<<" version='1.0'">>)),
|
?STREAM_HEADER(<<" version='1.0'">>)),
|
||||||
SASL = if StateData#state.tls_enabled ->
|
Auth = if StateData#state.tls_enabled ->
|
||||||
case
|
case jlib:nameprep(xml:get_attr_s(<<"from">>, Attrs)) of
|
||||||
(StateData#state.sockmod):get_peer_certificate(StateData#state.socket)
|
From when From /= <<"">>, From /= error ->
|
||||||
of
|
|
||||||
{ok, Cert} ->
|
|
||||||
case
|
case
|
||||||
(StateData#state.sockmod):get_verify_result(StateData#state.socket)
|
(StateData#state.sockmod):get_peer_certificate(StateData#state.socket)
|
||||||
of
|
of
|
||||||
0 ->
|
{ok, Cert} ->
|
||||||
[#xmlel{name = <<"mechanisms">>,
|
case
|
||||||
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
(StateData#state.sockmod):get_verify_result(StateData#state.socket)
|
||||||
children =
|
of
|
||||||
[#xmlel{name = <<"mechanism">>,
|
0 ->
|
||||||
attrs = [],
|
case
|
||||||
children =
|
idna:domain_utf8_to_ascii(From)
|
||||||
[{xmlcdata,
|
of
|
||||||
<<"EXTERNAL">>}]}]}];
|
false ->
|
||||||
CertVerifyRes ->
|
{error, From,
|
||||||
case StateData#state.tls_certverify of
|
<<"Cannot decode 'from' attribute">>};
|
||||||
true ->
|
PCAuthDomain ->
|
||||||
{error_cert_verif, CertVerifyRes,
|
case
|
||||||
Cert};
|
lists:any(fun (D) ->
|
||||||
false -> []
|
match_domain(PCAuthDomain,
|
||||||
end
|
D)
|
||||||
|
end,
|
||||||
|
get_cert_domains(Cert))
|
||||||
|
of
|
||||||
|
true ->
|
||||||
|
{ok, From,
|
||||||
|
<<"Success">>};
|
||||||
|
false ->
|
||||||
|
{error, From,
|
||||||
|
<<"Certificate host name mismatch">>}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
CertVerifyRes ->
|
||||||
|
{error, From,
|
||||||
|
p1_tls:get_cert_verify_string(CertVerifyRes,
|
||||||
|
Cert)}
|
||||||
|
end;
|
||||||
|
error ->
|
||||||
|
{error, From,
|
||||||
|
<<"Cannot get peer certificate">>}
|
||||||
end;
|
end;
|
||||||
error -> []
|
_ ->
|
||||||
|
{error, <<"(unknown)">>,
|
||||||
|
<<"Got no valid 'from' attribute">>}
|
||||||
end;
|
end;
|
||||||
true -> []
|
true ->
|
||||||
|
{no_verify, <<"(unknown)">>,
|
||||||
|
<<"TLS not (yet) enabled">>}
|
||||||
end,
|
end,
|
||||||
StartTLS = if StateData#state.tls_enabled -> [];
|
StartTLS = if StateData#state.tls_enabled -> [];
|
||||||
not StateData#state.tls_enabled and
|
not StateData#state.tls_enabled and
|
||||||
|
@ -267,11 +288,9 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||||
[#xmlel{name = <<"required">>,
|
[#xmlel{name = <<"required">>,
|
||||||
attrs = [], children = []}]}]
|
attrs = [], children = []}]}]
|
||||||
end,
|
end,
|
||||||
case SASL of
|
case Auth of
|
||||||
{error_cert_verif, CertVerifyResult, Certificate} ->
|
{error, RemoteServer, CertError}
|
||||||
CertError = p1_tls:get_cert_verify_string(CertVerifyResult,
|
when StateData#state.tls_certverify ->
|
||||||
Certificate),
|
|
||||||
RemoteServer = xml:get_attr_s(<<"from">>, Attrs),
|
|
||||||
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
|
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)",
|
||||||
[StateData#state.server, RemoteServer, CertError]),
|
[StateData#state.server, RemoteServer, CertError]),
|
||||||
send_text(StateData,
|
send_text(StateData,
|
||||||
|
@ -285,8 +304,26 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||||
<<"">>)),
|
<<"">>)),
|
||||||
ejabberd_s2s_out:stop_connection(Pid),
|
ejabberd_s2s_out:stop_connection(Pid),
|
||||||
{stop, normal, StateData};
|
{stop, normal, StateData};
|
||||||
_ ->
|
{VerifyResult, RemoteServer, Msg} ->
|
||||||
send_element(StateData,
|
{SASL, NewStateData} = case VerifyResult of
|
||||||
|
ok ->
|
||||||
|
{[#xmlel{name = <<"mechanisms">>,
|
||||||
|
attrs = [{<<"xmlns">>, ?NS_SASL}],
|
||||||
|
children =
|
||||||
|
[#xmlel{name = <<"mechanism">>,
|
||||||
|
attrs = [],
|
||||||
|
children =
|
||||||
|
[{xmlcdata,
|
||||||
|
<<"EXTERNAL">>}]}]}],
|
||||||
|
StateData#state{auth_domain = RemoteServer}};
|
||||||
|
error ->
|
||||||
|
?DEBUG("Won't accept certificate of ~s: ~s",
|
||||||
|
[RemoteServer, Msg]),
|
||||||
|
{[], StateData};
|
||||||
|
no_verify ->
|
||||||
|
{[], StateData}
|
||||||
|
end,
|
||||||
|
send_element(NewStateData,
|
||||||
#xmlel{name = <<"stream:features">>, attrs = [],
|
#xmlel{name = <<"stream:features">>, attrs = [],
|
||||||
children =
|
children =
|
||||||
SASL ++
|
SASL ++
|
||||||
|
@ -295,7 +332,7 @@ wait_for_stream({xmlstreamstart, _Name, Attrs},
|
||||||
Server, [],
|
Server, [],
|
||||||
[Server])}),
|
[Server])}),
|
||||||
{next_state, wait_for_feature_request,
|
{next_state, wait_for_feature_request,
|
||||||
StateData#state{server = Server}}
|
NewStateData#state{server = Server}}
|
||||||
end;
|
end;
|
||||||
{<<"jabber:server">>, _, Server, true}
|
{<<"jabber:server">>, _, Server, true}
|
||||||
when StateData#state.authenticated ->
|
when StateData#state.authenticated ->
|
||||||
|
@ -330,7 +367,7 @@ wait_for_stream(closed, StateData) ->
|
||||||
|
|
||||||
wait_for_feature_request({xmlstreamelement, El},
|
wait_for_feature_request({xmlstreamelement, El},
|
||||||
StateData) ->
|
StateData) ->
|
||||||
#xmlel{name = Name, attrs = Attrs, children = Els} = El,
|
#xmlel{name = Name, attrs = Attrs} = El,
|
||||||
TLS = StateData#state.tls,
|
TLS = StateData#state.tls,
|
||||||
TLSEnabled = StateData#state.tls_enabled,
|
TLSEnabled = StateData#state.tls_enabled,
|
||||||
SockMod =
|
SockMod =
|
||||||
|
@ -376,39 +413,11 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||||
{?NS_SASL, <<"auth">>} when TLSEnabled ->
|
{?NS_SASL, <<"auth">>} when TLSEnabled ->
|
||||||
Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
|
Mech = xml:get_attr_s(<<"mechanism">>, Attrs),
|
||||||
case Mech of
|
case Mech of
|
||||||
<<"EXTERNAL">> ->
|
<<"EXTERNAL">> when StateData#state.auth_domain /= <<"">> ->
|
||||||
Auth = jlib:decode_base64(xml:get_cdata(Els)),
|
AuthDomain = StateData#state.auth_domain,
|
||||||
AuthDomain = jlib:nameprep(Auth),
|
|
||||||
AuthRes = case
|
|
||||||
(StateData#state.sockmod):get_peer_certificate(StateData#state.socket)
|
|
||||||
of
|
|
||||||
{ok, Cert} ->
|
|
||||||
case
|
|
||||||
(StateData#state.sockmod):get_verify_result(StateData#state.socket)
|
|
||||||
of
|
|
||||||
0 ->
|
|
||||||
case AuthDomain of
|
|
||||||
error -> false;
|
|
||||||
_ ->
|
|
||||||
case
|
|
||||||
idna:domain_utf8_to_ascii(AuthDomain)
|
|
||||||
of
|
|
||||||
false -> false;
|
|
||||||
PCAuthDomain ->
|
|
||||||
lists:any(fun (D) ->
|
|
||||||
match_domain(PCAuthDomain,
|
|
||||||
D)
|
|
||||||
end,
|
|
||||||
get_cert_domains(Cert))
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
_ -> false
|
|
||||||
end;
|
|
||||||
error -> false
|
|
||||||
end,
|
|
||||||
AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>,
|
AllowRemoteHost = ejabberd_s2s:allow_host(<<"">>,
|
||||||
AuthDomain),
|
AuthDomain),
|
||||||
if AuthRes andalso AllowRemoteHost ->
|
if AllowRemoteHost ->
|
||||||
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
(StateData#state.sockmod):reset_stream(StateData#state.socket),
|
||||||
send_element(StateData,
|
send_element(StateData,
|
||||||
#xmlel{name = <<"success">>,
|
#xmlel{name = <<"success">>,
|
||||||
|
@ -420,8 +429,7 @@ wait_for_feature_request({xmlstreamelement, El},
|
||||||
jlib:make_jid(<<"">>, AuthDomain, <<"">>)),
|
jlib:make_jid(<<"">>, AuthDomain, <<"">>)),
|
||||||
{next_state, wait_for_stream,
|
{next_state, wait_for_stream,
|
||||||
StateData#state{streamid = new_id(),
|
StateData#state{streamid = new_id(),
|
||||||
authenticated = true,
|
authenticated = true}};
|
||||||
auth_domain = AuthDomain}};
|
|
||||||
true ->
|
true ->
|
||||||
send_element(StateData,
|
send_element(StateData,
|
||||||
#xmlel{name = <<"failure">>,
|
#xmlel{name = <<"failure">>,
|
||||||
|
@ -749,7 +757,7 @@ get_cert_domains(Cert) ->
|
||||||
case 'OTP-PUB-KEY':decode('X520CommonName', Val) of
|
case 'OTP-PUB-KEY':decode('X520CommonName', Val) of
|
||||||
{ok, {_, D1}} ->
|
{ok, {_, D1}} ->
|
||||||
D = if is_binary(D1) -> D1;
|
D = if is_binary(D1) -> D1;
|
||||||
is_binary(D1) -> (D1);
|
is_list(D1) -> list_to_binary(D1);
|
||||||
true -> error
|
true -> error
|
||||||
end,
|
end,
|
||||||
if D /= error ->
|
if D /= error ->
|
||||||
|
@ -770,8 +778,7 @@ get_cert_domains(Cert) ->
|
||||||
lists:flatmap(fun (#'Extension'{extnID =
|
lists:flatmap(fun (#'Extension'{extnID =
|
||||||
?'id-ce-subjectAltName',
|
?'id-ce-subjectAltName',
|
||||||
extnValue = Val}) ->
|
extnValue = Val}) ->
|
||||||
BVal = if is_binary(Val) -> iolist_to_binary(Val);
|
BVal = if is_list(Val) -> list_to_binary(Val);
|
||||||
is_binary(Val) -> Val;
|
|
||||||
true -> Val
|
true -> Val
|
||||||
end,
|
end,
|
||||||
case 'OTP-PUB-KEY':decode('SubjectAltName', BVal)
|
case 'OTP-PUB-KEY':decode('SubjectAltName', BVal)
|
||||||
|
@ -811,9 +818,9 @@ get_cert_domains(Cert) ->
|
||||||
_ -> []
|
_ -> []
|
||||||
end;
|
end;
|
||||||
({dNSName, D})
|
({dNSName, D})
|
||||||
when is_binary(D) ->
|
when is_list(D) ->
|
||||||
case
|
case
|
||||||
jlib:string_to_jid(D)
|
jlib:string_to_jid(list_to_binary(D))
|
||||||
of
|
of
|
||||||
#jid{luser = <<"">>,
|
#jid{luser = <<"">>,
|
||||||
lserver = LD,
|
lserver = LD,
|
||||||
|
|
Loading…
Reference in New Issue