1#!/usr/local/bin/perl 2 3use strict; 4use warnings; 5 6use Getopt::Long; 7use Data::Dumper; 8use IO::Socket::INET; 9use Net::SSLeay qw/XN_FLAG_RFC2253 ASN1_STRFLGS_ESC_MSB/; 10 11# Sorting keys helps keeping diffs at minimum between dumps. 12# 13# Quotekeys and Trailingcomma were set to match format used to 14# generate t/data/testcert_extended.crt.pem_dump when it was initially 15# imported to version control. They can likely be dropped in a future 16# release. 17$Data::Dumper::Sortkeys = 1; 18$Data::Dumper::Useqq = 1; 19$Data::Dumper::Quotekeys = 0; 20$Data::Dumper::Trailingcomma = 1; 21 22Net::SSLeay::randomize(); 23Net::SSLeay::load_error_strings(); 24Net::SSLeay::ERR_load_crypto_strings(); 25Net::SSLeay::SSLeay_add_ssl_algorithms(); 26 27# --- commandline options and global variables 28 29my ($g_host, $g_pem, $g_dump, $g_showusage); 30 31GetOptions( 32 'help|?' => \$g_showusage, 33 'dump' => \$g_dump, 34 'host=s@' => \$g_host, 35 'pem=s@' => \$g_pem, 36) or $g_showusage = 1; 37 38# --- subroutines 39 40sub show_usage { 41 die <<EOL; 42 43Usage: $0 <options> 44 -help -? show this help 45 -pem <file> process X509 certificate from file (PEM format) 46 -host <ip_or_dns>:<port> process X509 certificate presented by SSL server 47 -dump full dump of X509 certificate info 48 49Example: 50 $0 -pem file1.pem 51 $0 -pem file1.pem -pem file2.pem 52 $0 -host twitter.com:443 -dump 53 54EOL 55} 56 57sub get_cert_details { 58 my $x509 = shift; 59 my $rv = {}; 60 my $flag_rfc22536_utf8 = (XN_FLAG_RFC2253) & (~ ASN1_STRFLGS_ESC_MSB); 61 62 die 'ERROR: $x509 is NULL, gonna quit' unless $x509; 63 64 warn "Info: dumping subject\n"; 65 my $subj_name = Net::SSLeay::X509_get_subject_name($x509); 66 my $subj_count = Net::SSLeay::X509_NAME_entry_count($subj_name); 67 $rv->{subject}->{count} = $subj_count; 68 $rv->{subject}->{oneline} = Net::SSLeay::X509_NAME_oneline($subj_name); 69 $rv->{subject}->{print_rfc2253} = Net::SSLeay::X509_NAME_print_ex($subj_name); 70 $rv->{subject}->{print_rfc2253_utf8} = Net::SSLeay::X509_NAME_print_ex($subj_name, $flag_rfc22536_utf8); 71 $rv->{subject}->{print_rfc2253_utf8_decoded} = Net::SSLeay::X509_NAME_print_ex($subj_name, $flag_rfc22536_utf8, 1); 72 for my $i (0..$subj_count-1) { 73 my $entry = Net::SSLeay::X509_NAME_get_entry($subj_name, $i); 74 my $asn1_string = Net::SSLeay::X509_NAME_ENTRY_get_data($entry); 75 my $asn1_object = Net::SSLeay::X509_NAME_ENTRY_get_object($entry); 76 my $nid = Net::SSLeay::OBJ_obj2nid($asn1_object); 77 $rv->{subject}->{entries}->[$i] = { 78 oid => Net::SSLeay::OBJ_obj2txt($asn1_object,1), 79 data => Net::SSLeay::P_ASN1_STRING_get($asn1_string), 80 data_utf8_decoded => Net::SSLeay::P_ASN1_STRING_get($asn1_string, 1), 81 nid => ($nid>0) ? $nid : undef, 82 ln => ($nid>0) ? Net::SSLeay::OBJ_nid2ln($nid) : undef, 83 sn => ($nid>0) ? Net::SSLeay::OBJ_nid2sn($nid) : undef, 84 }; 85 } 86 87 warn "Info: dumping issuer\n"; 88 my $issuer_name = Net::SSLeay::X509_get_issuer_name($x509); 89 my $issuer_count = Net::SSLeay::X509_NAME_entry_count($issuer_name); 90 $rv->{issuer}->{count} = $issuer_count; 91 $rv->{issuer}->{oneline} = Net::SSLeay::X509_NAME_oneline($issuer_name); 92 $rv->{issuer}->{print_rfc2253} = Net::SSLeay::X509_NAME_print_ex($issuer_name); 93 $rv->{issuer}->{print_rfc2253_utf8} = Net::SSLeay::X509_NAME_print_ex($issuer_name, $flag_rfc22536_utf8); 94 $rv->{issuer}->{print_rfc2253_utf8_decoded} = Net::SSLeay::X509_NAME_print_ex($issuer_name, $flag_rfc22536_utf8, 1); 95 for my $i (0..$issuer_count-1) { 96 my $entry = Net::SSLeay::X509_NAME_get_entry($issuer_name, $i); 97 my $asn1_string = Net::SSLeay::X509_NAME_ENTRY_get_data($entry); 98 my $asn1_object = Net::SSLeay::X509_NAME_ENTRY_get_object($entry); 99 my $nid = Net::SSLeay::OBJ_obj2nid($asn1_object); 100 $rv->{issuer}->{entries}->[$i] = { 101 oid => Net::SSLeay::OBJ_obj2txt($asn1_object,1), 102 data => Net::SSLeay::P_ASN1_STRING_get($asn1_string), 103 data_utf8_decoded => Net::SSLeay::P_ASN1_STRING_get($asn1_string, 1), 104 nid => ($nid>0) ? $nid : undef, 105 ln => ($nid>0) ? Net::SSLeay::OBJ_nid2ln($nid) : undef, 106 sn => ($nid>0) ? Net::SSLeay::OBJ_nid2sn($nid) : undef, 107 }; 108 } 109 110 warn "Info: dumping alternative names\n"; 111 $rv->{subject}->{altnames} = [ Net::SSLeay::X509_get_subjectAltNames($x509) ]; 112 #XXX-TODO maybe add a function for dumping issuerAltNames 113 #$rv->{issuer}->{altnames} = [ Net::SSLeay::X509_get_issuerAltNames($x509) ]; 114 115 warn "Info: dumping hashes/fingerprints\n"; 116 $rv->{hash}->{subject} = { dec=>Net::SSLeay::X509_subject_name_hash($x509), hex=>sprintf("%X",Net::SSLeay::X509_subject_name_hash($x509)) }; 117 $rv->{hash}->{issuer} = { dec=>Net::SSLeay::X509_issuer_name_hash($x509), hex=>sprintf("%X",Net::SSLeay::X509_issuer_name_hash($x509)) }; 118 $rv->{hash}->{issuer_and_serial} = { dec=>Net::SSLeay::X509_issuer_and_serial_hash($x509), hex=>sprintf("%X",Net::SSLeay::X509_issuer_and_serial_hash($x509)) }; 119 $rv->{fingerprint}->{md5} = Net::SSLeay::X509_get_fingerprint($x509, "md5"); 120 $rv->{fingerprint}->{sha1} = Net::SSLeay::X509_get_fingerprint($x509, "sha1"); 121 my $sha1_digest = Net::SSLeay::EVP_get_digestbyname("sha1"); 122 $rv->{digest_sha1}->{pubkey} = Net::SSLeay::X509_pubkey_digest($x509, $sha1_digest); 123 $rv->{digest_sha1}->{x509} = Net::SSLeay::X509_digest($x509, $sha1_digest); 124 125 warn "Info: dumping expiration\n"; 126 $rv->{not_before} = Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notBefore($x509)); 127 $rv->{not_after} = Net::SSLeay::P_ASN1_TIME_get_isotime(Net::SSLeay::X509_get_notAfter($x509)); 128 129 warn "Info: dumping serial number\n"; 130 my $ai = Net::SSLeay::X509_get_serialNumber($x509); 131 $rv->{serial} = { 132 hex => Net::SSLeay::P_ASN1_INTEGER_get_hex($ai), 133 dec => Net::SSLeay::P_ASN1_INTEGER_get_dec($ai), 134 long => Net::SSLeay::ASN1_INTEGER_get($ai), 135 }; 136 $rv->{version} = Net::SSLeay::X509_get_version($x509); 137 138 warn "Info: dumping extensions\n"; 139 my $ext_count = Net::SSLeay::X509_get_ext_count($x509); 140 $rv->{extensions}->{count} = $ext_count; 141 for my $i (0..$ext_count-1) { 142 my $ext = Net::SSLeay::X509_get_ext($x509,$i); 143 my $asn1_string = Net::SSLeay::X509_EXTENSION_get_data($ext); 144 my $asn1_object = Net::SSLeay::X509_EXTENSION_get_object($ext); 145 my $nid = Net::SSLeay::OBJ_obj2nid($asn1_object); 146 $rv->{extensions}->{entries}->[$i] = { 147 critical => Net::SSLeay::X509_EXTENSION_get_critical($ext), 148 oid => Net::SSLeay::OBJ_obj2txt($asn1_object,1), 149 nid => ($nid>0) ? $nid : undef, 150 ln => ($nid>0) ? Net::SSLeay::OBJ_nid2ln($nid) : undef, 151 sn => ($nid>0) ? Net::SSLeay::OBJ_nid2sn($nid) : undef, 152 data => Net::SSLeay::X509V3_EXT_print($ext), 153 }; 154 } 155 156 warn "Info: dumping CDP\n"; 157 $rv->{cdp} = [ Net::SSLeay::P_X509_get_crl_distribution_points($x509) ]; 158 warn "Info: dumping extended key usage\n"; 159 $rv->{extkeyusage} = { 160 oid => [ Net::SSLeay::P_X509_get_ext_key_usage($x509,0) ], 161 nid => [ Net::SSLeay::P_X509_get_ext_key_usage($x509,1) ], 162 sn => [ Net::SSLeay::P_X509_get_ext_key_usage($x509,2) ], 163 ln => [ Net::SSLeay::P_X509_get_ext_key_usage($x509,3) ], 164 }; 165 warn "Info: dumping key usage\n"; 166 $rv->{keyusage} = [ Net::SSLeay::P_X509_get_key_usage($x509) ]; 167 warn "Info: dumping netscape cert type\n"; 168 $rv->{ns_cert_type} = [ Net::SSLeay::P_X509_get_netscape_cert_type($x509) ]; 169 170 warn "Info: dumping other info\n"; 171 $rv->{certificate_type} = Net::SSLeay::X509_certificate_type($x509); 172 $rv->{signature_alg} = Net::SSLeay::OBJ_obj2txt(Net::SSLeay::P_X509_get_signature_alg($x509)); 173 $rv->{pubkey_alg} = Net::SSLeay::OBJ_obj2txt(Net::SSLeay::P_X509_get_pubkey_alg($x509)); 174 $rv->{pubkey_size} = Net::SSLeay::EVP_PKEY_size(Net::SSLeay::X509_get_pubkey($x509)); 175 $rv->{pubkey_bits} = Net::SSLeay::EVP_PKEY_bits(Net::SSLeay::X509_get_pubkey($x509)); 176 if (Net::SSLeay::SSLeay >= 0x1000000f) { 177 $rv->{pubkey_id} = Net::SSLeay::EVP_PKEY_id(Net::SSLeay::X509_get_pubkey($x509)); 178 } 179 180 return $rv; 181} 182 183sub dump_details { 184 my ($data, $comment) = @_; 185 print "\n"; 186 eval { require Data::Dump }; 187 if (!$@) { 188 # Data::Dump creates nicer output 189 print "# $comment\n"; 190 print "# hashref dumped via Data::Dump\n"; 191 $Data::Dump::TRY_BASE64 = 0 if $Data::Dump::TRY_BASE64; 192 print Data::Dump::pp($data); 193 } 194 else { 195 print "# $comment\n"; 196 print "# hashref dumped via Data::Dumper\n"; 197 print Dumper($data); 198 } 199 print "\n"; 200} 201 202sub print_basic_info { 203 my ($data) = @_; 204 print "\n"; 205 print "Subject: ", $data->{subject}->{print_rfc2253}, "\n"; 206 print "Issuer: ", $data->{issuer}->{print_rfc2253}, "\n"; 207 print "NotBefore: ", $data->{not_before}, "\n"; 208 print "NotAfter: ", $data->{not_after}, "\n"; 209 print "SHA1: ", $data->{fingerprint}->{sha1}, "\n"; 210 print "MD5: ", $data->{fingerprint}->{md5}, "\n"; 211 print "\n"; 212} 213 214# --- main 215show_usage() if $g_showusage || (!$g_host && !$g_pem); 216 217if ($g_pem) { 218 for my $f(@$g_pem) { 219 die "ERROR: non existing file '$f'" unless -f $f; 220 221 warn "#### Going to load PEM file '$f'\n"; 222 my $bio = Net::SSLeay::BIO_new_file($f, 'rb') or die "ERROR: BIO_new_file failed"; 223 my $x509 = Net::SSLeay::PEM_read_bio_X509($bio) or die "ERROR: PEM_read_bio_X509 failed"; 224 225 my $cert_details = get_cert_details($x509); 226 227 warn "#### Certificate info\n"; 228 if ($g_dump) { 229 dump_details($cert_details, "exported via command: perl examples/x509_cert_details.pl -dump -pem $f > $f\_dump"); 230 } 231 else { 232 print_basic_info($cert_details); 233 } 234 warn "#### DONE\n"; 235 } 236} 237 238if ($g_host) { 239 for my $h (@$g_host) { 240 my ($host, $port) = split /:/, $h; 241 die "ERROR: invalid host '$h'" unless $host && $port =~ /\d+/; 242 243 warn "#### Going to connect to host=$host, port=$port\n"; 244 my $sock = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, Proto => 'tcp') or die "ERROR: cannot create socket"; 245 my $ctx = Net::SSLeay::CTX_new() or die "ERROR: CTX_new failed"; 246 Net::SSLeay::CTX_set_options($ctx, &Net::SSLeay::OP_ALL); 247 my $ssl = Net::SSLeay::new($ctx) or die "ERROR: new failed"; 248 Net::SSLeay::set_fd($ssl, fileno($sock)) or die "ERROR: set_fd failed"; 249 Net::SSLeay::connect($ssl) or die "ERROR: connect failed"; 250 my $x509 = Net::SSLeay::get_peer_certificate($ssl); 251 252 my $cert_details = get_cert_details($x509); 253 254 warn "#### Certificate info\n"; 255 if ($g_dump) { 256 dump_details($cert_details, "host: $h\n"); 257 } 258 else { 259 print_basic_info($cert_details); 260 } 261 warn "#### DONE\n"; 262 } 263} 264