Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Kraus committed Oct 19, 2016
0 parents commit 57e9786
Show file tree
Hide file tree
Showing 2 changed files with 372 additions and 0 deletions.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# check_certexp

Check validity of certificates reachable via HTTP(S)

## Prerequisites

Perl with Net/SSLeay and Date/Manip. For example on Centos7:

```
yum install perl-Net-SSLeay perl-Date-Manip
```

## Usage

```
[root@app1 vagrant]# ./check_certexp.pl -h
Check certificate expiry date.
Usage: check_certexp.pl -H host [-p proxy] [-i issuer] [-w warn]
[-c crit] [-t timeout] [-d] [-v]
-H, --hostname=ADDRESS[:PORT]
Host name or IP address, port defaults to 443
-p, --proxy=ADDRESS[:PORT]
Proxy name or IP address, port defaults to 443
-i, --issuer=NAME:NAME
Certificate issuer name(s)
-w, --warning=INTEGER
WARNING if less than specified number of days until expiry (default: 28)
-c, --critical=INTEGER
CRITICAL if less than specified number of days until expiry (default: 28)
-t, --timeout=INTEGER
Seconds before connection times out (default: 15)
-d
Enable debug output
-v
Enable verbose output, use multiple for different views
```
334 changes: 334 additions & 0 deletions check_certexp.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
#!/usr/bin/env perl

# Check certificate expiry.
#
# Copyright (c) 2016 Michael Kraus <[email protected]>.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

##############################################################################
#
# basic settings
#
##############################################################################

use warnings;
use strict;
use IO::Socket;
use Getopt::Long;
use Net::SSLeay;
use Date::Manip;

use vars qw($opt_H $opt_c $opt_w $opt_h $opt_i $opt_p $opt_t $opt_d $opt_v );

my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3);

sub expiry_date ($$$$);
sub get_cert_details ($);
sub verbose ();
sub die_crit ($);
sub die_warn ($);
sub die_ok ($);
sub die_unknown ($);
sub print_usage ();
sub print_help ();
my $suffix = '';

my $PROGNAME = 'check_certexp.pl';

my $CRIT = 28;
my $TIMEOUT = 15;

$SIG{ALRM} = sub { die_unknown("Timeout of $TIMEOUT seconds reached."); };
$ENV{PATH} = '';
$ENV{ENV} = '';
$ENV{TZ} = 'CET';



##############################################################################
#
# parse options
#
##############################################################################

Getopt::Long::Configure('bundling');
if (!GetOptions('h|help' => \$opt_h,
'H|hostname=s' => \$opt_H,
'c|critical=i' => \$opt_c,
'w|warning=i' => \$opt_w,
'p|proxy=s' => \$opt_p,
'i|issuer=s' => \$opt_i,
'd|debug' => \$opt_d,
'v|verbose+' => \$opt_v,
't|timeout=i' => \$opt_t,)) {
print "Error processing command line options\n";
print_usage();
exit $ERRORS{UNKNOWN};
}

# help
if ($opt_h) {
print_help();
exit $ERRORS{OK};
}

# debug
my $debug = 0;
if ($opt_d) {
$debug = 1;
}

# verbose
my $verbose = 0;
if ($opt_v) {
$verbose = $opt_v;
}

# host:port / proxy:port
unless ($opt_H) {
print "No target host specified\n";
print_usage();
exit $ERRORS{UNKNOWN};
}
my ($host, $port, $dhost, $dport, $url);
if ($opt_p) {
($host, $port) = split(/:/, $opt_p);
($dhost, $dport) = split(/:/, $opt_H);
$url = $dhost;

} else {
($host, $port) = split(/:/, $opt_H);
$dhost = 'no';
$url = $host;
}
unless ($port) { $port = 443; }
unless ($dport) { $dport = 443; }


# thresholds
my $critical = $opt_c ? $opt_c : $CRIT;
my $warning = $opt_w ? $opt_w : $critical;
if ($warning < $critical) {
print "WARNING threshold exceeds CRITICAL threshold\n";
print_usage();
exit $ERRORS{UNKNOWN};
}

# timeout
my $timeout = $opt_t ? $opt_t : $TIMEOUT;
alarm($timeout);

# issuers
my @issuers = split(/:/, $opt_i) if $opt_i;



##############################################################################
#
# main
#
##############################################################################

# get certificate details
my ($notafter_days, $notafter, $notbefore_days, $notbefore, $subject, $issuer) = expiry_date($host, $port, $dhost, $dport);

# verify subject
print "DEBUG Verify $url with subject [ ",$subject," ]:\n" if $debug;
my $subject_cn = $subject;
$subject_cn=~ s/\/.*CN=([^\/]+).*/$1/;
print "DEBUG Issuer (CN): ",$subject_cn,"\n" if $debug;
die_crit("Subject CN '$subject_cn' does not match: $url") if not $subject_cn =~ /$url/;

# verify issuer
my $issuer_cn = $issuer;
$issuer_cn =~ s/\/.*CN=([^\/]+).*/$1/;
print "DEBUG Issuer (CN): ",$issuer_cn,"\n" if $debug;
if (@issuers) {
die_crit("Issuer CN '$issuer_cn' does not match: " . join(':', @issuers)) if not grep($issuer_cn eq $_, @issuers);
$suffix = " (Issuer: $issuer)";
}

# not yet valid
if ($notbefore_days > 0) {
die_crit("Certificate will be valid in $notbefore_days $suffix");
}

# is expired
if ($notafter_days < 0) {
$notafter_days =~ s/^-//;
die_crit("Certificate expired $notafter_days ago $suffix");
}

# verify against thresholds
die_crit("Certificate expires in $notafter_days days $suffix") if $notafter_days < $critical;
die_warn("Certificate expires in $notafter_days days $suffix") if $notafter_days < $warning;
die_ok("Certificate expires in $notafter_days days $suffix");



##############################################################################
#
# subs
#
##############################################################################

sub expiry_date ($$$$) {
my ($host, $port, $dhost, $dport) = @_;
my ($buffer, $iaddr, $paddr, $ctx, $ssl, $crt, $end, $str);

print "DEBUG Connect to host: ",$host,":",$port,"\n" if $debug;

# connect
$| = 1;
my $sock = new IO::Socket::INET (
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
) || die_unknown("Error connecting to $host: $!");

# connect with proxy
unless ( $dhost eq 'no' ) {
print "DEBUG Connect via proxy to destination: ",$dhost,":",$dport,"\n" if $debug;
# send a "CONNECT" command to proxy
print $sock "CONNECT $dhost:$dport HTTP/1.0\r\n\r\n";

# get HTTP status code, bail out unless 2xx
my $recv_line = <$sock>;
my ($status) = (split(/\s+/,$recv_line))[1];
die_unknown("Received a bad status code \"$status\" from proxy server.") if ( int($status/100) != 2 );

# skip through remaining part of HTTP header (until blank line)
1 until ( <$sock> =~ /^[\r\n]+$/ );
}

# initialize SSL
Net::SSLeay::load_error_strings();
Net::SSLeay::SSLeay_add_ssl_algorithms();
Net::SSLeay::randomize();
$ctx = Net::SSLeay::CTX_new();
my $mode = &Net::SSLeay::VERIFY_NONE;
Net::SSLeay::CTX_set_verify($ctx, $mode);
$ssl = Net::SSLeay::new($ctx);
Net::SSLeay::set_fd($ssl, fileno($sock));
Net::SSLeay::connect($ssl);
# DO NOT DIE HERE IF CONNECTION FAILS - WE CAN GET SERVER CERTIFICATE EVEN WITHOUT SUCCESSFUL CONNECTION THEN

# get certificate
$crt = Net::SSLeay::get_peer_certificate($ssl)
|| die_unknown(Net::SSLeay::print_errs('Cannot get peer certificate') or 'Cannot get peer certificate');

# get dates
my $tmp;
my $notafter = Net::SSLeay::P_ASN1_UTCTIME_put2string(Net::SSLeay::X509_get_notAfter($crt));
print "DEBUG NotAfter: ",$notafter,"\n" if $debug;

$tmp = $notafter;
$tmp =~ s/ GMT//;
my $notafter_days = int((&UnixDate(&ParseDate($tmp), '%s') - time) / 86400);
print "DEBUG NotAfter (days): ",$notafter_days,"\n" if $debug;

my $notbefore = Net::SSLeay::P_ASN1_UTCTIME_put2string(Net::SSLeay::X509_get_notBefore($crt));
print "DEBUG NotBefore: ",$notbefore,"\n" if $debug;

$tmp = $notbefore;
$tmp =~ s/ GMT//;
my $notbefore_days = int((&UnixDate(&ParseDate($tmp), '%s') - time) / 86400);
print "DEBUG NotBefore (days): ",$notbefore_days,"\n" if $debug;

# get subject
my $subject = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_subject_name($crt));
print "DEBUG Subject: ",$subject,"\n" if $debug;

# get issuer
my $issuer = Net::SSLeay::X509_NAME_oneline(Net::SSLeay::X509_get_issuer_name($crt));
print "DEBUG Issuer: ",$issuer,"\n" if $debug;

# cleanup
Net::SSLeay::free($ssl);
Net::SSLeay::CTX_free($ctx);
$sock->close();

return ("$notafter_days", "$notafter", "$notbefore_days", "$notbefore", "$subject", "$issuer");
}

sub verbose () {
if ($verbose == 2) {
printf "Subject: %s\n",$subject if $subject;
printf "Issuer: %s\n",$issuer if $issuer;
printf "NotAfter: %s\n",$notafter if $notafter;
printf "NotBefore: %s\n",$notbefore if $notbefore;
}
if ($verbose == 1) {
printf "Subject CN: %s\n",$subject_cn if $subject_cn;
printf "Issuer CN: %s\n",$issuer_cn if $issuer_cn;
printf "NotAfter: %s\n",$notafter if $notafter;
printf "NotBefore: %s\n",$notbefore if $notbefore;
}
}


sub die_unknown ($) {
printf "UNKNOWN: %s\n", shift;
exit $ERRORS{UNKNOWN};
}

sub die_warn ($) {
printf "WARNING: %s\n", shift;
verbose() if $verbose;
exit $ERRORS{WARNING};
}

sub die_crit ($) {
printf "CRITICAL: %s\n", shift;
verbose() if $verbose;
exit $ERRORS{CRITICAL};
}

sub die_ok ($) {
printf "OK: %s\n", shift;
verbose() if $verbose;
exit $ERRORS{OK};
}

sub print_usage () {
print "Usage: $PROGNAME -H host [-p proxy] [-i issuer] [-w warn]\n" .
" [-c crit] [-t timeout] [-d] [-v]\n";
}

sub print_help () {
print "Check certificate expiry date.\n\n";
print_usage();
print <<EOF;
-H, --hostname=ADDRESS[:PORT]
Host name or IP address, port defaults to 443
-p, --proxy=ADDRESS[:PORT]
Proxy name or IP address, port defaults to 443
-i, --issuer=NAME:NAME
Certificate issuer name(s)
-w, --warning=INTEGER
WARNING if less than specified number of days until expiry (default: $CRIT)
-c, --critical=INTEGER
CRITICAL if less than specified number of days until expiry (default: $CRIT)
-t, --timeout=INTEGER
Seconds before connection times out (default: $TIMEOUT)
-d
Enable debug output
-v
Enable verbose output, use multiple for different views
EOF
}

0 comments on commit 57e9786

Please sign in to comment.