diff options
| -rw-r--r-- | Changelog | 2 | ||||
| -rwxr-xr-x | lacme | 72 | 
2 files changed, 38 insertions, 36 deletions
| @@ -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. @@ -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"; | 
