Easy to use Let's Encrypt compatible
Automatic Certificate Management Environment (ACME) client.
API overview
To successfully sign a SSL certificate for a domain name, you need to identify ownership of
your domain. You can also identify and sign certificate for multiple domain names and
explicitly use your own private keys and certificate signing request (CSR),
otherwise this library will generate them. Basic usage of acme-client
:
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
use acme_client::Directory;
let directory = Directory::lets_encrypt()?;
let account = directory.account_registration().register()?;
let authorization = account.authorization("example.com")?;
let http_challenge = authorization.get_http_challenge().ok_or("HTTP challenge not found")?;
http_challenge.save_key_authorization("/var/www")?;
http_challenge.validate()?;
let cert = account.certificate_signer(&["example.com"]).sign_certificate()?;
cert.save_signed_certificate("certificate.pem")?;
cert.save_private_key("certificate.key")?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
acme-client
supports signing a certificate for multiple domain names with SAN. You need to
validate ownership of each domain name:
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
use acme_client::Directory;
let directory = Directory::lets_encrypt()?;
let account = directory.account_registration().register()?;
let domains = ["example.com", "example.org"];
for domain in domains.iter() {
let authorization = account.authorization(domain)?;
}
let cert = account.certificate_signer(&domains).sign_certificate()?;
cert.save_signed_certificate("certificate.pem")?;
cert.save_private_key("certificate.key")?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
Account registration
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
use acme_client::Directory;
let directory = Directory::lets_encrypt()?;
let account = directory.account_registration()
.email("example@example.org")
.register()?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
Contact email address is optional. You can also use your own private key during
registration. See AccountRegistration helper for more
details.
If you already registed with your own keys before, you still need to use
register
method,
in this case it will identify your user account instead of creating a new one.
Identifying ownership of domain name
Before sending a certificate signing request to an ACME server, you need to identify ownership
of domain names you want to sign a certificate for. To do that you need to create an
Authorization object for a domain name and fulfill at least one challenge (http or dns for
Let's Encrypt).
To create an Authorization object for a domain:
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
# use acme_client::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# # let directory = Directory::from_url("https://acme-staging.api.letsencrypt.org/directory")
# .unwrap();
# let account = directory.account_registration().register().unwrap();
let authorization = account.authorization("example.com")?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
Authorization object will contain challenges created by
ACME server. You can create as many Authorization object as you want to verify ownership
of the domain names. For example if you want to sign a certificate for
example.com
and example.org
:
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
# use acme_client::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# let account = directory.account_registration().register().unwrap();
let domains = ["example.com", "example.org"];
for domain in domains.iter() {
let authorization = account.authorization(domain)?;
}
# Ok(()) }
# fn main () { try_main().unwrap(); }
Identifier validation challenges
When you send authorization request to an ACME server, it will generate
identifier validation challenges to provide assurence that an account holder is also
the entity that controls an identifier.
HTTP challenge
With HTTP validation, the client in an ACME transaction proves its
control over a domain name by proving that it can provision resources
on an HTTP server that responds for that domain name.
acme-client
has
save_key_authorization
method
to save vaditation file to a public directory. This directory must be accessible to outside
world.
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
# use acme_client::Directory;
# let directory = Directory::lets_encrypt()?;
# let account = directory.account_registration()
# .pkey_from_file("tests/user.key")? # .register()?;
let authorization = account.authorization("example.com")?;
let http_challenge = authorization.get_http_challenge().ok_or("HTTP challenge not found")?;
http_challenge.save_key_authorization("/var/www")?;
http_challenge.validate()?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
During validation, ACME server will check
http://example.com/.well-known/acme-challenge/{token}
to identify ownership of domain name.
You need to make sure token is publicly accessible.
DNS challenge:
The DNS challenge requires the client to provision a TXT record containing a designated
value under a specific validation domain name.
acme-client
can generated this value with
signature
method.
The user constructs the validation domain name by prepending the label "_acme-challenge"
to the domain name being validated, then provisions a TXT record with the digest value under
that name. For example, if the domain name being validated is "example.com", then the client
would provision the following DNS record:
_acme-challenge.example.com: dns_challenge.signature()
Example validation with DNS challenge:
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
# use acme_client::Directory;
# let directory = Directory::lets_encrypt()?;
# let account = directory.account_registration()
# .pkey_from_file("tests/user.key")? # .register()?;
let authorization = account.authorization("example.com")?;
let dns_challenge = authorization.get_dns_challenge().ok_or("DNS challenge not found")?;
let signature = dns_challenge.signature()?;
dns_challenge.validate()?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
Signing a certificate
After validating all the domain names you can send a sign certificate request. acme-client
provides CertificateSigner
helper for this. You can
use your own key and CSR or you can let CertificateSigner
to generate them for you.
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
# use acme_client::Directory;
# let directory = Directory::lets_encrypt()?;
# let account = directory.account_registration().register()?;
let domains = ["example.com", "example.org"];
let certificate_signer = account.certificate_signer(&domains);
let cert = certificate_signer.sign_certificate()?;
cert.save_signed_certificate("certificate.pem")?;
cert.save_private_key("certificate.key")?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
Revoking a signed certificate
You can use revoke_certificate
or revoke_certificate_from_file
methods to revoke a signed
certificate. You need to register with the same private key you registered before to
successfully revoke a signed certificate. You can also use private key used to generate CSR.
# use acme_client::error::Result;
# fn try_main() -> Result<()> {
# use acme_client::Directory;
# let directory = Directory::lets_encrypt()?;
let account = directory.account_registration()
.pkey_from_file("user.key")?
.register()?;
account.revoke_certificate_from_file("certificate.pem")?;
# Ok(()) }
# fn main () { try_main().unwrap(); }
References