aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog2
-rwxr-xr-xclient33
-rwxr-xr-xlacme-accountd16
3 files changed, 36 insertions, 15 deletions
diff --git a/Changelog b/Changelog
index e6becda..ffd9536 100644
--- a/Changelog
+++ b/Changelog
@@ -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
diff --git a/client b/client
index e62541c..7a63259 100755
--- a/client
+++ b/client
@@ -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())) {