aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2021-02-21 14:27:50 +0100
committerGuilhem Moulin <guilhem@fripost.org>2021-02-21 19:41:40 +0100
commitba6addf54cef0b1536dc87c42a41b4dc207ac884 (patch)
treeab057ee5f4675709e30a88a31aa57ab525c73cdb
parent16f7d75ac8e46a7905779931c871ac85c7e1aa04 (diff)
accountd: Pass JWA and JWK thumbprint via extended greeting data.
Passing the JWA to the ACME client is required if we want to support account keys other than RSA. As of 0.7 both lacme-accountd(1) and lacme(8) hardcode “RS256” (SHA256withRSA per RFC 7518 sec. A.1). Passing the JWK thumbprint is handy as it gives more flexibility if RFC 8555 sec. 8.1 were to be updated with another digest algorithm (it's currently hardcoded to SHA-256). A single lacme-account(1) instance might be used to sign requests from many clients, and it's easier to upgrade a single ‘lacme-accountd’ than many ‘lacme’. Moreover, in some restricted environments lacme-accountd might hide the JWK from the client to prevent ‘newAccount’ requests (such as contact updates); passing its thumbprint is enough for ‘newOrder’ requests.
-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())) {