aboutsummaryrefslogtreecommitdiffstats
path: root/lacme
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2024-06-13 03:32:04 +0200
committerGuilhem Moulin <guilhem@fripost.org>2024-06-13 16:48:05 +0200
commit9cb882a468843bf8ce9598de8769d5baaaaae3ea (patch)
treee53a8783f8658bcf0d9778bc07037ec06e5b18f4 /lacme
parentbf4d2d13ffcd894c6e7765dbd366f1163c69c9e1 (diff)
Fix post-issuance validation logic.
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:
Diffstat (limited to 'lacme')
-rwxr-xr-xlacme26
1 files changed, 21 insertions, 5 deletions
diff --git a/lacme b/lacme
index 19d78a9..b167f9b 100755
--- a/lacme
+++ b/lacme
@@ -822,21 +822,31 @@ elsif ($COMMAND eq 'newOrder' or $COMMAND eq 'new-cert') {
next;
}
- my $cert;
+ my @chain;
eval {
my $mem = Net::SSLeay::BIO_s_mem() or die;
my $bio = Net::SSLeay::BIO_new($mem) or die;
die "incomplete write" unless
Net::SSLeay::BIO_write($bio, $chain) == length($chain);
- my $x509 = Net::SSLeay::PEM_read_bio_X509($bio);
- $cert = Net::SSLeay::PEM_get_string_X509($x509);
+
+ my $sk_x509_info = Net::SSLeay::PEM_X509_INFO_read_bio($bio);
+
+ my $n = Net::SSLeay::sk_X509_INFO_num($sk_x509_info);
+ for (my $i = 0; $i < $n; $i++) {
+ my $x509_info = Net::SSLeay::sk_X509_INFO_value($sk_x509_info, $i);
+ my $x509 = Net::SSLeay::P_X509_INFO_get_x509($x509_info);
+ my $cert = Net::SSLeay::PEM_get_string_X509($x509);
+ push @chain, $cert;
+ }
+
Net::SSLeay::BIO_free($bio) or die;
};
- if ($@) {
+ if ($@ or !@chain) {
print STDERR "[$s] Error: Received bogus X.509 certificate from ACME server!\n";
$rv = 1;
next;
}
+ my $cert = shift @chain; # leave only the intermediate in @chain
# extract pubkeys from CSR and cert, and ensure they match
# XXX would be nice to use X509_get_X509_PUBKEY and X509_REQ_get_X509_PUBKEY here,
@@ -852,9 +862,15 @@ elsif ($COMMAND eq 'newOrder' or $COMMAND eq 'new-cert') {
# verify certificate validity against the CA bundle
if ((my $CAfile = $conf->{CAfile} // '@@datadir@@/lacme/ca-certificates.crt') ne '') {
+ my $chain_tmp = File::Temp::->new(SUFFIX => '.crt', TMPDIR => 1) // die;
+ $chain_tmp->say($_) foreach @chain;
+ $chain_tmp->flush();
+
my %args = (in => $cert);
$args{out} = \*STDERR if $OPTS{debug};
- my @options = ('-trusted', $CAfile, '-purpose', 'sslserver', '-x509_strict');
+ my @options = ('-trusted', $CAfile);
+ push @options, '-untrusted', $chain_tmp->filename() if @chain;
+ push @options, ('-purpose', 'sslserver', '-x509_strict');
push @options, '-show_chain' if $OPTS{debug};
if (spawn(\%args, 'openssl', 'verify', @options)) {
print STDERR "[$s] Error: Received invalid X.509 certificate from ACME server!\n";