Make ACME code working with ejabberd_pkix
This commit is contained in:
parent
c05626a1ba
commit
ce98226603
|
@ -1,21 +1,23 @@
|
|||
-module (ejabberd_acme).
|
||||
-behaviour(gen_server).
|
||||
-behavior(ejabberd_config).
|
||||
|
||||
-export([%% Ejabberdctl Commands
|
||||
get_certificates/1,
|
||||
%% ejabberdctl commands
|
||||
-export([get_certificates/1,
|
||||
renew_certificates/0,
|
||||
list_certificates/1,
|
||||
revoke_certificate/1,
|
||||
%% Command Options Validity
|
||||
is_valid_account_opt/1,
|
||||
revoke_certificate/1]).
|
||||
%% Command Options Validity
|
||||
-export([is_valid_account_opt/1,
|
||||
is_valid_verbose_opt/1,
|
||||
is_valid_domain_opt/1,
|
||||
is_valid_revoke_cert/1,
|
||||
%% Called by ejabberd_pkix
|
||||
certificate_exists/1,
|
||||
%% Key Related
|
||||
generate_key/0,
|
||||
to_public/1
|
||||
]).
|
||||
is_valid_revoke_cert/1]).
|
||||
%% Key Related
|
||||
-export([generate_key/0, to_public/1]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
-export([start_link/0, opt_type/1]).
|
||||
|
||||
-include("ejabberd.hrl").
|
||||
-include("logger.hrl").
|
||||
|
@ -24,17 +26,45 @@
|
|||
-include("ejabberd_acme.hrl").
|
||||
-include_lib("public_key/include/public_key.hrl").
|
||||
|
||||
-export([opt_type/1]).
|
||||
|
||||
-behavior(ejabberd_config).
|
||||
|
||||
%%
|
||||
%% Default ACME configuration
|
||||
%%
|
||||
|
||||
-define(DEFAULT_CONFIG_CONTACT, <<"mailto:example-admin@example.com">>).
|
||||
-define(DEFAULT_CONFIG_CA_URL, "https://acme-v01.api.letsencrypt.org").
|
||||
|
||||
-record(state, {}).
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||
|
||||
%%%===================================================================
|
||||
%%% gen_server callbacks
|
||||
%%%===================================================================
|
||||
init([]) ->
|
||||
case filelib:ensure_dir(filename:join(acme_certs_dir(), "foo")) of
|
||||
ok ->
|
||||
register_certfiles(),
|
||||
{ok, #state{}};
|
||||
{error, Why} ->
|
||||
?CRITICAL_MSG("Failed to create directory ~s: ~s",
|
||||
[acme_certs_dir(), file:format_error(Why)]),
|
||||
{stop, Why}
|
||||
end.
|
||||
|
||||
handle_call(_Request, _From, State) ->
|
||||
{stop, {unexpected_call, _Request, _From}, State}.
|
||||
|
||||
handle_cast(_Msg, State) ->
|
||||
?WARNING_MSG("unexpected cast: ~p", [_Msg]),
|
||||
{noreply, State}.
|
||||
|
||||
handle_info(_Info, State) ->
|
||||
?WARNING_MSG("unexpected info: ~p", [_Info]),
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% Command Functions
|
||||
|
@ -591,26 +621,6 @@ domain_certificate_exists(Domain) ->
|
|||
Certs = read_certificates_persistent(),
|
||||
lists:keyfind(Domain, 1, Certs).
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% Called by ejabberd_pkix to check
|
||||
%% if a certificate exists for a
|
||||
%% specific host
|
||||
%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
-spec certificate_exists(bitstring()) -> {true, file:filename()} | false.
|
||||
certificate_exists(Host) ->
|
||||
Certificates = read_certificates_persistent(),
|
||||
case lists:keyfind(Host, 1 , Certificates) of
|
||||
false ->
|
||||
false;
|
||||
{Host, #data_cert{path=Path}} ->
|
||||
{true, Path}
|
||||
end.
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
%%
|
||||
%% Certificate Request Functions
|
||||
|
@ -951,8 +961,8 @@ data_remove_certificate(Data, _DataCert = #data_cert{domain=Domain}) ->
|
|||
|
||||
-spec persistent_file() -> file:filename().
|
||||
persistent_file() ->
|
||||
MnesiaDir = mnesia:system_info(directory),
|
||||
filename:join(MnesiaDir, "acme.DAT").
|
||||
AcmeDir = acme_certs_dir(),
|
||||
filename:join(AcmeDir, "acme.DAT").
|
||||
|
||||
%% The persistent file should be read and written only by its owner
|
||||
-spec persistent_file_mode() -> 384.
|
||||
|
@ -1032,9 +1042,9 @@ save_certificate({error, _, _} = Error) ->
|
|||
Error;
|
||||
save_certificate({ok, DomainName, Cert}) ->
|
||||
try
|
||||
CertDir = get_config_cert_dir(),
|
||||
CertDir = acme_certs_dir(),
|
||||
DomainString = bitstring_to_list(DomainName),
|
||||
CertificateFile = filename:join([CertDir, DomainString ++ "_cert.pem"]),
|
||||
CertificateFile = filename:join([CertDir, DomainString ++ ".pem"]),
|
||||
%% TODO: At some point do the following using a Transaction so
|
||||
%% that there is no certificate saved if it cannot be added in
|
||||
%% certificate persistent storage
|
||||
|
@ -1045,7 +1055,7 @@ save_certificate({ok, DomainName, Cert}) ->
|
|||
path = CertificateFile
|
||||
},
|
||||
add_certificate_persistent(DataCert),
|
||||
ejabberd_pkix:add_certfile(CertificateFile),
|
||||
ok = ejabberd_pkix:add_certfile(CertificateFile),
|
||||
{ok, DomainName, saved}
|
||||
catch
|
||||
throw:Throw ->
|
||||
|
@ -1064,6 +1074,15 @@ save_renewed_certificate({ok, _, no_expire} = Cert) ->
|
|||
save_renewed_certificate({ok, DomainName, Cert}) ->
|
||||
save_certificate({ok, DomainName, Cert}).
|
||||
|
||||
-spec register_certfiles() -> ok.
|
||||
register_certfiles() ->
|
||||
Dir = acme_certs_dir(),
|
||||
Paths = filelib:wildcard(filename:join(Dir, "*.pem")),
|
||||
lists:foreach(
|
||||
fun(Path) ->
|
||||
ejabberd_pkix:add_certfile(Path)
|
||||
end, Paths).
|
||||
|
||||
-spec write_cert(file:filename(), binary(), bitstring()) -> {ok, bitstring(), saved}.
|
||||
write_cert(CertificateFile, Cert, DomainName) ->
|
||||
case file:write_file(CertificateFile, Cert) of
|
||||
|
@ -1121,17 +1140,9 @@ get_config_hosts() ->
|
|||
Hosts
|
||||
end.
|
||||
|
||||
-spec get_config_cert_dir() -> file:filename().
|
||||
get_config_cert_dir() ->
|
||||
case ejabberd_config:get_option(cert_dir, undefined) of
|
||||
undefined ->
|
||||
?WARNING_MSG("No cert_dir configuration has been specified in configuration", []),
|
||||
mnesia:system_info(directory);
|
||||
%% throw({error, configuration});
|
||||
CertDir ->
|
||||
CertDir
|
||||
end.
|
||||
|
||||
-spec acme_certs_dir() -> file:filename().
|
||||
acme_certs_dir() ->
|
||||
filename:join(ejabberd_pkix:certs_dir(), "acme").
|
||||
|
||||
generate_key() ->
|
||||
jose_jwk:generate_key({ec, secp256r1}).
|
||||
|
@ -1151,16 +1162,9 @@ parse_acme_opt({ca_url, CaUrl}) when is_bitstring(CaUrl) ->
|
|||
parse_acme_opt({contact, Contact}) when is_bitstring(Contact) ->
|
||||
{contact, Contact}.
|
||||
|
||||
parse_cert_dir_opt(Opt) when is_bitstring(Opt) ->
|
||||
true = filelib:is_dir(Opt),
|
||||
Opt.
|
||||
|
||||
-spec opt_type(acme) -> fun((acme_config()) -> (acme_config()));
|
||||
(cert_dir) -> fun((bitstring()) -> (bitstring()));
|
||||
(atom()) -> [atom()].
|
||||
opt_type(acme) ->
|
||||
fun parse_acme_opts/1;
|
||||
opt_type(cert_dir) ->
|
||||
fun parse_cert_dir_opt/1;
|
||||
opt_type(_) ->
|
||||
[acme, cert_dir].
|
||||
[acme].
|
||||
|
|
|
@ -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]).
|
||||
config_reloaded/0, certs_dir/0]).
|
||||
%% gen_server callbacks
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
terminate/2, code_change/3]).
|
||||
|
@ -190,8 +190,17 @@ init([]) ->
|
|||
end.
|
||||
|
||||
handle_call({add_certfile, Path}, _, State) ->
|
||||
{Result, NewState} = add_certfile(Path, State),
|
||||
{reply, Result, NewState};
|
||||
case add_certfile(Path, State) of
|
||||
{ok, State1} ->
|
||||
case build_chain_and_check(State1) of
|
||||
{ok, State2} ->
|
||||
{reply, ok, State2};
|
||||
Err ->
|
||||
{reply, Err, State}
|
||||
end;
|
||||
{Err, State1} ->
|
||||
{reply, Err, State1}
|
||||
end;
|
||||
handle_call({route_registered, Host}, _, State) ->
|
||||
case add_certfiles(Host, State) of
|
||||
{ok, NewState} ->
|
||||
|
@ -301,14 +310,7 @@ add_certfiles(Host, State) ->
|
|||
NewAccState
|
||||
end
|
||||
end, State, certfiles_from_config_options()),
|
||||
State2 = case ejabberd_acme:certificate_exists(Host) of
|
||||
{true, Path} ->
|
||||
{_, State3} = add_certfile(Path, State1),
|
||||
State3;
|
||||
false ->
|
||||
State1
|
||||
end,
|
||||
if State /= State2 ->
|
||||
if State /= State1 ->
|
||||
case build_chain_and_check(State1) of
|
||||
ok -> {ok, State1};
|
||||
{error, _} = Err -> Err
|
||||
|
|
|
@ -156,6 +156,8 @@ init([]) ->
|
|||
permanent, 5000, worker, [cyrsasl]},
|
||||
PKIX = {ejabberd_pkix, {ejabberd_pkix, start_link, []},
|
||||
permanent, 5000, worker, [ejabberd_pkix]},
|
||||
ACME = {ejabberd_acme, {ejabberd_acme, start_link, []},
|
||||
permanent, 5000, worker, [ejabberd_acme]},
|
||||
IQ = {ejabberd_iq, {ejabberd_iq, start_link, []},
|
||||
permanent, 5000, worker, [ejabberd_iq]},
|
||||
{ok, {{one_for_one, 10, 1},
|
||||
|
@ -168,6 +170,7 @@ init([]) ->
|
|||
Commands,
|
||||
Admin,
|
||||
PKIX,
|
||||
ACME,
|
||||
Listener,
|
||||
SystemMonitor,
|
||||
S2S,
|
||||
|
|
Loading…
Reference in New Issue