=encoding utf-8

=head1 NAME

Net::ACME - Client for the ACME protocol (e.g., Let’s Encrypt)

=head1 SYNOPSIS

    package MyACME::SomeService;

    sub _HOST { }   #return the name of the ACME host

    #See below for full examples.

=head1 DESCRIPTION

This module implements client logic for the ACME protocol,
the system for automated issuance of SSL certificates used by Let’s Encrypt.

The methods of this class return objects that correspond to the
respective ACME resource:

=over 4

=item * C<register()>: C<Net::ACME::Registration>

=item * C<start_domain_authz()>: C<Net::ACME::Authorization::Pending>

=item * C<get_certificate()>: C<Net::ACME::Certificate> or C<Net::ACME::Certificate::Pending>

=back

=head1 WHY USE THIS MODULE?

=over 4

=item * Closely based on cPanel’s widely used Let’s Encrypt plugin.

=item * Thorough error-checking: any deviation from what the ACME protocol
expects is reported immediately via an exception.

=item * Well-defined object system, including typed, queryable exceptions.

=item * Extensive test coverage.

=item * Light memory footprint - no Moose/Moo/etc.

=item * No careless overwriting of globals like C<$@>, C<$!>, and C<$?>.
(Hopefully your code isn’t susceptible to this anyway, but it’s just a good
precaution.)

=item * All dependencies are either core or pure Perl. For RSA crypto we use
L<Crypt::OpenSSL::RSA> if it’s available, or the system’s C<openssl> binary.
Net::ACME will run almost anywhere!

=back

=head1 STATUS

This module is now well-tested and should be safe for use in your application.

=head1 CUSTOMIZATION

B<OpenSSL>: This module will attempt to use L<Crypt::OpenSSL::RSA> if it is
available; if not, it falls back to the C<openssl> binary. On most systems this
should already be in the process’s search path, but if not, specify the binary’s
location by setting C<$Net::ACME::Crypt::OPENSSL_BIN_PATH>.

B<HTTPS options>: This module uses C<HTTP::Tiny> for its network operations.
In some instances it is desirable to specify custom C<SSL_options> in that
module’s constructor; to do this, populate
C<@Net::ACME::HTTP_Tiny::SSL_OPTIONS>.

=head1 URI vs. URL

This module uses “uri” for ACME-related objects and “url” for
HTTP-related ones. This apparent conflict is a result of maintaining
consistency with both the ACME specification (“uri”) and L<HTTP::Tiny> (“url”).

=head1 EXAMPLES

See the C<examples> directory in the distribution for complete, interactive
example scripts that also illustrate a bit of how ACME works.

See below for cut-paste-y examples.

=head1 EXAMPLE: REGISTRATION

    my $tos_url = Net::ACME::LetsEncrypt->get_terms_of_service();

    my $acme = Net::ACME::LetsEncrypt->new( key => $reg_rsa_pem );

    #Use this method any time you want to update contact information,
    #not just when you set up a new account.
    my $reg = $acme->register('mailto:who.am@i.tld', 'mailto:who@else.tld');

    $acme->accept_tos( $reg->uri(), $tos_url );

=head1 EXAMPLE: DOMAIN AUTHORIZATION & CERTIFICATE PROCUREMENT

    for my $domain (@domains) {
        my $authz_p = $acme->start_domain_authz($domain);

        for my $cmb_ar ( $authz_p->combinations() ) {

            #$cmb_ar is a set of challenges that the ACME server will
            #accept as proof of domain control. As of November 2016, these
            #sets all contain exactly one challenge each: “http-01”, etc.

            #Each member of @$cmb_ar is an instance of
            #Net::ACME::Challenge::Pending--maybe a subclass thereof such as
            #Net::ACME::Challenge::Pending::http_01.

            #At this point, you examine $cmb_ar and determine if this
            #combination is one that you’re interested in. You might try
            #something like:
            #
            #   next if @$cmb_ar > 1;
            #   next if $cmb_ar->[0]->type() ne 'http-01';

            #Once you’ve examined $cmb_ar and set up the appropriate response(s),
            #it’s time to tell the ACME server to send its challenge query.
            $acme->do_challenge($_) for @$cmb_ar;

            while (1) {
                if ( $authz_p->is_time_to_poll() ) {
                    my $poll = $authz_p->poll();

                    last if $poll->status() eq 'valid';

                    if ( $poll->status() eq 'invalid' ) {
                        my @failed = grep { $_->error() } $poll->challenges();

                        warn $_->to_string() . $/ for @failed;

                        die "Failed authorization for “$domain”!";
                    }

                }

                sleep 1;
            }
        }
    }

    #Make a key and CSR.
    #Creation of CSRs is well-documented so won’t be discussed here.

    my $cert = $acme->get_certificate($csr_pem);

    #This shouldn’t actually be necessary for Let’s Encrypt,
    #but the ACME protocol describes it.
    while ( !$cert->pem() ) {
        sleep 1;
        next if !$cert->is_time_to_poll();
        $cert = $cert->poll() || $cert;
    }

=head1 TODO

=over 4

=item * Find a way to sign with RSA in pure Perl. (NB: L<Crypt::RSA> has XS dependencies.)

=item * Support EC keys.

=item * Test and support more ACME v2 features (pending server support).

=back

=head1 THANKS

=over 4

=item * cPanel, Inc. for permission to adapt their ACME framework for
public consumption.

=item * Stephen Ludin for developing and maintaining C<Protocol::ACME>, from which
this module took its inspiration.

=back

=head1 SEE ALSO

I am aware of the following additional CPAN modules that implement this protocol:

=over 4

=item * L<Protocol::ACME>

=item * L<Crypt::LE>

=item * L<WWW::LetsEncrypt>

=item * L<Mojo::ACME>

=back

=head1 REPOSITORY (FEEDBACK/BUGS)

L<https://github.com/FGasper/p5-Net-ACME>

=head1 AUTHOR

Felipe Gasper (FELIPE)

=head1 LICENSE

This module is licensed under the same terms as Perl.