mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
Option to reject S2S connection if untrusted certificate (EJAB-464)
This commit is contained in:
parent
44b2002504
commit
b9bbe19d4c
@ -962,9 +962,11 @@ This is a detailed description of each option allowed by the listening modules:
|
||||
|
||||
There are some additional global options that can be specified in the ejabberd configuration file (outside \term{listen}):
|
||||
\begin{description}
|
||||
\titem{\{s2s\_use\_starttls, false|optional|required\}}
|
||||
\titem{\{s2s\_use\_starttls, false|optional|required|required\_trusted\}}
|
||||
\ind{options!s2s\_use\_starttls}\ind{STARTTLS}This option defines if
|
||||
s2s connections can optionally use STARTTLS encryption, or if it must be required.
|
||||
s2s connections don't use STARTTLS encryption; if STARTTLS can be used optionally;
|
||||
if STARTTLS is required to establish the connection;
|
||||
or if STARTTLS is required and the remote certificate must be valid and trusted.
|
||||
The default value is to not use STARTTLS: \term{false}.
|
||||
\titem{\{s2s\_certfile, Path\}} \ind{options!s2s\_certificate}Full path to a
|
||||
file containing a SSL certificate.
|
||||
@ -1070,7 +1072,7 @@ In this example, the following configuration defines that:
|
||||
on port 5223 (SSL, IP 192.168.0.1 and fdca:8ab6:a243:75ef::1) and denied
|
||||
for the user called `\term{bad}'.
|
||||
\item s2s connections are listened for on port 5269 (all IPv4 addresses)
|
||||
with STARTTLS for secured traffic required.
|
||||
with STARTTLS for secured traffic strictly required, and the certificates are verified.
|
||||
Incoming and outgoing connections of remote XMPP servers are denied,
|
||||
only two servers can connect: "jabber.example.org" and "example.com".
|
||||
\item Port 5280 is serving the Web Admin and the HTTP Polling service
|
||||
@ -1151,7 +1153,7 @@ In this example, the following configuration defines that:
|
||||
{service_check_from, false}]}
|
||||
]
|
||||
}.
|
||||
{s2s_use_starttls, required}.
|
||||
{s2s_use_starttls, required_trusted}.
|
||||
{s2s_certfile, "/path/to/ssl.pem"}.
|
||||
{s2s_default_policy, deny}.
|
||||
{{s2s_host,"jabber.example.org"}, allow}.
|
||||
|
@ -170,7 +170,7 @@
|
||||
|
||||
%%
|
||||
%% s2s_use_starttls: Enable STARTTLS + Dialback for S2S connections.
|
||||
%% Allowed values are: false optional required
|
||||
%% Allowed values are: false optional required required_trusted
|
||||
%% You must specify a certificate file.
|
||||
%%
|
||||
%%{s2s_use_starttls, optional}.
|
||||
|
@ -75,6 +75,7 @@
|
||||
tls = false,
|
||||
tls_enabled = false,
|
||||
tls_required = false,
|
||||
tls_certverify = false,
|
||||
tls_options = [],
|
||||
server,
|
||||
authenticated = false,
|
||||
@ -152,13 +153,15 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
{value, {_, S}} -> S;
|
||||
_ -> none
|
||||
end,
|
||||
{StartTLS, TLSRequired} = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
{StartTLS, TLSRequired, TLSCertverify} = case ejabberd_config:get_local_option(s2s_use_starttls) of
|
||||
UseTls when (UseTls==undefined) or (UseTls==false) ->
|
||||
{false, false};
|
||||
{false, false, false};
|
||||
UseTls when (UseTls==true) or (UseTls==optional) ->
|
||||
{true, false};
|
||||
{true, false, false};
|
||||
required ->
|
||||
{true, true}
|
||||
{true, true, false};
|
||||
required_trusted ->
|
||||
{true, true, true}
|
||||
end,
|
||||
TLSOpts = case ejabberd_config:get_local_option(s2s_certfile) of
|
||||
undefined ->
|
||||
@ -175,6 +178,7 @@ init([{SockMod, Socket}, Opts]) ->
|
||||
tls = StartTLS,
|
||||
tls_enabled = false,
|
||||
tls_required = TLSRequired,
|
||||
tls_certverify = TLSCertverify,
|
||||
tls_options = TLSOpts,
|
||||
timer = Timer}}.
|
||||
|
||||
@ -198,16 +202,18 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
StateData#state.tls_enabled ->
|
||||
case (StateData#state.sockmod):get_peer_certificate(
|
||||
StateData#state.socket) of
|
||||
{ok, _Cert} ->
|
||||
case (StateData#state.sockmod):get_verify_result(
|
||||
StateData#state.socket) of
|
||||
{ok, Cert} ->
|
||||
case (StateData#state.sockmod):get_verify_result(StateData#state.socket) of
|
||||
0 ->
|
||||
[{xmlelement, "mechanisms",
|
||||
[{"xmlns", ?NS_SASL}],
|
||||
[{xmlelement, "mechanism", [],
|
||||
[{xmlcdata, "EXTERNAL"}]}]}];
|
||||
_ ->
|
||||
[]
|
||||
CertVerifyRes ->
|
||||
case StateData#state.tls_certverify of
|
||||
true -> {error_cert_verif, CertVerifyRes, Cert};
|
||||
false -> []
|
||||
end
|
||||
end;
|
||||
error ->
|
||||
[]
|
||||
@ -225,14 +231,26 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
|
||||
[{xmlelement, "required", [], []}]
|
||||
}]
|
||||
end,
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
SASL ++ StartTLS ++
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
{next_state, wait_for_feature_request, StateData#state{server = Server}};
|
||||
case SASL of
|
||||
{error_cert_verif, CertVerifyResult, Certificate} ->
|
||||
CertError = tls:get_cert_verify_string(CertVerifyResult, Certificate),
|
||||
RemoteServer = xml:get_attr_s("from", Attrs),
|
||||
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", [StateData#state.server, RemoteServer, CertError]),
|
||||
send_text(StateData, xml:element_to_string(?SERRT_POLICY_VIOLATION("en", CertError))),
|
||||
{atomic, Pid} = ejabberd_s2s:find_connection(jlib:make_jid("", Server, ""), jlib:make_jid("", RemoteServer, "")),
|
||||
ejabberd_s2s_out:stop_connection(Pid),
|
||||
|
||||
{stop, normal, StateData};
|
||||
_ ->
|
||||
send_element(StateData,
|
||||
{xmlelement, "stream:features", [],
|
||||
SASL ++ StartTLS ++
|
||||
ejabberd_hooks:run_fold(
|
||||
s2s_stream_features,
|
||||
Server,
|
||||
[], [Server])}),
|
||||
{next_state, wait_for_feature_request, StateData#state{server = Server}}
|
||||
end;
|
||||
{"jabber:server", _, Server, true} when
|
||||
StateData#state.authenticated ->
|
||||
send_text(StateData, ?STREAM_HEADER(" version='1.0'")),
|
||||
|
@ -160,7 +160,7 @@ init([From, Server, Type]) ->
|
||||
{false, false};
|
||||
UseTls when (UseTls==true) or (UseTls==optional) ->
|
||||
{true, false};
|
||||
required ->
|
||||
UseTls when (UseTls==required) or (UseTls==required_trusted) ->
|
||||
{true, true}
|
||||
end,
|
||||
UseV10 = TLS,
|
||||
|
@ -39,6 +39,7 @@
|
||||
close/1,
|
||||
get_peer_certificate/1,
|
||||
get_verify_result/1,
|
||||
get_cert_verify_string/2,
|
||||
test/0]).
|
||||
|
||||
%% Internal exports, call-back functions.
|
||||
@ -63,8 +64,10 @@
|
||||
|
||||
-ifdef(SSL40).
|
||||
-define(CERT_DECODE, {public_key, pkix_decode_cert, plain}).
|
||||
-define(CERT_SELFSIGNED, {public_key, pkix_is_self_signed}).
|
||||
-else.
|
||||
-define(CERT_DECODE, {ssl_pkix, decode_cert, [pkix]}).
|
||||
-define(CERT_SELFSIGNED, {erlang, is_atom}). %% Dummy function for old OTPs
|
||||
-endif.
|
||||
|
||||
|
||||
@ -243,7 +246,9 @@ get_peer_certificate(#tlssock{tlsport = Port}) ->
|
||||
<<0, BCert/binary>> ->
|
||||
{CertMod, CertFun, CertSecondArg} = ?CERT_DECODE,
|
||||
case catch apply(CertMod, CertFun, [BCert, CertSecondArg]) of
|
||||
{ok, Cert} ->
|
||||
{ok, Cert} -> %% returned by R13 and older
|
||||
{ok, Cert};
|
||||
{'Certificate', _, _, _} = Cert ->
|
||||
{ok, Cert};
|
||||
_ ->
|
||||
error
|
||||
@ -311,3 +316,47 @@ loop(Port, Socket) ->
|
||||
end.
|
||||
|
||||
|
||||
get_cert_verify_string(CertVerifyRes, Cert) ->
|
||||
BCert = public_key:pkix_encode('Certificate', Cert, plain),
|
||||
{CertMod, CertFun} = ?CERT_SELFSIGNED,
|
||||
IsSelfsigned = apply(CertMod, CertFun, [BCert]),
|
||||
case {CertVerifyRes, IsSelfsigned} of
|
||||
{21, true} -> "self-signed certificate";
|
||||
_ -> cert_verify_code(CertVerifyRes)
|
||||
end.
|
||||
|
||||
%% http://www.openssl.org/docs/apps/verify.html
|
||||
cert_verify_code(0) -> "ok";
|
||||
cert_verify_code(2) -> "unable to get issuer certificate";
|
||||
cert_verify_code(3) -> "unable to get certificate CRL";
|
||||
cert_verify_code(4) -> "unable to decrypt certificate's signature";
|
||||
cert_verify_code(5) -> "unable to decrypt CRL's signature";
|
||||
cert_verify_code(6) -> "unable to decode issuer public key";
|
||||
cert_verify_code(7) -> "certificate signature failure";
|
||||
cert_verify_code(8) -> "CRL signature failure";
|
||||
cert_verify_code(9) -> "certificate is not yet valid";
|
||||
cert_verify_code(10) -> "certificate has expired";
|
||||
cert_verify_code(11) -> "CRL is not yet valid";
|
||||
cert_verify_code(12) -> "CRL has expired";
|
||||
cert_verify_code(13) -> "format error in certificate's notBefore field";
|
||||
cert_verify_code(14) -> "format error in certificate's notAfter field";
|
||||
cert_verify_code(15) -> "format error in CRL's lastUpdate field";
|
||||
cert_verify_code(16) -> "format error in CRL's nextUpdate field";
|
||||
cert_verify_code(17) -> "out of memory";
|
||||
cert_verify_code(18) -> "self signed certificate";
|
||||
cert_verify_code(19) -> "self signed certificate in certificate chain";
|
||||
cert_verify_code(20) -> "unable to get local issuer certificate";
|
||||
cert_verify_code(21) -> "unable to verify the first certificate";
|
||||
cert_verify_code(22) -> "certificate chain too long";
|
||||
cert_verify_code(23) -> "certificate revoked";
|
||||
cert_verify_code(24) -> "invalid CA certificate";
|
||||
cert_verify_code(25) -> "path length constraint exceeded";
|
||||
cert_verify_code(26) -> "unsupported certificate purpose";
|
||||
cert_verify_code(27) -> "certificate not trusted";
|
||||
cert_verify_code(28) -> "certificate rejected";
|
||||
cert_verify_code(29) -> "subject issuer mismatch";
|
||||
cert_verify_code(30) -> "authority and subject key identifier mismatch";
|
||||
cert_verify_code(31) -> "authority and issuer serial number mismatch";
|
||||
cert_verify_code(32) -> "key usage does not include certificate signing";
|
||||
cert_verify_code(50) -> "application verification failure";
|
||||
cert_verify_code(X) -> "Unknown OpenSSL error code: " ++ integer_to_list(X).
|
||||
|
@ -349,13 +349,16 @@ static int tls_drv_control(ErlDrvData handle,
|
||||
#ifdef SSL_MODE_RELEASE_BUFFERS
|
||||
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
#endif
|
||||
/* SSL_CTX_load_verify_locations(ctx, "/etc/ejabberd/ca_certificates.pem", NULL); */
|
||||
/* SSL_CTX_load_verify_locations(ctx, NULL, "/etc/ejabberd/ca_certs/"); */
|
||||
|
||||
if (command == SET_CERTIFICATE_FILE_ACCEPT)
|
||||
{
|
||||
/* This IF is commented to allow verification in all cases: */
|
||||
/* if (command == SET_CERTIFICATE_FILE_ACCEPT) */
|
||||
/* { */
|
||||
SSL_CTX_set_verify(ctx,
|
||||
SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE,
|
||||
verify_callback);
|
||||
}
|
||||
/* } */
|
||||
|
||||
ssl_ctx = ctx;
|
||||
hash_table_insert(buf, mtime, ssl_ctx);
|
||||
|
Loading…
Reference in New Issue
Block a user