From f09c95ea97c9bdee92f7c7622689aed540373a73 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Thu, 25 Feb 2021 00:30:37 +0100 Subject: lacme: split certificates using Net::SSLeay::PEM_* instead of calling openssl. --- Changelog | 2 ++ lacme | 72 +++++++++++++++++++++++++++++++-------------------------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/Changelog b/Changelog index 92b8a4d..d63c754 100644 --- a/Changelog +++ b/Changelog @@ -9,6 +9,8 @@ lacme (0.8.1) upstream; challenge files. + lacme: add 'owner' resp. 'mode' as (prefered) alias for 'chown' resp. 'chmod'. + + lacme: split certificates using Net::SSLeay::PEM_* instead of calling + openssl. - 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. diff --git a/lacme b/lacme index 102deb6..13c2ef5 100755 --- a/lacme +++ b/lacme @@ -37,7 +37,7 @@ 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 Net::SSLeay (); +use Net::SSLeay 1.46 (); # Clean up PATH $ENV{PATH} = join ':', qw{/usr/bin /bin}; @@ -658,32 +658,14 @@ sub spawn($@) { ############################################################################# # Install the certificate (optionally excluding the chain of trust) # -sub install_cert(%) { - my %args = @_; - my $path = $args{path} // die; +sub install_cert($$%) { + my ($path, $content, %args) = @_; my $fh = File::Temp::->new(TEMPLATE => "$path.XXXXXXXXXX", UNLINK => 0) // die; my $path_tmp = $fh->filename(); eval { - if ($args{nochain}) { - # keep only the first certificate in the file - pipe my $rd, my $wd or die "pipe: $!"; - my $pid = fork // die "fork: $!"; - unless ($pid) { - open STDIN, '<&', $rd or die "dup: $!"; - open STDOUT, '>&', $fh or die "dup: $!"; - exec qw/openssl x509 -outform PEM/ or die; - } - $rd->close() or die "close: $!"; - $wd->print($args{content}); - $wd->close() or die "close: $!"; - - waitpid $pid => 0; - die $? if $? > 0; - } else { - $fh->print($args{content}) or die "print: $!"; - } + $fh->print($content) or die "print: $!"; my $mode; if ((my $m = $args{mode}) ne "") { @@ -785,15 +767,15 @@ elsif ($COMMAND eq 'newOrder' or $COMMAND eq 'new-cert') { print STDERR " $_ = $conf->{$_}\n" foreach grep { defined $conf->{$_} } (sort keys %$conf); } - my @certs = grep {defined $_ and $_ ne ""} @$conf{qw/certificate-chain certificate/}; - unless (@certs) { + my @certpaths = grep {defined $_ and $_ ne ""} @$conf{qw/certificate-chain certificate/}; + unless (@certpaths) { print STDERR "[$s] Warning: Missing 'certificate' and 'certificate-chain', skipping\n"; $rv = 1; next; } # skip certificates that expire at least $conf->{'min-days'} days in the future - if (-f $certs[0] and defined (my $t = x509_enddate($certs[0]))) { + if (-f $certpaths[0] and defined (my $t = x509_enddate($certpaths[0]))) { my $d = $OPTS{'min-days'} // $conf->{'min-days'} // 21; if ($d >= 0 and $t - time > $d*86400) { my $d = POSIX::strftime('%Y-%m-%d %H:%M:%S UTC', gmtime($t)); @@ -826,18 +808,37 @@ elsif ($COMMAND eq 'newOrder' or $COMMAND eq 'new-cert') { } } - my ($x509, $csr_pubkey, $x509_pubkey); + my $chain; print STDERR "[$s] Will request authorization for: ".join(", ", @authz), "\n" if $OPTS{debug}; - if (acme_client({chdir => $challenge_dir, in => $csr, out => \$x509}, @authz)) { + if (acme_client({chdir => $challenge_dir, in => $csr, out => \$chain}, @authz)) { print STDERR "[$s] Error: Couldn't issue X.509 certificate!\n"; $rv = 1; next; } + my $cert; + 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); + Net::SSLeay::BIO_free($bio) or die; + }; + if ($@) { + print STDERR "[$s] Error: Received bogus X.509 certificate from ACME server!\n"; + $rv = 1; + next; + } + # 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, + # or EVP_PKEY_cmp(), but unfortunately Net::SSLeay 1.88 doesn't support these + my ($cert_pubkey, $csr_pubkey); + spawn({in => $cert, out => \$cert_pubkey}, qw/openssl x509 -inform PEM -noout -pubkey/); spawn({in => $csr, out => \$csr_pubkey }, qw/openssl req -inform DER -noout -pubkey/); - spawn({in => $x509, out => \$x509_pubkey}, qw/openssl x509 -inform PEM -noout -pubkey/); - unless (defined $x509_pubkey and defined $csr_pubkey and $x509_pubkey eq $csr_pubkey) { + unless (defined $cert_pubkey and defined $csr_pubkey and $cert_pubkey eq $csr_pubkey) { print STDERR "[$s] Error: Received bogus X.509 certificate from ACME server!\n"; $rv = 1; next; @@ -845,7 +846,7 @@ 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 %args = (in => $x509); + my %args = (in => $cert); $args{out} = \*STDERR if $OPTS{debug}; my @options = ('-trusted', $CAfile, '-purpose', 'sslserver', '-x509_strict'); push @options, '-show_chain' if $OPTS{debug}; @@ -856,25 +857,24 @@ elsif ($COMMAND eq 'newOrder' or $COMMAND eq 'new-cert') { } } - my %install = ( content => $x509, + # install certificate + my %install_opts = ( mode => $conf->{mode} // $conf->{chmod} // "", owner => $conf->{owner} // $conf->{chown} // "" ); - - # install certificate if ((my $path = $conf->{'certificate'} // "") ne "") { print STDERR "Installing X.509 certificate $path\n"; - install_cert(%install, path => $path, nochain => 1); + install_cert($path => $cert, %install_opts); } if ((my $path = $conf->{'certificate-chain'} // "") ne "") { print STDERR "Installing X.509 certificate chain $path\n"; - install_cert(%install, path => $path); + install_cert($path => $chain, %install_opts); } my @certopts = join ',', qw/no_header no_version no_pubkey no_sigdump/; open my $fh, '|-', qw/openssl x509 -noout -fingerprint -sha256 -text -certopt/, @certopts or die "fork: $!"; - print $fh $x509; + print $fh $cert; close $fh or die $! ? "close: $!" : "Error: x509(1ssl) exited with value ".($? >> 8)."\n"; -- cgit v1.2.3