mirror of
https://github.com/processone/ejabberd
synced 2025-10-04 02:09:33 +02:00
Support certificate revocation
This commit is contained in:
parent
61d1411ab3
commit
cc6f4b90fb
3 changed files with 80 additions and 7 deletions
|
@ -12,9 +12,9 @@
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-record(data_cert, {
|
-record(data_cert, {
|
||||||
domain :: list(),
|
domain :: bitstring(),
|
||||||
pem :: jose_jwk:key(),
|
pem :: bitstring(),
|
||||||
path :: file:filename()
|
path :: bitstring()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
-export([%% Ejabberdctl Commands
|
-export([%% Ejabberdctl Commands
|
||||||
get_certificates/2,
|
get_certificates/2,
|
||||||
list_certificates/1,
|
list_certificates/1,
|
||||||
|
revoke_certificate/2,
|
||||||
%% Command Options Validity
|
%% Command Options Validity
|
||||||
is_valid_account_opt/1,
|
is_valid_account_opt/1,
|
||||||
is_valid_verbose_opt/1,
|
is_valid_verbose_opt/1,
|
||||||
%% Misc
|
%% Misc
|
||||||
generate_key/0,
|
generate_key/0,
|
||||||
|
to_public/1,
|
||||||
%% Debugging Scenarios
|
%% Debugging Scenarios
|
||||||
scenario/3,
|
scenario/3,
|
||||||
scenario0/2,
|
scenario0/2,
|
||||||
|
@ -46,6 +48,48 @@ is_valid_verbose_opt("plain") -> true;
|
||||||
is_valid_verbose_opt("verbose") -> true;
|
is_valid_verbose_opt("verbose") -> true;
|
||||||
is_valid_verbose_opt(_) -> false.
|
is_valid_verbose_opt(_) -> false.
|
||||||
|
|
||||||
|
%%
|
||||||
|
%% Revoke Certificate
|
||||||
|
%%
|
||||||
|
|
||||||
|
%% Add a try-catch to this stub
|
||||||
|
revoke_certificate(CAUrl, Domain) ->
|
||||||
|
revoke_certificate0(CAUrl, Domain).
|
||||||
|
|
||||||
|
revoke_certificate0(CAUrl, Domain) ->
|
||||||
|
BinDomain = list_to_bitstring(Domain),
|
||||||
|
case domain_certificate_exists(BinDomain) of
|
||||||
|
{BinDomain, Certificate} ->
|
||||||
|
?INFO_MSG("Certificate: ~p found!!", [Certificate]),
|
||||||
|
ok = revoke_certificate1(CAUrl, Certificate),
|
||||||
|
{ok, deleted};
|
||||||
|
false ->
|
||||||
|
{error, not_found}
|
||||||
|
end.
|
||||||
|
|
||||||
|
revoke_certificate1(CAUrl, Cert = #data_cert{pem=PemEncodedCert}) ->
|
||||||
|
{ok, _AccId, PrivateKey} = ensure_account_exists(),
|
||||||
|
|
||||||
|
Certificate = prepare_certificate_revoke(PemEncodedCert),
|
||||||
|
|
||||||
|
{ok, Dirs, Nonce} = ejabberd_acme_comm:directory(CAUrl),
|
||||||
|
|
||||||
|
Req = [{<<"certificate">>, Certificate}],
|
||||||
|
{ok, [], Nonce1} = ejabberd_acme_comm:revoke_cert(Dirs, PrivateKey, Req, Nonce),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
prepare_certificate_revoke(PemEncodedCert) ->
|
||||||
|
PemList = public_key:pem_decode(PemEncodedCert),
|
||||||
|
PemCertEnc = lists:keyfind('Certificate', 1, PemList),
|
||||||
|
PemCert = public_key:pem_entry_decode(PemCertEnc),
|
||||||
|
DerCert = public_key:der_encode('Certificate', PemCert),
|
||||||
|
Base64Cert = base64url:encode(DerCert),
|
||||||
|
Base64Cert.
|
||||||
|
|
||||||
|
domain_certificate_exists(Domain) ->
|
||||||
|
{ok, Certs} = read_certificates_persistent(),
|
||||||
|
lists:keyfind(Domain, 1, Certs).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%% List Certificates
|
%% List Certificates
|
||||||
%%
|
%%
|
||||||
|
@ -58,7 +102,7 @@ list_certificates(Verbose) ->
|
||||||
Throw;
|
Throw;
|
||||||
E:R ->
|
E:R ->
|
||||||
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
|
||||||
{error, get_certificates}
|
{error, list_certificates}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
list_certificates0(Verbose) ->
|
list_certificates0(Verbose) ->
|
||||||
|
@ -321,7 +365,7 @@ ensure_account_exists() ->
|
||||||
make_csr(Attributes) ->
|
make_csr(Attributes) ->
|
||||||
Key = generate_key(),
|
Key = generate_key(),
|
||||||
{_, KeyKey} = jose_jwk:to_key(Key),
|
{_, KeyKey} = jose_jwk:to_key(Key),
|
||||||
KeyPub = jose_jwk:to_public(Key),
|
KeyPub = to_public(Key),
|
||||||
try
|
try
|
||||||
SubPKInfoAlgo = subject_pk_info_algo(KeyPub),
|
SubPKInfoAlgo = subject_pk_info_algo(KeyPub),
|
||||||
{ok, RawBinPubKey} = raw_binary_public_key(KeyPub),
|
{ok, RawBinPubKey} = raw_binary_public_key(KeyPub),
|
||||||
|
@ -420,7 +464,9 @@ attribute_parser_fun({AttrName, AttrVal}) ->
|
||||||
try
|
try
|
||||||
#'AttributeTypeAndValue'{
|
#'AttributeTypeAndValue'{
|
||||||
type = attribute_oid(AttrName),
|
type = attribute_oid(AttrName),
|
||||||
%% TODO: Check if every attribute should be encoded as common name
|
%% TODO: Check if every attribute should be encoded as
|
||||||
|
%% common name. Actually it doesn't matter in
|
||||||
|
%% practice. Only in theory in order to have cleaner code.
|
||||||
value = public_key:der_encode('X520CommonName', {printableString, AttrVal})
|
value = public_key:der_encode('X520CommonName', {printableString, AttrVal})
|
||||||
%% value = length_bitstring(list_to_bitstring(AttrVal))
|
%% value = length_bitstring(list_to_bitstring(AttrVal))
|
||||||
}
|
}
|
||||||
|
@ -469,6 +515,22 @@ not_before_not_after() ->
|
||||||
NotAfter = xmpp_util:encode_timestamp({MegS+1, Sec, MicS}),
|
NotAfter = xmpp_util:encode_timestamp({MegS+1, Sec, MicS}),
|
||||||
{NotBefore, NotAfter}.
|
{NotBefore, NotAfter}.
|
||||||
|
|
||||||
|
to_public(PrivateKey) ->
|
||||||
|
jose_jwk:to_public(PrivateKey).
|
||||||
|
%% case jose_jwk:to_key(PrivateKey) of
|
||||||
|
%% #'RSAPrivateKey'{modulus = Mod, publicExponent = Exp} ->
|
||||||
|
%% Public = #'RSAPublicKey'{modulus = Mod, publicExponent = Exp},
|
||||||
|
%% jose_jwk:from_key(Public);
|
||||||
|
%% _ ->
|
||||||
|
%% jose_jwk:to_public(PrivateKey)
|
||||||
|
%% end.
|
||||||
|
|
||||||
|
%% to_public(#'RSAPrivateKey'{modulus = Mod, publicExponent = Exp}) ->
|
||||||
|
%% #'RSAPublicKey'{modulus = Mod, publicExponent = Exp};
|
||||||
|
%% to_public(PrivateKey) ->
|
||||||
|
%% jose_jwk:to_public(PrivateKey).
|
||||||
|
|
||||||
|
|
||||||
is_error({error, _}) -> true;
|
is_error({error, _}) -> true;
|
||||||
is_error(_) -> false.
|
is_error(_) -> false.
|
||||||
|
|
||||||
|
@ -812,7 +874,9 @@ new_user_scenario(CAUrl, HttpDir) ->
|
||||||
generate_key() ->
|
generate_key() ->
|
||||||
?INFO_MSG("Generate RSA key pair~n", []),
|
?INFO_MSG("Generate RSA key pair~n", []),
|
||||||
Key = public_key:generate_key({rsa, 2048, 65537}),
|
Key = public_key:generate_key({rsa, 2048, 65537}),
|
||||||
jose_jwk:from_key(Key).
|
Key1 = Key#'RSAPrivateKey'{version = 'two-prime'},
|
||||||
|
jose_jwk:from_key(Key1).
|
||||||
|
%% jose_jwk:generate_key({rsa, 2048}).
|
||||||
-else.
|
-else.
|
||||||
generate_key() ->
|
generate_key() ->
|
||||||
?INFO_MSG("Generate EC key pair~n", []),
|
?INFO_MSG("Generate EC key pair~n", []),
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
%% Acme
|
%% Acme
|
||||||
get_certificate/1,
|
get_certificate/1,
|
||||||
list_certificates/1,
|
list_certificates/1,
|
||||||
|
revoke_certificate/1,
|
||||||
%% Purge DB
|
%% Purge DB
|
||||||
delete_expired_messages/0, delete_old_messages/1,
|
delete_expired_messages/0, delete_old_messages/1,
|
||||||
%% Mnesia
|
%% Mnesia
|
||||||
|
@ -258,6 +259,12 @@ get_commands_spec() ->
|
||||||
args_desc = ["Whether to print the whole certificate or just some metadata. Possible values: plain | verbose"],
|
args_desc = ["Whether to print the whole certificate or just some metadata. Possible values: plain | verbose"],
|
||||||
args = [{option, string}],
|
args = [{option, string}],
|
||||||
result = {certificates, {list,{certificate, string}}}},
|
result = {certificates, {list,{certificate, string}}}},
|
||||||
|
#ejabberd_commands{name = revoke_certificate, tags = [acme],
|
||||||
|
desc = "Revokes the selected certificate",
|
||||||
|
module = ?MODULE, function = revoke_certificate,
|
||||||
|
args_desc = ["The domain of the certificate in question"],
|
||||||
|
args = [{domain, string}],
|
||||||
|
result = {res, restuple}},
|
||||||
|
|
||||||
#ejabberd_commands{name = import_piefxis, tags = [mnesia],
|
#ejabberd_commands{name = import_piefxis, tags = [mnesia],
|
||||||
desc = "Import users data from a PIEFXIS file (XEP-0227)",
|
desc = "Import users data from a PIEFXIS file (XEP-0227)",
|
||||||
|
@ -580,6 +587,8 @@ list_certificates(Verbose) ->
|
||||||
{invalid_option, String}
|
{invalid_option, String}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
revoke_certificate(Domain) ->
|
||||||
|
ejabberd_acme:revoke_certificate("http://localhost:4000", Domain).
|
||||||
|
|
||||||
%%%
|
%%%
|
||||||
%%% Purge DB
|
%%% Purge DB
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue