diff options
| author | Guilhem Moulin <guilhem@fripost.org> | 2019-08-21 18:28:56 +0200 | 
|---|---|---|
| committer | Guilhem Moulin <guilhem@fripost.org> | 2019-08-21 18:28:56 +0200 | 
| commit | 04b39df93ef79903f7f3b911c61063df2a5f3da8 (patch) | |
| tree | e4339138908ec8ffb6e6942c947ac27de5d642cc /client | |
| parent | 7f4db501dfc80b4571833f95c090bdd0e1da240d (diff) | |
| parent | 454b29d61daaba8f19f0d890d59d259ef1416907 (diff) | |
Merge tag 'upstream/0.6' into debian
New release 0.6.
Diffstat (limited to 'client')
| -rwxr-xr-x | client | 115 | 
1 files changed, 69 insertions, 46 deletions
| @@ -50,6 +50,7 @@ use Fcntl qw/O_CREAT O_EXCL O_WRONLY/;  use Digest::SHA qw/sha256 sha256_hex/;  use MIME::Base64 qw/encode_base64 encode_base64url/; +use Date::Parse ();  use LWP::UserAgent ();  use Types::Serialiser ();  use JSON (); @@ -142,6 +143,21 @@ 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/) { +        $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. @@ -165,16 +181,16 @@ sub request_json_decode($;$$) {  #############################################################################  # JSON-encode the hash reference $h and send it to the ACME server $uri  # encapsulated it in a JSON Web Signature (JWS). -# https://tools.ietf.org/html/draft-ietf-acme-acme-12 +# https://tools.ietf.org/html/rfc8555  # -sub acme($@) { -    my $uri = shift; +sub acme($;$) { +    my ($uri, $h) = @_;      die "Missing nonce\n" unless defined $NONCE;      # Produce the JSON Web Signature: RFC 7515 section 5      my %header = ( alg => 'RS256', nonce => $NONCE, url => $uri );      defined $KID ? ($header{kid} = $KID) : ($header{jwk} = $JWK); -    my $payload = encode_base64url(json()->encode({ @_ })); +    my $payload = defined $h ? encode_base64url(json()->encode($h)) : "";      my $protected = encode_base64url(json()->encode(\%header));      my $data = $protected .'.'. $payload;      $S->printflush($data, "\r\n"); @@ -204,7 +220,7 @@ sub acme_resource($%) {          request(HEAD => $RES{newNonce});      }      my $uri = $RES{$r} // die "Unknown resource '$r'\n"; -    acme($uri, @_); +    acme($uri, {@_});  }  # Set the key ID (registration URI) @@ -228,6 +244,7 @@ if ($COMMAND eq 'account') {      my %h = ( contact => \@ARGV ) if @ARGV;      $h{onlyReturnExisting}   = Types::Serialiser::true unless $flags & 0x01;      $h{termsOfServiceAgreed} = Types::Serialiser::true if     $flags & 0x02; +    $h{status}               = "deactivated"           if     $flags & 0x04;      print STDERR "Requesting new registration ".(@ARGV ? ("for ".join(', ', @ARGV)) : "")."\n"          if $flags & 0x01; @@ -237,7 +254,7 @@ if ($COMMAND eq 'account') {      if ($r->is_success()) {          $KID = $r->header('Location'); -        $r = acme($KID, %h); +        $r = acme($KID, \%h);          request_json_decode($r, 1, \*STDOUT)              if $r->is_success() and $r->content_type() eq 'application/json';      } @@ -262,9 +279,10 @@ 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 => $_)); +        my $authz = request_json_decode(acme($_));          next unless $authz->{status} eq 'pending';          my $identifier = $authz->{identifier}->{value}; @@ -287,52 +305,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 = acme($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 = acme($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; | 
