Introduce option 'ca_file'

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"
This commit is contained in:
Evgeniy Khramtsov 2017-11-26 18:10:25 +03:00
parent 242b3d3968
commit 783ebd1080
2 changed files with 65 additions and 12 deletions

View 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) ->

View 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
%%====================================================================