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