| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  |  | 
| 2 |  |  |  |  |  |  | package IO::Socket::SSL::Utils; | 
| 3 | 14 |  |  | 14 |  | 7787 | use strict; | 
|  | 14 |  |  |  |  | 32 |  | 
|  | 14 |  |  |  |  | 522 |  | 
| 4 | 14 |  |  | 14 |  | 91 | use warnings; | 
|  | 14 |  |  |  |  | 29 |  | 
|  | 14 |  |  |  |  | 596 |  | 
| 5 | 14 |  |  | 14 |  | 73 | use Carp 'croak'; | 
|  | 14 |  |  |  |  | 28 |  | 
|  | 14 |  |  |  |  | 719 |  | 
| 6 | 14 |  |  | 14 |  | 84 | use Net::SSLeay; | 
|  | 14 |  |  |  |  | 35 |  | 
|  | 14 |  |  |  |  | 5041 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | # old versions of Exporter do not export 'import' yet | 
| 9 |  |  |  |  |  |  | require Exporter; | 
| 10 |  |  |  |  |  |  | *import = \&Exporter::import; | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | our $VERSION = '2.015'; | 
| 13 |  |  |  |  |  |  | our @EXPORT = qw( | 
| 14 |  |  |  |  |  |  | PEM_file2cert PEM_file2certs PEM_string2cert PEM_cert2file PEM_certs2file PEM_cert2string | 
| 15 |  |  |  |  |  |  | PEM_file2key PEM_string2key PEM_key2file PEM_key2string | 
| 16 |  |  |  |  |  |  | KEY_free CERT_free | 
| 17 |  |  |  |  |  |  | KEY_create_rsa CERT_asHash CERT_create | 
| 18 |  |  |  |  |  |  | ); | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | sub PEM_file2cert { | 
| 21 | 10 |  |  | 10 | 1 | 373 | my $file = shift; | 
| 22 | 10 | 50 |  |  |  | 391 | my $bio = Net::SSLeay::BIO_new_file($file,'r') or | 
| 23 |  |  |  |  |  |  | croak "cannot read $file: $!"; | 
| 24 | 10 |  |  |  |  | 960 | my $cert = Net::SSLeay::PEM_read_bio_X509($bio); | 
| 25 | 10 |  |  |  |  | 188 | Net::SSLeay::BIO_free($bio); | 
| 26 | 10 | 50 |  |  |  | 42 | $cert or croak "cannot parse $file as PEM X509 cert: ". | 
| 27 |  |  |  |  |  |  | Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()); | 
| 28 | 10 |  |  |  |  | 45 | return $cert; | 
| 29 |  |  |  |  |  |  | } | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub PEM_cert2file { | 
| 32 | 0 |  |  | 0 | 1 | 0 | my ($cert,$file) = @_; | 
| 33 | 0 | 0 |  |  |  | 0 | my $string = Net::SSLeay::PEM_get_string_X509($cert) | 
| 34 |  |  |  |  |  |  | or croak("cannot get string from cert"); | 
| 35 | 0 | 0 |  |  |  | 0 | open( my $fh,'>',$file ) or croak("cannot write $file: $!"); | 
| 36 | 0 |  |  |  |  | 0 | print $fh $string; | 
| 37 |  |  |  |  |  |  | } | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 14 |  |  | 14 |  | 118 | use constant PEM_R_NO_START_LINE => 108; | 
|  | 14 |  |  |  |  | 30 |  | 
|  | 14 |  |  |  |  | 58399 |  | 
| 40 |  |  |  |  |  |  | sub PEM_file2certs { | 
| 41 | 0 |  |  | 0 | 1 | 0 | my $file = shift; | 
| 42 | 0 | 0 |  |  |  | 0 | my $bio = Net::SSLeay::BIO_new_file($file,'r') or | 
| 43 |  |  |  |  |  |  | croak "cannot read $file: $!"; | 
| 44 | 0 |  |  |  |  | 0 | my @certs; | 
| 45 | 0 |  |  |  |  | 0 | while (1) { | 
| 46 | 0 | 0 |  |  |  | 0 | if (my $cert = Net::SSLeay::PEM_read_bio_X509($bio)) { | 
| 47 | 0 |  |  |  |  | 0 | push @certs, $cert; | 
| 48 |  |  |  |  |  |  | } else { | 
| 49 | 0 |  |  |  |  | 0 | Net::SSLeay::BIO_free($bio); | 
| 50 | 0 |  |  |  |  | 0 | my $error = Net::SSLeay::ERR_get_error(); | 
| 51 | 0 | 0 | 0 |  |  | 0 | last if ($error & 0xfff) == PEM_R_NO_START_LINE && @certs; | 
| 52 | 0 |  |  |  |  | 0 | croak "cannot parse $file as PEM X509 cert: " . | 
| 53 |  |  |  |  |  |  | Net::SSLeay::ERR_error_string($error); | 
| 54 |  |  |  |  |  |  | } | 
| 55 |  |  |  |  |  |  | } | 
| 56 | 0 |  |  |  |  | 0 | return @certs; | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | sub PEM_certs2file { | 
| 60 | 0 |  |  | 0 | 1 | 0 | my $file = shift; | 
| 61 | 0 | 0 |  |  |  | 0 | open( my $fh,'>',$file ) or croak("cannot write $file: $!"); | 
| 62 | 0 |  |  |  |  | 0 | for my $cert (@_) { | 
| 63 | 0 | 0 |  |  |  | 0 | my $string = Net::SSLeay::PEM_get_string_X509($cert) | 
| 64 |  |  |  |  |  |  | or croak("cannot get string from cert"); | 
| 65 | 0 |  |  |  |  | 0 | print $fh $string; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | sub PEM_string2cert { | 
| 71 | 592 |  |  | 592 | 1 | 287076 | my $string = shift; | 
| 72 | 592 |  |  |  |  | 3219 | my $bio = Net::SSLeay::BIO_new( Net::SSLeay::BIO_s_mem()); | 
| 73 | 592 |  |  |  |  | 4143 | Net::SSLeay::BIO_write($bio,$string); | 
| 74 | 592 |  |  |  |  | 52904 | my $cert = Net::SSLeay::PEM_read_bio_X509($bio); | 
| 75 | 592 |  |  |  |  | 3224 | Net::SSLeay::BIO_free($bio); | 
| 76 | 592 | 50 |  |  |  | 1739 | $cert or croak "cannot parse string as PEM X509 cert: ". | 
| 77 |  |  |  |  |  |  | Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()); | 
| 78 | 592 |  |  |  |  | 2051 | return $cert; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | sub PEM_cert2string { | 
| 82 | 0 |  |  | 0 | 1 | 0 | my $cert = shift; | 
| 83 | 0 |  | 0 |  |  | 0 | return Net::SSLeay::PEM_get_string_X509($cert) | 
| 84 |  |  |  |  |  |  | || croak("cannot get string from cert"); | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | sub PEM_file2key { | 
| 88 | 0 |  |  | 0 | 1 | 0 | my $file = shift; | 
| 89 | 0 | 0 |  |  |  | 0 | my $bio = Net::SSLeay::BIO_new_file($file,'r') or | 
| 90 |  |  |  |  |  |  | croak "cannot read $file: $!"; | 
| 91 | 0 |  |  |  |  | 0 | my $key = Net::SSLeay::PEM_read_bio_PrivateKey($bio); | 
| 92 | 0 |  |  |  |  | 0 | Net::SSLeay::BIO_free($bio); | 
| 93 | 0 | 0 |  |  |  | 0 | $key or croak "cannot parse $file as PEM private key: ". | 
| 94 |  |  |  |  |  |  | Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()); | 
| 95 | 0 |  |  |  |  | 0 | return $key; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub PEM_key2file { | 
| 99 | 0 |  |  | 0 | 1 | 0 | my ($key,$file) = @_; | 
| 100 | 0 | 0 |  |  |  | 0 | my $string = Net::SSLeay::PEM_get_string_PrivateKey($key) | 
| 101 |  |  |  |  |  |  | or croak("cannot get string from key"); | 
| 102 | 0 | 0 |  |  |  | 0 | open( my $fh,'>',$file ) or croak("cannot write $file: $!"); | 
| 103 | 0 |  |  |  |  | 0 | print $fh $string; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | sub PEM_string2key { | 
| 107 | 0 |  |  | 0 | 1 | 0 | my $string = shift; | 
| 108 | 0 |  |  |  |  | 0 | my $bio = Net::SSLeay::BIO_new( Net::SSLeay::BIO_s_mem()); | 
| 109 | 0 |  |  |  |  | 0 | Net::SSLeay::BIO_write($bio,$string); | 
| 110 | 0 |  |  |  |  | 0 | my $key = Net::SSLeay::PEM_read_bio_PrivateKey($bio); | 
| 111 | 0 |  |  |  |  | 0 | Net::SSLeay::BIO_free($bio); | 
| 112 | 0 | 0 |  |  |  | 0 | $key or croak "cannot parse string as PEM private key: ". | 
| 113 |  |  |  |  |  |  | Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()); | 
| 114 | 0 |  |  |  |  | 0 | return $key; | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | sub PEM_key2string { | 
| 118 | 0 |  |  | 0 | 1 | 0 | my $key = shift; | 
| 119 | 0 |  | 0 |  |  | 0 | return Net::SSLeay::PEM_get_string_PrivateKey($key) | 
| 120 |  |  |  |  |  |  | || croak("cannot get string from key"); | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | sub CERT_free { | 
| 124 | 78 |  |  | 78 | 1 | 27288 | Net::SSLeay::X509_free($_) for @_; | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | sub KEY_free { | 
| 128 | 0 |  |  | 0 | 1 | 0 | Net::SSLeay::EVP_PKEY_free($_) for @_; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | sub KEY_create_rsa { | 
| 132 | 16 |  | 50 | 16 | 1 | 86 | my $bits = shift || 2048; | 
| 133 | 16 |  |  |  |  | 94 | my $key = Net::SSLeay::EVP_PKEY_new(); | 
| 134 | 16 |  |  |  |  | 2788489 | my $rsa = Net::SSLeay::RSA_generate_key($bits, 0x10001); # 0x10001 = RSA_F4 | 
| 135 | 16 |  |  |  |  | 328 | Net::SSLeay::EVP_PKEY_assign_RSA($key,$rsa); | 
| 136 | 16 |  |  |  |  | 199 | return $key; | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | if (defined &Net::SSLeay::EC_KEY_generate_key) { | 
| 140 |  |  |  |  |  |  | push @EXPORT,'KEY_create_ec'; | 
| 141 |  |  |  |  |  |  | *KEY_create_ec = sub { | 
| 142 | 0 |  | 0 | 0 |  | 0 | my $curve = shift || 'prime256v1'; | 
| 143 | 0 |  |  |  |  | 0 | my $key = Net::SSLeay::EVP_PKEY_new(); | 
| 144 | 0 |  |  |  |  | 0 | my $ec = Net::SSLeay::EC_KEY_generate_key($curve); | 
| 145 | 0 |  |  |  |  | 0 | Net::SSLeay::EVP_PKEY_assign_EC_KEY($key,$ec); | 
| 146 | 0 |  |  |  |  | 0 | return $key; | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  | } | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # extract information from cert | 
| 151 |  |  |  |  |  |  | my %gen2i = qw( OTHERNAME 0 EMAIL 1 DNS 2 X400 3 DIRNAME 4 EDIPARTY 5 URI 6 IP 7 RID 8 ); | 
| 152 |  |  |  |  |  |  | my %i2gen = reverse %gen2i; | 
| 153 |  |  |  |  |  |  | sub CERT_asHash { | 
| 154 | 0 |  |  | 0 | 1 | 0 | my $cert = shift; | 
| 155 | 0 |  | 0 |  |  | 0 | my $digest_name = shift || 'sha256'; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 0 |  |  |  |  | 0 | my %hash = ( | 
| 158 |  |  |  |  |  |  | version => Net::SSLeay::X509_get_version($cert), | 
| 159 |  |  |  |  |  |  | not_before => _asn1t2t(Net::SSLeay::X509_get_notBefore($cert)), | 
| 160 |  |  |  |  |  |  | not_after => _asn1t2t(Net::SSLeay::X509_get_notAfter($cert)), | 
| 161 |  |  |  |  |  |  | serial => Net::SSLeay::P_ASN1_INTEGER_get_dec( | 
| 162 |  |  |  |  |  |  | Net::SSLeay::X509_get_serialNumber($cert)), | 
| 163 |  |  |  |  |  |  | signature_alg => Net::SSLeay::OBJ_obj2txt ( | 
| 164 |  |  |  |  |  |  | Net::SSLeay::P_X509_get_signature_alg($cert)), | 
| 165 |  |  |  |  |  |  | crl_uri  => [ Net::SSLeay::P_X509_get_crl_distribution_points($cert) ], | 
| 166 |  |  |  |  |  |  | keyusage => [ Net::SSLeay::P_X509_get_key_usage($cert) ], | 
| 167 |  |  |  |  |  |  | extkeyusage => { | 
| 168 |  |  |  |  |  |  | oid => [ Net::SSLeay::P_X509_get_ext_key_usage($cert,0) ], | 
| 169 |  |  |  |  |  |  | nid => [ Net::SSLeay::P_X509_get_ext_key_usage($cert,1) ], | 
| 170 |  |  |  |  |  |  | sn  => [ Net::SSLeay::P_X509_get_ext_key_usage($cert,2) ], | 
| 171 |  |  |  |  |  |  | ln  => [ Net::SSLeay::P_X509_get_ext_key_usage($cert,3) ], | 
| 172 |  |  |  |  |  |  | }, | 
| 173 |  |  |  |  |  |  | "pubkey_digest_$digest_name" => Net::SSLeay::X509_pubkey_digest( | 
| 174 |  |  |  |  |  |  | $cert,_digest($digest_name)), | 
| 175 |  |  |  |  |  |  | "x509_digest_$digest_name" => Net::SSLeay::X509_digest( | 
| 176 |  |  |  |  |  |  | $cert,_digest($digest_name)), | 
| 177 |  |  |  |  |  |  | "fingerprint_$digest_name" => Net::SSLeay::X509_get_fingerprint( | 
| 178 |  |  |  |  |  |  | $cert,$digest_name), | 
| 179 |  |  |  |  |  |  | ); | 
| 180 |  |  |  |  |  |  |  | 
| 181 | 0 |  |  |  |  | 0 | for([ subject => Net::SSLeay::X509_get_subject_name($cert) ], | 
| 182 |  |  |  |  |  |  | [ issuer => Net::SSLeay::X509_get_issuer_name($cert) ]) { | 
| 183 | 0 |  |  |  |  | 0 | my ($what,$subj) = @$_; | 
| 184 | 0 |  |  |  |  | 0 | my %subj; | 
| 185 | 0 |  |  |  |  | 0 | for ( 0..Net::SSLeay::X509_NAME_entry_count($subj)-1 ) { | 
| 186 | 0 |  |  |  |  | 0 | my $e = Net::SSLeay::X509_NAME_get_entry($subj,$_); | 
| 187 | 0 |  |  |  |  | 0 | my $k = Net::SSLeay::OBJ_obj2txt( | 
| 188 |  |  |  |  |  |  | Net::SSLeay::X509_NAME_ENTRY_get_object($e)); | 
| 189 | 0 |  |  |  |  | 0 | my $v = Net::SSLeay::P_ASN1_STRING_get( | 
| 190 |  |  |  |  |  |  | Net::SSLeay::X509_NAME_ENTRY_get_data($e)); | 
| 191 | 0 | 0 |  |  |  | 0 | if (!exists $subj{$k}) { | 
|  |  | 0 |  |  |  |  |  | 
| 192 | 0 |  |  |  |  | 0 | $subj{$k} = $v; | 
| 193 |  |  |  |  |  |  | } elsif (!ref $subj{$k}) { | 
| 194 | 0 |  |  |  |  | 0 | $subj{$k} = [ $subj{$k}, $v ]; | 
| 195 |  |  |  |  |  |  | } else { | 
| 196 | 0 |  |  |  |  | 0 | push @{$subj{$k}}, $v; | 
|  | 0 |  |  |  |  | 0 |  | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  | } | 
| 199 | 0 |  |  |  |  | 0 | $hash{$what} = \%subj; | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  |  | 
| 203 | 0 | 0 |  |  |  | 0 | if ( my @names = Net::SSLeay::X509_get_subjectAltNames($cert) ) { | 
| 204 | 0 |  |  |  |  | 0 | my $alt = $hash{subjectAltNames} = []; | 
| 205 | 0 |  |  |  |  | 0 | while (my ($t,$v) = splice(@names,0,2)) { | 
| 206 | 0 |  | 0 |  |  | 0 | $t = $i2gen{$t} || die "unknown type $t in subjectAltName"; | 
| 207 | 0 | 0 |  |  |  | 0 | if ( $t eq 'IP' ) { | 
| 208 | 0 | 0 |  |  |  | 0 | if (length($v) == 4) { | 
|  |  | 0 |  |  |  |  |  | 
| 209 | 0 |  |  |  |  | 0 | $v = join('.',unpack("CCCC",$v)); | 
| 210 |  |  |  |  |  |  | } elsif ( length($v) == 16 ) { | 
| 211 | 0 |  |  |  |  | 0 | my @v = unpack("nnnnnnnn",$v); | 
| 212 | 0 |  |  |  |  | 0 | my ($best0,$last0); | 
| 213 | 0 |  |  |  |  | 0 | for(my $i=0;$i<@v;$i++) { | 
| 214 | 0 | 0 |  |  |  | 0 | if ($v[$i] == 0) { | 
| 215 | 0 | 0 |  |  |  | 0 | if ($last0) { | 
| 216 | 0 |  |  |  |  | 0 | $last0->[1] = $i; | 
| 217 | 0 |  |  |  |  | 0 | $last0->[2]++; | 
| 218 | 0 | 0 |  |  |  | 0 | $best0 = $last0 if ++$last0->[2]>$best0->[2]; | 
| 219 |  |  |  |  |  |  | } else { | 
| 220 | 0 |  |  |  |  | 0 | $last0 = [ $i,$i,0 ]; | 
| 221 | 0 |  | 0 |  |  | 0 | $best0 ||= $last0; | 
| 222 |  |  |  |  |  |  | } | 
| 223 |  |  |  |  |  |  | } else { | 
| 224 | 0 |  |  |  |  | 0 | $last0 = undef; | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | } | 
| 227 | 0 | 0 |  |  |  | 0 | if ($best0) { | 
| 228 | 0 |  |  |  |  | 0 | $v = ''; | 
| 229 | 0 | 0 |  |  |  | 0 | $v .= join(':', map { sprintf( "%x",$_) } @v[0..$best0->[0]-1]) if $best0->[0]>0; | 
|  | 0 |  |  |  |  | 0 |  | 
| 230 | 0 |  |  |  |  | 0 | $v .= '::'; | 
| 231 | 0 | 0 |  |  |  | 0 | $v .= join(':', map { sprintf( "%x",$_) } @v[$best0->[1]+1..$#v]) if $best0->[1]<$#v; | 
|  | 0 |  |  |  |  | 0 |  | 
| 232 |  |  |  |  |  |  | } else { | 
| 233 | 0 |  |  |  |  | 0 | $v = join(':', map { sprintf( "%x",$_) } @v); | 
|  | 0 |  |  |  |  | 0 |  | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  | } | 
| 236 |  |  |  |  |  |  | } | 
| 237 | 0 |  |  |  |  | 0 | push @$alt,[$t,$v] | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 0 |  |  |  |  | 0 | my @ext; | 
| 242 | 0 |  |  |  |  | 0 | for( 0..Net::SSLeay::X509_get_ext_count($cert)-1 ) { | 
| 243 | 0 |  |  |  |  | 0 | my $e = Net::SSLeay::X509_get_ext($cert,$_); | 
| 244 | 0 |  |  |  |  | 0 | my $o = Net::SSLeay::X509_EXTENSION_get_object($e); | 
| 245 | 0 |  |  |  |  | 0 | my $nid = Net::SSLeay::OBJ_obj2nid($o); | 
| 246 | 0 | 0 |  |  |  | 0 | push @ext, { | 
|  |  | 0 |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | oid => Net::SSLeay::OBJ_obj2txt($o), | 
| 248 |  |  |  |  |  |  | nid => ( $nid > 0 ) ? $nid : undef, | 
| 249 |  |  |  |  |  |  | sn  => ( $nid > 0 ) ? Net::SSLeay::OBJ_nid2sn($nid) : undef, | 
| 250 |  |  |  |  |  |  | critical => Net::SSLeay::X509_EXTENSION_get_critical($e), | 
| 251 |  |  |  |  |  |  | data => Net::SSLeay::X509V3_EXT_print($e), | 
| 252 |  |  |  |  |  |  | } | 
| 253 |  |  |  |  |  |  | } | 
| 254 | 0 |  |  |  |  | 0 | $hash{ext} = \@ext; | 
| 255 |  |  |  |  |  |  |  | 
| 256 | 0 | 0 |  |  |  | 0 | if ( defined(&Net::SSLeay::P_X509_get_ocsp_uri)) { | 
| 257 | 0 |  |  |  |  | 0 | $hash{ocsp_uri} = [ Net::SSLeay::P_X509_get_ocsp_uri($cert) ]; | 
| 258 |  |  |  |  |  |  | } else { | 
| 259 | 0 |  |  |  |  | 0 | $hash{ocsp_uri} = []; | 
| 260 | 0 |  |  |  |  | 0 | for( @ext ) { | 
| 261 | 0 | 0 |  |  |  | 0 | $_->{sn} or next; | 
| 262 | 0 | 0 |  |  |  | 0 | $_->{sn} eq 'authorityInfoAccess' or next; | 
| 263 | 0 |  |  |  |  | 0 | push @{ $hash{ocsp_uri}}, $_->{data} =~m{\bOCSP - URI:(\S+)}g; | 
|  | 0 |  |  |  |  | 0 |  | 
| 264 |  |  |  |  |  |  | } | 
| 265 |  |  |  |  |  |  | } | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 0 |  |  |  |  | 0 | return \%hash; | 
| 268 |  |  |  |  |  |  | } | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | sub CERT_create { | 
| 271 | 120 | 50 |  | 120 | 1 | 50145 | my %args = @_%2 ? %{ shift() } :  @_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 272 |  |  |  |  |  |  |  | 
| 273 | 120 |  |  |  |  | 1168 | my $cert = Net::SSLeay::X509_new(); | 
| 274 | 120 |  | 50 |  |  | 709 | my $digest_name = delete $args{digest} || 'sha256'; | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | Net::SSLeay::ASN1_INTEGER_set( | 
| 277 |  |  |  |  |  |  | Net::SSLeay::X509_get_serialNumber($cert), | 
| 278 | 120 |  | 33 |  |  | 1191 | delete $args{serial} || rand(2**32), | 
| 279 |  |  |  |  |  |  | ); | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | # version default to 2 (V3) | 
| 282 |  |  |  |  |  |  | Net::SSLeay::X509_set_version($cert, | 
| 283 | 120 |  | 50 |  |  | 720 | delete $args{version} || 2 ); | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | # not_before default to now | 
| 286 |  |  |  |  |  |  | Net::SSLeay::ASN1_TIME_set( | 
| 287 |  |  |  |  |  |  | Net::SSLeay::X509_get_notBefore($cert), | 
| 288 |  |  |  |  |  |  | delete $args{not_before} || time() | 
| 289 | 120 |  | 33 |  |  | 1664 | ); | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | # not_after default to now+365 days | 
| 292 |  |  |  |  |  |  | Net::SSLeay::ASN1_TIME_set( | 
| 293 |  |  |  |  |  |  | Net::SSLeay::X509_get_notAfter($cert), | 
| 294 | 120 |  | 33 |  |  | 946 | delete $args{not_after} || time() + 365*86400 | 
| 295 |  |  |  |  |  |  | ); | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | # set subject | 
| 298 | 120 |  |  |  |  | 343 | my $subj_e = Net::SSLeay::X509_get_subject_name($cert); | 
| 299 |  |  |  |  |  |  | my $subj = delete $args{subject} || { | 
| 300 | 120 |  | 100 |  |  | 381 | organizationName => 'IO::Socket::SSL', | 
| 301 |  |  |  |  |  |  | commonName => 'IO::Socket::SSL Test' | 
| 302 |  |  |  |  |  |  | }; | 
| 303 |  |  |  |  |  |  |  | 
| 304 | 120 |  |  |  |  | 651 | while ( my ($k,$v) = each %$subj ) { | 
| 305 |  |  |  |  |  |  | # Not everything we get is nice - try with MBSTRING_UTF8 first and if it | 
| 306 |  |  |  |  |  |  | # fails try V_ASN1_T61STRING and finally V_ASN1_OCTET_STRING | 
| 307 | 116 | 50 |  |  |  | 355 | for (ref($v) ? @$v : ($v)) { | 
| 308 | 116 | 0 | 33 |  |  | 2181 | Net::SSLeay::X509_NAME_add_entry_by_txt($subj_e,$k,0x1000,$_,-1,0) | 
|  |  |  | 33 |  |  |  |  | 
| 309 |  |  |  |  |  |  | or Net::SSLeay::X509_NAME_add_entry_by_txt($subj_e,$k,20,$_,-1,0) | 
| 310 |  |  |  |  |  |  | or Net::SSLeay::X509_NAME_add_entry_by_txt($subj_e,$k,4,$_,-1,0) | 
| 311 |  |  |  |  |  |  | or croak("failed to add entry for $k - ". | 
| 312 |  |  |  |  |  |  | Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error())); | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  |  | 
| 316 | 120 |  |  |  |  | 3846 | my @ext = ( | 
| 317 |  |  |  |  |  |  | &Net::SSLeay::NID_subject_key_identifier => 'hash', | 
| 318 |  |  |  |  |  |  | &Net::SSLeay::NID_authority_key_identifier => 'keyid', | 
| 319 |  |  |  |  |  |  | ); | 
| 320 | 120 | 100 |  |  |  | 5796 | if ( my $altsubj = delete $args{subjectAltNames} ) { | 
| 321 |  |  |  |  |  |  | push @ext, | 
| 322 |  |  |  |  |  |  | &Net::SSLeay::NID_subject_alt_name => | 
| 323 | 22 |  |  |  |  | 379 | join(',', map { "$_->[0]:$_->[1]" } @$altsubj) | 
|  | 57 |  |  |  |  | 779 |  | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 | 120 |  | 66 |  |  | 460 | my $key = delete $args{key} || KEY_create_rsa(); | 
| 327 | 120 |  |  |  |  | 1340 | Net::SSLeay::X509_set_pubkey($cert,$key); | 
| 328 |  |  |  |  |  |  |  | 
| 329 | 120 |  |  |  |  | 321 | my $is = delete $args{issuer}; | 
| 330 | 120 |  | 66 |  |  | 604 | my $issuer_cert = delete $args{issuer_cert} || $is && $is->[0] || $cert; | 
| 331 | 120 |  | 66 |  |  | 458 | my $issuer_key  = delete $args{issuer_key}  || $is && $is->[1] || $key; | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 120 |  |  |  |  | 220 | my %purpose; | 
| 334 | 120 | 100 |  |  |  | 308 | if (my $p = delete $args{purpose}) { | 
| 335 | 2 | 50 |  |  |  | 29 | if (!ref($p)) { | 
|  |  | 50 |  |  |  |  |  | 
| 336 | 0 | 0 | 0 |  |  | 0 | $purpose{lc($2)} = (!$1 || $1 eq '+') ? 1:0 | 
| 337 |  |  |  |  |  |  | while $p =~m{([+-]?)(\w+)}g; | 
| 338 |  |  |  |  |  |  | } elsif (ref($p) eq 'ARRAY') { | 
| 339 | 0 |  |  |  |  | 0 | for(@$p) { | 
| 340 | 0 | 0 |  |  |  | 0 | m{^([+-]?)(\w+)$} or die "invalid entry in purpose: $_"; | 
| 341 | 0 | 0 | 0 |  |  | 0 | $purpose{lc($2)} = (!$1 || $1 eq '+') ? 1:0 | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  | } else { | 
| 344 | 2 |  |  |  |  | 38 | while( my ($k,$v) = each %$p) { | 
| 345 | 2 | 50 | 33 |  |  | 33 | $purpose{lc($k)} = ($v && $v ne '-')?1:0; | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  | } | 
| 348 |  |  |  |  |  |  | } | 
| 349 | 120 | 100 |  |  |  | 304 | if (delete $args{CA}) { | 
| 350 |  |  |  |  |  |  | # add defaults for CA | 
| 351 | 10 |  |  |  |  | 137 | %purpose = ( | 
| 352 |  |  |  |  |  |  | ca => 1, sslca => 1, emailca => 1, objca => 1, | 
| 353 |  |  |  |  |  |  | %purpose | 
| 354 |  |  |  |  |  |  | ); | 
| 355 |  |  |  |  |  |  | } | 
| 356 | 120 | 100 |  |  |  | 303 | if (!%purpose) { | 
| 357 | 108 |  |  |  |  | 408 | %purpose = (server => 1, client => 1); | 
| 358 |  |  |  |  |  |  | } | 
| 359 |  |  |  |  |  |  |  | 
| 360 | 120 |  |  |  |  | 281 | my (%key_usage,%ext_key_usage,%cert_type,%basic_constraints); | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 120 |  |  |  |  | 365 | my %dS = ( digitalSignature => \%key_usage ); | 
| 363 | 120 |  |  |  |  | 330 | my %kE = ( keyEncipherment => \%key_usage ); | 
| 364 | 120 |  |  |  |  | 507 | my %CA = ( 'CA:TRUE' => \%basic_constraints, %dS, keyCertSign => \%key_usage ); | 
| 365 | 120 |  |  |  |  | 290 | my @disable; | 
| 366 | 120 |  |  |  |  | 4235 | for( | 
| 367 |  |  |  |  |  |  | [ client  => { %dS, %kE, clientAuth => \%ext_key_usage, client  => \%cert_type } ], | 
| 368 |  |  |  |  |  |  | [ server  => { %dS, %kE, serverAuth => \%ext_key_usage, server  => \%cert_type } ], | 
| 369 |  |  |  |  |  |  | [ email   => { %dS, %kE, emailProtection => \%ext_key_usage, email => \%cert_type } ], | 
| 370 |  |  |  |  |  |  | [ objsign => { %dS, %kE, codeSigning => \%ext_key_usage, objsign => \%cert_type } ], | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | [ CA      => { %CA }], | 
| 373 |  |  |  |  |  |  | [ sslCA   => { %CA, sslCA => \%cert_type }], | 
| 374 |  |  |  |  |  |  | [ emailCA => { %CA, emailCA => \%cert_type }], | 
| 375 |  |  |  |  |  |  | [ objCA   => { %CA, objCA => \%cert_type }], | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | [ emailProtection  => { %dS, %kE, emailProtection => \%ext_key_usage, email => \%cert_type } ], | 
| 378 |  |  |  |  |  |  | [ codeSigning      => { %dS, %kE, codeSigning => \%ext_key_usage, objsign => \%cert_type } ], | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | [ timeStamping     => { timeStamping => \%ext_key_usage } ], | 
| 381 |  |  |  |  |  |  | [ digitalSignature => { digitalSignature => \%key_usage } ], | 
| 382 |  |  |  |  |  |  | [ nonRepudiation   => { nonRepudiation => \%key_usage } ], | 
| 383 |  |  |  |  |  |  | [ keyEncipherment  => { keyEncipherment => \%key_usage } ], | 
| 384 |  |  |  |  |  |  | [ dataEncipherment => { dataEncipherment => \%key_usage } ], | 
| 385 |  |  |  |  |  |  | [ keyAgreement     => { keyAgreement => \%key_usage } ], | 
| 386 |  |  |  |  |  |  | [ keyCertSign      => { keyCertSign => \%key_usage } ], | 
| 387 |  |  |  |  |  |  | [ cRLSign          => { cRLSign => \%key_usage } ], | 
| 388 |  |  |  |  |  |  | [ encipherOnly     => { encipherOnly => \%key_usage } ], | 
| 389 |  |  |  |  |  |  | [ decipherOnly     => { decipherOnly => \%key_usage } ], | 
| 390 |  |  |  |  |  |  | [ clientAuth       => { clientAuth   => \%ext_key_usage } ], | 
| 391 |  |  |  |  |  |  | [ serverAuth       => { serverAuth   => \%ext_key_usage } ], | 
| 392 |  |  |  |  |  |  | ) { | 
| 393 | 2640 | 100 |  |  |  | 5332 | exists $purpose{lc($_->[0])} or next; | 
| 394 | 258 | 50 |  |  |  | 670 | if (delete $purpose{lc($_->[0])}) { | 
| 395 | 258 |  |  |  |  | 434 | while (my($k,$h) = each %{$_->[1]}) { | 
|  | 1280 |  |  |  |  | 3489 |  | 
| 396 | 1022 |  |  |  |  | 1911 | $h->{$k} = 1; | 
| 397 |  |  |  |  |  |  | } | 
| 398 |  |  |  |  |  |  | } else { | 
| 399 | 0 |  |  |  |  | 0 | push @disable, $_->[1]; | 
| 400 |  |  |  |  |  |  | } | 
| 401 |  |  |  |  |  |  | } | 
| 402 | 120 | 50 |  |  |  | 1519 | die "unknown purpose ".join(",",keys %purpose) if %purpose; | 
| 403 | 120 |  |  |  |  | 300 | for(@disable) { | 
| 404 | 0 |  |  |  |  | 0 | while (my($k,$h) = each %$_) { | 
| 405 | 0 |  |  |  |  | 0 | delete $h->{$k}; | 
| 406 |  |  |  |  |  |  | } | 
| 407 |  |  |  |  |  |  | } | 
| 408 |  |  |  |  |  |  |  | 
| 409 | 120 | 100 |  |  |  | 272 | if (%basic_constraints) { | 
| 410 | 10 |  |  |  |  | 367 | push @ext,&Net::SSLeay::NID_basic_constraints, | 
| 411 |  |  |  |  |  |  | => join(",",'critical', sort keys %basic_constraints); | 
| 412 |  |  |  |  |  |  | } else { | 
| 413 | 110 |  |  |  |  | 3114 | push @ext, &Net::SSLeay::NID_basic_constraints => 'critical,CA:FALSE'; | 
| 414 |  |  |  |  |  |  | } | 
| 415 | 120 | 50 |  |  |  | 4353 | push @ext,&Net::SSLeay::NID_key_usage | 
| 416 |  |  |  |  |  |  | => join(",",'critical', sort keys %key_usage) if %key_usage; | 
| 417 | 120 | 50 |  |  |  | 3753 | push @ext,&Net::SSLeay::NID_netscape_cert_type | 
| 418 |  |  |  |  |  |  | => join(",",sort keys %cert_type) if %cert_type; | 
| 419 | 120 | 100 |  |  |  | 3461 | push @ext,&Net::SSLeay::NID_ext_key_usage | 
| 420 |  |  |  |  |  |  | => join(",",sort keys %ext_key_usage) if %ext_key_usage; | 
| 421 | 120 |  |  |  |  | 8038 | Net::SSLeay::P_X509_add_extensions($cert, $issuer_cert, @ext); | 
| 422 |  |  |  |  |  |  |  | 
| 423 | 120 |  |  |  |  | 272 | my %have_ext; | 
| 424 | 120 |  |  |  |  | 415 | for(my $i=0;$i<@ext;$i+=2) { | 
| 425 | 732 |  |  |  |  | 2162 | $have_ext{ $ext[$i] }++ | 
| 426 |  |  |  |  |  |  | } | 
| 427 | 120 | 50 |  |  |  | 300 | for my $ext (@{ $args{ext} || [] }) { | 
|  | 120 |  |  |  |  | 1021 |  | 
| 428 |  |  |  |  |  |  | my $nid = $ext->{nid} | 
| 429 |  |  |  |  |  |  | || $ext->{sn} && Net::SSLeay::OBJ_sn2nid($ext->{sn}) | 
| 430 | 0 |  | 0 |  |  | 0 | || croak "cannot determine NID of extension"; | 
| 431 | 0 | 0 |  |  |  | 0 | $have_ext{$nid} and next; | 
| 432 | 0 |  |  |  |  | 0 | my $val = $ext->{data}; | 
| 433 | 0 | 0 |  |  |  | 0 | if ($nid == 177) { | 
| 434 |  |  |  |  |  |  | # authorityInfoAccess: | 
| 435 |  |  |  |  |  |  | # OpenSSL i2v does not output the same way as expected by i2v :( | 
| 436 | 0 |  |  |  |  | 0 | for (split(/\n/,$val)) { | 
| 437 | 0 |  |  |  |  | 0 | s{ - }{;}; # "OCSP - URI:..." -> "OCSP;URI:..." | 
| 438 | 0 | 0 |  |  |  | 0 | $_ = "critical,$_" if $ext->{critical}; | 
| 439 | 0 |  |  |  |  | 0 | Net::SSLeay::P_X509_add_extensions($cert,$issuer_cert,$nid,$_); | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  | } else { | 
| 442 | 0 | 0 |  |  |  | 0 | $val = "critical,$val" if $ext->{critical}; | 
| 443 | 0 |  |  |  |  | 0 | Net::SSLeay::P_X509_add_extensions($cert, $issuer_cert, $nid, $val); | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  | } | 
| 446 |  |  |  |  |  |  |  | 
| 447 | 120 |  |  |  |  | 2231 | Net::SSLeay::X509_set_issuer_name($cert, | 
| 448 |  |  |  |  |  |  | Net::SSLeay::X509_get_subject_name($issuer_cert)); | 
| 449 | 120 |  |  |  |  | 388 | Net::SSLeay::X509_sign($cert,$issuer_key,_digest($digest_name)); | 
| 450 |  |  |  |  |  |  |  | 
| 451 | 120 |  |  |  |  | 1650 | return ($cert,$key); | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  |  | 
| 456 |  |  |  |  |  |  | if ( defined &Net::SSLeay::ASN1_TIME_timet ) { | 
| 457 |  |  |  |  |  |  | *_asn1t2t = \&Net::SSLeay::ASN1_TIME_timet | 
| 458 |  |  |  |  |  |  | } else { | 
| 459 |  |  |  |  |  |  | require Time::Local; | 
| 460 |  |  |  |  |  |  | my %mon2i = qw( | 
| 461 |  |  |  |  |  |  | Jan 0 Feb 1 Mar 2 Apr 3 May 4 Jun 5 | 
| 462 |  |  |  |  |  |  | Jul 6 Aug 7 Sep 8 Oct 9 Nov 10 Dec 11 | 
| 463 |  |  |  |  |  |  | ); | 
| 464 |  |  |  |  |  |  | *_asn1t2t = sub { | 
| 465 |  |  |  |  |  |  | my $t = Net::SSLeay::P_ASN1_TIME_put2string( shift ); | 
| 466 |  |  |  |  |  |  | my ($mon,$d,$h,$m,$s,$y,$tz) = split(/[\s:]+/,$t); | 
| 467 |  |  |  |  |  |  | defined( $mon = $mon2i{$mon} ) or die "invalid month in $t"; | 
| 468 |  |  |  |  |  |  | $tz ||= $y =~s{^(\d+)([A-Z]\S*)}{$1} && $2; | 
| 469 |  |  |  |  |  |  | if ( ! $tz ) { | 
| 470 |  |  |  |  |  |  | return Time::Local::timelocal($s,$m,$h,$d,$mon,$y) | 
| 471 |  |  |  |  |  |  | } elsif ( $tz eq 'GMT' ) { | 
| 472 |  |  |  |  |  |  | return Time::Local::timegm($s,$m,$h,$d,$mon,$y) | 
| 473 |  |  |  |  |  |  | } else { | 
| 474 |  |  |  |  |  |  | die "unexpected TZ $tz from ASN1_TIME_print"; | 
| 475 |  |  |  |  |  |  | } | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | { | 
| 480 |  |  |  |  |  |  | my %digest; | 
| 481 |  |  |  |  |  |  | sub _digest { | 
| 482 | 120 |  |  | 120 |  | 242 | my $digest_name = shift; | 
| 483 | 120 |  | 66 |  |  | 228327 | return $digest{$digest_name} ||= do { | 
| 484 | 6 |  |  |  |  | 112 | Net::SSLeay::SSLeay_add_ssl_algorithms(); | 
| 485 | 6 | 50 |  |  |  | 16883 | Net::SSLeay::EVP_get_digestbyname($digest_name) | 
| 486 |  |  |  |  |  |  | or die "Digest algorithm $digest_name is not available"; | 
| 487 |  |  |  |  |  |  | }; | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  | } | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | 1; | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | __END__ |