From 6f375631548a3562635af555bd453e4de40bf135 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 22 Feb 2021 14:33:34 +0100 Subject: accountd::conn(): Minor refactoring. --- lacme-accountd | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 0f5deb2..5794ec1 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -239,19 +239,22 @@ sub conn($$$) { $data =~ s/\r\n\z// or panic(); my ($header, $payload) = split(/\./, $data, 2); - unless (defined $header and $header =~ /\A[A-Za-z0-9\-_]+\z/) { + if (defined $header and $header =~ /\A[A-Za-z0-9\-_]+\z/) { + $header = decode_base64url($header); + } else { info("[$id] >>> Error: Refusing to sign request: Malformed protected header"); last; } - unless (defined $payload and $payload =~ /\A[A-Za-z0-9\-_]*\z/) { - # POST-as-GET yields an empty payload + if (defined $payload and $payload =~ /\A[A-Za-z0-9\-_]*\z/) { + # empty payloads are valid, cf. POST-as-GET + $payload = decode_base64url($payload); + } else { info("[$id] >>> Error: Refusing to sign request: Malformed payload"); last; } - logmsg(noquiet => "[$id] >>> OK signing request: ", - "header=base64url(", decode_base64url($header), "); ", - "playload=base64url(", decode_base64url($payload), ")"); + my $req = "header=base64url($header); playload=base64url($payload)"; + logmsg(noquiet => "[$id] >>> OK signing request: ", $req); my $sig = $SIGN->($data); $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!"; -- cgit v1.2.3 From 87fa9468a26c1902423839473049cd3325098c1a Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 22 Feb 2021 14:49:00 +0100 Subject: lacme-account: Improve log messages. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Again… --- lacme-accountd | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 5794ec1..68d0f39 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -83,7 +83,8 @@ sub error(@) { } sub panic(@) { my @loc = caller; - my @msg = (@_, " at line $loc[2] in $loc[1]"); + my @msg = ("PANIC at line $loc[2] in $loc[1]"); + push @msg, ": ", @_ if @_; info(@msg); exit 255; } @@ -234,29 +235,29 @@ sub conn($$$) { $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())) { $data =~ s/\r\n\z// or panic(); + # validate JWS Signing Input from RFC 7515: + # ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload)) my ($header, $payload) = split(/\./, $data, 2); if (defined $header and $header =~ /\A[A-Za-z0-9\-_]+\z/) { $header = decode_base64url($header); } else { - info("[$id] >>> Error: Refusing to sign request: Malformed protected header"); + info("[$id] NOSIGN [malformed JWS Protected Header]"); last; } if (defined $payload and $payload =~ /\A[A-Za-z0-9\-_]*\z/) { - # empty payloads are valid, cf. POST-as-GET + # empty payloads are valid, and used for POST-as-GET (RFC 8555 sec. 6.3) $payload = decode_base64url($payload); } else { - info("[$id] >>> Error: Refusing to sign request: Malformed payload"); + info("[$id] NOSIGN [malformed JWS Payload]"); last; } - my $req = "header=base64url($header); playload=base64url($payload)"; - logmsg(noquiet => "[$id] >>> OK signing request: ", $req); - - my $sig = $SIGN->($data); + my $req = "header=base64url($header) playload=base64url($payload)"; + my $sig = $SIGN->($data) // panic(); + logmsg(noquiet => "[$id] SIGNED ", $req); $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!"; } } @@ -270,9 +271,9 @@ if (defined $OPTS{stdio}) { next if $! == EINTR; # try again if accept(2) was interrupted by a signal panic("accept: $!"); }; - logmsg(noquiet => "[$count] >>> Accepted new connection"); + logmsg(noquiet => "[$count] Accepted new connection"); conn($conn, $conn, $count); - logmsg(noquiet => "[$count] >>> Connection terminated"); + logmsg(noquiet => "[$count] Connection terminated"); $conn->close() or warn "close: $!"; } } -- cgit v1.2.3 From 045d169339c5b973f0924269e6ca485e48de3668 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 22 Feb 2021 20:32:33 +0100 Subject: lacme-accountd: Refuse to sign JWS with an invalid Protected Header. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “The JWS Protected Header is a JSON object” — RFC 7515 sec. 2. “The JWS Protected Header MUST include the following fields: - "alg" - "nonce" - "url" - either "jwk" or "kid"” — RFC 8555 sec. 6.2. --- lacme-accountd | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 68d0f39..5478cc2 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -256,6 +256,19 @@ sub conn($$$) { } my $req = "header=base64url($header) playload=base64url($payload)"; + + eval { $header = JSON::->new->decode($header); }; + if ($@ or # couldn't decode (parse error) + # RFC 7515: not a JSON object + !defined($header) or ref($header) ne "HASH" or + # RFC 8555 sec. 6.2: the protected Header MUST include all these fields + grep !defined, @$header{qw/alg nonce url/} or + # RFC 8555 sec. 6.2: the protected header MUST include any of these fields + !grep defined, @$header{qw/jwk kid/}) { + info("[$id] NOSIGN [invalid JWS Protected Header] ", $req); + last; + } + my $sig = $SIGN->($data) // panic(); logmsg(noquiet => "[$id] SIGNED ", $req); $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!"; -- cgit v1.2.3 From 0fb2ebb14c538d736d9260fc6fae51d02375a999 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Mon, 22 Feb 2021 23:20:15 +0100 Subject: lacme-accountd: panic() upon internal error of the signing routine. It might croak and we want to log that error also. --- lacme-accountd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 5478cc2..5109888 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -269,7 +269,8 @@ sub conn($$$) { last; } - my $sig = $SIGN->($data) // panic(); + my $sig = eval { $SIGN->($data) }; + panic($@) if $@ or !defined $sig; logmsg(noquiet => "[$id] SIGNED ", $req); $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!"; } -- cgit v1.2.3 From 2d08a72c2f6b2afb04fb5382a5f592075a0004a8 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Tue, 23 Feb 2021 00:28:56 +0100 Subject: lacme-accountd: don't log debug messages unless --debug is set. --- lacme-accountd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 5109888..47a4c32 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -67,7 +67,7 @@ usage(0) if $OPTS{help}; my $LOG; sub logmsg($@) { my $lvl = shift // "all"; - if (defined $LOG) { + if (defined $LOG and ($lvl ne "debug" or $OPTS{debug})) { my $now = localtime; $LOG->printflush("[", $now, "] ", @_, "\n") or warn "print: $!"; } -- cgit v1.2.3 From 3a527c2159cdd23f489970f935edbccc37da1901 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Tue, 23 Feb 2021 00:58:46 +0100 Subject: lacme-accountd: Refactor logging logic. --- lacme-accountd | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 47a4c32..a35ac88 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -64,18 +64,21 @@ sub usage(;$$) { usage(1) unless GetOptions(\%OPTS, qw/config=s privkey=s socket=s stdio quiet|q debug help|h/); usage(0) if $OPTS{help}; -my $LOG; +my ($LOG, $LOGLEVEL); +my ($LOG_INFO, $LOG_VERBOSE, $LOG_DEBUG) = (0,1,2); sub logmsg($@) { - my $lvl = shift // "all"; - if (defined $LOG and ($lvl ne "debug" or $OPTS{debug})) { + my $lvl = shift; + if (defined $LOG and ($lvl <= $LOGLEVEL or $lvl <= $LOG_VERBOSE)) { + # --quiet flag hides verbose-level messages from the standard + # error but we add them to the logfile nonetheless my $now = localtime; $LOG->printflush("[", $now, "] ", @_, "\n") or warn "print: $!"; } - unless (($lvl eq "debug" and !$OPTS{debug}) or ($lvl eq "noquiet" and $OPTS{quiet})) { + if ($lvl <= $LOGLEVEL) { print STDERR @_, "\n" or warn "print: $!"; } } -sub info(@) { logmsg(all => @_); } +sub info(@) { logmsg($LOG_INFO => @_); } sub error(@) { my @msg = ("Error: ", @_); info(@msg); @@ -134,7 +137,7 @@ do { print STDERR "Ignoring missing configuration file at default location $conffile\n" if $OPTS{debug}; } - $OPTS{quiet} = 0 if $OPTS{debug}; + $LOGLEVEL = $OPTS{debug} ? $LOG_DEBUG : $OPTS{quiet} ? $LOG_INFO : $LOG_VERBOSE; error("'privkey' is not specified") unless defined $OPTS{privkey}; }; @@ -214,7 +217,7 @@ unless (defined $OPTS{stdio}) { my $umask = umask(0177) // panic("umask: $!"); - logmsg(noquiet => "Starting lacme Account Key Manager at $sockname"); + logmsg($LOG_VERBOSE => "Starting lacme Account Key Manager at $sockname"); socket(my $sock, PF_UNIX, SOCK_STREAM, 0) or panic("socket: $!"); my $sockaddr = Socket::sockaddr_un($sockname) // panic(); bind($sock, $sockaddr) or panic("bind: $!"); @@ -271,7 +274,7 @@ sub conn($$$) { my $sig = eval { $SIGN->($data) }; panic($@) if $@ or !defined $sig; - logmsg(noquiet => "[$id] SIGNED ", $req); + logmsg($LOG_VERBOSE => "[$id] SIGNED ", $req); $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!"; } } @@ -285,9 +288,9 @@ if (defined $OPTS{stdio}) { next if $! == EINTR; # try again if accept(2) was interrupted by a signal panic("accept: $!"); }; - logmsg(noquiet => "[$count] Accepted new connection"); + logmsg($LOG_VERBOSE => "[$count] Accepted new connection"); conn($conn, $conn, $count); - logmsg(noquiet => "[$count] Connection terminated"); + logmsg($LOG_VERBOSE => "[$count] Connection terminated"); $conn->close() or warn "close: $!"; } } @@ -297,11 +300,11 @@ if (defined $OPTS{stdio}) { # END { if (defined $SOCKNAME and -S $SOCKNAME) { - logmsg(debug => "Unlinking $SOCKNAME"); + logmsg($LOG_DEBUG => "Unlinking $SOCKNAME"); unlink $SOCKNAME or info("Error: unlink($SOCKNAME): $!"); } if (defined $S) { - logmsg(noquiet => "Shutting down and closing lacme Account Key Manager"); + logmsg($LOG_VERBOSE => "Shutting down and closing lacme Account Key Manager"); shutdown($S, SHUT_RDWR) or info("Error: shutdown: $!"); close $S or info("Error: close: $!"); } -- cgit v1.2.3 From faab30461b0f2b920e3dd19489ce458c0b38e6d9 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 24 Feb 2021 21:06:48 +0100 Subject: If restricting access via umask() fails, don't include errno in the error message. errno is not set on umask failure, see https://perldoc.perl.org/functions/umask. --- lacme-accountd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index a35ac88..98c11ad 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -215,7 +215,7 @@ unless (defined $OPTS{stdio}) { my @stat = stat($dirname) or error("stat($dirname): $!"); error("Insecure permissions on $dirname") if ($stat[2] & 0022) != 0; - my $umask = umask(0177) // panic("umask: $!"); + my $umask = umask(0177) // panic(); logmsg($LOG_VERBOSE => "Starting lacme Account Key Manager at $sockname"); socket(my $sock, PF_UNIX, SOCK_STREAM, 0) or panic("socket: $!"); @@ -225,7 +225,7 @@ unless (defined $OPTS{stdio}) { ($SOCKNAME, $S) = ($sockname, $sock); listen($S, 1) or panic("listen: $!"); - umask($umask) // panic("umask: $!"); + umask($umask) // panic(); }; -- cgit v1.2.3 From b3af3526b293f396da02a6276ea86ca17dcd2d03 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 25 Jan 2023 03:23:51 +0100 Subject: Prepare new release v0.8.1. --- lacme-accountd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lacme-accountd') diff --git a/lacme-accountd b/lacme-accountd index 98c11ad..a9f5469 100755 --- a/lacme-accountd +++ b/lacme-accountd @@ -23,7 +23,7 @@ use v5.14.2; use strict; use warnings; -our $VERSION = '0.8.0'; +our $VERSION = '0.8.1'; my $PROTOCOL_VERSION = 1; my $NAME = 'lacme-accountd'; -- cgit v1.2.3