diff options
-rw-r--r-- | Changelog | 2 | ||||
-rwxr-xr-x | client | 33 | ||||
-rwxr-xr-x | lacme-accountd | 16 |
3 files changed, 36 insertions, 15 deletions
@@ -69,6 +69,8 @@ lacme (0.7.1) upstream; connection through ssh. The new flag is documented to allow safe usage is authorized_keys(5) restrictions. + Remove dependency on List::Util (core module). + + accountd: Pass JWA and JWK thumbprint via extended greeting data. + This gives better forward flexibility. - lacme: delay webserver socket shutdown to after the process has terminated. - documentation: suggest to generate private key material with @@ -49,7 +49,7 @@ my $NAME = 'lacme-client'; use Errno 'EEXIST'; use Fcntl qw/O_CREAT O_EXCL O_WRONLY/; -use Digest::SHA qw/sha256 sha256_hex/; +use Digest::SHA 'sha256'; use MIME::Base64 qw/encode_base64 encode_base64url/; use Date::Parse (); @@ -70,24 +70,34 @@ open (my $CONFFILE, '<&=', $1+0) or die "fdopen $1: $!"; (shift @ARGV // die) =~ /\A(\d+)\z/ or die; open (my $S, '+<&=', $1+0) or die "fdopen $1: $!"; +# JSON keys need to be sorted lexicographically (for instance in the thumbprint) +sub json() { JSON::->new->utf8->canonical(); } + ############################################################################# # Read the protocol version and JSON Web Key (RFC 7517) from the # lacme-accountd socket # + +my ($JWK, $JWK_thumbprint, $ALG, $KID); do { my $greeting = $S->getline(); die "Error: Invalid client version\n" unless defined $greeting and - $greeting =~ /\A(\d+) OK(?:.*)\r\n\z/ and $1 == $PROTOCOL_VERSION; + $greeting =~ /\A(\d+) OK(?: (.*))?\r\n\z/ and $1 == $PROTOCOL_VERSION; + if (defined (my $extra = $2)) { + my $h = eval { JSON::->new->decode($extra) }; + if ($@ or !defined $h) { + print STDERR "WARN: Ignoring extra greeting data from accountd \"$extra\"\n"; + } else { + print STDERR "Received extra greeting data from accountd: $extra\n" if $ENV{DEBUG}; + ($JWK_thumbprint, $ALG) = @$h{qw/jwk-thumbprint alg/}; + } + } + my $jwk_str = $S->getline() // die "ERROR: No JWK from lacme-accountd\n"; + $JWK = JSON::->new->decode($jwk_str); + $JWK_thumbprint //= encode_base64url(sha256(json()->encode($JWK))); # SHA-256 is hardcoded, see RFC 8555 sec. 8.1 + $ALG //= "RS256"; }; -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(); } - -my $JWK_thumbprint = encode_base64url(sha256(json()->encode($JWK))); -my $NONCE; ############################################################################# @@ -111,6 +121,7 @@ my $UA = do { LWP::UserAgent::->new( agent => "$NAME/$VERSION", ssl_opts => \%ssl_opts ); } // die "Can't create LWP::UserAgent object"; $UA->default_header( 'Accept-Language' => 'en' ); +my $NONCE; ############################################################################# @@ -192,7 +203,7 @@ sub acme($;$) { die "Missing nonce\n" unless defined $NONCE; # Produce the JSON Web Signature: RFC 7515 section 5 - my %header = ( alg => 'RS256', nonce => $NONCE, url => $uri ); + my %header = ( alg => $ALG, nonce => $NONCE, url => $uri ); defined $KID ? ($header{kid} = $KID) : ($header{jwk} = $JWK); my $payload = defined $h ? encode_base64url(json()->encode($h)) : ""; my $protected = encode_base64url(json()->encode(\%header)); diff --git a/lacme-accountd b/lacme-accountd index 0f0b0d9..d4521f9 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -27,6 +27,7 @@ our $VERSION = '0.3'; my $PROTOCOL_VERSION = 1; my $NAME = 'lacme-accountd'; +use Digest::SHA 'sha256'; use Errno 'EINTR'; use File::Basename 'dirname'; use Getopt::Long qw/:config posix_default no_ignore_case gnu_getopt auto_version/; @@ -141,7 +142,7 @@ do { # Build the JSON Web Key (RFC 7517) from the account key's public parameters, # and determine the signing method $SIGN. # -my ($JWK, $SIGN); +my ($EXTRA_GREETING_STR, $JWK_STR, $SIGN); if ($OPTS{privkey} =~ /\A(file|gpg):(\p{Print}+)\z/) { my ($method, $filename) = ($1, spec_expand($2)); my ($fh, @command); @@ -174,13 +175,19 @@ if ($OPTS{privkey} =~ /\A(file|gpg):(\p{Print}+)\z/) { my ($n, $e) = $rsa->get_key_parameters(); # don't include private params! $_ = encode_base64url($_->to_bin()) foreach ($n, $e); - $JWK = { kty => 'RSA', n => $n, e => $e }; + my %extra_greeting; + my %jwk = ( kty => 'RSA', n => $n, e => $e ); + $extra_greeting{alg} = 'RS256'; # SHA256withRSA (RFC 7518 sec. A.1) $SIGN = sub($) { $rsa->sign($_[0]) }; + + # use of SHA-256 digest in the thumbprint is hardcoded, see RFC 8555 sec. 8.1 + $JWK_STR = JSON::->new->utf8->canonical->encode(\%jwk); + $extra_greeting{"jwk-thumbprint"} = encode_base64url(sha256($JWK_STR)); + $EXTRA_GREETING_STR = JSON::->new->encode(\%extra_greeting); } else { error("Unsupported method: $OPTS{privkey}"); } -my $JWK_STR = JSON::->new->encode($JWK); ############################################################################# @@ -219,7 +226,8 @@ unless (defined $OPTS{stdio}) { # sub conn($$$) { my ($in, $out, $id) = @_; - $out->printflush( "$PROTOCOL_VERSION OK", "\r\n", $JWK_STR, "\r\n" ) or warn "print: $!"; + $out->printflush( "$PROTOCOL_VERSION OK ", $EXTRA_GREETING_STR, "\r\n", + $JWK_STR, "\r\n" ) or warn "print: $!"; # sign whatever comes in while (defined (my $data = $in->getline())) { |