diff options
| -rw-r--r-- | Changelog | 6 | ||||
| -rw-r--r-- | INSTALL | 10 | ||||
| -rwxr-xr-x | client | 100 | 
3 files changed, 76 insertions, 40 deletions
| @@ -1,3 +1,9 @@ +lacme (0.6) UNRELEASED + + + client: poll order URL instead of each authz URL successively. + + -- Guilhem Moulin <guilhem@fripost.org>  Mon, 21 Jan 2019 02:07:58 +0100 +  lacme (0.5) upstream;    + Use ACME v2 endpoints (update protocol to the last draft of the spec @@ -20,6 +20,7 @@ lacme depends on OpenSSL and the following Perl modules:    - Config::Tiny    - Digest::SHA (core module) +  - Date::Parse    - Errno (core module)    - Fcntl (core module)    - File::Temp (core module) @@ -37,7 +38,14 @@ lacme depends on OpenSSL and the following Perl modules:  On Debian GNU/Linux systems, these dependencies can be installed with  the following command: -    apt-get install openssl libconfig-tiny-perl libjson-perl libwww-perl liblwp-protocol-https-perl libnet-ssleay-perl libtypes-serialiser-perl +    apt-get install openssl \ +        libconfig-tiny-perl \ +        libtimedate-perl \ +        libjson-perl \ +        libwww-perl \ +        liblwp-protocol-https-perl \ +        libnet-ssleay-perl \ +        libtypes-serialiser-perl  However Debian GNU/Linux users can also use gbp(1) from git-buildpackage  to build their own package: @@ -142,6 +142,22 @@ sub request_status_line($) {      return $msg;  } +# The request's Retry-After header (RFC 7231 sec. 7.1.3), converted to +# waiting time in seconds. +sub request_retry_after($) { +    my $r = shift; +    my $v = $r->header('Retry-After'); +    if (defined $v and $v !~ /\A\d+\z/) { +        require 'Date/Parse.pm'; +        $v = Date::Parse::str2time($v); +        if (defined $v) { +            $v = $v - time; +            undef $v if $v <= 0; +        } +    } +    return $v; +} +  # Parse and return the request's decoded content as JSON; or print the  # Status Line and fail if the request failed.  # If $dump is set, also pretty-print the decoded content. @@ -262,6 +278,7 @@ elsif ($COMMAND eq 'newOrder') {      my @identifiers = map {{ type => 'dns', value => $_ }} @ARGV;      my $r = acme_resource('newOrder', identifiers => \@identifiers);      my $order = request_json_decode($r); +    my $orderurl = $r->header('Location');      foreach (@{$order->{authorizations}}) {          my $authz = request_json_decode(request(GET => $_)); @@ -287,52 +304,57 @@ elsif ($COMMAND eq 'newOrder') {          } else {              die "Can't open $challenge->{token}: $!";          } - -        $r = acme($challenge->{url}); - -        # poll until the status become 'valid' -        # XXX poll the order URL instead, to get the status of all -        # challenges at once -        # https://github.com/letsencrypt/boulder/issues/3530 -        for ( my $i = 0, my $resp, my $status; -              $resp = request_json_decode($r), -              $status = $resp->{status} // 'pending', -              $status ne 'valid'; -              $r = request('GET' => $challenge->{url})) { -            if (defined (my $problem = $resp->{error})) { # problem document (RFC 7807) -                my $msg = $problem->{status}; -                $msg .= " " .$problem->{title}      if defined $problem->{title}; -                $msg .= " (".$problem->{detail}.")" if defined $problem->{detail}; -                die $msg, "\n"; -            } -            die "Error: Invalid challenge for $identifier (status: ".$status.")\n" -                if $status ne 'pending'; - -            my $sleep = 1; -            if (defined (my $retry_after = $r->header('Retry-After'))) { -                print STDERR "Retrying after $retry_after seconds...\n"; -                $sleep = $retry_after; -            } - -            $i += $sleep; -            die "Timeout exceeded while waiting for challenges to pass ($identifier)\n" -                if $timeout > 0 and $i >= $timeout; -            sleep $sleep; -        } +        my $r = acme($challenge->{url}); +        request_json_decode($r);      } -    $r = acme($order->{finalize}, csr => encode_base64url($csr)); -    my $resp = request_json_decode($r); +    # poll the order URL (to get the status of all challenges at once) +    # until the status become 'valid' +    my $orderstr = join(', ', map {uc($_->{type}) .":". $_->{value}} @identifiers); +    my $certuri; +    for (my $i = 0;;) { +        my $r = request('GET' => $orderurl); +        my $resp = request_json_decode($r); +        if (defined (my $problem = $resp->{error})) { # problem document (RFC 7807) +            my $msg = $problem->{status}; +            $msg .= " " .$problem->{title}      if defined $problem->{title}; +            $msg .= " (".$problem->{detail}.")" if defined $problem->{detail}; +            die $msg, "\n"; +        } +        my $status = $resp->{status}; +        if (!defined $status or $status eq "invalid") { +            die "Error: Invalid order $orderstr\n"; +        } +        elsif ($status eq "ready") { +            my $r = acme($order->{finalize}, csr => encode_base64url($csr)); +            my $resp = request_json_decode($r); +            $certuri = $resp->{certificate}; +            last; +        } +        elsif ($status eq "valid") { +            $certuri = $resp->{certificate} // +                die "Error: Missing \"certificate\" field in \"valid\" order\n"; +            last; +        } +        elsif ($status ne "pending" and $status ne "processing") { +            warn "Unknown order status: $status\n"; +        } -    my $uri = $resp->{certificate}; -    print STDERR "Certificate URI: $uri\n"; +        my $retry_after = request_retry_after($r) // 1; +        print STDERR "Retrying after $retry_after seconds...\n"; +        $i += $retry_after; +        die "Timeout exceeded while waiting for challenges to pass ($orderstr)\n" +            if $timeout > 0 and $i >= $timeout; +        sleep $retry_after; +    } -    # pool until the cert is available +    # poll until the cert is available +    print STDERR "Certificate URI: $certuri\n";      for (my $i = 0;;) { -        $r = request('GET' => $uri); +        $r = request('GET' => $certuri);          die request_status_line($r), "\n" unless $r->is_success();          last unless $r->code == 202; # Accepted -        my $retry_after = $r->header('Retry-After') // 1; +        my $retry_after = request_retry_after($r) // 1;          print STDERR "Retrying after $retry_after seconds...\n";          $i += $retry_after;          die "Timeout exceeded while waiting for certificate\n" if $timeout > 0 and $i >= $timeout; | 
