diff options
| author | Guilhem Moulin <guilhem@fripost.org> | 2018-04-26 20:29:44 +0200 | 
|---|---|---|
| committer | Guilhem Moulin <guilhem@fripost.org> | 2018-04-27 01:43:03 +0200 | 
| commit | d1bc3ad109a3000bda8a7876673ff9a0281e8c7b (patch) | |
| tree | b5202fae0c6b580b7760e7b6ea66647c2da4da8d | |
| parent | 5ea132288e4f83fa24ebf3f61b503e440aaccad5 (diff) | |
Use ACME v2 endpoints
https://tools.ietf.org/html/draft-ietf-acme-acme-12
| -rw-r--r-- | Changelog | 8 | ||||
| -rwxr-xr-x | client | 238 | ||||
| -rw-r--r-- | config/lacme.conf | 10 | ||||
| -rwxr-xr-x | lacme | 51 | ||||
| -rw-r--r-- | lacme-accountd.md | 4 | ||||
| -rw-r--r-- | lacme.md | 59 | 
6 files changed, 188 insertions, 182 deletions
| @@ -1,6 +1,12 @@  lacme (0.5) upstream; -  * Fix manpage generation with pandoc >=2.1 +  + Use ACME v2 endpoints (update protocol to the last draft of the spec +    https://tools.ietf.org/html/draft-ietf-acme-acme-12 ).  Remove the +	'reg=' command, and rename the 'new-reg', 'new-cert' and +	'revoke-cert' commands to 'account', 'newOrder', and 'revokeCert' +	respectively, to match the the URI resource names.  For backward +	compatibility 'new-cert' and 'revoke-cert' remain supported. +  - Fix manpage generation with pandoc >=2.1   -- Guilhem Moulin <guilhem@fripost.org>  Thu, 26 Apr 2018 16:48:13 +0200 @@ -51,6 +51,7 @@ use Digest::SHA qw/sha256 sha256_hex/;  use MIME::Base64 qw/encode_base64 encode_base64url/;  use LWP::UserAgent (); +use Types::Serialiser ();  use JSON ();  use Config::Tiny (); @@ -75,6 +76,7 @@ open (my $S, '+<&=', $1+0) or die "fdopen $1: $!";  die "Error: Invalid client version\n" unless      $S->getline() =~ /\A(\d+) OK(?:.*)\r\n\z/ and $1 == $PROTOCOL_VERSION;  my $JWK = JSON::->new->decode($S->getline()); +my $KID;  # JSON keys need to be sorted lexicographically (for instance in the thumbprint)  sub json() { JSON::->new->utf8->canonical(); } @@ -103,11 +105,12 @@ my $UA = do {      $ssl_opts{$_} = $args{$_} foreach grep /^SSL_/, keys %args;      LWP::UserAgent::->new( ssl_opts => \%ssl_opts );  } // die "Can't create LWP::UserAgent object"; +$UA->default_header( 'Accept-Language' => 'en' );  #############################################################################  # Send an HTTP request to the ACME server.  If $json is defined, send -# its encoding as the request content, with "application/json" as +# its encoding as the request content, with "application/jose+json" as  # Content-Type.  #  sub request($$;$) { @@ -116,34 +119,22 @@ sub request($$;$) {      my $req = HTTP::Request::->new($method => $uri) or die "Can't $method $uri";      if (defined $json) { -        $req->content_type('application/json'); +        $req->content_type('application/jose+json');          $req->content(json()->encode($json));      }      my $r = $UA->request($req) or die "Can't $method $uri"; -    $NONCE = $r->header('Replay-Nonce'); # undef $NONCE if the header is missing -    print STDERR "[$$] >>> ", $r->status_line, "\n", $r->headers->as_string, "\n" if $ENV{DEBUG}; +    $NONCE //= $r->header('Replay-Nonce'); # undef $NONCE if the header is missing +    print STDERR "[$$] >>> ", $r->status_line, "\n", $r->headers->as_string("\n") if $ENV{DEBUG};      return $r;  } -# List all 'Links' headers with the relationship $rel (RFC 5988) -sub request_link_rel($$) { -    my ($r, $rel) = @_; -    grep defined, map -        { /\A<([^>]+)>.*;\s*rel=([^;]+)/ -        ; my ($link, $rels) = ($1, $2 // '') -        ; (grep { $rel eq $_ } map { /^"(.*)"/ ? $1 : $_ } split(/\s+/, $rels)) ? $link : undef -        } -        $r->header('Link'); -} -  # The request's Status Line; if the Content-Type is -# application/problem+json, parse the decoded content as JSON and add -# the value of the 'detail' field to the Status Line. -# https://tools.ietf.org/html/draft-ietf-appsawg-http-problem +# application/problem+json (RFC 7807), parse the decoded content as JSON +# and add the value of the 'detail' field to the Status Line.  sub request_status_line($) {      my $r = shift;      my $msg = $r->status_line; -    if ($r->content_type() eq 'application/problem+json') { +    if (!$r->is_success() and $r->content_type() eq 'application/problem+json') {          my $content = json()->decode($r->decoded_content());          print STDERR json()->pretty->encode($content), "\n" if $ENV{DEBUG};          $msg .= " (".$content->{detail}.")" if defined $content->{detail}; @@ -154,121 +145,133 @@ sub request_status_line($) {  # 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. -sub request_json_decode($;$) { +sub request_json_decode($;$$) {      my $r = shift;      my $dump = shift || $ENV{DEBUG}; +    my $fh = shift // \*STDERR;      die request_status_line($r), "\n" unless $r->is_success();      my $content = $r->decoded_content();      die "Content-Type: ".$r->content_type()." is not application/json\n"          unless $r->content_type() eq 'application/json'; -    $content = json()->decode($content); -    print STDERR json()->pretty->encode($content), "\n" if $dump; -    return $content; +    my $json = json()->decode($content); +    print $fh (-t $fh ? (json()->pretty->encode($json)."\n") : $content) +        if $dump; +    return $json;  }  #############################################################################  # 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 +# https://tools.ietf.org/html/draft-ietf-acme-acme-12  # -sub acme($$) { -    my ($uri, $h) = @_; - -    # the ACME server MUST provide a Replay-Nonce header field in -    # response to a HEAD request for any valid resource -    request(HEAD => $uri) unless defined $NONCE; +sub acme($@) { +    my $uri = shift; +    die "Missing nonce\n" unless defined $NONCE;      # Produce the JSON Web Signature: RFC 7515 section 5 -    my $payload = encode_base64url(json()->encode($h)); -    my %header = ( alg => 'RS256', jwk => $JWK ); -    my $protected = encode_base64url(json()->encode({ %header, nonce => $NONCE })); +    my %header = ( alg => 'RS256', nonce => $NONCE, url => $uri ); +    defined $KID ? ($header{kid} = $KID) : ($header{jwk} = $JWK); +    my $payload = encode_base64url(json()->encode({ @_ })); +    my $protected = encode_base64url(json()->encode(\%header));      my $data = $protected .'.'. $payload;      $S->printflush($data, "\r\n");      my $sig = $S->getline();      $sig =~ s/\r\n\z// or die; +    undef $NONCE; # consume the nonce      # Flattened JSON Serialization, RFC 7515 section 7.2.2      request(POST => $uri, {          payload => $payload,          protected => $protected, -        header => \%header,          signature => $sig      });  } -my $SERVER_URI = $CONFIG->{server} // 'https://acme-v01.api.letsencrypt.org/'; -$SERVER_URI .= '/' unless substr($SERVER_URI, -1, 1) eq '/'; +my $SERVER_URI = $CONFIG->{server} // 'https://acme-v02.api.letsencrypt.org/directory';  my %RES;  # Get the resource URI from the directory  sub acme_resource($%) {      my $r = shift; -    # Query the root ACME directory to initialize the nonce and get the resources URIs -    %RES = %{ request_json_decode(request(GET => $SERVER_URI.'directory')) } unless %RES; -    my $uri = $RES{$r} // die "Missing ressource for \"$r\"\n"; -    acme($uri, {resource => $r, @_}); +    unless (%RES) { +        # query the ACME directory to get resources URIs +        %RES = %{ request_json_decode(request(GET => $SERVER_URI)) }; +        # send a HEAD request to the newNonce resource to get a fresh nonce +        die "Unknown resource 'newNonce'\n" unless defined $RES{newNonce}; +        request(HEAD => $RES{newNonce}); +    } +    my $uri = $RES{$r} // die "Unknown resource '$r'\n"; +    acme($uri, @_);  } - -############################################################################# -# new-reg AGREEMENT_URI [CONTACT ..] -# -if ($COMMAND eq 'new-reg') { -    my $agreement = shift @ARGV; -    print STDERR "Requesting new registration ".(@ARGV ? ("for ".join(', ', @ARGV)) : "")."\n"; - -    my %h = (contact => \@ARGV); -    $h{agreement} = $agreement if $agreement ne ''; -    my $r = acme_resource('new-reg', %h); - -    my ($terms) = request_link_rel($r, 'terms-of-service'); -    request_json_decode($r,1) if $r->is_success() and $ENV{DEBUG}; # pretty-print the JSON -    print STDERR request_status_line($r), "\n"; -    print STDERR "Subscriber Agreement URI: $terms\n" if defined $terms; -    print STDERR "Registration URI: ", $r->header('Location'), "\n"; -    exit ($r->is_success() ? 0 : 1); +# Set the key ID (registration URI) +sub set_kid(;$) { +    my $die = shift // 1; +    my $r = acme_resource('newAccount', onlyReturnExisting => Types::Serialiser::true ); +    if ($r->is_success()) { +        $KID = $r->header('Location'); +    } elsif ($die) { +        die request_status_line($r), "\n"; +    }  }  ############################################################################# -# reg=URI AGREEMENT_URI [CONTACT ..] +# account FLAGS [CONTACT ..]  # -elsif ($COMMAND =~ /\Areg=(\p{Print}+)\Z/) { -    die "Empty registration URI (use the 'new-reg' command to determine the URI)\n" if $1 eq ''; -    my $uri = $SERVER_URI.$1; -    my $agreement = shift @ARGV; - -    my %h = (resource => 'reg'); -    $h{agreement} = $agreement if $agreement ne ''; -    $h{contact} = \@ARGV if @ARGV; # don't empty the contact list -    my $r = acme($uri, \%h); - -    my ($terms) = request_link_rel($r, 'terms-of-service'); -    $r->is_success() ? request_json_decode($r,1)  # pretty-print the JSON -                     : print STDERR request_status_line($r), "\n"; -    print STDERR "Subscriber Agreement URI: $terms\n" if defined $terms; +if ($COMMAND eq 'account') { +    my $flags = shift @ARGV; + +    my %h = ( contact => \@ARGV ) if @ARGV; +    $h{onlyReturnExisting}   = Types::Serialiser::true unless $flags & 0x01; +    $h{termsOfServiceAgreed} = Types::Serialiser::true if     $flags & 0x02; + +    print STDERR "Requesting new registration ".(@ARGV ? ("for ".join(', ', @ARGV)) : "")."\n" +        if $flags & 0x01; + +    my $r = acme_resource('newAccount', %h); +    # TODO: list account orders: https://github.com/letsencrypt/boulder/issues/3335 + +    if ($r->is_success()) { +        $KID = $r->header('Location'); +        $r = acme($KID, %h); +        request_json_decode($r, 1, \*STDOUT) +            if $r->is_success() and $r->content_type() eq 'application/json'; +    } + +    print STDERR request_status_line($r), "\n" +        if !$r->is_success() or $ENV{DEBUG};      exit ($r->is_success() ? 0 : 1);  }  ############################################################################# -# new-cert AUTHZ [AUTHZ ..] +# newOrder AUTHZ [AUTHZ ..]  #   Read the CSR (in DER format) from STDIN, print the cert (in PEM format  #   to STDOUT)  # -elsif ($COMMAND eq 'new-cert') { +elsif ($COMMAND eq 'newOrder') {      die unless @ARGV;      my $timeout = $CONFIG->{timeout} // 10; -    foreach my $domain (@ARGV) { -        print STDERR "Processing new DNS authz for $domain\n" if $ENV{DEBUG}; -        my $r = acme_resource('new-authz', identifier => {type => 'dns', value => $domain}); +    my $csr = do { local $/ = undef; <STDIN> }; + +    set_kid(); +    my @identifiers = map {{ type => 'dns', value => $_ }} @ARGV; +    my $r = acme_resource('newOrder', identifiers => \@identifiers); +    my $order = request_json_decode($r); + +    foreach (@{$order->{authorizations}}) { +        my $authz = request_json_decode(request(GET => $_)); +        next unless $authz->{status} eq 'pending'; + +        my $identifier = $authz->{identifier}->{value}; +        my ($challenge) = grep {$_->{type} eq 'http-01'} @{$authz->{challenges} // []}; +        die "Missing 'http-01' challenge in server response for '$identifier'\n" +            unless defined $challenge; -        my ($challenge) = grep {$_->{type} eq 'http-01'} -                               @{request_json_decode($r)->{challenges} // []}; -        die "Missing 'http-01' challenge in server response" unless defined $challenge;          die "Invalid challenge token ".($challenge->{token} // '')."\n"              # ensure we don't write outside the cwd              unless ($challenge->{token} // '') =~ /\A[A-Za-z0-9_\-]+\z/; @@ -285,23 +288,25 @@ elsif ($COMMAND eq 'new-cert') {              die "Can't open $challenge->{token}: $!";          } -        $r = acme($challenge->{uri}, { -            resource => 'challenge', -            keyAuthorization => $keyAuthorization -        }); -        # wait until the status become 'valid' -        for ( my $i = 0, my $content, my $status; -              $content = request_json_decode($r), -              $status = $content->{status} // 'pending', +        $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->{uri})) { -            if (defined (my $problem = $content->{error})) { # problem document (RFC 7807) +              $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 $domain (status: ".$status.")\n" if $status ne 'pending'; +            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'))) { @@ -310,41 +315,39 @@ elsif ($COMMAND eq 'new-cert') {              }              $i += $sleep; -            die "Timeout exceeded while waiting for challenge to pass ($domain)\n" if $timeout > 0 and $i >= $timeout; +            die "Timeout exceeded while waiting for challenges to pass ($identifier)\n" +                if $timeout > 0 and $i >= $timeout;              sleep $sleep;          }      } -    my $csr = do { local $/ = undef; <STDIN> }; -    my $r = acme_resource('new-cert', csr => encode_base64url($csr)); -    die request_status_line($r), "\n" unless $r->is_success(); -    my $uri = $r->header('Location'); -    # https://acme-v01.api.letsencrypt.org/acme/cert/$serial +    $r = acme($order->{finalize}, csr => encode_base64url($csr)); +    my $resp = request_json_decode($r); + +    my $uri = $resp->{certificate};      print STDERR "Certificate URI: $uri\n"; -    if ($r->decoded_content() eq '') { # wait for the cert -        for (my $i = 0;;) { -            $r = request('GET' => $uri); -            die request_status_line($r), "\n" unless $r->is_success(); -            last unless $r->code == 202; # Accepted -            my $retry_after = $r->header('Retry-After') // 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; -            sleep $retry_after; -        } +    # pool until the cert is available +    for (my $i = 0;;) { +        $r = request('GET' => $uri); +        die request_status_line($r), "\n" unless $r->is_success(); +        last unless $r->code == 202; # Accepted +        my $retry_after = $r->header('Retry-After') // 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; +        sleep $retry_after;      } -    my $der = $r->decoded_content(); -    # conversion DER -> PEM +    # keep only the leaf certificate      pipe my $rd, my $wd or die "Can't pipe: $!";      my $pid = fork // die "Can't fork: $!";      unless ($pid) {          open STDIN, '<&', $rd or die "Can't dup: $!"; -        exec qw/openssl x509 -inform DER -outform PEM/ or die; +        exec qw/openssl x509 -outform PEM/ or die;      }      $rd->close() or die "Can't close: $!"; -    $wd->print($der); +    $wd->print( $r->decoded_content() );      $wd->close() or die "Can't close: $!";      waitpid $pid => 0; @@ -353,17 +356,20 @@ elsif ($COMMAND eq 'new-cert') {  ############################################################################# -# revoke-cert +# revokeCert  #   The certificate to revoke is passed (in DER format) to STDIN; this  #   is required since the ACME client might not have read access to the  #   X.509 file  # -elsif ($COMMAND eq 'revoke-cert') { +elsif ($COMMAND eq 'revokeCert') {      die if @ARGV;      my $der = do { local $/ = undef; <STDIN> };      close STDIN or die "Can't close: $!"; -    my $r = acme_resource('revoke-cert', certificate => encode_base64url($der)); +    # send a KID if the request is signed with the acccount key, otherwise send a JWK +    set_kid(0); + +    my $r = acme_resource('revokeCert', certificate => encode_base64url($der));      exit 0 if $r->is_success();      die request_status_line($r), "\n";  } diff --git a/config/lacme.conf b/config/lacme.conf index 3cc1b34..39c8654 100644 --- a/config/lacme.conf +++ b/config/lacme.conf @@ -1,4 +1,4 @@ -# For certificate issuance (new-cert command), specify a space-separated +# For certificate issuance (newOrder command), specify a space-separated  # certificate configuration files or directories to use  #  #config-certs = lacme-certs.conf lacme-certs.conf.d/ @@ -33,11 +33,11 @@  #  #command = /usr/lib/lacme/client -# Root URI of the ACME server.  NOTE: Use the staging server -# <https://acme-staging.api.letsencrypt.org/> for testing as it has -# relaxed rate-limiting. +# URI of the ACME server's directory.  NOTE: Use the staging server +# <https://acme-staging-v02.api.letsencrypt.org/directory> for testing +# as it has relaxed rate-limiting.  # -#server = https://acme-v01.api.letsencrypt.org/ +#server = https://acme-v02.api.letsencrypt.org/directory  # Timeout in seconds after which the client stops polling the ACME  # server and considers the request failed. @@ -62,11 +62,11 @@ sub usage(;$$) {      }      exit $rv;  } -usage(1) unless GetOptions(\%OPTS, qw/config=s config-certs=s@ socket=s agreement-uri=s min-days=i quiet|q debug help|h/); +usage(1) unless GetOptions(\%OPTS, qw/config=s config-certs=s@ socket=s register tos-agreed min-days=i quiet|q debug help|h/);  usage(0) if $OPTS{help};  $COMMAND = shift(@ARGV) // usage(1, "Missing command"); -$COMMAND = $COMMAND =~ /\A(new-reg|reg=\p{Print}*|new-cert|revoke-cert)\z/ ? $1 +$COMMAND = $COMMAND =~ /\A(account|newOrder|new-cert|revokeCert|revoke-cert)\z/ ? $1           : usage(1, "Invalid command: $COMMAND"); # validate and untaint $COMMAND  @ARGV = map { /\A(\p{Print}*)\z/ ? $1 : die } @ARGV; # untaint @ARGV @@ -556,7 +556,7 @@ sub spawn($@) {      if (defined $args->{in}) {          pipe $in_rd, $in_wd or die "pipe: $!";      } -    if (defined $args->{out}) { +    if (defined $args->{out} and ref $args->{out} ne 'GLOB') {          pipe $out_rd, $out_wd or die "pipe: $!";      } @@ -570,11 +570,13 @@ sub spawn($@) {          } else {              open STDIN, '<', '/dev/null' or die "Can't open /dev/null: $!";          } -        if (defined $args->{out}) { +        if (!defined $args->{out}) { +            open STDOUT, '>', '/dev/null' or die "Can't open /dev/null: $!"; +        } elsif (ref $args->{out} ne 'GLOB') {              close $out_rd or die "Can't close: $!";              open STDOUT, '>&', $out_wd or die "Can't dup: $!"; -        } else { -            open STDOUT, '>', '/dev/null' or die "Can't open /dev/null: $!"; +        } elsif (fileno(STDOUT) != fileno($args->{out})) { +            open STDOUT, '>&', $args->{out} or die "Can't dup: $!";          }          exec { $exec[0] } @exec or die;      } @@ -590,14 +592,18 @@ sub spawn($@) {          $in_wd->print($args->{in});          $in_wd->close() or die "Can't close: $!";      } -    if (defined $args->{out}) { +    if (defined $args->{out} and ref $args->{out} ne 'GLOB') {          $out_wd->close() or die "Can't close: $!"; -        ${$args->{out}} = do { local $/ = undef; $out_rd->getline() }; +        if (ref $args->{out} eq 'CODE') { +            $args->{out}->($out_rd); +        } elsif (ref $args->{out} eq 'SCALAR') { +            ${$args->{out}} = do { local $/ = undef; $out_rd->getline() }; +        }          $out_rd->close() or die "Can't close: $!";      }      waitpid $pid => 0;      pop @CLEANUP; -    undef ${$args->{out}} if defined $args->{out} and $? > 0; +    undef ${$args->{out}} if defined $args->{out} and ref $args->{out} eq 'SCALAR' and $? > 0;      return $? > 255 ? ($? >> 8) : $? > 0 ? 1 : 0;  } @@ -638,25 +644,21 @@ sub install_cert($$;$) {  ############################################################################# -# new-reg [--agreement-uri=URI] [CONTACT ..] -# reg=URI [--agreement-uri=URI] [CONTACT ..] +# account [--tos-agreed] [CONTACT ..]  # -if ($COMMAND eq 'new-reg' or $COMMAND =~ /^reg=/) { -    die "Invalid registration URI (use the 'new-reg' command to determine the URI)\n" -        if $COMMAND eq 'reg='; -    $OPTS{'agreement-uri'} = $OPTS{'agreement-uri'} =~ /\A(\p{Print}+)\z/ ? $1 -                           : die "Invalid value for --agreement-uri\n" -        if defined $OPTS{'agreement-uri'}; - -    unshift @ARGV, ($OPTS{'agreement-uri'} // ''); -    exit acme_client({}, @ARGV); +if ($COMMAND eq 'account') { +    my $flags = 0; +    $flags |= 1 if $OPTS{'register'}; +    $flags |= 2 if $OPTS{'tos-agreed'}; +    exit acme_client({out => \*STDOUT}, $flags, @ARGV);  }  ############################################################################# -# new-cert [SECTION ..] +# newOrder [SECTION ..]  # -elsif ($COMMAND eq 'new-cert') { +elsif ($COMMAND eq 'newOrder' or $COMMAND eq 'new-cert') { +    $COMMAND = 'newOrder';      my $conffiles = defined $OPTS{'config-certs'} ? $OPTS{'config-certs'}                    : defined $CONFIG->{_}->{'config-certs'} ? [ split(/\s+/, $CONFIG->{_}->{'config-certs'}) ]                    : [ "$NAME-certs.conf", "$NAME-certs.conf.d/" ]; @@ -820,11 +822,12 @@ elsif ($COMMAND eq 'new-cert') {  ############################################################################# -# revoke-cert FILE [FILE ..] +# revokeCert FILE [FILE ..]  # -elsif ($COMMAND eq 'revoke-cert') { +elsif ($COMMAND eq 'revokeCert' or $COMMAND eq 'revoke-cert') {      die "Nothing to revoke\n" unless @ARGV;      my $rv = 0; +    $COMMAND = 'revokeCert';      foreach my $filename (@ARGV) {          print STDERR "Revoking $filename\n"; diff --git a/lacme-accountd.md b/lacme-accountd.md index 4d3e1a5..59d9bd9 100644 --- a/lacme-accountd.md +++ b/lacme-accountd.md @@ -122,13 +122,13 @@ Run `lacme-accountd` in a first terminal:  Then, while `lacme-accountd` is running, execute locally [`lacme`(1)] in  another terminal: -    ~$ sudo lacme --socket=$XDG_RUNTIME_DIR/S.lacme new-cert +    ~$ sudo lacme --socket=$XDG_RUNTIME_DIR/S.lacme newOrder  Alternatively, use [OpenSSH] 6.7 or later to forward the socket and  execute [`lacme`(1)] remotely:      ~$ ssh -oExitOnForwardFailure=yes -tt -R /path/to/remote.sock:$XDG_RUNTIME_DIR/S.lacme user@example.org \ -       sudo lacme --socket=/path/to/remote.sock new-cert +       sudo lacme --socket=/path/to/remote.sock newOrder  See also  ======== @@ -34,7 +34,7 @@ with its own executable:      component with access to the private key material of the server      keys.  It is used to fork the [ACME] client (and optionally the      [ACME] webserver) after dropping root privileges. -    For certificate issuances (`new-cert` command), it also generates +    For certificate issuances (`newOrder` command), it also generates      Certificate Signing Requests, then verifies the validity of the      issued certificate, and optionally reloads or restarts services when      the *notify* option is set. @@ -48,7 +48,7 @@ with its own executable:      UNIX-domain socket to the [ACME] client: data signatures are      requested by writing the data to be signed to the socket. - 4. For certificate issuances (`new-cert` command), an optional + 4. For certificate issuances (`newOrder` command), an optional      webserver (specified with the *command* option of the [`[webserver]`      section](#webserver-section) of the configuration file), which is      spawned by the “master” `lacme`.  (The only challenge type currently @@ -61,44 +61,36 @@ with its own executable:  Commands  ======== -`lacme` [`--agreement-uri=`*URI*] `new-reg` [*CONTACT* …] +`lacme account` [`--tos-agreed`] [`--register`] [*CONTACT* …] -:   Register the account key managed by [`lacme-accountd`(1)].  A list -    of *CONTACT* information (such as `maito:` URIs) can be specified in -    order for the server to contact the client for issues related to -    this registration (such as notifications about server-initiated -    revocations). +:   Register (if `--registered` is set) a [`lacme-accountd`(1)]-managed +    account key.  A list of *CONTACT* information (such as `maito:` +    URIs) can be specified in order for the [ACME] server to contact the +    client for issues related to this registration (such as +    notifications about server-initiated revocations).  `--tos-agreed` +    indicates agreement with the [ACME] server's Terms of Service (and +    might be required for registration). -    `--agreement-uri=` can be used to specify a *URI* referring to a -    subscriber agreement or terms of service provided by the server; -    adding this options indicates the client's agreement with the -    referenced terms.  Note that the server might require the client to -    agree to subscriber agreement before performing any further actions. +    If the account key is already registered, update the contact info +    with the given list of *CONTACT* information. -    If the account key is already registered, `lacme` prints the URI of -    the existing registration and aborts. +    Upon success, `lacme` prints the new or updated Account Object from +    the [ACME] server. -`lacme` [`--agreement-uri=`*URI*] `reg=`*URI* [*CONTACT* …] - -:   Dump or edit the registration *URI* (relative to the [ACME] server -    URI, which is specified with the *server* option of the [`[client]` -    section](#client-section) of the configuration file). - -    When specified, the list of *CONTACT* information and the agreement -    *URI* are sent to the server to replace the existing values. - -`lacme` [`--config-certs=`*FILE*] [`--min-days=`*INT*] `new-cert` [*SECTION* …] +`lacme` [`--config-certs=`*FILE*] [`--min-days=`*INT*] `newOrder` [*SECTION* …]  :   Read the certificate configuration *FILE* (see the **[certificate      configuration file](#certificate-configuration-file)** section below      for the configuration options), and request new Certificate Issuance      for each of its sections (or the given list of *SECTION*s). +    Command alias: `new-order`. -`lacme` `revoke-cert` *FILE* [*FILE* …] +`lacme` `revokeCert` *FILE* [*FILE* …]  :   Request that the given certificate(s) *FILE*(s) be revoked.  For      this command, [`lacme-accountd`(1)] can be pointed to either the      account key or the server's private key. +    Command alias: `revoke-cert`.  Generic options  =============== @@ -149,7 +141,7 @@ Default section  *config-certs* -:   For certificate issuances (`new-cert` command), specify the +:   For certificate issuances (`newOrder` command), specify the      space-separated list of certificate configuration files or      directories to use (see the **[certificate configuration      file](#certificate-configuration-file)** section below for the @@ -198,7 +190,7 @@ of [ACME] commands and dialogues with the remote [ACME] server).  *server*  :   Root URI of the [ACME] server. -    Default: `https://acme-v01.api.letsencrypt.org/`. +    Default: `https://acme-v02.api.letsencrypt.org/directory`.  *timeout* @@ -322,7 +314,7 @@ If the section (including its header) is absent or commented out,  Certificate configuration file  ============================== -For certificate issuances (`new-cert` command), a separate file is used +For certificate issuances (`newOrder` command), a separate file is used  to configure paths to the certificate and key, as well as the subject,  subjectAltName, etc. to generate Certificate Signing Requests.  Each section denotes a separate certificate issuance. @@ -405,17 +397,16 @@ Valid options are:  Examples  ======== -    ~$ sudo lacme new-reg mailto:noreply@example.com -    ~$ sudo lacme reg=/acme/reg/123456 --agreement-uri=https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf -    ~$ sudo lacme new-cert -    ~$ sudo lacme revoke-cert /path/to/server/certificate.pem +    ~$ sudo lacme account --register --tos-agreed mailto:noreply@example.com +    ~$ sudo lacme newOrder +    ~$ sudo lacme revokeCert /path/to/server/certificate.pem  See also  ========  [`lacme-accountd`(1)] -[ACME]: https://tools.ietf.org/html/draft-ietf-acme-acme-02 +[ACME]: https://tools.ietf.org/html/draft-ietf-acme-acme-12  [`lacme-accountd`(1)]: lacme-accountd.1.html  [`iptables`(8)]: http://linux.die.net/man/8/iptables  [`ciphers`(1ssl)]: https://www.openssl.org/docs/manmaster/apps/ciphers.html | 
