diff options
| author | Guilhem Moulin <guilhem@debian.org> | 2023-01-25 03:32:04 +0100 | 
|---|---|---|
| committer | Guilhem Moulin <guilhem@debian.org> | 2023-01-25 03:32:04 +0100 | 
| commit | 33687a2e3aea5ae69add7812315445ad23748fab (patch) | |
| tree | 952a06618d7da373043debef8a8c28d4c8371385 /lacme-accountd | |
| parent | 2a981ac3829f27d3179eb6b6e682dc17cc9c4225 (diff) | |
| parent | b3af3526b293f396da02a6276ea86ca17dcd2d03 (diff) | |
Merge tag 'v0.8.1' into debian/latest
Release version 0.8.1
Diffstat (limited to 'lacme-accountd')
| -rwxr-xr-x | lacme-accountd | 71 | 
1 files changed, 46 insertions, 25 deletions
| diff --git a/lacme-accountd b/lacme-accountd index 0f5deb2..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'; @@ -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) { +    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); @@ -83,7 +86,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;  } @@ -133,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};  }; @@ -211,9 +215,9 @@ 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(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: $!"); @@ -221,7 +225,7 @@ unless (defined $OPTS{stdio}) {      ($SOCKNAME, $S) = ($sockname, $sock);      listen($S, 1) or panic("listen: $!"); -    umask($umask) // panic("umask: $!"); +    umask($umask) // panic();  }; @@ -234,26 +238,43 @@ 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); -        unless (defined $header and $header =~ /\A[A-Za-z0-9\-_]+\z/) { -            info("[$id] >>> Error: Refusing to sign request: Malformed protected header"); +        if (defined $header and $header =~ /\A[A-Za-z0-9\-_]+\z/) { +            $header = decode_base64url($header); +        } else { +            info("[$id] NOSIGN [malformed JWS Protected Header]");              last;          } -        unless (defined $payload and $payload =~ /\A[A-Za-z0-9\-_]*\z/) { -            # POST-as-GET yields an empty payload -            info("[$id] >>> Error: Refusing to sign request: Malformed payload"); +        if (defined $payload and $payload =~ /\A[A-Za-z0-9\-_]*\z/) { +            # empty payloads are valid, and used for POST-as-GET (RFC 8555 sec. 6.3) +            $payload = decode_base64url($payload); +        } else { +            info("[$id] NOSIGN [malformed JWS 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)"; + +        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); +        my $sig = eval { $SIGN->($data) }; +        panic($@) if $@ or !defined $sig; +        logmsg($LOG_VERBOSE => "[$id] SIGNED ", $req);          $out->printflush( encode_base64url($sig), "\r\n" ) or warn "print: $!";      }  } @@ -267,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: $!";      }  } @@ -279,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: $!");      } | 
