| 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 |  |  |  |  |  |  |  |