On Mon, Jul 16, 2007 at 01:59:10PM -0400, Ed Ravin wrote:

>> > It seems like this can be done with the openssl command line, but I
>> > can only get certificate date information _after_ the certificate
>> > expires.  If anyone knows how to extract an SSL certificate's
>No need to parse out the certificate with sed - as implied in my previous
>message, openssl seems to be able to ignore the non-certificate portions
>of the file:
>    openssl s_client -connect www.example.com:443 2>/dev/null </dev/null | 
> openssl x509 -noout -enddate
>But if I was scripting this, I would call the two openssl commands
>separately and save the output to a file, so that I could detect failures
>more reliably...

Here's a little something I use.  It does three things:
1) Checks local certs (if you want to run it on the server that holds
the certs).  You can override the directory it looks in for certs with
--certdir=/path/to/certs.  And you can have it ignore certain patterns
when getting the list of certs by editing the @excludeCertDomains
variable (line 26).
2) Checks remote certs on Apache webservers.  You can specific domains
to check by editing the @manualDomains variable (line 24).
3) For both #1 and #2 it does a whois check for each domain, (a valid
cert does no good if the domain registration has expired).

Options:
--certdir=/PATH            Default is /etc/ssl/certs
--certsonly                Only test local certs
--debug                    Lots of ugly debug info
--expirelimit=INTEGER      Number of days before expiration to warn about
--remoteonly               Only test certs on webservers
--showexcludes             Show local certs being excluded
--verbose                  Verbose output

Yes, yes.  I know the code is ugly.  It was written a long time ago back
when I was first learning perl.  I would do it a lot differently now,
but it works and we don't fix it if it's not broken.  :-)
-- 
Regards...              Todd
OS X: We've been fighting the "It's a mac" syndrome with upper management
for  years  now.  Lately  we've  taken  to  just  referring  to  new  mac 
installations  as  "Unix"  installations  when  presenting proposals  and 
updates.  For some reason, they have no problem with that.          -- /.
Linux kernel 2.6.17-6mdv   2 users,  load average: 0.36, 0.37, 0.36
#!/usr/bin/perl -w

use strict;
use Net::Whois::Raw;
use Getopt::Long;
use Date::Calc qw( Parse_Date Decode_Date_US2 Decode_Date_EU2 Delta_Days );

# Once a week, run 'checkSSLCertStatus.pl --verbose' in a cron job and
# pipe it to 'mail -s "Weekely Cert Status Check" [EMAIL PROTECTED]'

$|++;
my %opts;

GetOptions( \%opts,
    'certdir:s',
        'certsonly',
        'debug',
        'expirelimit:i',
    'remoteonly',
        'showexcludes',
        'verbose',
);

my @manualDomains = ( 'secure.domain1.com', 'login.domain2.com',
    );
my @excludeCertDomains = ( 'domain1.com',
        'domain2.com', 'domain3.com',
    );
my $expireLimit = $opts{expirelimit} || 30;  # days
my $checkEnd = $expireLimit * 24 * 60 * 60;  # seconds
my $results;
my $today = localtime();
my $now = localtime();
my ($nowyear,$nowmonth,$nowday);
if ( ($nowyear,$nowmonth,$nowday) = Parse_Date($now) ) {
  if ( $opts{debug} ) {
    print "NOW: Year $nowyear, Month $nowmonth, Day $nowday\n";
  }
}


if ( !$opts{remoteonly} ) {
  my $certDir = $opts{certdir} || "/etc/ssl/certs";
  my @certs = `cd $certDir; ls *.cert`;
  # Manually exclude these local certs from being checked.  Our certs are named
  # domain1.com.conf, domain2.com.conf, etc, we exclude these specific ones.
  my @excludes = @excludeCertDomains;
  
  # This checks local certs.
  CERT: foreach my $cert ( @certs ) {
    chomp $cert;
    if ( $cert =~ /.*\.\d+\.cert$/ ) {
      if ( $opts{debug} ) {
        print "Skipping $cert\n\n";
      }
      next CERT;
    }
    foreach my $exclude ( @excludes ) {
      if ( $cert =~ /^\Q$exclude\E/ ) {
        if ( $opts{debug} || $opts{showexcludes} ) {
          print "Excluded $cert\n";
        }
      next CERT;
      }
    }
    if ( $opts{debug} ) {
      print "Processing $cert\n";
    }
    my @certCheck = `openssl x509 -in $certDir/$cert -dates -subject -checkend 
$checkEnd`;
    chomp @certCheck;
    $results->{$cert}->{start}  = $certCheck[0];
    $results->{$cert}->{start}  =~ s/notBefore=//;
    $results->{$cert}->{start}  =~ s/\s\s+/ /g;
    $results->{$cert}->{expire} = $certCheck[1];
    $results->{$cert}->{expire} =~ s/notAfter=//;
    $results->{$cert}->{expire} =~ s/\s\s+/ /g;
    $results->{$cert}->{domain} = $certCheck[2];
    $results->{$cert}->{domain} =~ s#.+/CN=(.+)$#$1#;
    $results->{$cert}->{result} = $certCheck[3];
  
    if ( $opts{debug} ) {
      print $results->{$cert}->{start} . " to " . 
        $results->{$cert}->{expire} . "\n";
      print $results->{$cert}->{domain} . ": " .
        $results->{$cert}->{result} . "\n\n";
    }
  }
  
  foreach my $cert ( keys %{$results} ) {
    if ( $results->{$cert}->{result} =~ /^Certificate will expire$/ ) {
      print "$cert expiration date: " . $results->{$cert}->{expire} . "\n";
    } else {
      if ( $opts{verbose} ) {
        print "OK: $cert\n";
      }
    }
  }
}

# Only wanted to check local certs
exit if $opts{certsonly};

foreach my $domain ( @manualDomains ) {
  $results->{$domain}->{domain}=$domain;
}

# This checks certs served by apache
my $completed;
DOMAIN: foreach my $cert ( keys %{$results} ) {
  my ($domain,$whois,$expiration);
  my ($year,$month,$day);
  # Strip leading and trailing cruft to get the domain name.  All our
  # secure domains start with login.domain11.com or secure.domain11.com
  # but you can add anything to be excluded here.
  $domain = $cert;
  $domain =~ s/^(login|secure)\.//;
  $domain =~ s/\.cert$//;
  if ( $opts{debug} ) {
    print "\nDEBUG: processing cert $cert, domain reduced to $domain\n";
  } 
  
  # skip to the next cert if this domain has already been looked up
  if ( ! $completed->{$domain} ) {
    if ( $whois = whois($domain) ) {
      if ( $whois =~ /(Expiration +Date:|Record expires on|Expires 
on\.*:|Expiry Date)\w?(.*)/i ) {
        $expiration = $2;
        chomp $expiration;
        $expiration =~ s/\.$//;
        $expiration =~ s/(..):(..):(..)//;
        $expiration =~ s/\r//;
        if ( $opts{debug} ) {
                print "DEBUG: In loop for $domain -> $expiration\n";
        }
        $completed->{$domain}=1;
        if ( ($year,$month,$day) = Parse_Date($expiration) or 
             ($year,$month,$day) = Decode_Date_US2($expiration) or
             ($year,$month,$day) = Decode_Date_EU2($expiration) ) {
          $results->{$cert}->{decoded}->{year}=$year;
          $results->{$cert}->{decoded}->{month}=$month;
          $results->{$cert}->{decoded}->{day}=$day;
       } 

      } else {
        if ( $whois =~ /(expiration|expire)(.*)/i ) {
          print "DEBUG:  $1  $2\n";
        }
        $expiration = "unable to be extracted";
      }
    } else {
      $expiration = "whois failed";
    }
    if ( $opts{debug} ) {
      print "DEBUG: Expiration string: $expiration\n";
      print "DEBUG: Expiration decoded:  Year $year, Month $month, Day $day\n";
    }
    my $daysTilExpired = Delta_Days($nowyear, $nowmonth, $nowday,
                                    $year,    $month,    $day    );
    if ( $opts{verbose} || $daysTilExpired < $expireLimit ) {
      print "Days until cert for domain $domain expires: $daysTilExpired 
($expiration)\n";
    }
  } else {
    if ( $opts{debug} ) {
      print "DEBUG: Domain $domain already processed\n";
    }
  }

  # Sucks slowing things down this much, but it's required to keep
  # Verisign from blocking our whois lookups when run from the
  # weekly cronjob.
  sleep 10;
}

Attachment: pgpyoYGdTrZWC.pgp
Description: PGP signature

_______________________________________________
mon mailing list
mon@linux.kernel.org
http://linux.kernel.org/mailman/listinfo/mon

Reply via email to