NAME

Crypt::LE - Let's Encrypt API interfacing module.

VERSION

Version 0.02

SYNOPSIS

use Crypt::LE;
use File::Slurp;
   
my $le = Crypt::LE->new();
$le->load_account_key('account.pem');
$le->load_csr('domain.csr');
$le->register();
$le->accept_tos();
$le->request_challenge();
$le->accept_challenge(\&process_challenge);
$le->verify_challenge();
$le->request_certificate();
my $cert = $le->certificate();
write_file('domain.cert', $cert) if $cert;
...
sub process_challenge {
   my $challenge = shift;
   print "Challenge for $challenge->{domain} requires:\n";
   print "A file '/.well-known/acme-challenge/$challenge->{token}' with the text: $challenge->{token}.$challenge->{fingerprint}\n";
   print "When done, press <Enter>";
   <STDIN>;
   return 1;
};

DESCRIPTION

Crypt::LE provides the functionality necessary to use Let's Encrypt API and generate free SSL certificates for your domains. It can also be used to generate RSA keys and Certificate Signing Requests.

Crypt::LE can be easily extended with custom plugins to handle Let's Encrypt challenges. See Crypt::LE::Challenge::Simple module as an example of such plugin.

Crypt::LE is shipped with a self-sufficient client for obtaining SSL certificates - le.pl.

Usage example: le.pl --key account.key --csr domain.csr --csr-key domain.key --crt domain.crt --domains "www.domain.ext,domain.ext" --generate-missing

That will generate an account key and a CSR (plus key) if they are missing. If any of those files exist, they will just be loaded, so it is safe to re-run the client.

EXPORT

Crypt::LE does not export anything by default, but allows you to import the following constants:

  • OK

  • READ_ERROR

  • LOAD_ERROR

  • INVALID_DATA

  • DATA_MISMATCH

  • ERROR

To import all of those, use ':errors' tag:

use Crypt::LE ':errors';
...
$le->load_account_key('account.pem') == OK or die "Could not load the account key: " . $le->error_details;

If you don't want to use error codes while checking whether the last called method has failed or not, you can use the rule of thumb that on success it will return zero. You can also call error() or error_description() methods, which will be set with some values on error.

METHODS (API SETUP)

The following methods are provided for the API setup. Please note that account key setup by default requests the resource directory from Let's Encrypt servers. This can be changed by resetting the 'autodir' parameter of the constructor.

new()

Create a new instance of the class. Initialize the object with passed parameters. Normally you don't need to use any, but the following are supported:

ua

User-agent name to use while sending requests to Let's Encrypt servers. By default set to module name and version.

server

Server URL address to connect to (http or https prefix is required). Only needed if the default live or staging server URLs have changed and this module has not yet been updated with the new information. You can then explicitly set the URL you need.

live

Set to true to connect to a live Let's Encrypt server. By default it is not set, so staging server is used, where you can test the whole process of getting SSL certificates.

debug

Activates printing debug messages to the standard output when set. If set to 1, only standard messages are printed. If set to any greater value, then structures and server responses are printed as well.

autodir

Enables automatic retrieval of the resource directory (required for normal API processing) from Let's Encrypt servers. Enabled by default.

delay

Specifies the time in seconds to wait before Let's Encrypt servers are are checked for the challenge verification results again. By default set to 2 seconds. Non-integer values are supported (so for example you can set it to 1.5 if you like).

Returns: Crypt::LE object.

load_account_key($filename)

Loads the private account key from the file in PEM or DER formats.

Returns: OK | READ_ERROR | LOAD_ERROR | INVALID_DATA.

generate_account_key()

Generates a new private account key of the $keysize bits (4096 by default). The key is additionally validated for not being divisible by small primes.

Returns: OK | INVALID_DATA.

account_key()

Returns: A previously loaded or generated private key in PEM format or undef.

load_csr($filename [, $domains])

Loads Certificate Signing Requests from file. Domains list can be omitted or it can be given as a string of comma-separated names or as an array reference. If omitted, then names will be loaded from the CSR. If it is given, then the list of names will be verified against those found on CSR.

Returns: OK | READ_ERROR | LOAD_ERROR | INVALID_DATA | DATA_MISMATCH.

generate_csr($domains)

Generates a new Certificate Signing Requests based on a new RSA key of $keysize bits (4096 by default). Domains list is mandatory and can be given as a string of comma-separated names or as an array reference. Please note that you need Crypt::OpenSSL::PKCS10 module installed for this to work.

Returns: OK | ERROR | INVALID_DATA.

csr()

Returns: A previously loaded or generated CSR in PEM format or undef.

csr_key()

Returns: A private key of a previously generated CSR in PEM format or undef.

set_account_email([$email])

Sets (or resets if no parameter is given) an email address that will be used for registration requests.

Returns: OK | INVALID_DATA.

METHODS (API WORKFLOW)

The following methods are provided for the API workflow processing. All but accept_challenge() methods interact with Let's Encrypt servers.

directory()

Loads resource pointers from Let's Encrypt. This method needs to be called before the registration. It will be called automatically upon account key loading/generation unless you have reset the 'autodir' parameter when creating a new Crypt::LE instance.

Returns: OK | LOAD_ERROR.

register()

Registers an account key with Let's Encrypt. If the key is already registered, it will be handled automatically.

Returns: OK | ERROR.

accept_tos()

Accepts Terms of Service set by Let's Encrypt.

Returns: OK | ERROR.

request_challenge()

Requests challenges for domains on your CSR. On error you can call failed_domains() method, which returns an array reference to domain names for which the challenge was not requested successfully.

Returns: OK | ERROR.

accept_challenge($callback [, $type])

Sets up a callback, which will be called for each non-verified domain to satisfy the requested challenge. Each callback will receive one parameter - a hash reference with the following keys:

domain

The domain name being processed (lower-case)

token

The challenge token

fingerprint

The account key fingerprint

The type of the challenge accepted is optional and it is 'http' by default. The following values are currently available: 'http', 'tls', 'dns'. New values which might be added by Let's Encrypt will be supported automaticaly. On error you can call failed_domains() method, which returns an array reference to domain names for which the challenge was not accepted successfully.

The callback shoud return a true value on success.

The callback could be either a code reference (for example to a subroutine in your program) or a blessed reference to a module handling the challenge. In the latter case the module should have methods defined for handling appropriate challenge type, such as:

  • handle_challenge_http()

  • handle_challenge_tls()

  • handle_challenge_dns()

For example, you can create a module like this to handle the challenge:

package Crypt::LE::Challenge::Simple;

sub new { bless {}, shift }

sub handle_challenge_http {
 my $self = shift;
 my $challenge = shift;
 print "Domain: $challenge->{domain} expects '$challenge->{token}.$challenge->{fingerprint}' text in '$challenge->{token}' file\n";
 <STDIN>;
 return 1;
};

1;

You can then use it as shown below:

use Crypt::LE;
use Crypt::LE::Challenge::Simple;

my $le = Crypt::LE->new();
my $simple_challenge = Crypt::LE::Challenge::Simple->new();
...
$le->accept_challenge($simple_challenge);

If you create a plugin handling challenges for Crypt::LE, please use Crypt::LE::Challenge:: namespace, so it would be easier to find those. For a boilerplate of the plugin handling the challenges, see Crypt::LE::Challenge::Simple.

Returns: OK | ERROR | INVALID_DATA.

verify_challenge()

Asks Let's Encrypt server to verify the results of the challenge. On error you can call failed_domains() method, which returns an array reference to domain names for which the challenge was not verified successfully.

Returns: OK | ERROR.

request_certificate()

Requests the certificate for your CSR.

Returns: OK | ERROR.

request_issuer_certificate()

Requests the issuer's certificate.

Returns: OK | ERROR.

METHODS (COMMON GETTERS)

The following methods are the common getters you can use to get more details about the outcome of the workflow run and return some retrieved data, such as registration info and certificates for your domains.

tos()

Returns: The link to a Terms of Service document or undef.

tos_changed()

Returns: True if Terms of Service have been changed (or you haven't yet accepted them). Otherwise returns false.

new_registration()

Returns: True if new key has been registered. Otherwise returns false.

registration_info()

Returns: Registration information structure returned by Let's Encrypt for your key or undef.

certificate()

Returns: The last received certificate or undef.

certificate_url()

Returns: The URL of the last received certificate or undef.

issuer()

Returns: The issuer's certificate or undef.

issuer_url()

Returns: The URL of the issuer's certificate or undef.

domains()

Returns: An array reference to the domain names loaded from CSR or undef.

failed_domains()

Returns: An array reference to the domain names for which challenge processing has failed (on any of request/accept/verify steps) or undef.

error()

Returns: Last error (can be a code or a structure) or undef.

error_details()

Returns: Last error details if available or a generic 'error' string otherwise. Empty string if the last called method returned OK.

AUTHOR

Alexander Yezhov, <leader at cpan.org> Domain Knowledge Ltd. https://do-know.com/

BUGS

Considering that this module has been written in a rather quick manner after I decided to give a go to Let's Encrypt certificates and found that CPAN seems to be lacking some easy ways to leverage LE API from Perl, expect some bugs. The initial goal was to make this work, make it easy to use and possibly remove the need to use openssl command line.

Please report any bugs or feature requests to bug-crypt-le at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Crypt-LE. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Crypt::LE

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright 2016 Alexander Yezhov.

This program is free software; you can redistribute it and/or modify it under the terms of the the Artistic License (2.0). You may obtain a copy of the full license at:

http://www.perlfoundation.org/artistic_license_2_0

Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license.

If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license.

This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder.

This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed.

Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.