25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-24 16:23:40 +01:00

Support certificate revocation

This commit is contained in:
Konstantinos Kallas 2017-07-27 18:25:44 +03:00
parent 61d1411ab3
commit cc6f4b90fb
3 changed files with 80 additions and 7 deletions

View File

@ -12,9 +12,9 @@
}).
-record(data_cert, {
domain :: list(),
pem :: jose_jwk:key(),
path :: file:filename()
domain :: bitstring(),
pem :: bitstring(),
path :: bitstring()
}).

View File

@ -3,11 +3,13 @@
-export([%% Ejabberdctl Commands
get_certificates/2,
list_certificates/1,
revoke_certificate/2,
%% Command Options Validity
is_valid_account_opt/1,
is_valid_verbose_opt/1,
%% Misc
generate_key/0,
to_public/1,
%% Debugging Scenarios
scenario/3,
scenario0/2,
@ -46,6 +48,48 @@ is_valid_verbose_opt("plain") -> true;
is_valid_verbose_opt("verbose") -> true;
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
%%
@ -58,7 +102,7 @@ list_certificates(Verbose) ->
Throw;
E:R ->
?ERROR_MSG("Unknown ~p:~p, ~p", [E, R, erlang:get_stacktrace()]),
{error, get_certificates}
{error, list_certificates}
end.
list_certificates0(Verbose) ->
@ -321,7 +365,7 @@ ensure_account_exists() ->
make_csr(Attributes) ->
Key = generate_key(),
{_, KeyKey} = jose_jwk:to_key(Key),
KeyPub = jose_jwk:to_public(Key),
KeyPub = to_public(Key),
try
SubPKInfoAlgo = subject_pk_info_algo(KeyPub),
{ok, RawBinPubKey} = raw_binary_public_key(KeyPub),
@ -420,7 +464,9 @@ attribute_parser_fun({AttrName, AttrVal}) ->
try
#'AttributeTypeAndValue'{
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 = length_bitstring(list_to_bitstring(AttrVal))
}
@ -469,6 +515,22 @@ not_before_not_after() ->
NotAfter = xmpp_util:encode_timestamp({MegS+1, Sec, MicS}),
{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(_) -> false.
@ -812,7 +874,9 @@ new_user_scenario(CAUrl, HttpDir) ->
generate_key() ->
?INFO_MSG("Generate RSA key pair~n", []),
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.
generate_key() ->
?INFO_MSG("Generate EC key pair~n", []),

View File

@ -47,6 +47,7 @@
%% Acme
get_certificate/1,
list_certificates/1,
revoke_certificate/1,
%% Purge DB
delete_expired_messages/0, delete_old_messages/1,
%% 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 = [{option, 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],
desc = "Import users data from a PIEFXIS file (XEP-0227)",
@ -580,6 +587,8 @@ list_certificates(Verbose) ->
{invalid_option, String}
end.
revoke_certificate(Domain) ->
ejabberd_acme:revoke_certificate("http://localhost:4000", Domain).
%%%
%%% Purge DB