Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/scripts/mk-ca-bundle.pl
2065 views
1
#!/usr/bin/env perl
2
# ***************************************************************************
3
# * _ _ ____ _
4
# * Project ___| | | | _ \| |
5
# * / __| | | | |_) | |
6
# * | (__| |_| | _ <| |___
7
# * \___|\___/|_| \_\_____|
8
# *
9
# * Copyright (C) Daniel Stenberg, <[email protected]>, et al.
10
# *
11
# * This software is licensed as described in the file COPYING, which
12
# * you should have received as part of this distribution. The terms
13
# * are also available at https://curl.se/docs/copyright.html.
14
# *
15
# * You may opt to use, copy, modify, merge, publish, distribute and/or sell
16
# * copies of the Software, and permit persons to whom the Software is
17
# * furnished to do so, under the terms of the COPYING file.
18
# *
19
# * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20
# * KIND, either express or implied.
21
# *
22
# * SPDX-License-Identifier: curl
23
# *
24
# ***************************************************************************
25
# This Perl script creates a fresh ca-bundle.crt file for use with libcurl.
26
# It downloads certdata.txt from Mozilla's source tree (see URL below),
27
# then parses certdata.txt and extracts CA Root Certificates into PEM format.
28
# These are then processed with the OpenSSL commandline tool to produce the
29
# final ca-bundle.crt file.
30
# The script is based on the parse-certs script written by Roland Krikava.
31
# This Perl script works on almost any platform since its only external
32
# dependency is the OpenSSL commandline tool for optional text listing.
33
# Hacked by Guenter Knauf.
34
#
35
use Encode;
36
use Getopt::Std;
37
use MIME::Base64;
38
use strict;
39
use warnings;
40
use vars qw($opt_b $opt_d $opt_f $opt_h $opt_i $opt_k $opt_l $opt_m $opt_n $opt_p $opt_q $opt_s $opt_t $opt_u $opt_v $opt_w);
41
use List::Util;
42
use Text::Wrap;
43
use Time::Local;
44
my $MOD_SHA = "Digest::SHA";
45
eval "require $MOD_SHA";
46
if($@) {
47
$MOD_SHA = "Digest::SHA::PurePerl";
48
eval "require $MOD_SHA";
49
}
50
eval "require LWP::UserAgent";
51
52
my %urls = (
53
'autoland' => 'https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/autoland/security/nss/lib/ckfw/builtins/certdata.txt',
54
'beta' => 'https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/beta/security/nss/lib/ckfw/builtins/certdata.txt',
55
'release' => 'https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt',
56
);
57
58
$opt_d = 'release';
59
60
# If the OpenSSL commandline is not in search path you can configure it here!
61
my $openssl = 'openssl';
62
63
my $version = '1.29';
64
65
$opt_w = 76; # default base64 encoded lines length
66
67
# default cert types to include in the output (default is to include CAs which
68
# may issue SSL server certs)
69
my $default_mozilla_trust_purposes = "SERVER_AUTH";
70
my $default_mozilla_trust_levels = "TRUSTED_DELEGATOR";
71
$opt_p = $default_mozilla_trust_purposes . ":" . $default_mozilla_trust_levels;
72
73
my @valid_mozilla_trust_purposes = (
74
"DIGITAL_SIGNATURE",
75
"NON_REPUDIATION",
76
"KEY_ENCIPHERMENT",
77
"DATA_ENCIPHERMENT",
78
"KEY_AGREEMENT",
79
"KEY_CERT_SIGN",
80
"CRL_SIGN",
81
"SERVER_AUTH",
82
"CLIENT_AUTH",
83
"CODE_SIGNING",
84
"EMAIL_PROTECTION",
85
"IPSEC_END_SYSTEM",
86
"IPSEC_TUNNEL",
87
"IPSEC_USER",
88
"TIME_STAMPING",
89
"STEP_UP_APPROVED"
90
);
91
92
my @valid_mozilla_trust_levels = (
93
"TRUSTED_DELEGATOR", # CAs
94
"NOT_TRUSTED", # Don't trust these certs.
95
"MUST_VERIFY_TRUST", # This explicitly tells us that it ISN'T a CA but is
96
# otherwise ok. In other words, this should tell the
97
# app to ignore any other sources that claim this is
98
# a CA.
99
"TRUSTED" # This cert is trusted, but only for itself and not
100
# for delegates (i.e. it is not a CA).
101
);
102
103
my $default_signature_algorithms = $opt_s = "MD5";
104
105
my @valid_signature_algorithms = (
106
"MD5",
107
"SHA1",
108
"SHA256",
109
"SHA384",
110
"SHA512"
111
);
112
113
$0 =~ s@.*(/|\\)@@;
114
$Getopt::Std::STANDARD_HELP_VERSION = 1;
115
getopts('bd:fhiklmnp:qs:tuvw:');
116
117
if(!defined($opt_d)) {
118
# to make plain "-d" use not cause warnings, and actually still work
119
$opt_d = 'release';
120
}
121
122
# Use predefined URL or else custom URL specified on command line.
123
my $url;
124
if(defined($urls{$opt_d})) {
125
$url = $urls{$opt_d};
126
if(!$opt_k && $url !~ /^https:\/\//i) {
127
die "The URL for '$opt_d' is not HTTPS. Use -k to override (insecure).\n";
128
}
129
}
130
else {
131
$url = $opt_d;
132
}
133
134
if($opt_i) {
135
print ("=" x 78 . "\n");
136
print "Script Version : $version\n";
137
print "Perl Version : $]\n";
138
print "Operating System Name : $^O\n";
139
print "Getopt::Std.pm Version : ${Getopt::Std::VERSION}\n";
140
print "Encode::Encoding.pm Version : ${Encode::Encoding::VERSION}\n";
141
print "MIME::Base64.pm Version : ${MIME::Base64::VERSION}\n";
142
print "LWP::UserAgent.pm Version : ${LWP::UserAgent::VERSION}\n" if($LWP::UserAgent::VERSION);
143
print "LWP.pm Version : ${LWP::VERSION}\n" if($LWP::VERSION);
144
print "Digest::SHA.pm Version : ${Digest::SHA::VERSION}\n" if($Digest::SHA::VERSION);
145
print "Digest::SHA::PurePerl.pm Version : ${Digest::SHA::PurePerl::VERSION}\n" if($Digest::SHA::PurePerl::VERSION);
146
print ("=" x 78 . "\n");
147
}
148
149
sub warning_message() {
150
if($opt_d =~ m/^risk$/i) { # Long Form Warning and Exit
151
print "Warning: Use of this script may pose some risk:\n";
152
print "\n";
153
print " 1) If you use HTTP URLs they are subject to a man in the middle attack\n";
154
print " 2) Default to 'release', but more recent updates may be found in other trees\n";
155
print " 3) certdata.txt file format may change, lag time to update this script\n";
156
print " 4) Generally unwise to blindly trust CAs without manual review & verification\n";
157
print " 5) Mozilla apps use additional security checks aren't represented in certdata\n";
158
print " 6) Use of this script will make a security engineer grind his teeth and\n";
159
print " swear at you. ;)\n";
160
exit;
161
} else { # Short Form Warning
162
print "Warning: Use of this script may pose some risk, -d risk for more details.\n";
163
}
164
}
165
166
sub HELP_MESSAGE() {
167
print "Usage:\t${0} [-b] [-d<certdata>] [-f] [-i] [-k] [-l] [-n] [-p<purposes:levels>] [-q] [-s<algorithms>] [-t] [-u] [-v] [-w<l>] [<outputfile>]\n";
168
print "\t-b\tbackup an existing version of ca-bundle.crt\n";
169
print "\t-d\tspecify Mozilla tree to pull certdata.txt or custom URL\n";
170
print "\t\t Valid names are:\n";
171
print "\t\t ", join( ", ", map { ( $_ =~ m/$opt_d/ ) ? "$_ (default)" : "$_" } sort keys %urls ), "\n";
172
print "\t-f\tforce rebuild even if certdata.txt is current\n";
173
print "\t-i\tprint version info about used modules\n";
174
print "\t-k\tallow URLs other than HTTPS, enable HTTP fallback (insecure)\n";
175
print "\t-l\tprint license info about certdata.txt\n";
176
print "\t-m\tinclude meta data in output\n";
177
print "\t-n\tno download of certdata.txt (to use existing)\n";
178
print wrap("\t","\t\t", "-p\tlist of Mozilla trust purposes and levels for certificates to include in output. Takes the form of a comma separated list of purposes, a colon, and a comma separated list of levels. (default: $default_mozilla_trust_purposes:$default_mozilla_trust_levels)"), "\n";
179
print "\t\t Valid purposes are:\n";
180
print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_purposes ) ), "\n";
181
print "\t\t Valid levels are:\n";
182
print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_levels ) ), "\n";
183
print "\t-q\tbe really quiet (no progress output at all)\n";
184
print wrap("\t","\t\t", "-s\tcomma separated list of certificate signatures/hashes to output in plain text mode. (default: $default_signature_algorithms)\n");
185
print "\t\t Valid signature algorithms are:\n";
186
print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_signature_algorithms ) ), "\n";
187
print "\t-t\tinclude plain text listing of certificates\n";
188
print "\t-u\tunlink (remove) certdata.txt after processing\n";
189
print "\t-v\tbe verbose and print out processed CAs\n";
190
print "\t-w <l>\twrap base64 output lines after <l> chars (default: ${opt_w})\n";
191
exit;
192
}
193
194
sub VERSION_MESSAGE() {
195
print "${0} version ${version} running Perl ${]} on ${^O}\n";
196
}
197
198
warning_message() unless ($opt_q || $url =~ m/^(ht|f)tps:/i );
199
HELP_MESSAGE() if($opt_h);
200
201
sub report($@) {
202
my $output = shift;
203
204
print STDERR $output . "\n" unless $opt_q;
205
}
206
207
sub is_in_list($@) {
208
my $target = shift;
209
210
return defined(List::Util::first { $target eq $_ } @_);
211
}
212
213
# Parses $param_string as a case insensitive comma separated list with optional
214
# whitespace validates that only allowed parameters are supplied
215
sub parse_csv_param($$@) {
216
my $description = shift;
217
my $param_string = shift;
218
my @valid_values = @_;
219
220
my @values = map {
221
s/^\s+//; # strip leading spaces
222
s/\s+$//; # strip trailing spaces
223
uc $_ # return the modified string as upper case
224
} split( ',', $param_string );
225
226
# Find all values which are not in the list of valid values or "ALL"
227
my @invalid = grep { !is_in_list($_,"ALL",@valid_values) } @values;
228
229
if(scalar(@invalid) > 0) {
230
# Tell the user which parameters were invalid and print the standard help
231
# message which will exit
232
print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join( ", ", map { "\"$_\"" } @invalid ), "\n";
233
HELP_MESSAGE();
234
}
235
236
@values = @valid_values if(is_in_list("ALL",@values));
237
238
return @values;
239
}
240
241
sub sha256 {
242
my $result;
243
if($Digest::SHA::VERSION || $Digest::SHA::PurePerl::VERSION) {
244
open(FILE, $_[0]) or die "Can't open '$_[0]': $!";
245
binmode(FILE);
246
$result = $MOD_SHA->new(256)->addfile(*FILE)->hexdigest;
247
close(FILE);
248
} else {
249
# Use OpenSSL command if Perl Digest::SHA modules not available
250
$result = `"$openssl" dgst -r -sha256 "$_[0]"`;
251
$result =~ s/^([0-9a-f]{64}) .+/$1/is;
252
}
253
return $result;
254
}
255
256
257
sub oldhash {
258
my $hash = "";
259
open(C, "<$_[0]") || return 0;
260
while(<C>) {
261
chomp;
262
if($_ =~ /^\#\# SHA256: (.*)/) {
263
$hash = $1;
264
last;
265
}
266
}
267
close(C);
268
return $hash;
269
}
270
271
if( $opt_p !~ m/:/ ) {
272
print "Error: Mozilla trust identifier list must include both purposes and levels\n";
273
HELP_MESSAGE();
274
}
275
276
(my $included_mozilla_trust_purposes_string, my $included_mozilla_trust_levels_string) = split( ':', $opt_p );
277
my @included_mozilla_trust_purposes = parse_csv_param( "trust purpose", $included_mozilla_trust_purposes_string, @valid_mozilla_trust_purposes );
278
my @included_mozilla_trust_levels = parse_csv_param( "trust level", $included_mozilla_trust_levels_string, @valid_mozilla_trust_levels );
279
280
my @included_signature_algorithms = parse_csv_param( "signature algorithm", $opt_s, @valid_signature_algorithms );
281
282
sub should_output_cert(%) {
283
my %trust_purposes_by_level = @_;
284
285
foreach my $level (@included_mozilla_trust_levels) {
286
# for each level we want to output, see if any of our desired purposes are
287
# included
288
return 1 if( defined( List::Util::first { is_in_list( $_, @included_mozilla_trust_purposes ) } @{$trust_purposes_by_level{$level}} ) );
289
}
290
291
return 0;
292
}
293
294
my $crt = $ARGV[0] || 'ca-bundle.crt';
295
(my $txt = $url) =~ s@(.*/|\?.*)@@g;
296
297
my $stdout = $crt eq '-';
298
my $resp;
299
my $fetched;
300
301
my $oldhash = oldhash($crt);
302
303
report "SHA256 of old file: $oldhash";
304
305
if(!$opt_n) {
306
report "Downloading $txt ...";
307
308
# If we have an HTTPS URL then use curl
309
if($url =~ /^https:\/\//i) {
310
my $curl = `curl -V`;
311
if($curl) {
312
if($curl =~ /^Protocols:.* https( |$)/m) {
313
report "Get certdata with curl!";
314
my $proto = !$opt_k ? "--proto =https" : "";
315
my $quiet = $opt_q ? "-s" : "";
316
my @out = `curl -Lw %{response_code} $proto $quiet -o "$txt" "$url"`;
317
if(!$? && @out && $out[0] == 200) {
318
$fetched = 1;
319
report "Downloaded $txt";
320
}
321
else {
322
report "Failed downloading via HTTPS with curl";
323
if(-e $txt && !unlink($txt)) {
324
report "Failed to remove '$txt': $!";
325
}
326
}
327
}
328
else {
329
report "curl lacks https support";
330
}
331
}
332
else {
333
report "curl not found";
334
}
335
}
336
337
# If nothing was fetched then use LWP
338
if(!$fetched) {
339
if($url =~ /^https:\/\//i) {
340
report "Falling back to HTTP";
341
$url =~ s/^https:\/\//http:\/\//i;
342
}
343
if(!$opt_k) {
344
report "URLs other than HTTPS are disabled by default, to enable use -k";
345
exit 1;
346
}
347
report "Get certdata with LWP!";
348
if(!defined(${LWP::UserAgent::VERSION})) {
349
report "LWP is not available (LWP::UserAgent not found)";
350
exit 1;
351
}
352
my $ua = new LWP::UserAgent(agent => "$0/$version");
353
$ua->env_proxy();
354
$resp = $ua->mirror($url, $txt);
355
if($resp && $resp->code eq '304') {
356
report "Not modified";
357
exit 0 if -e $crt && !$opt_f;
358
}
359
else {
360
$fetched = 1;
361
report "Downloaded $txt";
362
}
363
if(!$resp || $resp->code !~ /^(?:200|304)$/) {
364
report "Unable to download latest data: "
365
. ($resp? $resp->code . ' - ' . $resp->message : "LWP failed");
366
exit 1 if -e $crt || ! -r $txt;
367
}
368
}
369
}
370
371
my $filedate = $resp ? $resp->last_modified : (stat($txt))[9];
372
my $datesrc = "as of";
373
if(!$filedate) {
374
# mxr.mozilla.org gave us a time, hg.mozilla.org does not!
375
$filedate = time();
376
$datesrc="downloaded on";
377
}
378
379
# get the hash from the download file
380
my $newhash= sha256($txt);
381
382
if(!$opt_f && $oldhash eq $newhash) {
383
report "Downloaded file identical to previous run\'s source file. Exiting";
384
if($opt_u && -e $txt && !unlink($txt)) {
385
report "Failed to remove $txt: $!\n";
386
}
387
exit;
388
}
389
390
report "SHA256 of new file: $newhash";
391
392
my $currentdate = scalar gmtime($filedate);
393
394
my $format = $opt_t ? "plain text and " : "";
395
if($stdout) {
396
open(CRT, '> -') or die "Couldn't open STDOUT: $!\n";
397
} else {
398
open(CRT,">$crt.~") or die "Couldn't open $crt.~: $!\n";
399
}
400
print CRT <<EOT;
401
##
402
## Bundle of CA Root Certificates
403
##
404
## Certificate data from Mozilla ${datesrc}: ${currentdate} GMT
405
##
406
## Find updated versions here: https://curl.se/docs/caextract.html
407
##
408
## This is a bundle of X.509 certificates of public Certificate Authorities
409
## (CA). These were automatically extracted from Mozilla's root certificates
410
## file (certdata.txt). This file can be found in the mozilla source tree:
411
## ${url}
412
##
413
## It contains the certificates in ${format}PEM format and therefore
414
## can be directly used with curl / libcurl / php_curl, or with
415
## an Apache+mod_ssl webserver for SSL client authentication.
416
## Just configure this file as the SSLCACertificateFile.
417
##
418
## Conversion done with mk-ca-bundle.pl version $version.
419
## SHA256: $newhash
420
##
421
422
EOT
423
424
report "Processing '$txt' ...";
425
my $caname;
426
my $certnum = 0;
427
my $skipnum = 0;
428
my $start_of_cert = 0;
429
my $main_block = 0;
430
my $main_block_name;
431
my $trust_block = 0;
432
my $trust_block_name;
433
my @precert;
434
my $cka_value;
435
my $valid = 0;
436
437
open(TXT,"$txt") or die "Couldn't open $txt: $!\n";
438
while(<TXT>) {
439
if(/\*\*\*\*\* BEGIN LICENSE BLOCK \*\*\*\*\*/) {
440
print CRT;
441
print if($opt_l);
442
while(<TXT>) {
443
print CRT;
444
print if($opt_l);
445
last if(/\*\*\*\*\* END LICENSE BLOCK \*\*\*\*\*/);
446
}
447
next;
448
}
449
# The input file format consists of blocks of Mozilla objects.
450
# The blocks are separated by blank lines but may be related.
451
elsif(/^\s*$/) {
452
$main_block = 0;
453
$trust_block = 0;
454
next;
455
}
456
# Each certificate has a main block.
457
elsif(/^# Certificate "(.*)"/) {
458
(!$main_block && !$trust_block) or die "Unexpected certificate block";
459
$main_block = 1;
460
$main_block_name = $1;
461
# Reset all other certificate variables.
462
$trust_block = 0;
463
$trust_block_name = "";
464
$valid = 0;
465
$start_of_cert = 0;
466
$caname = "";
467
$cka_value = "";
468
undef @precert;
469
next;
470
}
471
# Each certificate's main block is followed by a trust block.
472
elsif(/^# Trust for (?:Certificate )?"(.*)"/) {
473
(!$main_block && !$trust_block) or die "Unexpected trust block";
474
$trust_block = 1;
475
$trust_block_name = $1;
476
if($main_block_name ne $trust_block_name) {
477
die "cert name \"$main_block_name\" != trust name \"$trust_block_name\"";
478
}
479
next;
480
}
481
# Ignore other blocks.
482
#
483
# There is a documentation comment block, a BEGINDATA block, and a bunch of
484
# blocks starting with "# Explicitly Distrust <certname>".
485
#
486
# The latter is for certificates that have already been removed and are not
487
# included. Not all explicitly distrusted certificates are ignored at this
488
# point, just those without an actual certificate.
489
elsif(!$main_block && !$trust_block) {
490
next;
491
}
492
elsif(/^#/) {
493
# The commented lines in a main block are plaintext metadata that describes
494
# the certificate. Issuer, Subject, Fingerprint, etc.
495
if($main_block) {
496
push @precert, $_ if not /^#$/;
497
if(/^# Not Valid After : (.*)/) {
498
my $stamp = $1;
499
use Time::Piece;
500
# Not Valid After : Thu Sep 30 14:01:15 2021
501
my $t = Time::Piece->strptime($stamp, "%a %b %d %H:%M:%S %Y");
502
my $delta = ($t->epoch - time()); # negative means no longer valid
503
if($delta < 0) {
504
$skipnum++;
505
report "Skipping: $main_block_name is not valid anymore" if($opt_v);
506
$valid = 0;
507
}
508
else {
509
$valid = 1;
510
}
511
}
512
}
513
next;
514
}
515
elsif(!$valid) {
516
next;
517
}
518
519
chomp;
520
521
if($main_block) {
522
if(/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
523
!$start_of_cert or die "Duplicate CKO_CERTIFICATE object";
524
$start_of_cert = 1;
525
next;
526
}
527
elsif(!$start_of_cert) {
528
next;
529
}
530
elsif(/^CKA_LABEL UTF8 \"(.*)\"/) {
531
($caname eq "") or die "Duplicate CKA_LABEL attribute";
532
$caname = $1;
533
if($caname ne $main_block_name) {
534
die "caname \"$caname\" != cert name \"$main_block_name\"";
535
}
536
next;
537
}
538
elsif(/^CKA_VALUE MULTILINE_OCTAL/) {
539
($cka_value eq "") or die "Duplicate CKA_VALUE attribute";
540
while(<TXT>) {
541
last if(/^END/);
542
chomp;
543
my @octets = split(/\\/);
544
shift @octets;
545
for(@octets) {
546
$cka_value .= chr(oct);
547
}
548
}
549
next;
550
}
551
else {
552
next;
553
}
554
}
555
556
if(!$trust_block || !$start_of_cert || $caname eq "" || $cka_value eq "") {
557
die "Certificate extraction failed";
558
}
559
560
my %trust_purposes_by_level;
561
562
if(/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/) {
563
# now scan the trust part to determine how we should trust this cert
564
while(<TXT>) {
565
if(/^\s*$/) {
566
$trust_block = 0;
567
last;
568
}
569
if(/^CKA_TRUST_([A-Z_]+)\s+CK_TRUST\s+CKT_NSS_([A-Z_]+)\s*$/) {
570
if(!is_in_list($1,@valid_mozilla_trust_purposes)) {
571
report "Warning: Unrecognized trust purpose for cert: $caname. Trust purpose: $1. Trust Level: $2";
572
} elsif(!is_in_list($2,@valid_mozilla_trust_levels)) {
573
report "Warning: Unrecognized trust level for cert: $caname. Trust purpose: $1. Trust Level: $2";
574
} else {
575
push @{$trust_purposes_by_level{$2}}, $1;
576
}
577
}
578
}
579
580
# Sanity check that an explicitly distrusted certificate only has trust
581
# purposes with a trust level of NOT_TRUSTED.
582
#
583
# Certificate objects that are explicitly distrusted are in a certificate
584
# block that starts # Certificate "Explicitly Distrust(ed) <certname>",
585
# where "Explicitly Distrust(ed) " was prepended to the original cert name.
586
if($caname =~ /distrust/i ||
587
$main_block_name =~ /distrust/i ||
588
$trust_block_name =~ /distrust/i) {
589
my @levels = keys %trust_purposes_by_level;
590
if(scalar(@levels) != 1 || $levels[0] ne "NOT_TRUSTED") {
591
die "\"$caname\" must have all trust purposes at level NOT_TRUSTED.";
592
}
593
}
594
595
if(!should_output_cert(%trust_purposes_by_level)) {
596
$skipnum ++;
597
report "Skipping: $caname lacks acceptable trust level" if($opt_v);
598
} else {
599
my $encoded = MIME::Base64::encode_base64($cka_value, '');
600
$encoded =~ s/(.{1,${opt_w}})/$1\n/g;
601
my $pem = "-----BEGIN CERTIFICATE-----\n"
602
. $encoded
603
. "-----END CERTIFICATE-----\n";
604
print CRT "\n$caname\n";
605
my $maxStringLength = length(decode('UTF-8', $caname, Encode::FB_CROAK | Encode::LEAVE_SRC));
606
print CRT ("=" x $maxStringLength . "\n");
607
if($opt_t) {
608
foreach my $key (sort keys %trust_purposes_by_level) {
609
my $string = $key . ": " . join(", ", @{$trust_purposes_by_level{$key}});
610
print CRT $string . "\n";
611
}
612
}
613
if($opt_m) {
614
print CRT for @precert;
615
}
616
if(!$opt_t) {
617
print CRT $pem;
618
} else {
619
my $pipe = "";
620
foreach my $hash (@included_signature_algorithms) {
621
$pipe = "|$openssl x509 -" . $hash . " -fingerprint -noout -inform PEM";
622
if(!$stdout) {
623
$pipe .= " >> $crt.~";
624
close(CRT) or die "Couldn't close $crt.~: $!";
625
}
626
open(TMP, $pipe) or die "Couldn't open openssl pipe: $!";
627
print TMP $pem;
628
close(TMP) or die "Couldn't close openssl pipe: $!";
629
if(!$stdout) {
630
open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!";
631
}
632
}
633
$pipe = "|$openssl x509 -text -inform PEM";
634
if(!$stdout) {
635
$pipe .= " >> $crt.~";
636
close(CRT) or die "Couldn't close $crt.~: $!";
637
}
638
open(TMP, $pipe) or die "Couldn't open openssl pipe: $!";
639
print TMP $pem;
640
close(TMP) or die "Couldn't close openssl pipe: $!";
641
if(!$stdout) {
642
open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!";
643
}
644
}
645
report "Processed: $caname" if($opt_v);
646
$certnum++;
647
}
648
}
649
}
650
close(TXT) or die "Couldn't close $txt: $!\n";
651
close(CRT) or die "Couldn't close $crt.~: $!\n";
652
unless($stdout) {
653
if($opt_b && -e $crt) {
654
my $bk = 1;
655
while(-e "$crt.~${bk}~") {
656
$bk++;
657
}
658
rename $crt, "$crt.~${bk}~" or die "Failed to create backup $crt.~$bk}~: $!\n";
659
} elsif( -e $crt ) {
660
unlink( $crt ) or die "Failed to remove $crt: $!\n";
661
}
662
rename "$crt.~", $crt or die "Failed to rename $crt.~ to $crt: $!\n";
663
}
664
if($opt_u && -e $txt && !unlink($txt)) {
665
report "Failed to remove $txt: $!\n";
666
}
667
report "Done ($certnum CA certs processed, $skipnum skipped).";
668
669