Project

General

Profile

Actions

Bug #16894

open

Let's Encrypt fullchain file is truncated when moving from /tmp/acme to /conf/acme

Added by Ron Bauman 22 days ago. Updated 16 days ago.

Status:
New
Priority:
Normal
Assignee:
-
Category:
ACME
Target version:
-
Start date:
Due date:
% Done:

0%

Estimated time:
Plus Target Version:
Affected Version:
2.8.1
Affected Plus Version:
Affected Architecture:
All

Description

ACME package 1.2. With "Write ACME certificates to /conf/acme/" option selected in General Settings, the [domain].fullchain file in /conf/acme does not contain the Root YR cert. The cert is present in the fullchain.cer file found in /tmp/acme after the domain cert is issued from Let's Encrypt. If the [domain].fullchain file is subsequently used by an application on a server where the Root YR certificate is not in its list of CA certs, application authentication can fail.

A description of the problem and the current workaround: https://forum.netgate.com/topic/200786/certificate-chain-causing-application-authentication-failure.

Actions #1

Updated by Jay Sols 18 days ago

Also broken in pfsense 26.03 w/ Acme Package 1.2.

Actions #2

Updated by Dan Singletary 16 days ago

Confirming this on ACME package 1.2. Same truncation here, and I traced it to the source.

ROOT CAUSE

The exporter builds the chain from the leaf cert plus only its single immediate CA.
In /usr/local/pkg/acme/acme.inc, acme_write_certificate():
  $ca = lookup_ca($cert['caref']);   // leaf's immediate issuer ONLY
  ...
  file_put_contents("{$prefix}/{$cert['descr']}.fullchain", $crt . $ca);
It looks up exactly one CA (the cert's caref), never walks the rest of the chain, and
ignores acme.sh's own fullchain.cer (which is complete). The same export is driven by
acme_write_all_certificates() from acme_command.sh (the "Write certificates to /conf/acme"
/ writecerts path). This was latent until Let's Encrypt's "Generation Y" hierarchy. The old chain was
single-level (leaf -> R10/R11 -> ISRG Root X1, with X1 in the trust store), so leaf +
immediate CA happened to be a complete chain. Gen-Y is two-level: leaf -> Let's Encrypt YR1 -> ISRG Root YR (cross-signed by ISRG Root X1) The single-CA export stops at YR1 and drops the cross-signed Root YR, so
/conf/acme/<domain>.fullchain no longer chains to a trusted root. acme.sh's
/tmp/acme/.../fullchain.cer has all three certs; the package re-truncates on export.

WORKAROUND

1. Set Preferred Chain = "ISRG Root X1" on the cert, so acme.sh writes the cross-signed
(3-cert) chain into fullchain.cer.
2. Add a post-issuance "Shell command" action that copies the real fullchain over the
truncated export:
  cp /tmp/acme/CERTNAME/*/fullchain.cer /conf/acme/DOMAIN.fullchain
The action runs after the writecerts step (acme_command.sh: writecerts then actionlist),
so it is the last writer.

SUGGESTED FIX

acme_write_certificate() should export the complete chain: simplest is to copy acme.sh's
fullchain.cer (it already honors Preferred Chain), or follow each CA's parent caref up to
the root when assembling $ca, instead of a single lookup_ca($cert['caref']).
Actions

Also available in: Atom PDF