From 9cb882a468843bf8ce9598de8769d5baaaaae3ea Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 13 Jun 2024 03:32:04 +0200 Subject: Fix post-issuance validation logic. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than adding intermediates in the certificate bundle we now validate the leaf certificate with intermediates as untrusted (used for chain building only). Only the root certificates are used as trust anchor. Not pining intermediate certificates anymore is in line with Let's Encrypt's latest recommendations: Rotating the set of intermediates we issue from helps keep the Internet agile and more secure. It encourages automation and efficiency, and discourages outdated practices like key pinning. “Key Pinning” is a practice in which clients — either ACME clients getting certificates for their site, or apps connecting to their own backend servers — decide to trust only a single issuing intermediate certificate rather than delegating trust to the system trust store. Updating pinned keys is a manual process, which leads to an increased risk of errors and potential business continuity failures. — https://letsencrypt.org/2024/03/19/new-intermediate-certificates: --- tests/cert-install | 82 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 29 deletions(-) (limited to 'tests') diff --git a/tests/cert-install b/tests/cert-install index e24fe34..279309f 100644 --- a/tests/cert-install +++ b/tests/cert-install @@ -28,6 +28,55 @@ EOF grepstderr -Fxq "[bad3] Warning: Couldn't generate CSR, skipping" +check_spki() { + local p1="$1" p2="$2" s1 s2 + s1="$(openssl x509 -in "$p1" -noout -pubkey \ + | openssl pkey -pubin -outform DER \ + | openssl dgst -sha256 \ + | sed 's/.*=\s*//')" + s2="$(openssl pkey -in "$p2" -pubout -outform DER \ + | openssl dgst -sha256 \ + | sed 's/.*=\s*//')" + if [ -n "$s1" ] && [ "$s1" = "$s2" ]; then + return 0 + else + printf "%s != %s\\n" "$s1" "$s2" >&2 + return 1 + fi +} +check_chain() { + local priv="$1" chain="$2" leaf="${3-}" pem0 + + csplit -f "${chain%.crt}.chain.pem" "$chain" \ + "/-----BEGIN CERTIFICATE-----/" "{*}" + + pem0="${chain%.crt}.chain.pem00" + if [ ! -s "$pem0" ]; then + # 00 is empty, leaf cert is at 01 + rm -f -- "$pem0" + pem0="${chain%.crt}.chain.pem01" + fi + test -s "$pem0" || return 1 + check_spki "$pem0" "$priv" + + if [ -n "$leaf" ]; then + diff --ignore-blank-lines --unified "$pem0" "$leaf" || return 1 + fi + + leaf="${chain%.crt}.leaf.pem" + mv -T -- "$pem0" "$leaf" + + intermediates="${chain%.crt}.intermediates.pem" + sed "/^$/d" "${chain%.crt}.chain.pem"[0-9]* >"$intermediates" + test -s "$intermediates" || return 1 # ensure there is at least one intermediate + + openssl verify -trusted /usr/share/lacme/ca-certificates.crt \ + -untrusted "$intermediates" \ + -purpose sslserver -x509_strict \ + -show_chain \ + -- "$leaf" || return 1 +} + # 'certificate' installs only the leaf certificate openssl genpkey -algorithm RSA -out /etc/lacme/test1.key subject="/CN=$(head -c10 /dev/urandom | base32 -w0 | tr "A-Z" "a-z").$DOMAINNAME" @@ -42,23 +91,9 @@ lacme newOrder test1 2>"$STDERR" || fail newOrder test1 test /etc/lacme/test1.crt -nt /etc/lacme/test1.key sed -n "0,/^-----END CERTIFICATE-----$/ p" /etc/lacme/test1.crt >/etc/lacme/test1.pem diff --unified /etc/lacme/test1.crt /etc/lacme/test1.pem +check_spki /etc/lacme/test1.crt /etc/lacme/test1.key -check_hash() { - local p1="$1" p2 s1 s2 - s1="$(openssl x509 -in "$p1" -noout -hash)" - for p2 in /usr/share/lacme/ca-certificates.pem.*; do - s2="$(openssl x509 -in "$p2" -noout -hash)" - if [ "$s1" = "$s2" ]; then - return 0 - fi - done - return 1 -} -csplit -f /usr/share/lacme/ca-certificates.pem. /usr/share/lacme/ca-certificates.crt \ - "/-----BEGIN CERTIFICATE-----/" "{*}" -rm -f /usr/share/lacme/ca-certificates.pem.00 - # 'certificate-chain' appends the chain of trust openssl genpkey -algorithm RSA -out /etc/lacme/test2.key cat >"/etc/lacme/lacme-certs.conf.d/test2.conf" <<- EOF @@ -70,16 +105,7 @@ EOF lacme newOrder test2 2>"$STDERR" || fail newOrder test2 test /etc/lacme/test2.crt -nt /etc/lacme/test2.key -csplit -f /etc/lacme/test2.chain.pem /etc/lacme/test2.crt \ - "/-----BEGIN CERTIFICATE-----/" "{*}" -test -s /etc/lacme/test2.chain.pem01 # leaf cert (00 is empty) -rm -f /etc/lacme/test2.chain.pem0[01] -test -s /etc/lacme/test2.chain.pem02 # depth 1 - -# all certificates at depth >=1 must be in our CA bundle -for p in /etc/lacme/test2.chain.pem*; do - check_hash "$p" -done +check_chain /etc/lacme/test2.key /etc/lacme/test2.crt # 'certificate' + 'certificate-chain' openssl genpkey -algorithm RSA -out /etc/lacme/test3.key @@ -94,10 +120,8 @@ EOF lacme newOrder test3 2>"$STDERR" || fail newOrder test3 test /etc/lacme/test3.pem -nt /etc/lacme/test3.key test /etc/lacme/test3.crt -nt /etc/lacme/test3.key -csplit -f /etc/lacme/test3.chain.pem /etc/lacme/test3.crt \ - "/-----BEGIN CERTIFICATE-----/" "{*}" -sed -i "/^$/d" /etc/lacme/test3.chain.pem* -diff -q /etc/lacme/test3.chain.pem01 /etc/lacme/test3.pem +check_chain /etc/lacme/test3.key /etc/lacme/test3.crt /etc/lacme/test3.pem + st="$(stat -c "%U:%G %#a" /etc/lacme/test3.pem)" [ "$st" = "root:root 0644" ] st="$(stat -c "%U:%G %#a" /etc/lacme/test3.crt)" -- cgit v1.2.3