Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@dtonhofer
Created April 28, 2019 11:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dtonhofer/01018844971235b511d241b537c332ee to your computer and use it in GitHub Desktop.
Save dtonhofer/01018844971235b511d241b537c332ee to your computer and use it in GitHub Desktop.
Split a certificate bundle like "/etc/pki/tls/certs/ca-bundle.crt" into individual certificates labeled by issuer
#!/usr/bin/perl
use strict;
use warnings;
# ===
# Synopsis:
#
# sbin/split_ca_bundle.pl /etc/pki/tls/certs/ca-bundle.crt
# sbin/split_ca_bundle.pl /etc/pki/tls/certs/ca-bundle.crt
#
# The result of splitting the CA bundle goes to a new directory created in the
# current working directory.
# ===
# Description:
#
# 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 written to a newly created directory. This directory is created
# in the current working directory.
#
# Created files are named "certificate.XXX" or "trusted-certificate.XXX",
# with XX an index value. The issuer/subject name (which should be the same
# as these are self-signed certificates) is append to the name.
#
# 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
#
# -------
# Author: David Tonhofer
# License: Public Domain
# -------
use File::Temp qw(tempdir); # Perl core module
use File::Spec::Functions qw(catfile); # Perl core module
# ---
# Try to create a temporary directory to be filled with certificates;
# The directory is created in tmpdir(); use "DIR => $dir" as additional
# argument to change that.
# ---
# my $tgdir = tempdir("explode_bundle_XXXX", TMPDIR => 1);
my $tgdir = tempdir("explode_bundle_XXXX"); # explode in current directory
if (!$tgdir) {
die "Could not create temporary directory: $!\n"
}
else {
print STDERR "Created temporary directory '$tgdir' into which result will be written.\n"
}
chdir $tgdir || die "Could not change working directory to '$tgdir': $!\n";
# ---
# Read the file with certificates in a single slupr (from STDIN or from name of cmdline
# ---
my @lines = <> or die "Could not 'slurp' input (file or STDIN): $!\n";
# ---
# Read and split using a simple state machine
# ---
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*$/) {
$trusted = $2;
($fn,$fh) = createCertFile($line,$count);
$state = "inside";
$count++
}
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 or die "Could not close file '$fn': $!\n";
#
# 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";
}
#
# Append Issuer and Subject to filename
#
my ($issuer,$subject,$selfsigned) = getIssuerAndSubject($fn);
my $fnewname;
if ($selfsigned) {
$fnewname = "$fn ($subject)" # can leave out the issuer
}
else {
$fnewname = "$fn ($issuer ---> $subject)"
}
rename($fn,$fnewname) or die "Could not rename '$fn' to '$fnewname': $!\n";
}
else {
print $fh "$line\n"
}
}
}
if ($state eq "inside") {
die "Last certificate was not properly terminated\n"
}
print STDERR "Done. Everything can be found in temporary directory '$tgdir'.\n";
# ---
# Create new file to accept certificate data
# ---
sub createCertFile {
my($line,$count) = @_;
$line =~ /^(-----BEGIN (TRUSTED )?CERTIFICATE-----)\s*$/ || die "Not-matching line '$line'\n";
my $marker = $1;
my $trusted = $2;
my $prefix = "";
$prefix = "trusted-" if ($trusted);
my $xcount = sprintf("%03d",$count); #prefix count with 0s
my $fn = "${prefix}certificate.$xcount";
die "File '$fn' exists!" if (-e $fn);
print STDERR "Certificate data goes to file '$fn'\n";
open($fh, '>:encoding(UTF-8)', $fn) || die "Could not create file '$fn': $!\n";
print $fh "$marker\n";
return ($fn,$fh)
}
# ---
# Grep for Issuer and Subject in just created file (is it in UTF-8?)
# ---
sub getIssuerAndSubject {
my($fn) = @_;
my $issuer;
my $subject;
my $fullIssuer;
my $fullSubject;
my $rest;
my $ref; # a reference to either $issuer or $subject
open(my $refh, '<:encoding(UTF-8)', $fn) || die "Could not open file '$fn' for reading: $!\n";
while (my $line = <$refh>) {
# We see things like: "Issuer: C = PL, O = Krajowa Izba Rozliczeniowa S.A., CN = SZAFIR ROOT CA2"
if ($line =~ /^\s+Issuer:\s+(.+)$/) {
die "There are two 'Issuer' lines in fn'\n" if ($fullIssuer);
$rest = $1;
$fullIssuer = $1;
$ref = \$issuer # change issuer below
}
elsif ($line =~ /^\s+Subject:\s+(.+)$/) {
die "There are two 'Subject' lines in fn'\n" if ($fullSubject);
$rest = $1;
$fullSubject = $1;
$ref = \$subject # change subject below
}
else {
$rest = undef
}
if ($rest) {
die "ref undefined while rest defined" unless $ref;
if ($rest =~ /\bCN\s*=\s*(.+?)\s*(,|$)/) {
$$ref = $1
}
else {
# Sometimes there is no CN, let's use the OU then
if ($rest =~ /\bOU\s*=\s*(.+?)\s*(,|$)/) {
$$ref = $1
}
else {
# Sometimes there is no OU, let's use the C and then
# This is the case of Taiwan
my $country = '?';
my $org = '?';
if ($rest =~ /\bC\s*=\s*(.+?)\s*(,|$)/) {
$country = $1
}
if ($rest =~ /\bO\s*=\s*(.+?)\s*(,|$)/) {
$org = $1
}
$$ref = "$country $org"
}
}
}
}
close $refh || die "Could not close file '$fn': $!\n";
my $selfsigned = ($issuer eq $subject);
return ($issuer,$subject,$selfsigned)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment