<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Long;
use Data::Dumper;
use IO::Socket::INET;
use Net::SSLeay qw/XN_FLAG_RFC2253 ASN1_STRFLGS_ESC_MSB/;

Net::SSLeay::randomize();
Net::SSLeay::load_error_strings();
Net::SSLeay::ERR_load_crypto_strings();
Net::SSLeay::SSLeay_add_ssl_algorithms();

# --- commandline options and global variables

my ($g_host, $g_pem, $g_dump, $g_showusage);

GetOptions(
  'help|?'  =&gt; \$g_showusage,
  'dump'    =&gt; \$g_dump,
  'host=s@' =&gt; \$g_host,
  'pem=s@'  =&gt; \$g_pem,
) or $g_showusage = 1;

# --- subroutines

sub show_usage {
  die &lt;&lt;EOL;

Usage: $0 &lt;options&gt;
  -help -?                  show this help
  -pem &lt;file&gt;               process X509 certificate from file (PEM format)
  -host &lt;ip_or_dns&gt;:&lt;port&gt;  process X509 certificate presented by SSL server
  -dump                     full dump of X509 certificate info

Example:
  $0 -pem file1.pem
  $0 -pem file1.pem -pem file2.pem
  $0 -host twitter.com:443 -dump

EOL
}

sub get_cert_details {
  my $x509 = shift;
  my $rv = {};
  my $flag_rfc22536_utf8 = (XN_FLAG_RFC2253) &amp; (~ ASN1_STRFLGS_ESC_MSB);

  die 'ERROR: $x509 is NULL, gonna quit' unless $x509;

  warn "Info: dumping subject\n";
  my $subj_name = Net::SSLeay::X509_get_subject_name($x509);
  my $subj_count = Net::SSLeay::X509_NAME_entry_count($subj_name);
  $rv-&gt;{subject}-&gt;{count} = $subj_count;
  $rv-&gt;{subject}-&gt;{oneline} = Net::SSLeay::X509_NAME_oneline($subj_name);
  $rv-&gt;{subject}-&gt;{print_rfc2253} = Net::SSLeay::X509_NAME_print_ex($subj_name);
  $rv-&gt;{subject}-&gt;{print_rfc2253_utf8} = Net::SSLeay::X509_NAME_print_ex($subj_name, $flag_rfc22536_utf8);
  $rv-&gt;{subject}-&gt;{print_rfc2253_utf8_decoded} = Net::SSLeay::X509_NAME_print_ex($subj_name, $flag_rfc22536_utf8, 1);
  for my $i (0..$subj_count-1) {
    my $entry = Net::SSLeay::X509_NAME_get_entry($subj_name, $i);
    my $asn1_string = Net::SSLeay::X509_NAME_ENTRY_get_data($entry);
    my $asn1_object = Net::SSLeay::X509_NAME_ENTRY_get_object($entry);
    my $nid = Net::SSLeay::OBJ_obj2nid($asn1_object);
    $rv-&gt;{subject}-&gt;{entries}-&gt;[$i] = {
          oid  =&gt; Net::SSLeay::OBJ_obj2txt($asn1_object,1),
          data =&gt; Net::SSLeay::P_ASN1_STRING_get($asn1_string),
          data_utf8_decoded =&gt; Net::SSLeay::P_ASN1_STRING_get($asn1_string, 1),
          nid  =&gt; ($nid&gt;0) ? $nid : undef,
          ln   =&gt; ($nid&gt;0) ? Net::SSLeay::OBJ_nid2ln($nid) : undef,
          sn   =&gt; ($nid&gt;0) ? Net::SSLeay::OBJ_nid2sn($nid) : undef,
    };
  }

  warn "Info: dumping issuer\n";
  my $issuer_name = Net::SSLeay::X509_get_issuer_name($x509);
  my $issuer_count = Net::SSLeay::X509_NAME_entry_count($issuer_name);
  $rv-&gt;{issuer}-&gt;{count} = $issuer_count;
  $rv-&gt;{issuer}-&gt;{oneline} = Net::SSLeay::X509_NAME_oneline($issuer_name);
  $rv-&gt;{issuer}-&gt;{print_rfc2253} = Net::SSLeay::X509_NAME_print_ex($issuer_name);
  $rv-&gt;{issuer}-&gt;{print_rfc2253_utf8} = Net::SSLeay::X509_NAME_print_ex($issuer_name, $flag_rfc22536_utf8);
  $rv-&gt;{issuer}-&gt;{print_rfc2253_utf8_decoded} = Net::SSLeay::X509_NAME_print_ex($issuer_name, $flag_rfc22536_utf8, 1);
  for my $i (0..$issuer_count-1) {
    my $entry = Net::SSLeay::X509_NAME_get_entry($issuer_name, $i);
    my $asn1_string = Net::SSLeay::X509_NAME_ENTRY_get_data($entry);
    my $asn1_object = Net::SSLeay::X509_NAME_ENTRY_get_object($entry);
    my $nid = Net::SSLeay::OBJ_obj2nid($asn1_object);
    $rv-&gt;{issuer}-&gt;{entries}-&gt;[$i] = {
          oid  =&gt; Net::SSLeay::OBJ_obj2txt($asn1_object,1),
          data =&gt; Net::SSLeay::P_ASN1_STRING_get($asn1_string),
          data_utf8_decoded =&gt; Net::SSLeay::P_ASN1_STRING_get($asn1_string, 1),
          nid  =&gt; ($nid&gt;0) ? $nid : undef,
          ln   =&gt; ($nid&gt;0) ? Net::SSLeay::OBJ_nid2ln($nid) : undef,
          sn   =&gt; ($nid&gt;0) ? Net::SSLeay::OBJ_nid2sn($nid) : undef,
    };
  }

  warn "Info: dumping alternative names\n";
  $rv-&gt;{subject}-&gt;{altnames} = [ Net::SSLeay::X509_get_subjectAltNames($x509) ];
  #XXX-TODO maybe add a function for dumping issuerAltNames
  #$rv-&gt;{issuer}-&gt;{altnames} = [ Net::SSLeay::X509_get_issuerAltNames($x509) ];

  warn "Info: dumping hashes/fingerprints\n";
  $rv-&gt;{hash}-&gt;{subject} = { dec=&gt;Net::SSLeay::X509_subject_name_hash($x509), hex=&gt;sprintf("%X",Net::SSLeay::X509_subject_name_hash($x509)) };
  $rv-&gt;{hash}-&gt;{issuer}  = { dec=&gt;Net::SSLeay::X509_issuer_name_hash($x509),  hex=&gt;sprintf("%X",Net::SSLeay::X509_issuer_name_hash($x509)) };
  $rv-&gt;{hash}-&gt;{issuer_and_serial} = { dec=&gt;Net::SSLeay::X509_issuer_and_serial_hash($x509), hex=&gt;sprintf("%X",Net::SSLeay::X509_issuer_and_serial_hash($x509)) };
  $rv-&gt;{fingerprint}-&gt;{md5}  = Net::SSLeay::X509_get_fingerprint($x509, "md5");
  $rv-&gt;{fingerprint}-&gt;{sha1} = Net::SSLeay::X509_get_fingerprint($x509, "sha1");
  my $sha1_digest = Net::SSLeay::EVP_get_digestbyname("sha1");
  $rv-&gt;{digest_sha1}-&gt;{pubkey} = Net::SSLeay::X509_pubkey_digest($x509, $sha1_digest);
  $rv-&gt;{digest_sha1}-&gt;{x509} = Net::SSLeay::X509_digest($x509, $sha1_digest);

  warn "Info: dumping expiration\n";
  $rv-&gt;{not_before} = Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notBefore($x509));
  $rv-&gt;{not_after}  = Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notAfter($x509));

  warn "Info: dumping serial number\n";
  my $ai = Net::SSLeay::X509_get_serialNumber($x509);
  $rv-&gt;{serial} = {
    hex  =&gt; Net::SSLeay::P_ASN1_INTEGER_get_hex($ai),
    dec  =&gt; Net::SSLeay::P_ASN1_INTEGER_get_dec($ai),
    long =&gt; Net::SSLeay::ASN1_INTEGER_get($ai),
  };
  $rv-&gt;{version} = Net::SSLeay::X509_get_version($x509);

  warn "Info: dumping extensions\n";
  my $ext_count = Net::SSLeay::X509_get_ext_count($x509);
  $rv-&gt;{extensions}-&gt;{count} = $ext_count;
  for my $i (0..$ext_count-1) {
    my $ext = Net::SSLeay::X509_get_ext($x509,$i);
    my $asn1_string = Net::SSLeay::X509_EXTENSION_get_data($ext);
    my $asn1_object = Net::SSLeay::X509_EXTENSION_get_object($ext);
    my $nid = Net::SSLeay::OBJ_obj2nid($asn1_object);
    $rv-&gt;{extensions}-&gt;{entries}-&gt;[$i] = {
        critical =&gt; Net::SSLeay::X509_EXTENSION_get_critical($ext),
        oid      =&gt; Net::SSLeay::OBJ_obj2txt($asn1_object,1),
        nid      =&gt; ($nid&gt;0) ? $nid : undef,
        ln       =&gt; ($nid&gt;0) ? Net::SSLeay::OBJ_nid2ln($nid) : undef,
        sn       =&gt; ($nid&gt;0) ? Net::SSLeay::OBJ_nid2sn($nid) : undef,
        data     =&gt; Net::SSLeay::X509V3_EXT_print($ext),
    };
  }

  warn "Info: dumping CDP\n";
  $rv-&gt;{cdp} = [ Net::SSLeay::P_X509_get_crl_distribution_points($x509) ];
  warn "Info: dumping extended key usage\n";
  $rv-&gt;{extkeyusage} = {
    oid =&gt; [ Net::SSLeay::P_X509_get_ext_key_usage($x509,0) ],
    nid =&gt; [ Net::SSLeay::P_X509_get_ext_key_usage($x509,1) ],
    sn  =&gt; [ Net::SSLeay::P_X509_get_ext_key_usage($x509,2) ],
    ln  =&gt; [ Net::SSLeay::P_X509_get_ext_key_usage($x509,3) ],
  };
  warn "Info: dumping key usage\n";
  $rv-&gt;{keyusage} = [ Net::SSLeay::P_X509_get_key_usage($x509) ];
  warn "Info: dumping netscape cert type\n";
  $rv-&gt;{ns_cert_type} = [ Net::SSLeay::P_X509_get_netscape_cert_type($x509) ];

  warn "Info: dumping other info\n";
  $rv-&gt;{certificate_type} = Net::SSLeay::X509_certificate_type($x509);
  $rv-&gt;{signature_alg} = Net::SSLeay::OBJ_obj2txt(Net::SSLeay::P_X509_get_signature_alg($x509));
  $rv-&gt;{pubkey_alg} = Net::SSLeay::OBJ_obj2txt(Net::SSLeay::P_X509_get_pubkey_alg($x509));
  $rv-&gt;{pubkey_size} = Net::SSLeay::EVP_PKEY_size(Net::SSLeay::X509_get_pubkey($x509));
  $rv-&gt;{pubkey_bits} = Net::SSLeay::EVP_PKEY_bits(Net::SSLeay::X509_get_pubkey($x509));
  $rv-&gt;{pubkey_id} = Net::SSLeay::EVP_PKEY_id(Net::SSLeay::X509_get_pubkey($x509));

  return $rv;
}

sub dump_details {
  my ($data, $comment) = @_;
  print "\n";
  eval { require Data::Dump };
  if (!$@) {
    # Data::Dump creates nicer output
    print "# $comment\n";
    print "# hashref dumped via Data::Dump\n";
    $Data::Dump::TRY_BASE64 = 0 if $Data::Dump::TRY_BASE64;
    print Data::Dump::pp($data);
  }
  else {
    print "# $comment\n";
    print "# hashref dumped via Data::Dumper\n";
    print Dumper($data);
  }
  print "\n";
}

sub print_basic_info {
  my ($data) = @_;
  print "\n";
  print "Subject:   ", $data-&gt;{subject}-&gt;{print_rfc2253}, "\n";
  print "Issuer:    ", $data-&gt;{issuer}-&gt;{print_rfc2253}, "\n";
  print "NotBefore: ", $data-&gt;{not_before}, "\n";
  print "NotAfter:  ", $data-&gt;{not_after}, "\n";
  print "SHA1: ", $data-&gt;{fingerprint}-&gt;{sha1}, "\n";
  print "MD5:  ", $data-&gt;{fingerprint}-&gt;{md5}, "\n";
  print "\n";
}

# --- main
show_usage() if $g_showusage || (!$g_host &amp;&amp; !$g_pem);

if ($g_pem) {
  for my $f(@$g_pem) {
    die "ERROR: non existing file '$f'" unless -f $f;

    warn "#### Going to load PEM file '$f'\n";
    my $bio = Net::SSLeay::BIO_new_file($f, 'rb') or die "ERROR: BIO_new_file failed";
    my $x509 = Net::SSLeay::PEM_read_bio_X509($bio) or die "ERROR: PEM_read_bio_X509 failed";

    my $cert_details = get_cert_details($x509);

    warn "#### Certificate info\n";
    if ($g_dump) {
      dump_details($cert_details, "exported via command: perl examples/x509_cert_details.pl -dump -pem $f &gt; $f\_dump");
    }
    else {
      print_basic_info($cert_details);
    }
    warn "#### DONE\n";
  }
}

if ($g_host) {
  for my $h (@$g_host) {
    my ($host, $port) = split /:/, $h;
    die "ERROR: invalid host '$h'" unless $host &amp;&amp; $port =~ /\d+/;

    warn "#### Going to connect to host=$host, port=$port\n";
    my $sock = IO::Socket::INET-&gt;new(PeerAddr =&gt; $host, PeerPort =&gt; $port, Proto =&gt; 'tcp') or die "ERROR: cannot create socket";
    my $ctx = Net::SSLeay::CTX_new() or die "ERROR: CTX_new failed";
    Net::SSLeay::CTX_set_options($ctx, &amp;Net::SSLeay::OP_ALL);
    my $ssl = Net::SSLeay::new($ctx) or die "ERROR: new failed";
    Net::SSLeay::set_fd($ssl, fileno($sock)) or die "ERROR: set_fd failed";
    Net::SSLeay::connect($ssl) or die "ERROR: connect failed";
    my $x509 = Net::SSLeay::get_peer_certificate($ssl);

    my $cert_details = get_cert_details($x509);

    warn "#### Certificate info\n";
    if ($g_dump) {
      dump_details($cert_details, "host: $h\n");
    }
    else {
      print_basic_info($cert_details);
    }
    warn "#### DONE\n";
  }
}
</pre></body></html>