From a72a7f830af9bd72a86cbc7aed2f78e46ea0a781 Mon Sep 17 00:00:00 2001 From: Konstantinos Kallas Date: Sat, 12 Aug 2017 17:14:23 +0300 Subject: [PATCH] Add support to revoke a certificate by providing the pem This is important so that a user can revoke a certificate that is not acquired or logged from our acme client --- src/ejabberd_acme.erl | 52 ++++++++++++++++++++++++++++++++---------- src/ejabberd_admin.erl | 14 ++++++++---- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/ejabberd_acme.erl b/src/ejabberd_acme.erl index 541aa2879..dd8f08575 100644 --- a/src/ejabberd_acme.erl +++ b/src/ejabberd_acme.erl @@ -9,6 +9,7 @@ is_valid_account_opt/1, is_valid_verbose_opt/1, is_valid_domain_opt/1, + is_valid_revoke_cert/1, %% Key Related generate_key/0, to_public/1 @@ -53,6 +54,11 @@ is_valid_domain_opt(DomainString) -> SeparatedDomains -> true end. + +-spec is_valid_revoke_cert(string()) -> boolean(). +is_valid_revoke_cert(DomainOrFile) -> + lists:prefix("file:", DomainOrFile) orelse + lists:prefix("domain:", DomainOrFile). @@ -457,10 +463,10 @@ get_utc_validity(#'Certificate'{tbsCertificate = TbsCertificate}) -> %% -spec revoke_certificate(string()) -> {ok, deleted} | {error, _}. -revoke_certificate(Domain) -> +revoke_certificate(DomainOrFile) -> try CAUrl = binary_to_list(get_config_ca_url()), - revoke_certificate0(CAUrl, Domain) + revoke_certificate0(CAUrl, DomainOrFile) catch throw:Throw -> Throw; @@ -469,28 +475,50 @@ revoke_certificate(Domain) -> {error, revoke_certificate} end. --spec revoke_certificate0(url(), string()) -> {ok, deleted} | {error, not_found}. -revoke_certificate0(CAUrl, Domain) -> - BinDomain = list_to_bitstring(Domain), - case domain_certificate_exists(BinDomain) of - {BinDomain, Certificate} -> - ok = revoke_certificate1(CAUrl, Certificate), +-spec revoke_certificate0(url(), string()) -> {ok, deleted}. +revoke_certificate0(CAUrl, DomainOrFile) -> + ParsedCert = parse_revoke_cert_argument(DomainOrFile), + revoke_certificate1(CAUrl, ParsedCert). + +-spec revoke_certificate1(url(), {domain, bitstring()} | {file, file:filename()}) -> + {ok, deleted}. +revoke_certificate1(CAUrl, {domain, Domain}) -> + case domain_certificate_exists(Domain) of + {Domain, Cert = #data_cert{pem=PemCert}} -> + ok = revoke_certificate2(CAUrl, PemCert), + ok = remove_certificate_persistent(Cert), {ok, deleted}; false -> - {error, not_found} + ?ERROR_MSG("Certificate for domain: ~p not found", [Domain]), + throw({error, not_found}) + end; +revoke_certificate1(CAUrl, {file, File}) -> + case file:read_file(File) of + {ok, Pem} -> + ok = revoke_certificate2(CAUrl, Pem), + {ok, deleted}; + {error, Reason} -> + ?ERROR_MSG("Error: ~p reading pem certificate-key file: ~p", [Reason, File]), + throw({error, Reason}) end. + --spec revoke_certificate1(url(), data_cert()) -> ok. -revoke_certificate1(CAUrl, Cert = #data_cert{pem=PemEncodedCert}) -> +-spec revoke_certificate2(url(), data_cert()) -> ok. +revoke_certificate2(CAUrl, PemEncodedCert) -> {Certificate, CertPrivateKey} = prepare_certificate_revoke(PemEncodedCert), {ok, Dirs, Nonce} = ejabberd_acme_comm:directory(CAUrl), Req = [{<<"certificate">>, Certificate}], {ok, [], Nonce1} = ejabberd_acme_comm:revoke_cert(Dirs, CertPrivateKey, Req, Nonce), - ok = remove_certificate_persistent(Cert), ok. +-spec parse_revoke_cert_argument(string()) -> {domain, bitstring()} | {file, file:filename()}. +parse_revoke_cert_argument([$f, $i, $l, $e, $:|File]) -> + {file, File}; +parse_revoke_cert_argument([$d, $o, $m, $a, $i, $n, $: | Domain]) -> + {domain, list_to_bitstring(Domain)}. + -spec prepare_certificate_revoke(pem()) -> bitstring(). prepare_certificate_revoke(PemEncodedCert) -> PemList = public_key:pem_decode(PemEncodedCert), diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index ceafed567..87ddb9327 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -270,8 +270,8 @@ get_commands_spec() -> #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}], + args_desc = ["The domain or file (in pem format) of the certificate in question {domain:Domain | file:File}"], + args = [{domain_or_file, string}], result = {res, restuple}}, #ejabberd_commands{name = import_piefxis, tags = [mnesia], @@ -602,8 +602,14 @@ list_certificates(Verbose) -> {invalid_option, String} end. -revoke_certificate(Domain) -> - ejabberd_acme:revoke_certificate(Domain). +revoke_certificate(DomainOrFile) -> + case ejabberd_acme:is_valid_revoke_cert(DomainOrFile) of + true -> + ejabberd_acme:revoke_certificate(DomainOrFile); + false -> + String = io_lib:format("Bad argument: ~s", [DomainOrFile]), + {invalid_argument, String} + end. %%% %%% Purge DB