use strict;
$^W=1;
use Getopt::Long;
use Cwd qw( abs_path );
use LWP::UserAgent;
use Date::Parse;
use File::Temp qw/ :POSIX /;
use File::Copy;
use File::Compare;
use IO::Handle;
my $openssl = "openssl";
my $errorlevel = 1;
my @CApath = ();
my @CAcerts = ();
GetOptions("CApath=s" => \@CApath,
"openssl:s" => \$openssl,
"errorlevel:i" => \$errorlevel,
);
IO::Handle->autoflush();
scan_capaths();
scan_cacerts();
show_cacerts();
sub scan_capaths {
msglog(2, "Scaninng CApaths...");
foreach my $dir ( @CApath ) {
my $absdir = abs_path($dir);
msglog(3, "Scanning CApath: $absdir...");
opendir (DIR, $absdir) || msglog(0, "Unable to read CApath: $absdir.");
my @certs = grep { /.*\.\d+/ } readdir (DIR);
close (DIR);
foreach my $CAfile (@certs) {
my $certinfo = {absdir => $absdir,
CAfile => $CAfile,
};
push @CAcerts, $certinfo;
msglog(3, "Found (possible) CA-certificate: $CAfile");
};
};
msglog(3, "Scanning CApaths... done.");
};
sub scan_cacerts {
msglog(2, "Scaninng CA-certificates...");
foreach my $certinfo ( @CAcerts ) {
msglog(3, "Scanning CA-cert: " . $certinfo->{CAfile} );
scan_cacert( $certinfo );
};
msglog(2, "Scaninng CA-certificates... done.");
};
sub scan_cacert {
my $certinfo = $_[0];
my $crl_dist_point = 0;
my $validity = 0;
open (OPENSSL, "$openssl x509 -in " . $certinfo->{absdir} . "/" . $certinfo->{CAfile} . " -noout -text |") || {$certinfo->{badcert} = 1};
while (<OPENSSL>) {
chomp;
$certinfo->{badcert} = 0;
if ( $_ =~ /Validity/ and $validity == 0 ) {
$validity++;
};
if ( $_ =~ /Not Before:/ and $validity > 0 and $validity < 3 ) {
$certinfo->{notbefore} = str2time( ${ [split (/:/, $_, 2)] }[1] );
$validity++;
};
if ( $_ =~ /Not After :/ and $validity > 0 and $validity < 3 ) {
$certinfo->{notafter} = str2time( ${ [split(/:/, $_, 2) ]}[1] );
$validity++;
};
if ( $_ =~ /X509v3 CRL Distribution Points:/ ) {
$crl_dist_point = 1;
};
if ( $crl_dist_point eq 1 and $_ =~ /^$/ ) {
$crl_dist_point = 0;
};
if ( $crl_dist_point eq 1 and $_ =~ /URI:/ ) {
chomp;
$certinfo->{crluri} = ${ [split(/:/, $_, 2)] }[1];
};
if ( $_ =~ /Issuer:/ ) {
$certinfo->{issuer} = ${ [split(/:/, $_, 2)] }[1];
msglog(4, "Issuer: " . $certinfo->{issuer});
};
};
close OPENSSL;
};
sub show_cacerts {
msglog(2, "Downloading CRL's...");
foreach my $certinfo ( @CAcerts ) {
if (defined $certinfo->{badcert} && $certinfo->{badcert}) {
next;
} elsif (not defined $certinfo->{crluri}) {
msglog(1, $certinfo->{CAfile} . " has no CRL Distribution Point");
} else {
download_crl( $certinfo );
};
};
msglog(2, "Downloading CRL's... done.");
};
sub download_crl {
my $certinfo = $_[0];
my $ua = LWP::UserAgent->new();
my $time = time;
my $absCAfile = $certinfo->{absdir} . "/" . $certinfo->{CAfile};
if ( $time < $certinfo->{notbefore} ) {
msglog(1, "$absCAfile: too new.\n");
} elsif ( $time > $certinfo->{notafter} ) {
msglog(0, "$absCAfile: too old.\n");
} else {
(my $crlfile = $absCAfile) =~ s/(.*\.)(\d+)/$1r$2/;
my ($tmpfh, $tmpfile) = tmpnam();
msglog(2, "Downloading " . $certinfo->{CAfile} . " / " . $certinfo->{crluri} . "...");
my $response = $ua->get( $certinfo->{crluri} );
if ($response->is_success) {
msglog(2, "Downloading " . $certinfo->{CAfile} . " / " . $certinfo->{crluri} . "... " . $response->status_line);
if ( $response->content =~ /-----BEGIN X509 CRL-----/ ) {
printf $tmpfh $response->content;
} else {
open(OPENSSL, "| $openssl crl -inform DER -outform PEM -out $tmpfile");
print OPENSSL $response->content;
close OPENSSL;
};
open (OPENSSL, "$openssl crl -in $tmpfile -inform PEM -noout -CAfile $absCAfile 2>&1 |");
while (<OPENSSL>) {
if ($_ =~ /verify OK/) {
if ( -e $crlfile ) {
if ( compare( $tmpfile, $crlfile) == 0 ) {
msglog(1, $certinfo->{CAfile} . " / " . $certinfo->{crluri} . " is equal to present CRL.");
} else {
move( $crlfile, make_backup_filename($crlfile) );
move( $tmpfile, $crlfile );
}
} else {
move( $tmpfile, $crlfile );
};
chmod 0644, $crlfile;
} else {
msglog(0, $certinfo->{CAfile} . " CRL does not verify!");
};
};
close OPENSSL;
unlink $tmpfile;
} else {
msglog(0, "Downloading " . $certinfo->{CAfile} . " / " . $certinfo->{crluri} . "... " . $response->status_line);
};
};
};
sub make_backup_filename {
return $_[0] . "~";
};
sub msglog {
my ($level, $msg) = @_;
printf STDERR $msg . "\n" unless $level >= $errorlevel;
};