]> granicus.if.org Git - ejabberd/commitdiff
Option to reject S2S connection if untrusted certificate (EJAB-464)
authorBadlop <badlop@process-one.net>
Sat, 11 Dec 2010 01:28:50 +0000 (02:28 +0100)
committerBadlop <badlop@process-one.net>
Sat, 11 Dec 2010 01:29:53 +0000 (02:29 +0100)
doc/guide.tex
src/ejabberd.cfg.example
src/ejabberd_s2s_in.erl
src/ejabberd_s2s_out.erl
src/tls/tls.erl
src/tls/tls_drv.c

index bccbe586dec54b21b8a76edd60163da9abf4490a..0ae3053e5739a0fa04afc4a79176f549277836c5 100644 (file)
@@ -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}.
index 45cdfb4d061b580fd6b847b54f8b2f72155799ce..a95689b359318e5c5865e85a1fbf061ff93459ab 100644 (file)
 
 %%
 %% 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}.
index 7bc183aa730d8165aa8cb91ef1dacd5a5840f3bb..eb8c05a6e88d1020e9202dfe59f3801dc643be46 100644 (file)
@@ -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'")),
index 00e7fa1a99e3291f081ce01f02c2442fe78c215c..f59e8ec8e9a876eaebe394fcf530a42b5a5e9dbd 100644 (file)
@@ -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,
index 46f01b36907370c311926a32c1c0843318e2c7d5..7c90569cd0e98450a82419f6dec0fa35aba55705 100644 (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.
 
 -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).
index fd4e7fff29b00fa8686ad763e1782a9013444252..ae87076250fe4aa4431eb77e2d2a14a4f74cae79 100644 (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);