diff options
-rw-r--r-- | Changelog | 17 | ||||
-rw-r--r-- | Makefile | 6 | ||||
-rwxr-xr-x | client | 39 | ||||
-rwxr-xr-x | lacme | 28 | ||||
-rwxr-xr-x | lacme-accountd | 2 | ||||
-rwxr-xr-x | test | 2 | ||||
-rw-r--r-- | tests/account-encrypted-gpg | 2 | ||||
-rw-r--r-- | tests/account-encrypted-openssl | 1 | ||||
-rw-r--r-- | tests/accountd | 1 | ||||
-rw-r--r-- | tests/accountd-kid | 4 | ||||
-rw-r--r-- | tests/cert-revoke | 4 | ||||
-rw-r--r-- | tests/cert-verify | 2 | ||||
-rw-r--r-- | tests/drop-privileges | 4 | ||||
-rw-r--r-- | tests/old-accountd | 3 | ||||
-rw-r--r-- | tests/old-lacme | 1 |
15 files changed, 70 insertions, 46 deletions
@@ -1,3 +1,14 @@ +lacme (0.8.2) upstream; + + + client: Handle "ready" → "processing" → "valid" status change during + newOrder, instead of just "ready" → "valid". The latter may be what + we observe when the server is fast enough, but according to RFC 8555 + sec. 7.1.6 the state actually transitions via "processing" state and + we need to account for that. + - Test suite: Point stretch's archive URL to archive.d.o. + + -- Guilhem Moulin <guilhem@fripost.org> Tue, 25 Apr 2023 20:06:22 +0200 + lacme (0.8.1) upstream; + lacme-accountd: improve log messages and refactor logging logic. @@ -11,6 +22,8 @@ lacme (0.8.1) upstream; 'chmod'. + lacme: split certificates using Net::SSLeay::PEM_* instead of calling openssl. + + lacme: pass a temporary JSON file with the client configuration to + the internal client, so it doesn't have to parse the INI file again. - lacme: in the [accountd] config, let lacme-accountd(1) do the %-expansion for 'config', not lacme(8) when building the command. - lacme-accountd: don't log debug messages unless --debug is set. @@ -23,8 +36,10 @@ lacme (0.8.1) upstream; - lacme: ignore empty values in settings 'chown', 'chmod', 'certificate' and 'certificate-chain'. - lacme: return an error when the 'mode'/'chown' isn't a number. + - Makefile: replace '$(dir $@)' with '$(@D)'. + - Test suite: Adjust against current Let's Encrypt staging environment. - -- Guilhem Moulin <guilhem@fripost.org> Mon, 22 Feb 2021 12:04:28 +0100 + -- Guilhem Moulin <guilhem@fripost.org> Wed, 25 Jan 2023 03:23:51 +0100 lacme (0.8.0) upstream; @@ -19,7 +19,7 @@ $(BUILDDIR)/certs/ca-certificates.crt: \ certs/isrg-root-x2.pem \ certs/lets-encrypt-r[34].pem \ certs/lets-encrypt-e[12].pem - mkdir -pv -- $(dir $@) + mkdir -pv -- $(@D) cat -- $^ >$@ # Staging Environment for tests, see https://letsencrypt.org/docs/staging-environment/ @@ -27,7 +27,7 @@ $(BUILDDIR)/certs-staging/ca-certificates.crt: \ certs-staging/letsencrypt-stg-root-x[12].pem \ certs-staging/letsencrypt-stg-int-r[34].pem \ certs-staging/letsencrypt-stg-int-e[12].pem - mkdir -pv -- $(dir $@) + mkdir -pv -- $(@D) cat -- $^ >$@ prefix ?= $(DESTDIR) @@ -52,7 +52,7 @@ lacme_client_group ?= nogroup acmeapi_server ?= https://acme-v02.api.letsencrypt.org/directory $(BUILDDIR)/%: % - mkdir -pv -- $(dir $@) + mkdir -pv -- $(@D) cp --no-dereference --preserve=mode,links,xattr -vfT -- "$<" "$@" sed -i "s#@@bindir@@#$(bindir)#g; \ s#@@sbindir@@#$(sbindir)#g; \ @@ -43,7 +43,7 @@ use warnings; # instance own by another user and created with umask 0177) is not a # problem since SOCKET_FD can be bound as root prior to the execve(2). -our $VERSION = '0.8.0'; +our $VERSION = '0.8.2'; my $PROTOCOL_VERSION = 1; my $NAME = 'lacme-client'; @@ -56,8 +56,6 @@ use Date::Parse (); use LWP::UserAgent (); use JSON (); -use Config::Tiny (); - # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; delete @ENV{qw/IFS CDPATH ENV BASH_ENV/}; @@ -107,11 +105,7 @@ do { my $CONFIG = do { my $conf = do { local $/ = undef; <$CONFFILE> }; - close $CONFFILE or die "close: $!"; - my $h = Config::Tiny::->read_string($conf) or die Config::Tiny::->errstr()."\n"; - $h->{_} //= {}; - $h->{client}->{$_} //= $h->{_}->{$_} foreach keys %{$h->{_}}; # add defaults - $h->{client}; + JSON::->new->decode($conf); }; my $UA = do { my %args = %$CONFIG; @@ -352,11 +346,12 @@ elsif ($COMMAND eq 'newOrder') { } # poll the order URL (to get the status of all challenges at once) - # until the status become 'valid' + # until the status become 'valid'; see RFC 8555 sec. 7.1.6 for the + # the status change flow my $orderstr = join(', ', map {uc($_->{type}) .":". $_->{value}} @identifiers); my $certuri; - for (my $i = 0;;) { - my $r = acme($orderurl); + for (my $i = 0, my $url = $orderurl, my $payload;;) { + my $r = acme($url => $payload); my $resp = request_json_decode($r); if (defined (my $problem = $resp->{error})) { # problem document (RFC 7807) my $msg = $problem->{status}; @@ -367,19 +362,21 @@ elsif ($COMMAND eq 'newOrder') { my $status = $resp->{status}; if (!defined $status or $status eq "invalid") { die "Error: Invalid order $orderstr\n"; - } - elsif ($status eq "ready") { - my $r = acme($order->{finalize}, {csr => encode_base64url($csr)}); - my $resp = request_json_decode($r); - $certuri = $resp->{certificate}; - last; - } - elsif ($status eq "valid") { + } elsif ($status eq "pending") { + # keep retrying + } elsif ($status eq "ready") { + $url = $order->{finalize}; + $payload = {csr => encode_base64url($csr)}; + # retry after moving to "processing" or "valid" state + next; + } elsif ($status eq "processing") { + $url = $orderurl; + undef $payload; + } elsif ($status eq "valid") { $certuri = $resp->{certificate} // die "Error: Missing \"certificate\" field in \"valid\" order\n"; last; - } - elsif ($status ne "pending" and $status ne "processing") { + } else { warn "Unknown order status: $status\n"; } @@ -22,7 +22,7 @@ use v5.14.2; use strict; use warnings; -our $VERSION = '0.8.0'; +our $VERSION = '0.8.2'; my $NAME = 'lacme'; use Errno 'EINTR'; @@ -37,13 +37,14 @@ use Socket 1.95 qw/AF_UNIX AF_INET AF_INET6 PF_UNIX PF_INET PF_INET6 PF_UNSPEC use Config::Tiny (); use Date::Parse (); +use JSON (); use Net::SSLeay 1.46 (); # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; delete @ENV{qw/IFS CDPATH ENV BASH_ENV/}; -my ($COMMAND, %OPTS, $CONFFILE, $CONFIG, @CLEANUP); +my ($COMMAND, %OPTS, $CONFIG, @CLEANUP); $SIG{$_} = sub() { exit 1 } foreach qw/INT TERM/; # run the END block upon SIGINT/SIGTERM @@ -99,14 +100,12 @@ sub spec_expand($) { return $str; } -sub set_FD_CLOEXEC($$); my $CONFFILENAME = spec_expand($OPTS{config} // "%E/lacme/$NAME.conf"); do { print STDERR "Using configuration file: $CONFFILENAME\n" if $OPTS{debug}; - open $CONFFILE, '<', $CONFFILENAME or die "Can't open $CONFFILENAME: $!\n"; - my $conf = do { local $/ = undef; <$CONFFILE> }; - # don't close $CONFFILE so we can pass it to the client - set_FD_CLOEXEC($CONFFILE, 1); + open my $fh, '<', $CONFFILENAME or die "Can't open $CONFFILENAME: $!\n"; + my $conf = do { local $/ = undef; <$fh> }; + close $fh or die "close: $!"; my $h = Config::Tiny::->read_string($conf) or die Config::Tiny::->errstr()."\n"; my $defaults = delete $h->{_} // {}; @@ -573,19 +572,26 @@ sub acme_client($@) { die "connect: $!"; } } + set_FD_CLOEXEC($client, 1); + + my $client_config; + do { + my $tmp = File::Temp::->new(TMPDIR => 1, TEMPLATE => "lacme-client.conf.json-XXXXXXXXXX", UNLINK => 1) // die; + print $tmp JSON::->new->encode($conf); + open $client_config, "<", $tmp->filename() or die "open: $!"; + }; # use execve(2) rather than a Perl pseudo-process to ensure that the # child doesn't have access to the parent's memory my ($cmd, @args2) = split(/\s+/, $conf->{command}) or die "Empty client command\n"; - my @fileno = map { fileno($_) =~ /^(\d+)$/ ? $1 : die } ($CONFFILE, $client); # untaint fileno - set_FD_CLOEXEC($client, 1); + my @fileno = map { fileno($_) =~ /^(\d+)$/ ? $1 : die } ($client_config, $client); # untaint fileno my $rv = spawn({in => $args->{in}, out => $args->{out}, child => sub() { drop_privileges($conf->{user}, $conf->{group}, $args->{chdir} // '/'); umask(0022) // die; - set_FD_CLOEXEC($_, 0) foreach ($CONFFILE, $client); - seek($CONFFILE, SEEK_SET, 0) or die "seek: $!"; + set_FD_CLOEXEC($_, 0) for ($client_config, $client); $ENV{DEBUG} = $OPTS{debug} // 0; }}, $cmd, @args2, $COMMAND, @fileno, @args); + close $client_config or die "close: $!\n"; if (defined $cleanup) { @CLEANUP = grep { $_ ne $cleanup } @CLEANUP; diff --git a/lacme-accountd b/lacme-accountd index 98c11ad..8d2c599 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.2'; my $PROTOCOL_VERSION = 1; my $NAME = 'lacme-accountd'; @@ -67,7 +67,7 @@ else if [ -f "tests/$t" ]; then TESTS+=( "$t" ) else - echo "Error: '$1': no such test" >&2 + echo "Error: '$t': no such test" >&2 exit 1 fi done diff --git a/tests/account-encrypted-gpg b/tests/account-encrypted-gpg index fd1e4ac..7cb978d 100644 --- a/tests/account-encrypted-gpg +++ b/tests/account-encrypted-gpg @@ -9,7 +9,7 @@ keyid="$(gpg --list-secret-key --with-colons | grep -m1 ^fpr: | cut -sd: -f10)" gpg --encrypt -r "$keyid" /etc/lacme/account.key sed -ri '0,\|^#?privkey\s*=.*| {s||privkey = gpg:/etc/lacme/account.key.gpg|}' /etc/lacme/lacme-accountd.conf -export GPG_TTY="$(tty)" +export GPG_TTY="$(tty)" TERM="linux" lacme account # vim: set filetype=sh : diff --git a/tests/account-encrypted-openssl b/tests/account-encrypted-openssl index e79a528..a3ad707 100644 --- a/tests/account-encrypted-openssl +++ b/tests/account-encrypted-openssl @@ -5,6 +5,7 @@ PASSPHRASE="test" openssl rsa -aes128 -passout pass:"$PASSPHRASE" </etc/lacme/account.key >/etc/lacme/account.enc.key sed -ri '0,\|^#?privkey\s*=.*| {s||privkey = file:/etc/lacme/account.enc.key|}' /etc/lacme/lacme-accountd.conf +export TERM="linux" lacme account # vim: set filetype=sh : diff --git a/tests/accountd b/tests/accountd index 7e8fd4c..433f8ad 100644 --- a/tests/accountd +++ b/tests/accountd @@ -65,6 +65,7 @@ grep -F "Error: " ~lacme-account/.local/share/lacme/accountd.log # rotate the log and start accountd rm -f ~lacme-account/.local/share/lacme/accountd.log runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" --quiet & PID=$! +sleep 1 # run lacme(8) multiple times using that single lacme-accountd(1) instance lacme --socket="$SOCKET" --debug account 2>"$STDERR" || fail diff --git a/tests/accountd-kid b/tests/accountd-kid index 1f282fd..8a4b53c 100644 --- a/tests/accountd-kid +++ b/tests/accountd-kid @@ -23,6 +23,7 @@ EOF SOCKET=~lacme-account/S.lacme runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" --quiet & PID=$! +sleep 1 # newAccount resource fails as per RFC 8555 sec. 6.2 it requires a JWK ! lacme --socket="$SOCKET" account 2>"$STDERR" || fail @@ -37,6 +38,7 @@ wait rm ~lacme-account/.local/share/lacme/accountd.log runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" --quiet & PID=$! +sleep 1 # newOrder works fine without JWK lacme --socket="$SOCKET" newOrder @@ -46,7 +48,7 @@ test /etc/lacme/simpletest.rsa.crt -nt /etc/lacme/simpletest.rsa.key lacme --socket="$SOCKET" revokeCert /etc/lacme/simpletest.rsa.crt ! lacme --socket="$SOCKET" revokeCert /etc/lacme/simpletest.rsa.crt 2>"$STDERR" || fail grepstderr -Fxq "Revoking /etc/lacme/simpletest.rsa.crt" -grepstderr -Fxq "400 Bad Request (Certificate already revoked)" +grepstderr -Fq "400 Bad Request (unable to revoke" grepstderr -Fxq "Warning: Couldn't revoke /etc/lacme/simpletest.rsa.crt" kill $PID diff --git a/tests/cert-revoke b/tests/cert-revoke index f3d585e..179ccba 100644 --- a/tests/cert-revoke +++ b/tests/cert-revoke @@ -18,7 +18,7 @@ test /etc/lacme/simpletest.ecdsa.crt -nt /etc/lacme/simpletest.ecdsa.key lacme revokeCert /etc/lacme/simpletest.ecdsa.crt ! lacme revokeCert /etc/lacme/simpletest.ecdsa.crt 2>"$STDERR" || fail grepstderr -Fxq "Revoking /etc/lacme/simpletest.ecdsa.crt" -grepstderr -Fxq "400 Bad Request (Certificate already revoked)" +grepstderr -Fq "400 Bad Request (unable to revoke" grepstderr -Fxq "Warning: Couldn't revoke /etc/lacme/simpletest.ecdsa.crt" # and the RSA certificate using the service key @@ -26,7 +26,7 @@ mv -vfT /etc/lacme/simpletest.rsa.key /etc/lacme/account.key lacme revokeCert /etc/lacme/simpletest.rsa.crt ! lacme revokeCert /etc/lacme/simpletest.rsa.crt 2>"$STDERR" || fail grepstderr -Fxq "Revoking /etc/lacme/simpletest.rsa.crt" -grepstderr -Fxq "400 Bad Request (Certificate already revoked)" +grepstderr -Fq "400 Bad Request (unable to revoke" grepstderr -Fxq "Warning: Couldn't revoke /etc/lacme/simpletest.rsa.crt" # vim: set filetype=sh : diff --git a/tests/cert-verify b/tests/cert-verify index 49629f2..4d254c6 100644 --- a/tests/cert-verify +++ b/tests/cert-verify @@ -14,7 +14,7 @@ openssl verify -no-CApath -CAfile /etc/ssl/certs/ca-certificates.crt -show_chain mv /usr/share/lacme/ca-certificates.crt /usr/share/lacme/ca-certificates.crt.back ! lacme newOrder 2>"$STDERR" || fail -grepstderr -Fxq "Can't open /usr/share/lacme/ca-certificates.crt for reading, No such file or directory" +grepstderr -Fxq "Could not open file or uri for loading certs of trusted certificates from /usr/share/lacme/ca-certificates.crt" grepstderr -Fxq "[simpletest-rsa] Error: Received invalid X.509 certificate from ACME server!" # verification error for unrelated CA bundle diff --git a/tests/drop-privileges b/tests/drop-privileges index fd432d9..8deb8f1 100644 --- a/tests/drop-privileges +++ b/tests/drop-privileges @@ -123,8 +123,8 @@ check_client() { grep -Exq "[0-9]+ 0700 $UID:$GID socket:\[[0-9]+\]" "$prefix/fd" || return 1 sed -ri '0,\#^[0-9]+ .* socket:\[[0-9]+\]$# {//d}' "$prefix/fd" - grep -Exq "[0-9]+ 0500 $UID:$GID /etc/lacme/lacme\.conf" "$prefix/fd" || return 1 - sed -ri '0,\#^[0-9]+ .* /etc/lacme/lacme\.conf$# {//d}' "$prefix/fd" + grep -Eq "^[0-9]+ 0500 $UID:$GID /tmp/lacme-client.conf\.json-" "$prefix/fd" || return 1 + sed -ri '0,\#^[0-9]+ .* /tmp/lacme-client.conf\.json-# {//d}' "$prefix/fd" ! test -s "$prefix/fd" || return 1 } check_webserver() { diff --git a/tests/old-accountd b/tests/old-accountd index b44f7ec..3ad4b31 100644 --- a/tests/old-accountd +++ b/tests/old-accountd @@ -12,7 +12,7 @@ cat >~lacme-account/.config/lacme/lacme-accountd.conf <<-EOF privkey = file:/etc/lacme/account.key EOF -echo "deb http://deb.debian.org/debian stretch main" >>/etc/apt/sources.list +echo "deb http://archive.debian.org/debian stretch main" >>/etc/apt/sources.list DEBIAN_FRONTEND="noninteractive" apt update DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends \ --reinstall --allow-downgrades \ @@ -21,6 +21,7 @@ DEBIAN_FRONTEND="noninteractive" apt install -y --no-install-recommends \ SOCKET=~lacme-account/S.lacme runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" & PID=$! +sleep 1 lacme --socket="$SOCKET" account lacme --socket="$SOCKET" newOrder diff --git a/tests/old-lacme b/tests/old-lacme index fa7d827..b1c9f88 100644 --- a/tests/old-lacme +++ b/tests/old-lacme @@ -26,6 +26,7 @@ mv -f /usr/share/lacme/ca-certificates.crt.back /usr/share/lacme/ca-certificates SOCKET=~lacme-account/S.lacme runuser -u lacme-account -- lacme-accountd --socket="$SOCKET" & PID=$! +sleep 1 sed -ri "s/^\[accountd]$/#&/" /etc/lacme/lacme.conf # https://bugs.debian.org/955767 lacme --socket="$SOCKET" account lacme --socket="$SOCKET" newOrder |