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; |