diff --git a/src/ejabberd_pkix.erl b/src/ejabberd_pkix.erl index e2f6c78ef..ef25386cd 100644 --- a/src/ejabberd_pkix.erl +++ b/src/ejabberd_pkix.erl @@ -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) -> diff --git a/src/ejabberd_s2s.erl b/src/ejabberd_s2s.erl index 1e389d712..67f242122 100644 --- a/src/ejabberd_s2s.erl +++ b/src/ejabberd_s2s.erl @@ -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 %%====================================================================