File Coverage

blib/lib/Net/SSL/ExpireDate.pm
Criterion Covered Total %
statement 64 244 26.2
branch 16 78 20.5
condition 7 13 53.8
subroutine 16 22 72.7
pod 4 4 100.0
total 107 361 29.6


line stmt bran cond sub pod time code
1             package Net::SSL::ExpireDate;
2              
3 8     8   1354617 use strict;
  8         15  
  8         278  
4 8     8   36 use warnings;
  8         23  
  8         393  
5 8     8   40 use Carp;
  8         12  
  8         809  
6              
7             our $VERSION = '1.25';
8              
9 8     8   45 use base qw(Class::Accessor);
  8         20  
  8         2064  
10 8     8   25807 use Crypt::OpenSSL::X509 qw(FORMAT_ASN1);
  8         446217  
  8         990  
11 8     8   4764 use Date::Parse;
  8         75111  
  8         1428  
12 8     8   10171 use DateTime;
  8         4687256  
  8         474  
13 8     8   98 use DateTime::Duration;
  8         25  
  8         307  
14 8     8   4305 use Time::Duration::Parse;
  8         16657  
  8         558  
15 8     8   3783 use UNIVERSAL::require;
  8         10766  
  8         7069  
16              
17             my $Socket = 'IO::Socket::INET6';
18             unless ($Socket->require) {
19             $Socket = 'IO::Socket::INET';
20             $Socket->require or die $@;
21             }
22              
23             __PACKAGE__->mk_accessors(qw(type target));
24              
25             my $SSL3_RT_CHANGE_CIPHER_SPEC = 20;
26             my $SSL3_RT_ALERT = 21;
27             my $SSL3_RT_HANDSHAKE = 22;
28             my $SSL3_RT_APPLICATION_DATA = 23;
29              
30             my $SSL3_MT_HELLO_REQUEST = 0;
31             my $SSL3_MT_CLIENT_HELLO = 1;
32             my $SSL3_MT_SERVER_HELLO = 2;
33             my $SSL3_MT_CERTIFICATE = 11;
34             my $SSL3_MT_SERVER_KEY_EXCHANGE = 12;
35             my $SSL3_MT_CERTIFICATE_REQUEST = 13;
36             my $SSL3_MT_SERVER_DONE = 14;
37             my $SSL3_MT_CERTIFICATE_VERIFY = 15;
38             my $SSL3_MT_CLIENT_KEY_EXCHANGE = 16;
39             my $SSL3_MT_FINISHED = 20;
40              
41             my $SSL3_AL_WARNING = 0x01;
42             my $SSL3_AL_FATAL = 0x02;
43              
44             my $SSL3_AD_CLOSE_NOTIFY = 0;
45             my $SSL3_AD_UNEXPECTED_MESSAGE = 10; # fatal
46             my $SSL3_AD_BAD_RECORD_MAC = 20; # fatal
47             my $SSL3_AD_DECOMPRESSION_FAILURE = 30; # fatal
48             my $SSL3_AD_HANDSHAKE_FAILURE = 40; # fatal
49             my $SSL3_AD_NO_CERTIFICATE = 41;
50             my $SSL3_AD_BAD_CERTIFICATE = 42;
51             my $SSL3_AD_UNSUPPORTED_CERTIFICATE = 43;
52             my $SSL3_AD_CERTIFICATE_REVOKED = 44;
53             my $SSL3_AD_CERTIFICATE_EXPIRED = 45;
54             my $SSL3_AD_CERTIFICATE_UNKNOWN = 46;
55             my $SSL3_AD_ILLEGAL_PARAMETER = 47; # fatal
56              
57             sub new {
58 4     4 1 486817 my ($class, %opt) = @_;
59              
60 4         34 my $self = bless {
61             type => undef,
62             target => undef,
63             expire_date => undef,
64             timeout => undef,
65             }, $class;
66              
67 4 100 100     40 if ( $opt{https} or $opt{ssl} ) {
    50          
68 2         16 $self->{type} = 'ssl';
69 2   66     11 $self->{target} = $opt{https} || $opt{ssl};
70             } elsif ($opt{file}) {
71 2         18 $self->{type} = 'file';
72 2         8 $self->{target} = $opt{file};
73 2 50       94 if (! -r $self->{target}) {
74 0         0 croak "$self->{target}: $!";
75             }
76             } else {
77 0         0 croak "missing option: neither ssl nor file";
78             }
79 4 50       18 if ($opt{timeout}) {
80 0         0 $self->{timeout} = $opt{timeout};
81             }
82 4 50       23 if ($opt{sni}) {
83 0         0 $self->{sni} = $opt{sni};
84             }
85              
86 4         15 return $self;
87             }
88              
89             sub expire_date {
90 2     2 1 1332 my $self = shift;
91              
92 2 100       12 if (! $self->{expire_date}) {
93 1 50       20 if ($self->{type} eq 'ssl') {
    50          
94 0         0 my ($host, $port) = split /:/, $self->{target}, 2;
95 0   0     0 $port ||= 443;
96             ### $host
97             ### $port
98 0         0 my $cert = eval { _peer_certificate($host, $port, $self->{timeout}, $self->{sni}); };
  0         0  
99 0 0       0 warn $@ if $@;
100 0 0       0 return unless $cert;
101 0         0 my $x509 = Crypt::OpenSSL::X509->new_from_string($cert, FORMAT_ASN1);
102 0         0 my $begin_date_str = $x509->notBefore;
103 0         0 my $expire_date_str = $x509->notAfter;
104              
105 0         0 $self->{expire_date} = DateTime->from_epoch(epoch => str2time($expire_date_str));
106 0         0 $self->{begin_date} = DateTime->from_epoch(epoch => str2time($begin_date_str));
107              
108             } elsif ($self->{type} eq 'file') {
109 1         4695 my $x509 = Crypt::OpenSSL::X509->new_from_file($self->{target});
110 1         55 $self->{expire_date} = DateTime->from_epoch(epoch => str2time($x509->notAfter));
111 1         1646 $self->{begin_date} = DateTime->from_epoch(epoch => str2time($x509->notBefore));
112             } else {
113 0         0 croak "unknown type: $self->{type}";
114             }
115             }
116              
117 2         766 return $self->{expire_date};
118             }
119              
120             sub begin_date {
121 2     2 1 3013 my $self = shift;
122              
123 2 50       15 if (! $self->{begin_date}) {
124 0         0 $self->expire_date;
125             }
126              
127 2         19 return $self->{begin_date};
128             }
129              
130             *not_after = \&expire_date;
131             *not_before = \&begin_date;
132              
133             sub is_expired {
134 3     3 1 3385 my ($self, $duration) = @_;
135 3   66     23 $duration ||= DateTime::Duration->new();
136              
137 3 50       229 if (! $self->{begin_date}) {
138 0         0 $self->expire_date;
139             }
140              
141 3 100       28 if (! ref($duration)) { # if scalar
142 1         11 $duration = DateTime::Duration->new(seconds => parse_duration($duration));
143             }
144              
145 3         206 my $dx = DateTime->now()->add_duration( $duration );
146             ### dx: $dx->iso8601
147              
148 3 100       3689 return DateTime->compare($dx, $self->{expire_date}) >= 0 ? 1 : ();
149             }
150              
151             sub _peer_certificate {
152 0     0     my($host, $port, $timeout, $sni) = @_;
153              
154 0           my $cert;
155              
156 8     8   61 no warnings 'once';
  8         17  
  8         563  
157 8     8   42 no strict 'refs'; ## no critic
  8         67  
  8         15596  
158 0           *{$Socket.'::write_atomically'} = sub {
159 0     0     my($self, $data) = @_;
160              
161 0           my $length = length $data;
162 0           my $offset = 0;
163 0           my $read_byte = 0;
164              
165 0           while ($length > 0) {
166 0   0       my $r = $self->syswrite($data, $length, $offset) || last;
167 0           $offset += $r;
168 0           $length -= $r;
169 0           $read_byte += $r;
170             }
171              
172 0           return $read_byte;
173 0           };
174              
175 0           my $sock = {
176             PeerAddr => $host,
177             PeerPort => $port,
178             Proto => 'tcp',
179             Timeout => $timeout,
180             };
181              
182 0 0         $sock = $Socket->new( %$sock ) or croak "cannot create socket: $!";
183              
184 0           my $servername;
185 0 0         if ($sni) {
    0          
186 0           $servername = $sni;
187             } elsif ($host !~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) {
188 0           $servername = $host;
189             }
190 0           _send_client_hello($sock, $servername);
191              
192 0           my $do_loop = 1;
193 0           while ($do_loop) {
194 0           my $record = _get_record($sock);
195 0 0         if ($record->{type} != $SSL3_RT_HANDSHAKE) {
196 0 0         if ($record->{type} == $SSL3_RT_ALERT) {
197 0           my $d1 = unpack 'C', substr $record->{data}, 0, 1;
198 0           my $d2 = unpack 'C', substr $record->{data}, 1, 1;
199 0 0         if ($d1 eq $SSL3_AL_WARNING) {
200             ; # go ahead
201             } else {
202 0           croak "record type is SSL3_AL_FATAL. [desctioption: $d2]";
203             }
204             } else {
205 0           croak "record type is not HANDSHAKE";
206             }
207             }
208              
209 0           while (my $handshake = _get_handshake($record)) {
210 0 0         croak "too many loop" if $do_loop++ >= 10;
211 0 0         if ($handshake->{type} == $SSL3_MT_HELLO_REQUEST) {
    0          
    0          
    0          
    0          
    0          
212             ;
213             } elsif ($handshake->{type} == $SSL3_MT_CERTIFICATE_REQUEST) {
214             ;
215             } elsif ($handshake->{type} == $SSL3_MT_SERVER_HELLO) {
216             ;
217             } elsif ($handshake->{type} == $SSL3_MT_CERTIFICATE) {
218 0           my $data = $handshake->{data};
219 0           my $len1 = $handshake->{length};
220 0           my $len2 = (vec($data, 0, 8)<<16)+(vec($data, 1, 8)<<8)+vec($data, 2, 8);
221 0           my $len3 = (vec($data, 3, 8)<<16)+(vec($data, 4, 8)<<8)+vec($data, 5, 8);
222 0 0         croak "X509: length error" if $len1 != $len2 + 3;
223 0           $cert = substr $data, 6; # DER format
224             } elsif ($handshake->{type} == $SSL3_MT_SERVER_KEY_EXCHANGE) {
225             ;
226             } elsif ($handshake->{type} == $SSL3_MT_SERVER_DONE) {
227 0           $do_loop = 0;
228             } else {
229             ;
230             }
231             }
232              
233             }
234              
235 0 0         _sendalert($sock, $SSL3_AL_FATAL, $SSL3_AD_HANDSHAKE_FAILURE) or croak $!;
236 0           $sock->close;
237              
238 0           return $cert;
239             }
240              
241             sub _send_client_hello {
242 0     0     my($sock, $servername) = @_;
243              
244 0           my(@buf, $len);
245             # Record Layer
246             # Content Type: Handshake
247 0           push @buf, $SSL3_RT_HANDSHAKE;
248             # Version: TLS 1.0 (SSL 3.1)
249 0           push @buf, 3, 1;
250             # Length: set later
251 0           push @buf, undef, undef;
252 0           my $pos_record_len = $#buf-1;
253              
254             ## Handshake Protocol: Client Hello
255 0           push @buf, $SSL3_MT_CLIENT_HELLO;
256             ## Length: set later
257 0           push @buf, undef, undef, undef;
258 0           my $pos_handshake_len = $#buf-2;
259             ## Version: TLS 1.2
260 0           push @buf, 3, 3; # TLS 1.2
261             ## Random
262 0           my $time = time;
263 0           push @buf, (($time>>24) & 0xFF);
264 0           push @buf, (($time>>16) & 0xFF);
265 0           push @buf, (($time>> 8) & 0xFF);
266 0           push @buf, (($time ) & 0xFF);
267 0           for (1..28) {
268 0           push @buf, int(rand(0xFF));
269             }
270             ## Session ID Length: 0
271 0           push @buf, 0;
272              
273             # https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29
274 0           my @cipher_suites = (
275             0xc02c, # TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
276             0xc030, # TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
277             0x009f, # TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
278             0xcca9, # TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
279             0xcca8, # TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
280             0xccaa, # TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
281             0xc02b, # TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
282             0xc02f, # TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
283             0x009e, # TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
284             0xc024, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
285             0xc028, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
286             0x006b, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
287             0xc023, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
288             0xc027, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
289             0x0067, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
290             0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
291             0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
292             0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA
293             0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
294             0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
295             0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA
296             0x009d, # TLS_RSA_WITH_AES_256_GCM_SHA384
297             0x009c, # TLS_RSA_WITH_AES_128_GCM_SHA256
298             0x003d, # TLS_RSA_WITH_AES_256_CBC_SHA256
299             0x003c, # TLS_RSA_WITH_AES_128_CBC_SHA256
300             0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA
301             0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA
302             0x00ff, # TLS_EMPTY_RENEGOTIATION_INFO_SCSV
303             );
304 0           $len = scalar(@cipher_suites) * 2;
305             ## Cipher Suites Length
306 0           push @buf, (($len >> 8) & 0xFF);
307 0           push @buf, (($len ) & 0xFF);
308             ## Cipher Suites
309 0           for my $i (@cipher_suites) {
310 0           push @buf, (($i >> 8) & 0xFF);
311 0           push @buf, (($i ) & 0xFF);
312             }
313              
314             ## Compression Methods Length
315 0           push @buf, 1;
316             ## Compression Methods: null
317 0           push @buf, 0;
318              
319             ## Extensions Length: set later
320 0           my @ext = (undef, undef);
321              
322             ## Extension: server_name
323 0 0         if ($servername) {
324             # SNI (Server Name Indication)
325 0           my $sn_len = length $servername;
326             ### Type: Server Name
327 0           push @ext, 0, 0;
328             ### Length
329             # 5 is this part(2) + Server Name Indication Length(3)
330 0           push @ext, ((($sn_len+5) >> 8) & 0xFF);
331 0           push @ext, ((($sn_len+5) ) & 0xFF);
332             ### Server Name Indication extension
333             #### Server Name list length
334             # 3 is this part(2) + Server Name Type(1)
335 0           push @ext, ((($sn_len+3) >> 8) & 0xFF);
336 0           push @ext, ((($sn_len+3) ) & 0xFF);
337             #### Server Name Type: host_name
338 0           push @ext, 0;
339             #### Server Name length
340 0           push @ext, (($sn_len >> 8) & 0xFF);
341 0           push @ext, (($sn_len ) & 0xFF);
342             #### Server Name
343 0           for my $c (split //, $servername) {
344 0           push @ext, ord($c);
345             }
346             }
347              
348             ## Extension: supported_groups
349             ### Type: supported_groups
350 0           push @ext, 0x00, 0x0a;
351              
352 0           my @supported_groups = (
353             0x0017, # secp256r1
354             0x0018, # secp384r1
355             0x0019, # secp521r1
356             0x001d, # x25519
357             0x001e, # x448
358             );
359              
360             ### Length
361             # Supported Group List Length(2) + Supported Groups
362 0           $len = 2 + scalar(@supported_groups) * 2;
363 0           push @ext, (($len >> 8) & 0xFF);
364 0           push @ext, (($len ) & 0xFF);
365              
366             ### Supported Group List Length
367 0           $len = scalar(@supported_groups) * 2;
368 0           push @ext, (($len >> 8) & 0xFF);
369 0           push @ext, (($len ) & 0xFF);
370              
371             ### Supported Groups
372 0           for my $i (@supported_groups) {
373 0           push @ext, (($i >> 8) & 0xFF);
374 0           push @ext, (($i ) & 0xFF);
375             }
376              
377             ## Extension: signature_algorithms (>= TLSv1.2)
378             ### Type: signature_algorithms
379 0           push @ext, 0x00, 0x0D;
380              
381             # https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.1.4.1
382             # enum {
383             # none(0), md5(1), sha1(2), sha224(3), sha256(4), sha384(5),
384             # sha512(6), (255)
385             # } HashAlgorithm;
386             # enum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255)
387             # } SignatureAlgorithm;
388 0           my @signature_algorithms = (
389             0x0403, # ecdsa_secp256r1_sha256
390             0x0503, # ecdsa_secp384r1_sha384
391             0x0603, # ecdsa_secp521r1_sha512
392             0x0807, # ed25519
393             0x0808, # ed448
394             0x0809, # rsa_pss_pss_sha256
395             0x080a, # rsa_pss_pss_sha384
396             0x080b, # rsa_pss_pss_sha512
397             0x0804, # rsa_pss_rsae_sha256
398             0x0805, # rsa_pss_rsae_sha384
399             0x0806, # rsa_pss_rsae_sha512
400             0x0401, # rsa_pkcs1_sha256
401             0x0501, # rsa_pkcs1_sha384
402             0x0601, # rsa_pkcs1_sha512
403             0x0303, # SHA224 ECDSA
404             0x0203, # ecdsa_sha1
405             0x0301, # SHA224 RSA
406             0x0201, # rsa_pkcs1_sha1
407             0x0302, # SHA224 DSA
408             0x0202, # SHA1 DSA
409             0x0402, # SHA256 DSA
410             0x0502, # SHA384 DSA
411             0x0602, # SHA512 DSA
412             );
413              
414             ### Length
415             # Signature Hash Algorithms Length(2) + Signature hash Algorithms
416 0           $len = 2 + scalar(@signature_algorithms) * 2;
417 0           push @ext, (($len >> 8) & 0xFF);
418 0           push @ext, (($len ) & 0xFF);
419              
420             ### Signature Hash Algorithms Length
421 0           $len = scalar(@signature_algorithms) * 2;
422 0           push @ext, (($len >> 8) & 0xFF);
423 0           push @ext, (($len ) & 0xFF);
424              
425             ### Signature Hash Algorithms
426 0           for my $i (@signature_algorithms) {
427 0           push @ext, (($i >> 8) & 0xFF);
428 0           push @ext, (($i ) & 0xFF);
429             }
430              
431             ## Extension: ec_point_formats
432             ### Type: ec_point_formats
433 0           push @ext, 0x00, 0x0b;
434             ### Length: 4
435 0           push @ext, 0x00, 0x04;
436             ### EC point formats Length: 3
437 0           push @ext, 0x03;
438             ### Elliptic curves point formats
439 0           push @ext, 0x00; # uncompressed
440 0           push @ext, 0x01; # ansiX962_compressed_prime
441 0           push @ext, 0x02; # ansiX962_compressed_char2 (2)
442              
443             ## Extension: Heartbeat
444 0           push @ext,
445             0x00, 0x0F, # Type: heartbeat
446             0x00, 0x01, # Lengh
447             0x01, # Peer allowed to send requests
448             ;
449              
450             ## Extensions Length
451 0           my $ext_len = scalar(@ext) - 2;
452 0 0         if ($ext_len > 0) {
453 0           $ext[0] = (($ext_len) >> 8) & 0xFF;
454 0           $ext[1] = (($ext_len) ) & 0xFF;
455 0           push @buf, @ext;
456             }
457              
458             # Record Length
459 0           $len = scalar(@buf) - $pos_record_len - 2;
460 0           $buf[ $pos_record_len ] = (($len >> 8) & 0xFF);
461 0           $buf[ $pos_record_len+1 ] = (($len ) & 0xFF);
462              
463             ## Handshake Length
464 0           $len = scalar(@buf) - $pos_handshake_len - 3;
465 0           $buf[ $pos_handshake_len ] = (($len >> 16) & 0xFF);
466 0           $buf[ $pos_handshake_len+1 ] = (($len >> 8) & 0xFF);
467 0           $buf[ $pos_handshake_len+2 ] = (($len ) & 0xFF);
468              
469 0           my $data = '';
470 0           for my $c (@buf) {
471 0           $data .= pack('C', $c);
472             }
473              
474 0           return $sock->write_atomically($data);
475             }
476              
477             sub _get_record {
478 0     0     my($sock) = @_;
479              
480 0           my $record = {
481             type => -1,
482             version => -1,
483             length => -1,
484             read => 0,
485             data => "",
486             };
487              
488 0 0         $sock->read($record->{type} , 1) or croak "cannot read type";
489 0           $record->{type} = unpack 'C', $record->{type};
490              
491 0 0         $sock->read($record->{version}, 2) or croak "cannot read version";
492 0           $record->{version} = unpack 'n', $record->{version};
493              
494 0 0         $sock->read($record->{length}, 2) or croak "cannot read length";
495 0           $record->{length} = unpack 'n', $record->{length};
496              
497 0 0         $sock->read($record->{data}, $record->{length}) or croak "cannot read data";
498              
499 0           return $record;
500             }
501              
502             sub _get_handshake {
503 0     0     my($record) = @_;
504              
505 0           my $handshake = {
506             type => -1,
507             length => -1,
508             data => "",
509             };
510              
511 0 0         return if $record->{read} >= $record->{length};
512              
513 0           $handshake->{type} = vec($record->{data}, $record->{read}++, 8);
514 0 0         return if $record->{read} + 3 > $record->{length};
515              
516             $handshake->{length} =
517             (vec($record->{data}, $record->{read}++, 8)<<16)
518             +(vec($record->{data}, $record->{read}++, 8)<< 8)
519 0           +(vec($record->{data}, $record->{read}++, 8) );
520              
521 0 0         if ($handshake->{length} > 0) {
522 0           $handshake->{data} = substr($record->{data}, $record->{read}, $handshake->{length});
523 0           $record->{read} += $handshake->{length};
524 0 0         return if $record->{read} > $record->{length};
525             } else {
526 0           $handshake->{data}= undef;
527             }
528              
529 0           return $handshake;
530             }
531              
532             sub _sendalert {
533 0     0     my($sock, $level, $desc) = @_;
534              
535 0           my(@buf, $len);
536             # Record Layer
537             # Content Type: Alert
538 0           push @buf, $SSL3_RT_ALERT;
539             # Version: TLS 1.0 (SSL 3.1)
540 0           push @buf, 3, 1;
541             # Length:
542 0           push @buf, 0x00, 0x02;
543             # Alert Message
544             ## Level: Fatal (2)
545 0           push @buf, $level;
546             ## Description: Handshake Failure (40)
547 0           push @buf, $desc;
548              
549 0           my $data = '';
550 0           for my $c (@buf) {
551 0           $data .= pack('C', $c);
552             }
553              
554 0           return $sock->write_atomically($data);
555             }
556              
557             1; # Magic true value required at end of module
558              
559             __END__
560              
561             =head1 NAME
562              
563             Net::SSL::ExpireDate - obtain expiration date of certificate
564              
565             =head1 SYNOPSIS
566              
567             use Net::SSL::ExpireDate;
568              
569             $ed = Net::SSL::ExpireDate->new( https => 'example.com' );
570             $ed = Net::SSL::ExpireDate->new( https => 'example.com:10443' );
571             $ed = Net::SSL::ExpireDate->new( ssl => 'example.com:465' ); # smtps
572             $ed = Net::SSL::ExpireDate->new( ssl => 'example.com:995' ); # pop3s
573             $ed = Net::SSL::ExpireDate->new( file => '/etc/ssl/cert.pem' );
574              
575             if (defined $ed->expire_date) {
576             # do something
577             $expire_date = $ed->expire_date; # return DateTime instance
578              
579             $expired = $ed->is_expired; # examine already expired
580              
581             $expired = $ed->is_expired('2 months'); # will expire after 2 months
582             $expired = $ed->is_expired(DateTime::Duration->new(months=>2)); # ditto
583             }
584              
585             =head1 DESCRIPTION
586              
587             Net::SSL::ExpireDate get certificate from network (SSL) or local
588             file and obtain its expiration date.
589              
590             =head1 METHODS
591              
592             =head2 new
593              
594             $ed = Net::SSL::ExpireDate->new( %option )
595              
596             This method constructs a new "Net::SSL::ExpireDate" instance and
597             returns it. %option is to specify certificate.
598              
599             KEY VALUE
600             ----------------------------
601             ssl "hostname[:port]"
602             https (same as above ssl)
603             file "path/to/certificate"
604             timeout "Timeout in seconds"
605             sni "Server Name Indicator"
606              
607             =head2 expire_date
608              
609             $expire_date = $ed->expire_date;
610              
611             Return expiration date by "DateTime" instance.
612              
613             =head2 begin_date
614              
615             $begin_date = $ed->begin_date;
616              
617             Return beginning date by "DateTime" instance.
618              
619             =head2 not_after
620              
621             Synonym for expire_date.
622              
623             =head2 not_before
624              
625             Synonym for begin_date.
626              
627             =head2 is_expired
628              
629             $expired = $ed->is_expired;
630              
631             Obtain already expired or not.
632              
633             You can specify interval to obtain will expire on the future time.
634             Acceptable intervals are human readable string (parsed by
635             "Time::Duration::Parse") and "DateTime::Duration" instance.
636              
637             # will expire after 2 months
638             $expired = $ed->is_expired('2 months');
639             $expired = $ed->is_expired(DateTime::Duration->new(months=>2));
640              
641             =head2 type
642              
643             return type of examinee certificate. "ssl" or "file".
644              
645             =head2 target
646              
647             return hostname or path of examinee certificate.
648              
649             =head1 BUGS AND LIMITATIONS
650              
651             No bugs have been reported.
652              
653             Please report any bugs or feature requests to
654             C<bug-net-ssl-expiredate@rt.cpan.org>, or through the web interface at
655             L<http://rt.cpan.org>.
656              
657             =head1 AUTHOR
658              
659             HIROSE Masaaki E<lt>hirose31 _at_ gmail.comE<gt>
660              
661             =head1 REPOSITORY
662              
663             L<http://github.com/hirose31/net-ssl-expiredate>
664              
665             git clone git://github.com/hirose31/net-ssl-expiredate.git
666              
667             patches and collaborators are welcome.
668              
669             =head1 SEE ALSO
670              
671             =head1 COPYRIGHT & LICENSE
672              
673             Copyright HIROSE Masaaki
674              
675             This library is free software; you can redistribute it and/or modify
676             it under the same terms as Perl itself.
677              
678             =cut
679