File Coverage

blib/lib/IO/Socket/SSL/Utils.pm
Criterion Covered Total %
statement 97 222 43.6
branch 29 118 24.5
condition 20 68 29.4
subroutine 11 22 50.0
pod 15 15 100.0
total 172 445 38.6


line stmt bran cond sub pod time code
1              
2             package IO::Socket::SSL::Utils;
3 14     14   7399 use strict;
  14         32  
  14         495  
4 14     14   57 use warnings;
  14         27  
  14         939  
5 14     14   66 use Carp 'croak';
  14         21  
  14         663  
6 14     14   59 use Net::SSLeay;
  14         20  
  14         4208  
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 812 my $file = shift;
22 10 50       407 my $bio = Net::SSLeay::BIO_new_file($file,'r') or
23             croak "cannot read $file: $!";
24 10         5013 my $cert = Net::SSLeay::PEM_read_bio_X509($bio);
25 10         140 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         47 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   260 use constant PEM_R_NO_START_LINE => 108;
  14         52  
  14         50032  
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 584     584 1 99964 my $string = shift;
72 584         2608 my $bio = Net::SSLeay::BIO_new( Net::SSLeay::BIO_s_mem());
73 584         2647 Net::SSLeay::BIO_write($bio,$string);
74 584         247975 my $cert = Net::SSLeay::PEM_read_bio_X509($bio);
75 584         3134 Net::SSLeay::BIO_free($bio);
76 584 50       1341 $cert or croak "cannot parse string as PEM X509 cert: ".
77             Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error());
78 584         1919 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 24871 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 55 my $bits = shift || 2048;
133 16         69 my $key = Net::SSLeay::EVP_PKEY_new();
134 16         9602255 my $rsa = Net::SSLeay::RSA_generate_key($bits, 0x10001); # 0x10001 = RSA_F4
135 16         252 Net::SSLeay::EVP_PKEY_assign_RSA($key,$rsa);
136 16         125 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 37212 my %args = @_%2 ? %{ shift() } : @_;
  0         0  
272              
273 120         1317 my $cert = Net::SSLeay::X509_new();
274 120   50     693 my $digest_name = delete $args{digest} || 'sha256';
275              
276             Net::SSLeay::ASN1_INTEGER_set(
277             Net::SSLeay::X509_get_serialNumber($cert),
278 120   33     540 delete $args{serial} || do {
279             Net::SSLeay::RAND_bytes(my $buf, 4);
280             unpack('N', $buf);
281             },
282             );
283              
284             # version default to 2 (V3)
285             Net::SSLeay::X509_set_version($cert,
286 120   50     750 delete $args{version} || 2 );
287              
288             # not_before default to now
289             Net::SSLeay::ASN1_TIME_set(
290             Net::SSLeay::X509_get_notBefore($cert),
291             delete $args{not_before} || time()
292 120   33     1485 );
293              
294             # not_after default to now+365 days
295             Net::SSLeay::ASN1_TIME_set(
296             Net::SSLeay::X509_get_notAfter($cert),
297 120   33     877 delete $args{not_after} || time() + 365*86400
298             );
299              
300             # set subject
301 120         252 my $subj_e = Net::SSLeay::X509_get_subject_name($cert);
302             my $subj = delete $args{subject} || {
303 120   100     323 organizationName => 'IO::Socket::SSL',
304             commonName => 'IO::Socket::SSL Test'
305             };
306              
307 120         565 while ( my ($k,$v) = each %$subj ) {
308             # Not everything we get is nice - try with MBSTRING_UTF8 first and if it
309             # fails try V_ASN1_T61STRING and finally V_ASN1_OCTET_STRING
310 116 50       358 for (ref($v) ? @$v : ($v)) {
311 116 0 33     2645 Net::SSLeay::X509_NAME_add_entry_by_txt($subj_e,$k,0x1000,$_,-1,0)
      33        
312             or Net::SSLeay::X509_NAME_add_entry_by_txt($subj_e,$k,20,$_,-1,0)
313             or Net::SSLeay::X509_NAME_add_entry_by_txt($subj_e,$k,4,$_,-1,0)
314             or croak("failed to add entry for $k - ".
315             Net::SSLeay::ERR_error_string(Net::SSLeay::ERR_get_error()));
316             }
317             }
318              
319 120         4199 my @ext = (
320             &Net::SSLeay::NID_subject_key_identifier => 'hash',
321             &Net::SSLeay::NID_authority_key_identifier => 'keyid',
322             );
323 120 100       3839 if ( my $altsubj = delete $args{subjectAltNames} ) {
324             push @ext,
325             &Net::SSLeay::NID_subject_alt_name =>
326 22         246 join(',', map { "$_->[0]:$_->[1]" } @$altsubj)
  57         550  
327             }
328              
329 120   66     348 my $key = delete $args{key} || KEY_create_rsa();
330 120         1454 Net::SSLeay::X509_set_pubkey($cert,$key);
331              
332 120         232 my $is = delete $args{issuer};
333 120   66     578 my $issuer_cert = delete $args{issuer_cert} || $is && $is->[0] || $cert;
334 120   66     429 my $issuer_key = delete $args{issuer_key} || $is && $is->[1] || $key;
335              
336 120         181 my %purpose;
337 120 100       287 if (my $p = delete $args{purpose}) {
338 2 50       13 if (!ref($p)) {
    50          
339 0 0 0     0 $purpose{lc($2)} = (!$1 || $1 eq '+') ? 1:0
340             while $p =~m{([+-]?)(\w+)}g;
341             } elsif (ref($p) eq 'ARRAY') {
342 0         0 for(@$p) {
343 0 0       0 m{^([+-]?)(\w+)$} or die "invalid entry in purpose: $_";
344 0 0 0     0 $purpose{lc($2)} = (!$1 || $1 eq '+') ? 1:0
345             }
346             } else {
347 2         13 while( my ($k,$v) = each %$p) {
348 2 50 33     20 $purpose{lc($k)} = ($v && $v ne '-')?1:0;
349             }
350             }
351             }
352 120 100       242 if (delete $args{CA}) {
353             # add defaults for CA
354 10         78 %purpose = (
355             ca => 1, sslca => 1, emailca => 1, objca => 1,
356             %purpose
357             );
358             }
359 120 100       288 if (!%purpose) {
360 108         339 %purpose = (server => 1, client => 1);
361             }
362              
363 120         217 my (%key_usage,%ext_key_usage,%cert_type,%basic_constraints);
364              
365 120         326 my %dS = ( digitalSignature => \%key_usage );
366 120         318 my %kE = ( keyEncipherment => \%key_usage );
367 120         540 my %CA = ( 'CA:TRUE' => \%basic_constraints, %dS, keyCertSign => \%key_usage );
368 120         231 my @disable;
369 120         4555 for(
370             [ client => { %dS, %kE, clientAuth => \%ext_key_usage, client => \%cert_type } ],
371             [ server => { %dS, %kE, serverAuth => \%ext_key_usage, server => \%cert_type } ],
372             [ email => { %dS, %kE, emailProtection => \%ext_key_usage, email => \%cert_type } ],
373             [ objsign => { %dS, %kE, codeSigning => \%ext_key_usage, objsign => \%cert_type } ],
374              
375             [ CA => { %CA }],
376             [ sslCA => { %CA, sslCA => \%cert_type }],
377             [ emailCA => { %CA, emailCA => \%cert_type }],
378             [ objCA => { %CA, objCA => \%cert_type }],
379              
380             [ emailProtection => { %dS, %kE, emailProtection => \%ext_key_usage, email => \%cert_type } ],
381             [ codeSigning => { %dS, %kE, codeSigning => \%ext_key_usage, objsign => \%cert_type } ],
382              
383             [ timeStamping => { timeStamping => \%ext_key_usage } ],
384             [ digitalSignature => { digitalSignature => \%key_usage } ],
385             [ nonRepudiation => { nonRepudiation => \%key_usage } ],
386             [ keyEncipherment => { keyEncipherment => \%key_usage } ],
387             [ dataEncipherment => { dataEncipherment => \%key_usage } ],
388             [ keyAgreement => { keyAgreement => \%key_usage } ],
389             [ keyCertSign => { keyCertSign => \%key_usage } ],
390             [ cRLSign => { cRLSign => \%key_usage } ],
391             [ encipherOnly => { encipherOnly => \%key_usage } ],
392             [ decipherOnly => { decipherOnly => \%key_usage } ],
393             [ clientAuth => { clientAuth => \%ext_key_usage } ],
394             [ serverAuth => { serverAuth => \%ext_key_usage } ],
395             ) {
396 2640 100       4270 exists $purpose{lc($_->[0])} or next;
397 258 50       550 if (delete $purpose{lc($_->[0])}) {
398 258         331 while (my($k,$h) = each %{$_->[1]}) {
  1280         2701  
399 1022         1631 $h->{$k} = 1;
400             }
401             } else {
402 0         0 push @disable, $_->[1];
403             }
404             }
405 120 50       1611 die "unknown purpose ".join(",",keys %purpose) if %purpose;
406 120         215 for(@disable) {
407 0         0 while (my($k,$h) = each %$_) {
408 0         0 delete $h->{$k};
409             }
410             }
411              
412 120 100       224 if (%basic_constraints) {
413 10         237 push @ext,&Net::SSLeay::NID_basic_constraints,
414             => join(",",'critical', sort keys %basic_constraints);
415             } else {
416 110         2877 push @ext, &Net::SSLeay::NID_basic_constraints => 'critical,CA:FALSE';
417             }
418 120 50       3162 push @ext,&Net::SSLeay::NID_key_usage
419             => join(",",'critical', sort keys %key_usage) if %key_usage;
420 120 50       2949 push @ext,&Net::SSLeay::NID_netscape_cert_type
421             => join(",",sort keys %cert_type) if %cert_type;
422 120 100       2552 push @ext,&Net::SSLeay::NID_ext_key_usage
423             => join(",",sort keys %ext_key_usage) if %ext_key_usage;
424 120         10625 Net::SSLeay::P_X509_add_extensions($cert, $issuer_cert, @ext);
425              
426 120         233 my %have_ext;
427 120         400 for(my $i=0;$i<@ext;$i+=2) {
428 732         1881 $have_ext{ $ext[$i] }++
429             }
430 120 50       155 for my $ext (@{ delete $args{ext} || [] }) {
  120         547  
431             my $nid = $ext->{nid}
432             || $ext->{sn} && Net::SSLeay::OBJ_sn2nid($ext->{sn})
433 0   0     0 || croak "cannot determine NID of extension";
434 0 0       0 $have_ext{$nid} and next;
435 0         0 my $val = $ext->{data};
436 0 0       0 if ($nid == 177) {
437             # authorityInfoAccess:
438             # OpenSSL i2v does not output the same way as expected by i2v :(
439 0         0 for (split(/\n/,$val)) {
440 0         0 s{ - }{;}; # "OCSP - URI:..." -> "OCSP;URI:..."
441 0 0       0 $_ = "critical,$_" if $ext->{critical};
442 0         0 Net::SSLeay::P_X509_add_extensions($cert,$issuer_cert,$nid,$_);
443             }
444             } else {
445 0 0       0 $val = "critical,$val" if $ext->{critical};
446 0         0 Net::SSLeay::P_X509_add_extensions($cert, $issuer_cert, $nid, $val);
447             }
448             }
449              
450             die "unknown arguments: ". join(" ", sort keys %args)
451 120 50 33     455 if !delete $args{ignore_invalid_args} && %args;
452              
453 120         2325 Net::SSLeay::X509_set_issuer_name($cert,
454             Net::SSLeay::X509_get_subject_name($issuer_cert));
455 120         354 Net::SSLeay::X509_sign($cert,$issuer_key,_digest($digest_name));
456              
457 120         1677 return ($cert,$key);
458             }
459              
460              
461              
462             if ( defined &Net::SSLeay::ASN1_TIME_timet ) {
463             *_asn1t2t = \&Net::SSLeay::ASN1_TIME_timet
464             } else {
465             require Time::Local;
466             my %mon2i = qw(
467             Jan 0 Feb 1 Mar 2 Apr 3 May 4 Jun 5
468             Jul 6 Aug 7 Sep 8 Oct 9 Nov 10 Dec 11
469             );
470             *_asn1t2t = sub {
471             my $t = Net::SSLeay::P_ASN1_TIME_put2string( shift );
472             my ($mon,$d,$h,$m,$s,$y,$tz) = split(/[\s:]+/,$t);
473             defined( $mon = $mon2i{$mon} ) or die "invalid month in $t";
474             $tz ||= $y =~s{^(\d+)([A-Z]\S*)}{$1} && $2;
475             if ( ! $tz ) {
476             return Time::Local::timelocal($s,$m,$h,$d,$mon,$y)
477             } elsif ( $tz eq 'GMT' ) {
478             return Time::Local::timegm($s,$m,$h,$d,$mon,$y)
479             } else {
480             die "unexpected TZ $tz from ASN1_TIME_print";
481             }
482             }
483             }
484              
485             {
486             my %digest;
487             sub _digest {
488 120     120   225 my $digest_name = shift;
489 120   66     142118 return $digest{$digest_name} ||= do {
490 6         21 Net::SSLeay::SSLeay_add_ssl_algorithms();
491 6 50       13020 Net::SSLeay::EVP_get_digestbyname($digest_name)
492             or die "Digest algorithm $digest_name is not available";
493             };
494             }
495             }
496              
497              
498             1;
499              
500             __END__