NAME
Net::ACME2 - Client logic for the ACME (Let's Encrypt) protocol
SYNOPSIS
package SomeCA::ACME;
use parent qw( Net::ACME2 );
use constant {
DIRECTORY_PATH => '/acme-directory',
};
# %opts are the parameters given to new().
sub HOST {
my ($class, %opts) = @_;
# You can make this depend on the %opts if you want.
return 'acme.someca.net';
}
package main;
my $acme = SomeCA::ACME->new(
key => $account_key_pem_or_der,
key_id => undef,
);
#for a new account
{
my $terms_url = $acme->get_terms_of_service();
$acme->create_account(
termsOfServiceAgreed => 1,
);
}
#Save $acme->key_id() somewhere so you can use it again.
my $order = $acme->create_order(
identifiers => [
{ type => 'dns', value => '*.example.com' },
],
);
my $authz = $acme->get_authorization( ($order->authorizations())[0] );
my @challenges = $authz->challenges();
# ... Pick a challenge, and satisfy it.
$acme->accept_challenge($challenge);
sleep 1 while 'valid' ne $acme->poll_authorization($authz);
# ... Make a key and CSR for *.example.com
$acme->finalize_order($order, $csr_pem_or_der);
while ($order->status() ne 'valid') {
sleep 1;
$acme->poll_order($order);
}
# ... and now fetch the certificate chain:
my $pem_chain = $acme->get_certificate_chain($order);
See /examples in the distribution for more fleshed-out examples.
To use Let’s Encrypt, see Net::ACME2::LetsEncrypt.
DESCRIPTION
This library implements client logic for the ACME (Automated Certificate Management Environment) protocol, as standardized in RFC 8555 and popularized by Let’s Encrypt.
STATUS
This is a production-grade implementation. While breaking changes at this point are unlikely, please always check the changelog before upgrading to a new version of this module.
FEATURES
Support for both ECDSA and RSA encrytion.
Support for http-01, dns-01, and tls-alpn-01 challenges.
Comprehensive error handling with typed, X::Tiny-based exceptions.
Supports blocking and (experimentally) non-blocking I/O.
This is a pure-Perl solution. Most of its dependencies are either core modules or pure Perl themselves. XS is necessary to communicate with the ACME server via TLS; however, most Perl installations already include the necessary logic (i.e., Net::SSLeay) for TLS.
In short, Net::ACME2 will run anywhere that Perl can speak TLS, which is almost everywhere that Perl runs.
ERROR HANDLING
All thrown exceptions are instances of Net::ACME2::X::Generic. Specific error classes aren’t yet defined.
CRYPTOGRAPHY & SPEED
Crypt::Perl provides all cryptographic operations that this library needs using pure Perl. While this satisfies this module’s intent to be as pure-Perl as possible, there are a couple of significant drawbacks to this approach: firstly, it’s slower than XS-based code, and secondly, it loses the security benefits of the vetting that more widely-used cryptography libraries receive.
To address these problems, Net::ACME2 will, after parsing a key, look for and prefer the following XS-based libraries for cryptography instead:
Crypt::OpenSSL::RSA (based on OpenSSL)
CryptX (based on LibTomCrypt)
If the above are unavailable to you, then you may be able to speed up your Math::BigInt installation; see that module’s documentation for more details.
EXPERIMENTAL: NON-BLOCKING (ASYNCHRONOUS) I/O
By default, Net::ACME2 uses blocking I/O.
To facilitate asynchronous/non-blocking I/O, you may give an async_ua
to new()
. This value must be an object that implements request()
. That method should mimic HTTP::Tiny’s method of the same name except that, instead of returning a hash reference, it should return a promise. (à la Promise::XS, Promise::ES6, Mojo::Promise, etc.) That promise’s resolution should be a single value that mimics HTTP::Tiny::request()
’s return structure.
When a Net::ACME2 instance is created with async_ua
, several of the methods described below return promises. These promises resolve to the values that otherwise would be returned directly in synchronous mode. Any exception that would be thrown in synchronous mode is given as the promise’s rejection value. This document’s convention to indicate a function that, in asynchronous mode, returns a promise is:
promise($whatever) = ...
This distribution ships with Net::ACME2::Curl, a wrapper around Net::Curl::Promiser, which in turns wraps Net::Curl::Multi. This provides out-of-the-box support for Perl’s most widely-used event interfaces; see Net::Curl::Promiser’s documentation for more details.
METHODS
CLASS->new( %OPTS )
Instantiates an ACME2 object, which you’ll use for all interactions with the ACME server. %OPTS is:
key
- Required. The private key to associate with the ACME2 user. Anything thatCrypt::Perl::PK::parse_key()
can parse is acceptable.key_id
- Optional. As returned bykey_id()
. Saves a round-trip to the ACME2 server, so you should give this if you have it.directory
- Optional. A hash reference to use as the directory contents. Saves a round-trip to the ACME2 server, but there’s no built-in logic to determine when the cache goes invalid. Caveat emptor.async_ua
- Optional. Provides a custom UA object to facilitate non-blocking I/O. This object MUST implement the interface described above.
$id = OBJ->key_id()
Returns the object’s cached key ID, either as given at instantiation or as fetched in create_account()
.
OBJ->http_timeout( [$NEW] )
A passthrough interface to the underlying HTTP::Tiny object’s timeout()
method.
Throws an exception if async_ua
was given to new()
.
promise($url) = CLASS->get_terms_of_service()
Returns the URL for the terms of service. Callable as either a class method or an instance method.
promise($created_yn) = OBJ->create_account( %OPTS )
Creates an account using the ACME2 object’s key and the passed %OPTS, which are as described in the ACME2 spec (cf. newAccount
). Boolean values may be given as simple Perl booleans.
Returns 1 if the account is newly created or 0 if the account already existed.
NB: create_new_account()
is an alias for this method.
promise($order) = OBJ->create_order( %OPTS )
Returns a Net::ACME2::Order object. %OPTS is as described in the ACME spec (cf. newOrder
). Boolean values may be given as simple Perl booleans.
NB: create_new_order()
is an alias for this method.
promise($authz) = OBJ->get_authorization( $URL )
Fetches the authorization’s information based on the given $URL and returns a Net::ACME2::Authorization object.
The URL is as given by Net::ACME2::Order’s authorizations()
method.
$str = OBJ->make_key_authorization( $CHALLENGE )
Accepts an instance of Net::ACME2::Challenge (probably a subclass thereof) and returns a key authorization string suitable for handling the given $CHALLENGE. See /examples in the distribution for example usage.
If you’re using HTTP authorization and are on the same server as the domains’ document roots, then look at the handler logic in Net::ACME2::Challenge::http_01 for a potentially simpler way to handle HTTP challenges.
promise() = OBJ->accept_challenge( $CHALLENGE )
Signal to the ACME server that the CHALLENGE is ready.
promise($status) = OBJ->poll_authorization( $AUTHORIZATION )
Accepts a Net::ACME2::Authorization instance and polls the ACME server for that authorization’s status. The $AUTHORIZATION object is then updated with the results of the poll.
As a courtesy, this returns the $AUTHORIZATION’s new status()
.
promise($status) = OBJ->finalize_order( $ORDER, $CSR )
Finalizes an order and updates the $ORDER object with the returned status. $CSR may be in either DER or PEM format.
As a courtesy, this returns the $ORDER’s status()
. If this does not equal valid
, then you should probably poll_order()
until it does.
promise($status) = OBJ->poll_order( $ORDER )
Like poll_authorization()
but handles a Net::ACME2::Order object instead.
promise($cert) = OBJ->get_certificate_chain( $ORDER )
Fetches the $ORDER’s certificate chain and returns it in the format implied by the application/pem-certificate-chain
MIME type. See the ACME protocol specification for details about this format.
TODO
Add pre-authorization support if there is ever a production use for it.
Expose the Retry-After header via the module API.
There is currently no way to fetch an order or challenge’s properties via URL. Prior to ACME’s adoption of “POST-as-GET” this was doable via a plain GET to the URL, but that’s no longer possible. If there’s a need, I’ll consider adding such logic to Net::ACME2. (It’s trivial to add; I’d just like to keep things as simple as possible.)
Add (more) tests.
SEE ALSO
Crypt::LE is another ACME client library.
Crypt::Perl provides this library’s default cryptography backend. See this distribution’s /examples directory for sample usage to generate keys and CSRs.
Net::ACME implements client logic for the variant of this protocol that Let’s Encrypt first deployed.