aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog6
-rw-r--r--INSTALL10
-rwxr-xr-xclient100
3 files changed, 76 insertions, 40 deletions
diff --git a/Changelog b/Changelog
index 633222a..31ad97d 100644
--- a/Changelog
+++ b/Changelog
@@ -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
diff --git a/INSTALL b/INSTALL
index 82de279..155c7aa 100644
--- a/INSTALL
+++ b/INSTALL
@@ -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:
diff --git a/client b/client
index 838b184..9ce52a0 100755
--- a/client
+++ b/client
@@ -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;