How can I split a CA certificate bundle into separ

2019-02-12 22:35发布

I'm working with OpenSSL and need a sane default list of CAs. I'm using Mozilla's list of trusted CAs, as bundled by cURL. However, I need to split this bundle of CA certs, because the OpenSSL documentation says:

If CApath is not NULL, it points to a directory containing CA certificates in PEM format. The files each contain one CA certificate. The files are looked up by the CA subject name hash value, which must hence be available.

For example, using the ca-bundle.crt file directly works fine:

openssl-1.0.1g> ./apps/openssl s_client -connect www.google.com:443 -CAfile /home/user/certs/ca-bundle.crt
...
    Verify return code: 0 (ok)
---
DONE

But specifying the directory containing the ca-bundle.crt file does not work:

openssl-1.0.1g> ./apps/openssl s_client -connect www.google.com:443 -CApath /opt/aspera/certs
    Verify return code: 20 (unable to get local issuer certificate)
---
DONE

I presume this is because my folder doesn't adhere to what the documentation asks for (namely, a directory containing CA certs in PEM format, with each file containing one cert, named by hash value). My directory just has the single bundle of certs.

How can I split my bundle of certs to adhere to OpenSSL's request that each cert be in an individual file? Bonus points if the hashing can be done too (though if needed I could write a script to do that myself if all the certs are in individual files).

5条回答
放我归山
2楼-- · 2019-02-12 22:49

Just to give an alternative; facing the same issue I ended up with csplit:

csplit -k -f bar foo.pem '/END CERTIFICATE/+1' {10}
查看更多
3楼-- · 2019-02-12 22:55

If you want to get a single certificate out of a multi-certificate PEM, try:

$ awk '/subject.*CN=host.domain.com/,/END CERTIFICATE/' INPUT.PEM

source

查看更多
太酷不给撩
4楼-- · 2019-02-12 22:59

Here is mine in Perl (so much code, but I like gonzo programming):

#!/usr/bin/perl -w                                                                                                                                                                                                                           

# -------
# Split "certificate bundles" like those found in /etc/pki/tls/certs into
# individual files and append the X509 cleartext description to each file.
#
# The file to split is given on the command line or piped via STDIN.
#
# Files are simply created in the current directory!
#
# Created files are named "certificate.XX" or "trusted-certificate.XX",
# with XX an index value.
#
# If a file with the same name as the output file already exists, it is not 
# overwritten. Instead a new name with a higher index is tried.
#
# This works for bundles of both trusted and non-trusted certificates.
#
# See http://tygerclan.net/?q=node/49 for another program of this kind, 
# which sets the name of the split-off files in function of the subject
# -------

my @lines = <> or die "Could not slurp: $!";

my $state = "outside"; # reader state machine state
my $count = 0;         # index of the certificate file we create
my $fh;                # file handle of the certificate file we create
my $fn;                # file name of the certificate file we create
my $trusted;           # either undef or "TRUSTED" depend on type of certificate

for my $line (@lines) {
   chomp $line;
   if ($state eq "outside") {
      if ($line =~ /^(-----BEGIN (TRUSTED )?CERTIFICATE-----)\s*$/) {         
         my $marker  = $1;
         $trusted    = $2;
         $state      = "inside";
         my $created = 0;
         my $prefix  = "";
         if ($trusted) {
            $prefix = "trusted-"
         }
         while (!$created) {
            $fn = "${prefix}certificate.$count"; 
            $count++;
            if (-f $fn) {
               # print STDERR "File '$fn' exists; increasing version number to $count\n";
            } 
            else {
               print STDERR "Certificate data goes to file '$fn'\n";
               open($fh,">$fn") || die "Could not create file '$fn': $!\n";
               $created = 1;
               print $fh "$marker\n"
            }
         }
      }
      else {
         print STDERR "Skipping line '$line'\n"
      }
   }
   else {
      if ($line =~ /^(-----END (TRUSTED )?CERTIFICATE-----)\s*$/) {
         my $marker       = $1;
         my $trustedCheck = $2;
         if (!((($trusted && $trustedCheck) || (!$trusted && !$trustedCheck)))) {
            die "Trusted flag difference detected\n"
         }
         $state = "outside";
         print $fh "$marker\n";
         print STDERR "Closing file '$fn'\n";
         close $fh;
         # Append x509 cleartext output by calling openssl tool
         `openssl x509 -noout -text -in '$fn' >> '$fn'`;
         if ($? != 0) { 
            die "Could not run 'openssl x509' command: $!\n";
         }
      } 
      else {
         print $fh "$line\n"
      }
   }
}

if ($state eq "inside") {
   die "Last certificate was not properly terminated\n";
}
查看更多
我欲成王,谁敢阻挡
5楼-- · 2019-02-12 23:03

You can split the bundle with awk, like this, in an appropriate directory:

awk 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > "cert." c ".pem"}' < ca-bundle.pem 

Then, create the links OpenSSL wants by running the c_rehash utility that comes with OpenSSL:

c_rehash .

Note: use 'gawk' on non linux-platforms - as above relies on a GNU specific feature.

查看更多
Anthone
6楼-- · 2019-02-12 23:09

The following Ruby-script will split the bundle (with one or more certificates in it) into files named after the hashes -- side-stepping the c_rehash step in most cases.

To use, cd into the right directory (such as /etc/ssl/certs/) and run the script with the path to your certificate bundle as the sole argument. For example: ruby /tmp/split-certificates.rb ca-root-nss.crt.

#!/usr/bin/env ruby

require 'openssl'

blob = IO.binread(ARGV[0]) # Read the entire file at once

DELIMITER = "\n-----END CERTIFICATE-----\n"
blobs = blob.split(DELIMITER)

blobs.each do |blob|
    blob.strip!
    blob += DELIMITER # Does not break DER
    begin
        cert = OpenSSL::X509::Certificate.new blob
    rescue
        puts "Skipping what seems like junk"
        next
    end
    begin
        # XXX Need to handle clashes, suffix other than 0
        filename=sprintf("%x.0", cert.subject.hash)
        File.open(filename,
            File::WRONLY|File::CREAT|File::EXCL) do |f|
            f.write(blob)
        end
    rescue Errno::EEXIST
        puts "#{filename} already exists, skipping"
    end
end
查看更多
登录 后发表回答