%% 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]).
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]
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
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},
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),
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",
-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) ->
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) ->
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})
{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} ->
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
%%====================================================================