]> granicus.if.org Git - ejabberd/commitdiff
Introduce option 'ca_file'
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 26 Nov 2017 15:10:25 +0000 (18:10 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Sun, 26 Nov 2017 15:10:25 +0000 (18:10 +0300)
The option is supposed to be used as a fallback for certificates
validation. For instance, the option will be used if 's2s_cafile'
option is not set. The value should be a path to a file containing
CA certificate(s) in PEM format, e.g.:

ca_file: "/etc/ssl/certs/ca-bundle.pem"

src/ejabberd_pkix.erl
src/ejabberd_s2s.erl

index e2f6c78ef66038f7f7533ca9f8e367350036ecb4..ef25386cd62855e66e5b5905d5ab22faf45bbfe5 100644 (file)
@@ -28,7 +28,7 @@
 %% API
 -export([start_link/0, add_certfile/1, format_error/1, opt_type/1,
         get_certfile/1, try_certfile/1, route_registered/1,
-        config_reloaded/0, certs_dir/0]).
+        config_reloaded/0, certs_dir/0, ca_file/0]).
 %% gen_server callbacks
 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).
@@ -146,7 +146,11 @@ config_reloaded() ->
     gen_server:call(?MODULE, config_reloaded, 60000).
 
 opt_type(ca_path) ->
-    fun(Path) -> iolist_to_binary(Path) end;
+    fun(Path) -> binary_to_list(Path) end;
+opt_type(ca_file) ->
+    fun(Path) ->
+           binary_to_list(misc:try_read_file(Path))
+    end;
 opt_type(certfiles) ->
     fun(CertList) ->
            [binary_to_list(Path) || Path <- CertList]
@@ -157,7 +161,7 @@ opt_type(O) when O == c2s_certfile; O == s2s_certfile; O == domain_certfile ->
            misc:try_read_file(File)
     end;
 opt_type(_) ->
-    [ca_path, certfiles, c2s_certfile, s2s_certfile, domain_certfile].
+    [ca_path, ca_file, certfiles, c2s_certfile, s2s_certfile, domain_certfile].
 
 %%%===================================================================
 %%% gen_server callbacks
@@ -175,7 +179,7 @@ init([]) ->
                       erlang:function_exported(
                         public_key, short_name_hash, 1)
               end,
-    if Validate -> check_ca_dir();
+    if Validate -> check_ca();
        true -> ok
     end,
     State = #state{validate = Validate, notify = Notify},
@@ -524,6 +528,10 @@ validate_path([Cert|_] = Certs) ->
 ca_dir() ->
     ejabberd_config:get_option(ca_path, "/etc/ssl/certs").
 
+-spec ca_file() -> string() | undefined.
+ca_file() ->
+    ejabberd_config:get_option(ca_file).
+
 -spec certs_dir() -> string().
 certs_dir() ->
     MnesiaDir = mnesia:system_info(directory),
@@ -543,11 +551,12 @@ clean_dir(Dir) ->
              end
       end, Files).
 
--spec check_ca_dir() -> ok.
-check_ca_dir() ->
+-spec check_ca() -> ok.
+check_ca() ->
+    CAFile = ca_file(),
     case wildcard(filename:join(ca_dir(), "*.0")) of
-       [] ->
-           Hint = "configuring 'ca_path' option might help",
+       [] when CAFile == undefined ->
+           Hint = "configuring 'ca_path' or 'ca_file' options might help",
            case file:list_dir(ca_dir()) of
                {error, Why} ->
                    ?WARNING_MSG("failed to read CA directory ~s: ~s; ~s",
@@ -563,10 +572,23 @@ check_ca_dir() ->
 
 -spec find_local_issuer(cert()) -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
 find_local_issuer(Cert) ->
+    case find_issuer_in_dir(Cert, ca_dir()) of
+       {ok, IssuerCert} ->
+           {ok, IssuerCert};
+       {error, _} = Err ->
+           case ca_file() of
+               undefined -> Err;
+               CAFile -> find_issuer_in_file(Cert, CAFile)
+           end
+    end.
+
+-spec find_issuer_in_dir(cert(), file:filename_all())
+      -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
+find_issuer_in_dir(Cert, CADir) ->
     {ok, {_, IssuerID}} = public_key:pkix_issuer_id(Cert, self),
     Hash = short_name_hash(IssuerID),
     filelib:fold_files(
-      ca_dir(), Hash ++ "\\.[0-9]+", false,
+      CADir, Hash ++ "\\.[0-9]+", false,
       fun(_, {ok, IssuerCert}) ->
              {ok, IssuerCert};
         (CertFile, Acc) ->
@@ -586,6 +608,29 @@ find_local_issuer(Cert) ->
              end
       end, {error, {bad_cert, unknown_ca}}).
 
+-spec find_issuer_in_file(cert(), file:filename_all() | undefined)
+      -> {ok, cert()} | {error, {bad_cert, unknown_ca}}.
+find_issuer_in_file(_Cert, undefined) ->
+    {error, {bad_cert, unknown_ca}};
+find_issuer_in_file(Cert, CAFile) ->
+    try
+       {ok, Data} = file:read_file(CAFile),
+       {ok, IssuerCerts, _} = pem_decode(Data),
+       lists:foldl(
+         fun(_, {ok, _} = Res) ->
+                 Res;
+            (IssuerCert, Err) ->
+                 case public_key:pkix_is_issuer(Cert, IssuerCert) of
+                     true -> {ok, IssuerCert};
+                     false -> Err
+                 end
+         end, {error, {bad_cert, unknown_ca}}, IssuerCerts)
+    catch _:{badmatch, {error, Why}} ->
+           ?ERROR_MSG("failed to read CA certificates from \"~s\": ~s",
+                      [CAFile, format_error(Why)]),
+           {error, {bad_cert, unknown_ca}}
+    end.
+
 -spec match_cert_keys([{path, [cert()]}], [priv_key()])
       -> {ok, [{cert(), priv_key()}]} | {error, {bad_cert, missing_priv_key}}.
 match_cert_keys(CertPaths, PrivKeys) ->
index 1e389d712b2937d1ad20dfb6e2b23b9c4209d69f..67f2421229b2ef3f7e19979af79fa7cc3a769465 100644 (file)
@@ -222,8 +222,7 @@ tls_options(LServer, DefaultOpts) ->
                    DHFile -> lists:keystore(dhfile, 1, TLSOpts3,
                                            {dhfile, DHFile})
                end,
-    TLSOpts5 = case ejabberd_config:get_option(
-                     {s2s_cafile, LServer}) of
+    TLSOpts5 = case get_cafile(LServer) of
                   undefined -> TLSOpts4;
                   CAFile -> lists:keystore(cafile, 1, TLSOpts4,
                                            {cafile, CAFile})
@@ -267,7 +266,7 @@ queue_type(LServer) ->
       {s2s_queue_type, LServer},
       ejabberd_config:default_queue_type(LServer)).
 
--spec get_certfile(binary()) -> file:filename_all().
+-spec get_certfile(binary()) -> file:filename_all() | undefined.
 get_certfile(LServer) ->
     case ejabberd_pkix:get_certfile(LServer) of
        {ok, CertFile} ->
@@ -278,6 +277,15 @@ get_certfile(LServer) ->
              ejabberd_config:get_option({s2s_certfile, LServer}))
     end.
 
+-spec get_cafile(binary()) -> file:filename_all() | undefined.
+get_cafile(LServer) ->
+    case ejabberd_config:get_option({s2s_cafile, LServer}) of
+       undefined ->
+           ejabberd_pkix:ca_file();
+       File ->
+           File
+    end.
+
 %%====================================================================
 %% gen_server callbacks
 %%====================================================================