Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
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 drinking their drinks and smoking their pipes about the Monastery: (2)
As of 2024-04-26 00:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found