aboutsummaryrefslogtreecommitdiffstats
path: root/lacme
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2021-02-25 00:30:37 +0100
committerGuilhem Moulin <guilhem@fripost.org>2021-02-25 00:37:17 +0100
commitf09c95ea97c9bdee92f7c7622689aed540373a73 (patch)
tree8f72e639c798857282e0983eae2da6ea99ede9f3 /lacme
parent491998131f18d136ca37f15898d07062ad7a1fae (diff)
lacme: split certificates using Net::SSLeay::PEM_* instead of calling openssl.
Diffstat (limited to 'lacme')
-rwxr-xr-xlacme72
1 files changed, 36 insertions, 36 deletions
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";