algesten/acme-lib

acme-lib is a library for accessing ACME (Automatic Certificate Management Environment)

services such as http_challenge and authorizations

acme-lib

services such as Let's Encrypt.

Uses ACME v2 to issue/renew certificates.

Example

use acme_lib::{Error, Directory, DirectoryUrl};
use acme_lib::persist::FilePersist;
use acme_lib::create_p384_key;

fn request_cert() -> Result<(), Error> {

// Use DirectoryUrl::LetsEncrypStaging for dev/testing.
let url = DirectoryUrl::LetsEncrypt;

// Save/load keys and certificates to current dir.
let persist = FilePersist::new(".");

// Create a directory entrypoint.
let dir = Directory::from_url(persist, url)?;

// Reads the private account key from persistence, or
// creates a new one before accessing the API to establish
// that it's there.
let acc = dir.account("[email protected]")?;

// Order a new TLS certificate for a domain.
let mut ord_new = acc.new_order("mydomain.io", &[])?;

// If the ownership of the domain(s) have already been
// authorized in a previous order, you might be able to
// skip validation. The ACME API provider decides.
let ord_csr = loop {
    // are we done?
    if let Some(ord_csr) = ord_new.confirm_validations() {
        break ord_csr;
    }

    // Get the possible authorizations (for a single domain
    // this will only be one element).
    let auths = ord_new.authorizations()?;

    // For HTTP, the challenge is a text file that needs to
    // be placed in your web server's root:
    //
    // /var/www/.well-known/acme-challenge/<token>
    //
    // The important thing is that it's accessible over the
    // web for the domain(s) you are trying to get a
    // certificate for:
    //
    // http://mydomain.io/.well-known/acme-challenge/<token>
    let chall = auths[0].http_challenge();

    // The token is the filename.
    let token = chall.http_token();
    let path = format!(".well-known/acme-challenge/{}", token);

    // The proof is the contents of the file
    let proof = chall.http_proof();

    // Here you must do "something" to place
    // the file/contents in the correct place.
    // update_my_web_server(&path, &proof);

    // After the file is accessible from the web, the calls
    // this to tell the ACME API to start checking the
    // existence of the proof.
    //
    // The order at ACME will change status to either
    // confirm ownership of the domain, or fail due to the
    // not finding the proof. To see the change, we poll
    // the API with 5000 milliseconds wait between.
    chall.validate(5000)?;

    // Update the state against the ACME API.
    ord_new.refresh()?;
};

// Ownership is proven. Create a private key for
// the certificate. These are provided for convenience, you
// can provide your own keypair instead if you want.
let pkey_pri = create_p384_key();

// Submit the CSR. This causes the ACME provider to enter a
// state of "processing" that must be polled until the
// certificate is either issued or rejected. Again we poll
// for the status change.
let ord_cert =
    ord_csr.finalize_pkey(pkey_pri, 5000)?;

// Now download the certificate. Also stores the cert in
// the persistence.
let cert = ord_cert.download_and_save_cert()?;

Ok(())
}

Domain ownership

Most website TLS certificates tries to prove ownership/control over the domain they are issued for. For ACME, this means proving you control either a web server answering HTTP requests to the domain, or the DNS server answering name lookups against the domain.

To use this library, there are points in the flow where you would need to modify either the web server or DNS server before progressing to get the certificate.

See http_challenge and dns_challenge.

Multiple domains

When creating a new order, it's possible to provide multiple alt-names that will also be part of the certificate. The ACME API requires you to prove ownership of each such domain. See authorizations.

Rate limits

The ACME API provider Let's Encrypt uses rate limits to ensure the API i not being abused. It might be tempting to put the delay_millis really low in some of this libraries' polling calls, but balance this against the real risk of having access cut off.

Use staging for dev!

Especially take care to use the Let`s Encrypt staging environment for development where the rate limits are more relaxed.

See DirectoryUrl::LetsEncryptStaging.

Implementation details

The library tries to pull in as few dependencies as possible. (For now) that means using synchronous I/O and blocking cals. This doesn't rule out a futures based version later.

It is written by following the ACME draft spec 18, and relies heavily on the openssl crate to make JWK/JWT and sign requests to the API.

License: MIT

Issues

Collection of the latest Issues

trevyn

trevyn

Comment Icon0

The current ureq 1.5.5 dependency is pulling in an extra old version of rustls (0.19); since I noticed that you're also the author of ureq, is this an easy update?

null-dev

null-dev

Comment Icon0

I'm running into a case where if I pass a non-wildcard name into new_order as the primary name but include a wildcard name in the aliases, the cert is persisted under the wildcard name instead of the non-wildcard primary name. I suspect this is happening because:

Section 7.4 of the ACME spec states that:

Clients MUST NOT make any assumptions about the sort order of "identifiers" or "authorizations" elements in the returned order object.

This means that the primary_name that I pass into new_order might not actually be used to persist the cert here as the order of the domains could change: https://github.com/algesten/acme-lib/blob/9d4702c986af394352bffb3deb4d7d647712caec/src/order/mod.rs#L293

The spec is a bit confusing because it also says the following in Section 7.1.3:

The elements of the "authorizations" and "identifiers" arrays are immutable once set. The server MUST NOT change the contents of either array after they are created. If a client observes a change in the contents of either array, then it SHOULD consider the order invalid.

I believe they just mean that entries can't be added/deleted though and that the ordering of entries can still change.

algesten

algesten

Comment Icon3

#15 #16 and #17 all point to a similar story: The desire to have a persistence that is less controlled by acme-lib.

The current idea is that the user either uses the default persistence implementations or implements Persist, which is a simple key/value protocol. The question is what is lacking in this approach?

gorup

gorup

Comment Icon4

Hi!

As long as you consider your library somewhat production ready, you should add this library to the list of libraries on Let's Encrypt's website: here. There are directions on how to add the library to that list at the bottom of that page. I found your lib just poking around crates.io, but it would be helpful to have this posted on that list.

PS Thanks for the lib, I am going to try to work it into a library I have!

Information - Updated Feb 25, 2022

Stars: 49
Forks: 25
Issues: 20

Repositories & Extras

Rust bindings for libinjection

Add libinjection to dependencies of Cargo

Rust bindings for libinjection

Rust library for Self Organising Maps (SOM)

Add rusticsom as a dependency in Cargo

Rust library for Self Organising Maps (SOM)

Rust bindings for the C++ api of PyTorch

LIghtweight wrapper for pytorch eg libtorch in rust

Rust bindings for the C++ api of PyTorch

Rust leveldb bindings

Almost-complete bindings for leveldb for Rust

Rust leveldb bindings

rust-analyzer is a modular compiler frontend for the Rust language

It also contains some tips &amp; tricks to help you be more productive when using rust-analyzer

rust-analyzer is a modular compiler frontend for the Rust language

Rust-Lightning is a Bitcoin Lightning library written in Rust

lightning, does not handle networking, persistence, or any other I/O

Rust-Lightning is a Bitcoin Lightning library written in Rust

Rust FUSE - Filesystem in Userspace

Rust library crate for easy implementation of Crate documentation

Rust FUSE - Filesystem in Userspace

Rust crate to implement a counterpart to the PBRT book's (3rd edition) C++ code:

Some images of the test scenes are shown below, but you can find more

Rust crate to implement a counterpart to the PBRT book's (3rd edition) C++ code:

Rust Persian Calendar

1** provides functionality for conversion among Persian (Solar Hijri) and Gregorian calendars

Rust Persian Calendar

Rustorm is an SQL-centered ORM with focus on ease of use on conversion of database...

This project exists thanks to all the people who contribute

Rustorm is an SQL-centered ORM with focus on ease of use on conversion of database...

The Rust Programming Language for Espressif chips

This fork enables projects to be built for the Xtensa-based ESP32, ESP32-SXX and ESP8266 using esp-rs organization has been formed to develop runtime, pac and...

The Rust Programming Language for Espressif chips

A cross-platform, fast, and safe general purpose C library written in Rust

The library is organized as a series of modules

A cross-platform, fast, and safe general purpose C library written in Rust
Facebook Instagram Twitter GitHub Dribbble
Privacy