Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Getting an SSL Certificate Expiration Date

by enemyofthestate (Monk)
on Feb 08, 2022 at 22:06 UTC ( [id://11141247]=perlquestion: print w/replies, xml ) Need Help??

enemyofthestate has asked for the wisdom of the Perl Monks concerning the following question:

Is there a way to get the SSL certificate information from LWP::UserAgent? Specifically I am looking for the expiration date. The information has to have been retrieved during the SSL handshake so I am wondering if there is any way I get at it. Even the raw cert would be helpful.

This is part of a project to monitor the ssl offloading on haproxy servers and an F5 BigIP. The testing done by XYMon properly considers a 400 or 500 status from the upstream servers to be a failure which leaves the accuracy of the monitoring at the mercy of less than ideal code in the upstream pool.

With LWP::UserAgent I can apply my own filters to give me better reporting of how the offload devices are actually performing.

Not doing anything fancy, just a straightforward request

# get site headers if it is reachable # create the agent my $ua = new LWP::UserAgent; my $url = "https://" . $site; # try to connect my $resp = $ua->get($url); # we don't care if the response code is a 200, 300, or 400 but 500 i +s still a # bad thing if ($resp->code >= 500) { $resp_line = $resp->status_line ? $resp->status_line : "Unable to +connect to " . $site; $resp_headers = ""; $status_color = "red"; } else { $resp_line = $resp->protocol . " " . $resp->status_line; $resp_headers = $resp->headers_as_string; chomp($resp_headers); } # calculate Elapsed Time my $et = tv_interval ($t0); my $time = localtime; my $httpout = sprintf $HTTPFMT, $site, "http", $status_color, $time, + $url, $resp_line, $resp_headers, $et; # send output to xymon send_2_xymon($XYMON_SVR, $XYMON_PORT, $httpout);

If worse comes to worse, I can get it calling openssl and parsing the output. It would just be kind of nice to not have to make a second connection to the site.

Replies are listed 'Best First'.
Re: Getting an SSL Certificate Expiration Date
by hippo (Bishop) on Feb 08, 2022 at 22:40 UTC

    My understanding is that you can supply your own callback via LWP::UserAgent to do your own processing at the certificate-validation phase. That would avoid the need for a second connection. This isn't something I have done but the docs suggest it might be a feasible approach.

    my $ua = LWP::UserAgent->new ( ssl_opts => { SSL_verify_callback => \&my_handler } );

    🦛

      Good idea, some quick googling found this post by Graham Knop from which I adapted the code:

      use warnings; use strict; use LWP::UserAgent; my $ua = LWP::UserAgent->new( ssl_opts => { SSL_verify_callback => sub { my ($ok, $ctx_store) = @_; my $cert = Net::SSLeay::X509_STORE_CTX_get_current_cert($c +tx_store); print Net::SSLeay::P_ASN1_TIME_get_isotime( Net::SSLeay::X509_get_notAfter($cert) ), "\n"; return $ok; }, }, ); $ua->get('https://www.perlmonks.org/'); __END__ 2038-01-18T23:59:59Z 2029-08-21T23:59:59Z 2022-09-02T23:59:59Z

      Note the documentation also says "The callback will be called for each element in the certificate chain."

      Another post in the above thread points to Net::SSL::ExpireDate.

Re: Getting an SSL Certificate Expiration Date
by haukex (Archbishop) on Feb 08, 2022 at 22:41 UTC

    I'm not enough of an expert with LWP to know if this is the "right" way, but it's based on what worked for me over in Re: "This site is not secure" warning message:

    use warnings; use strict; use LWP::UserAgent; use Net::SSLeay; use LWP::Protocol::https (); # just to make sure this is installed use Class::Method::Modifiers qw/around/; # wrap this method to fetch additional info from the cert around 'LWP::Protocol::https::_get_sock_info' => sub { my $orig = shift; my ($self, $res, $sock) = @_; my $cert = $sock->peer_certificate; $res->push_header("Client-SSL-Cert-NotAfter" => Net::SSLeay::P_ASN1_TIME_get_isotime( Net::SSLeay::X509_get_notAfter($cert) ) ); $orig->(@_); }; my $ua = LWP::UserAgent->new; my $res = $ua->get("https://www.perlmonks.org/"); die $res->status_line unless $res->is_success; my @issuer = $res->header("client-ssl-cert-issuer"); my @subject = $res->header("client-ssl-cert-subject"); my @notAfter = $res->header("client-ssl-cert-notafter"); print " Issuer: @issuer\n Subject: @subject\nnotAfter: @notAfter\n"; __END__ Issuer: /C=US/ST=New Jersey/L=Jersey City/O=The USERTRUST Network/CN +=USERTrust RSA Domain Validation Secure Server CA Subject: /CN=perlmonks.org notAfter: 2022-09-02T23:59:59Z

    Update: See also my other post.

      Thanks haukex for this code! really useful. I must admit I tried something similar quite few times, with no success.

      Thanks to enemyofthestate for the interesting question and hippo for the contribution pointing to the right callback.

      What I ended using could be interesting and useful for someone else so I share it: I used the online service provided by digicert and a chrome extension (contextual menu ie: right clicking on a URL), an extension generated with my Automatic chrome extension generator

      extgen.pl SSL_Check https://www.digicert.com/help/?host=

      L*

      There are no rules, there are no thumbs..
      Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Getting an SSL Certificate Expiration Date
by enemyofthestate (Monk) on Feb 09, 2022 at 20:21 UTC

    That was incredibly helpful. Thank you. This is not my cleanest work and needs better error checking. It does, however, work as expected.

    #!/usr/bin/env perl #------------------------------------------------ use strict; use warnings; #------------------------------------------------ # extras my $DEBUG = 1; use LWP::UserAgent; use Time::HiRes qw(gettimeofday tv_interval); use Date::Parse; my $XYMON_SVR = "127.0.0.1"; my $XYMON_PORT = 1984; my $WARN_DAYS = 30; my @MONTH = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); #------------------------------------------------ # output formats # month day, year hh:mm:ss zone my $TIME_FMT = "%s %s, %s %s:%s:%s %s"; # output for http my $HTTPFMT="status %s.%s %s %s %s %s %s %s seconds\n"; # output fro sslcert my $SSLCERTFMT="status %s.%s %s %s Certificate for %s expires in %d days Expiration Date: %s\n"; #------------------------------------------------ # globals ## certificate info my %g_certinfo = (); #------------------------------------------------ # main #------------------------------------------------ sub main { # get the site name my $site = $ARGV[0]; return 1 unless $site; my $resp_line = ""; my $resp_headers = ""; # always start as green my $status_color = "green"; # start the timer my $t0 = [gettimeofday]; #---------------------------------------------- # get site headers if it is reachable # create the agent my $ua = LWP::UserAgent->new ( ssl_opts => {SSL_verify_callback => \&xtract_cert } ); # define teh url my $url = "https://" . $site; # try to connect my $resp = $ua->get($url); # we don't care if the response code is a 200, 300, or 400 but 500 i +s still a # bad thing if ($resp->code >= 500) { $resp_line = $resp->status_line ? $resp->status_line : "Unable to +connect to " . $site; $resp_headers = ""; $status_color = "red"; } else { $resp_line = $resp->protocol . " " . $resp->status_line; $resp_headers = $resp->headers_as_string; chomp($resp_headers); } # calculate Elapsed Time my $et = tv_interval ($t0); # time for status line my $time = localtime(); # send https status to xymon my $httpout = sprintf $HTTPFMT, $site, "http", $status_color, $time, + $url, $resp_line, $resp_headers, $et; send_2_xymon($XYMON_SVR, $XYMON_PORT, $httpout); # convert time from LWP to seconds since epoch my $epoch_xpire = str2time( $g_certinfo{EndDate} ); # if no epoch_xpire then there is a problem. return 1 unless ($epoch_xpire); # convert expiration time to human readable format my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = +localtime($epoch_xpire); $year += 1900; my $tz = $isdst > 0 ? "PDT" : "PST"; my $expire_date = sprintf $TIME_FMT, $MONTH[$mon], $mday, $year, $ho +ur, $min, $sec, $tz; # current time since epoch my $epoch_now = time(); # how much time is left on cert my $remain_sec = $epoch_xpire - $epoch_now; my $remain_day = int($remain_sec / 86400 + 0.5); if ($remain_sec > $WARN_DAYS * 86400) { $status_color = "green"; } elsif ($remain_sec < 0) { $status_color = "red"; } else { $status_color = "yellow"; } my $sslcertout = sprintf $SSLCERTFMT, $site, "sslcert", $status_colo +r, $time, $url, $remain_day, $expire_date; send_2_xymon($XYMON_SVR, $XYMON_PORT, $sslcertout); return 0; } #------------------------------------------------ # extract some certificate information #------------------------------------------------ sub xtract_cert { my ($ok, $ctx_store) = @_; my $cert = Net::SSLeay::X509_STORE_CTX_get_current_cert($ctx_store); $g_certinfo{EndDate} = Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSL +eay::X509_get_notAfter($cert)); return $ok; } #------------------------------------------------ # send the data to xymon #------------------------------------------------ sub send_2_xymon { use IO::Socket; my ($server,$port,$output) = @_; if ($DEBUG) { print $output, "\n"; return ""; } # open a socket my $socket = new IO::Socket::INET ( PeerAddr => $server, PeerPort => $port, Proto => 'tcp', ); return "Could not create socket: $!n" unless $socket; # send data over socket print $socket $output; # close socket close($socket); # return empty string on success return ""; } exit main();

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11141247]
Approved by choroba
Front-paged by Discipulus
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (4)
As of 2024-03-29 11:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found