line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
|
2
|
|
|
|
|
|
|
package IO::Socket::SSL::Utils; |
3
|
14
|
|
|
14
|
|
7837
|
use strict; |
|
14
|
|
|
|
|
36
|
|
|
14
|
|
|
|
|
550
|
|
4
|
14
|
|
|
14
|
|
76
|
use warnings; |
|
14
|
|
|
|
|
46
|
|
|
14
|
|
|
|
|
487
|
|
5
|
14
|
|
|
14
|
|
83
|
use Carp 'croak'; |
|
14
|
|
|
|
|
44
|
|
|
14
|
|
|
|
|
756
|
|
6
|
14
|
|
|
14
|
|
95
|
use Net::SSLeay; |
|
14
|
|
|
|
|
35
|
|
|
14
|
|
|
|
|
4635
|
|
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
|
334
|
my $file = shift; |
22
|
10
|
50
|
|
|
|
340
|
my $bio = Net::SSLeay::BIO_new_file($file,'r') or |
23
|
|
|
|
|
|
|
croak "cannot read $file: $!"; |
24
|
10
|
|
|
|
|
842
|
my $cert = Net::SSLeay::PEM_read_bio_X509($bio); |
25
|
10
|
|
|
|
|
159
|
Net::SSLeay::BIO_free($bio); |
26
|
10
|
50
|
|
|
|
39
|
$cert or croak "cannot parse $file as PEM X509 cert: ". |
27
|
|
|
|
|
|
|
Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()); |
28
|
10
|
|
|
|
|
103
|
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
|
|
106
|
use constant PEM_R_NO_START_LINE => 108; |
|
14
|
|
|
|
|
31
|
|
|
14
|
|
|
|
|
57509
|
|
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
|
287741
|
my $string = shift; |
72
|
592
|
|
|
|
|
3115
|
my $bio = Net::SSLeay::BIO_new( Net::SSLeay::BIO_s_mem()); |
73
|
592
|
|
|
|
|
4102
|
Net::SSLeay::BIO_write($bio,$string); |
74
|
592
|
|
|
|
|
54222
|
my $cert = Net::SSLeay::PEM_read_bio_X509($bio); |
75
|
592
|
|
|
|
|
3202
|
Net::SSLeay::BIO_free($bio); |
76
|
592
|
50
|
|
|
|
1892
|
$cert or croak "cannot parse string as PEM X509 cert: ". |
77
|
|
|
|
|
|
|
Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()); |
78
|
592
|
|
|
|
|
2093
|
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
|
36543
|
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
|
98
|
my $bits = shift || 2048; |
133
|
16
|
|
|
|
|
81
|
my $key = Net::SSLeay::EVP_PKEY_new(); |
134
|
16
|
|
|
|
|
2330899
|
my $rsa = Net::SSLeay::RSA_generate_key($bits, 0x10001); # 0x10001 = RSA_F4 |
135
|
16
|
|
|
|
|
286
|
Net::SSLeay::EVP_PKEY_assign_RSA($key,$rsa); |
136
|
16
|
|
|
|
|
167
|
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
|
46207
|
my %args = @_%2 ? %{ shift() } : @_; |
|
0
|
|
|
|
|
0
|
|
272
|
|
|
|
|
|
|
|
273
|
120
|
|
|
|
|
1268
|
my $cert = Net::SSLeay::X509_new(); |
274
|
120
|
|
50
|
|
|
732
|
my $digest_name = delete $args{digest} || 'sha256'; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
Net::SSLeay::ASN1_INTEGER_set( |
277
|
|
|
|
|
|
|
Net::SSLeay::X509_get_serialNumber($cert), |
278
|
120
|
|
33
|
|
|
1142
|
delete $args{serial} || rand(2**32), |
279
|
|
|
|
|
|
|
); |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# version default to 2 (V3) |
282
|
|
|
|
|
|
|
Net::SSLeay::X509_set_version($cert, |
283
|
120
|
|
50
|
|
|
678
|
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
|
|
|
1640
|
); |
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
|
|
|
859
|
delete $args{not_after} || time() + 365*86400 |
295
|
|
|
|
|
|
|
); |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# set subject |
298
|
120
|
|
|
|
|
320
|
my $subj_e = Net::SSLeay::X509_get_subject_name($cert); |
299
|
|
|
|
|
|
|
my $subj = delete $args{subject} || { |
300
|
120
|
|
100
|
|
|
461
|
organizationName => 'IO::Socket::SSL', |
301
|
|
|
|
|
|
|
commonName => 'IO::Socket::SSL Test' |
302
|
|
|
|
|
|
|
}; |
303
|
|
|
|
|
|
|
|
304
|
120
|
|
|
|
|
640
|
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
|
|
|
|
362
|
for (ref($v) ? @$v : ($v)) { |
308
|
116
|
0
|
33
|
|
|
2175
|
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
|
|
|
|
|
3833
|
my @ext = ( |
317
|
|
|
|
|
|
|
&Net::SSLeay::NID_subject_key_identifier => 'hash', |
318
|
|
|
|
|
|
|
&Net::SSLeay::NID_authority_key_identifier => 'keyid', |
319
|
|
|
|
|
|
|
); |
320
|
120
|
100
|
|
|
|
4268
|
if ( my $altsubj = delete $args{subjectAltNames} ) { |
321
|
|
|
|
|
|
|
push @ext, |
322
|
|
|
|
|
|
|
&Net::SSLeay::NID_subject_alt_name => |
323
|
22
|
|
|
|
|
347
|
join(',', map { "$_->[0]:$_->[1]" } @$altsubj) |
|
57
|
|
|
|
|
686
|
|
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
120
|
|
66
|
|
|
437
|
my $key = delete $args{key} || KEY_create_rsa(); |
327
|
120
|
|
|
|
|
1314
|
Net::SSLeay::X509_set_pubkey($cert,$key); |
328
|
|
|
|
|
|
|
|
329
|
120
|
|
|
|
|
308
|
my $is = delete $args{issuer}; |
330
|
120
|
|
66
|
|
|
578
|
my $issuer_cert = delete $args{issuer_cert} || $is && $is->[0] || $cert; |
331
|
120
|
|
66
|
|
|
460
|
my $issuer_key = delete $args{issuer_key} || $is && $is->[1] || $key; |
332
|
|
|
|
|
|
|
|
333
|
120
|
|
|
|
|
218
|
my %purpose; |
334
|
120
|
100
|
|
|
|
287
|
if (my $p = delete $args{purpose}) { |
335
|
2
|
50
|
|
|
|
23
|
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
|
|
|
|
|
27
|
while( my ($k,$v) = each %$p) { |
345
|
2
|
50
|
33
|
|
|
24
|
$purpose{lc($k)} = ($v && $v ne '-')?1:0; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
} |
349
|
120
|
100
|
|
|
|
293
|
if (delete $args{CA}) { |
350
|
|
|
|
|
|
|
# add defaults for CA |
351
|
10
|
|
|
|
|
123
|
%purpose = ( |
352
|
|
|
|
|
|
|
ca => 1, sslca => 1, emailca => 1, objca => 1, |
353
|
|
|
|
|
|
|
%purpose |
354
|
|
|
|
|
|
|
); |
355
|
|
|
|
|
|
|
} |
356
|
120
|
100
|
|
|
|
290
|
if (!%purpose) { |
357
|
108
|
|
|
|
|
390
|
%purpose = (server => 1, client => 1); |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
|
360
|
120
|
|
|
|
|
257
|
my (%key_usage,%ext_key_usage,%cert_type,%basic_constraints); |
361
|
|
|
|
|
|
|
|
362
|
120
|
|
|
|
|
383
|
my %dS = ( digitalSignature => \%key_usage ); |
363
|
120
|
|
|
|
|
288
|
my %kE = ( keyEncipherment => \%key_usage ); |
364
|
120
|
|
|
|
|
558
|
my %CA = ( 'CA:TRUE' => \%basic_constraints, %dS, keyCertSign => \%key_usage ); |
365
|
120
|
|
|
|
|
254
|
my @disable; |
366
|
120
|
|
|
|
|
4143
|
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
|
|
|
|
5429
|
exists $purpose{lc($_->[0])} or next; |
394
|
258
|
50
|
|
|
|
662
|
if (delete $purpose{lc($_->[0])}) { |
395
|
258
|
|
|
|
|
369
|
while (my($k,$h) = each %{$_->[1]}) { |
|
1280
|
|
|
|
|
3444
|
|
396
|
1022
|
|
|
|
|
1909
|
$h->{$k} = 1; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
} else { |
399
|
0
|
|
|
|
|
0
|
push @disable, $_->[1]; |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
} |
402
|
120
|
50
|
|
|
|
1564
|
die "unknown purpose ".join(",",keys %purpose) if %purpose; |
403
|
120
|
|
|
|
|
314
|
for(@disable) { |
404
|
0
|
|
|
|
|
0
|
while (my($k,$h) = each %$_) { |
405
|
0
|
|
|
|
|
0
|
delete $h->{$k}; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
120
|
100
|
|
|
|
276
|
if (%basic_constraints) { |
410
|
10
|
|
|
|
|
383
|
push @ext,&Net::SSLeay::NID_basic_constraints, |
411
|
|
|
|
|
|
|
=> join(",",'critical', sort keys %basic_constraints); |
412
|
|
|
|
|
|
|
} else { |
413
|
110
|
|
|
|
|
2916
|
push @ext, &Net::SSLeay::NID_basic_constraints => 'critical,CA:FALSE'; |
414
|
|
|
|
|
|
|
} |
415
|
120
|
50
|
|
|
|
3835
|
push @ext,&Net::SSLeay::NID_key_usage |
416
|
|
|
|
|
|
|
=> join(",",'critical', sort keys %key_usage) if %key_usage; |
417
|
120
|
50
|
|
|
|
3586
|
push @ext,&Net::SSLeay::NID_netscape_cert_type |
418
|
|
|
|
|
|
|
=> join(",",sort keys %cert_type) if %cert_type; |
419
|
120
|
100
|
|
|
|
3314
|
push @ext,&Net::SSLeay::NID_ext_key_usage |
420
|
|
|
|
|
|
|
=> join(",",sort keys %ext_key_usage) if %ext_key_usage; |
421
|
120
|
|
|
|
|
7557
|
Net::SSLeay::P_X509_add_extensions($cert, $issuer_cert, @ext); |
422
|
|
|
|
|
|
|
|
423
|
120
|
|
|
|
|
289
|
my %have_ext; |
424
|
120
|
|
|
|
|
433
|
for(my $i=0;$i<@ext;$i+=2) { |
425
|
732
|
|
|
|
|
2177
|
$have_ext{ $ext[$i] }++ |
426
|
|
|
|
|
|
|
} |
427
|
120
|
50
|
|
|
|
300
|
for my $ext (@{ $args{ext} || [] }) { |
|
120
|
|
|
|
|
1017
|
|
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
|
|
|
|
|
2229
|
Net::SSLeay::X509_set_issuer_name($cert, |
448
|
|
|
|
|
|
|
Net::SSLeay::X509_get_subject_name($issuer_cert)); |
449
|
120
|
|
|
|
|
460
|
Net::SSLeay::X509_sign($cert,$issuer_key,_digest($digest_name)); |
450
|
|
|
|
|
|
|
|
451
|
120
|
|
|
|
|
1550
|
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
|
|
239
|
my $digest_name = shift; |
483
|
120
|
|
66
|
|
|
231336
|
return $digest{$digest_name} ||= do { |
484
|
6
|
|
|
|
|
89
|
Net::SSLeay::SSLeay_add_ssl_algorithms(); |
485
|
6
|
50
|
|
|
|
16712
|
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__ |