File Coverage

blib/lib/Net/SSL/ExpireDate.pm
Criterion Covered Total %
statement 64 244 26.2
branch 16 78 20.5
condition 5 13 38.4
subroutine 16 22 72.7
pod 4 4 100.0
total 105 361 29.0


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