Option to reject S2S connection if untrusted certificate (EJAB-464)

This commit is contained in:
Badlop 2011-01-11 01:49:32 +01:00
parent d523901ddc
commit 2d0d46e296
6 changed files with 93 additions and 25 deletions

View File

@ -945,9 +945,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.
@ -1059,7 +1061,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
@ -1140,7 +1142,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}.

View File

@ -180,7 +180,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}.

View File

@ -63,6 +63,7 @@
tls = false,
tls_enabled = false,
tls_required = false,
tls_certverify = false,
tls_options = [],
server,
authenticated = false,
@ -123,13 +124,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 ->
@ -146,6 +149,7 @@ init([{SockMod, Socket}, Opts]) ->
tls = StartTLS,
tls_enabled = false,
tls_required = TLSRequired,
tls_certverify = TLSCertverify,
tls_options = TLSOpts,
timer = Timer}}.
@ -172,14 +176,16 @@ wait_for_stream({xmlstreamstart, Opening}, 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 ->
[exmpp_server_sasl:feature(
["EXTERNAL"])];
_ ->
[]
CertVerifyRes ->
case StateData#state.tls_certverify of
true -> {error_cert_verif, CertVerifyRes, Cert};
false -> []
end
end;
error ->
[]
@ -193,12 +199,23 @@ wait_for_stream({xmlstreamstart, Opening}, StateData) ->
(not StateData#state.tls_enabled) ->
[exmpp_server_tls:feature(StateData#state.tls_required)]
end,
Features = SASL ++ StartTLS ++ ejabberd_hooks:run_fold(
c2s_stream_features,
Server,
[], [Server]),
send_element(StateData, exmpp_stream:features(Features)),
{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 = exmpp_stream:get_initiating_entity(Opening),
?INFO_MSG("Closing s2s connection: ~s <--> ~s (~s)", [StateData#state.server, RemoteServer, CertError]),
send_element(StateData, exmpp_stream:error('policy-violation', {"en", CertError})),
{atomic, Pid} = ejabberd_s2s:find_connection(exmpp_jid:make(Server), exmpp_jid:make(RemoteServer)),
ejabberd_s2s_out:stop_connection(Pid),
{stop, normal, StateData};
_ ->
Features = SASL ++ StartTLS ++ ejabberd_hooks:run_fold(
c2s_stream_features,
Server,
[], [Server]),
send_element(StateData, exmpp_stream:features(Features)),
{next_state, wait_for_feature_request, StateData#state{server = Server}}
end;
{?NS_JABBER_SERVER, _, Server, true} when
StateData#state.authenticated ->
Opening_Reply = exmpp_stream:opening_reply(Opening,

View File

@ -151,7 +151,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,

View File

@ -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.
@ -237,7 +238,9 @@ get_peer_certificate(#tlssock{tlsport = Port}) ->
case port_control(Port, ?GET_PEER_CERTIFICATE, []) of
<<0, BCert/binary>> ->
case catch public_key:pkix_decode_cert(BCert, plain) of
{ok, Cert} ->
{ok, Cert} -> %% returned by R13 and older
{ok, Cert};
{'Certificate', _, _, _} = Cert ->
{ok, Cert};
_ ->
error
@ -305,3 +308,46 @@ loop(Port, Socket) ->
end.
get_cert_verify_string(CertVerifyRes, Cert) ->
BCert = public_key:pkix_encode('Certificate', Cert, plain),
IsSelfsigned = public_key:pkix_is_self_signed(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).

View File

@ -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);