acme-client 0.4.0

Easy to use ACME client library to issue, renew and revoke TLS certificates
Documentation

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::Directory;

let directory = Directory::lets_encrypt().unwrap();
let account = directory.account_registration().register().unwrap();

// Create a identifier authorization for example.com
let authorization = account.authorization("example.com").unwrap();

// Validate ownership of example.com with http challenge
let http_challenge = authorization.get_http_challenge().unwrap();
http_challenge.save_key_authorization("/var/www").unwrap();
http_challenge.validate().unwrap();

let cert = account.certificate_signer(&["example.com"]).sign_certificate().unwrap();
cert.save_signed_certificate("certificate.pem").unwrap();
cert.save_private_key("certificate.key").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::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).unwrap();
    // ...
}

let cert = account.certificate_signer(&domains).sign_certificate().unwrap();
cert.save_signed_certificate("certificate.pem").unwrap();
cert.save_private_key("certificate.key").unwrap();

Account registration

use acme_client::Directory;

let directory = Directory::lets_encrypt().unwrap();
# // Use staging directory for doc test
# let directory = Directory::from_url("https://acme-staging.api.letsencrypt.org/directory")
#   .unwrap();
let account = directory.account_registration()
                       .email("example@example.org")
                       .register()
                       .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 in order to to sign a certificate. 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::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# // Use staging directory for doc test
# 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").unwrap();

Authorization object will contain challenges created by ACME server. You can create as many Authorization object as you want to verifiy ownership of the domain names. For example if you want to sign a certificate for example.com and example.org:

# use acme_client::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# // Use staging directory for doc test
# let directory = Directory::from_url("https://acme-staging.api.letsencrypt.org/directory")
#   .unwrap();
# let account = directory.account_registration().register().unwrap();
let domains = ["example.com", "example.org"];
for domain in domains.iter() {
    let authorization = account.authorization(domain).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::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# // Use staging directory for doc test
# let directory = Directory::from_url("https://acme-staging.api.letsencrypt.org/directory")
#   .unwrap();
# let account = directory.account_registration()
#                        .pkey_from_file("tests/user.key").unwrap() // use test key for doc test
#                        .register()
#                        .unwrap();
let authorization = account.authorization("example.com").unwrap();
let http_challenge = authorization.get_http_challenge().unwrap();

// This method will save key authorization into
// /var/www/.well-known/acme-challenge/ directory.
http_challenge.save_key_authorization("/var/www").unwrap();

// Validate ownership of example.com with http challenge
http_challenge.validate().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::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# // Use staging directory for doc test
# let directory = Directory::from_url("https://acme-staging.api.letsencrypt.org/directory")
#   .unwrap();
# let account = directory.account_registration()
#                        .pkey_from_file("tests/user.key").unwrap() // use test key for doc test
#                        .register()
#                        .unwrap();
let authorization = account.authorization("example.com").unwrap();
let dns_challenge = authorization.get_dns_challenge().unwrap();
let signature = dns_challenge.signature().unwrap();

// User creates a TXT record for _acme-challenge.example.com with the value of signature.

// Validate ownership of example.com with DNS challenge
dns_challenge.validate().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::Directory;
# let directory = Directory::lets_encrypt().unwrap();
# let account = directory.account_registration().register().unwrap();
let domains = ["example.com", "example.org"];

// ... validate ownership of domain names

let certificate_signer = account.certificate_signer(&domains);
let cert = certificate_signer.sign_certificate().unwrap();
cert.save_signed_certificate("certificate.pem").unwrap();
cert.save_private_key("certificate.key").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::Directory;
# let directory = Directory::lets_encrypt().unwrap();
let account = directory.account_registration()
                       .pkey_from_file("user.key").unwrap()
                       .register().unwrap();
account.revoke_certificate_from_file("certificate.pem").unwrap();

References