Annotation of embedaddon/curl/lib/mk-ca-bundle.pl, revision 1.1.1.1

1.1       misho       1: #!/usr/bin/env perl
                      2: # ***************************************************************************
                      3: # *                                  _   _ ____  _
                      4: # *  Project                     ___| | | |  _ \| |
                      5: # *                             / __| | | | |_) | |
                      6: # *                            | (__| |_| |  _ <| |___
                      7: # *                             \___|\___/|_| \_\_____|
                      8: # *
                      9: # * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, 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.haxx.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: # ***************************************************************************
                     23: # This Perl script creates a fresh ca-bundle.crt file for use with libcurl.
                     24: # It downloads certdata.txt from Mozilla's source tree (see URL below),
                     25: # then parses certdata.txt and extracts CA Root Certificates into PEM format.
                     26: # These are then processed with the OpenSSL commandline tool to produce the
                     27: # final ca-bundle.crt file.
                     28: # The script is based on the parse-certs script written by Roland Krikava.
                     29: # This Perl script works on almost any platform since its only external
                     30: # dependency is the OpenSSL commandline tool for optional text listing.
                     31: # Hacked by Guenter Knauf.
                     32: #
                     33: use Encode;
                     34: use Getopt::Std;
                     35: use MIME::Base64;
                     36: use strict;
                     37: use warnings;
                     38: 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);
                     39: use List::Util;
                     40: use Text::Wrap;
                     41: use Time::Local;
                     42: my $MOD_SHA = "Digest::SHA";
                     43: eval "require $MOD_SHA";
                     44: if ($@) {
                     45:   $MOD_SHA = "Digest::SHA::PurePerl";
                     46:   eval "require $MOD_SHA";
                     47: }
                     48: eval "require LWP::UserAgent";
                     49: 
                     50: my %urls = (
                     51:   'nss' =>
                     52:     'https://hg.mozilla.org/projects/nss/raw-file/default/lib/ckfw/builtins/certdata.txt',
                     53:   'central' =>
                     54:     'https://hg.mozilla.org/mozilla-central/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt',
                     55:   'beta' =>
                     56:     'https://hg.mozilla.org/releases/mozilla-beta/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt',
                     57:   'release' =>
                     58:     'https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt',
                     59: );
                     60: 
                     61: $opt_d = 'release';
                     62: 
                     63: # If the OpenSSL commandline is not in search path you can configure it here!
                     64: my $openssl = 'openssl';
                     65: 
                     66: my $version = '1.28';
                     67: 
                     68: $opt_w = 76; # default base64 encoded lines length
                     69: 
                     70: # default cert types to include in the output (default is to include CAs which may issue SSL server certs)
                     71: my $default_mozilla_trust_purposes = "SERVER_AUTH";
                     72: my $default_mozilla_trust_levels = "TRUSTED_DELEGATOR";
                     73: $opt_p = $default_mozilla_trust_purposes . ":" . $default_mozilla_trust_levels;
                     74: 
                     75: my @valid_mozilla_trust_purposes = (
                     76:   "DIGITAL_SIGNATURE",
                     77:   "NON_REPUDIATION",
                     78:   "KEY_ENCIPHERMENT",
                     79:   "DATA_ENCIPHERMENT",
                     80:   "KEY_AGREEMENT",
                     81:   "KEY_CERT_SIGN",
                     82:   "CRL_SIGN",
                     83:   "SERVER_AUTH",
                     84:   "CLIENT_AUTH",
                     85:   "CODE_SIGNING",
                     86:   "EMAIL_PROTECTION",
                     87:   "IPSEC_END_SYSTEM",
                     88:   "IPSEC_TUNNEL",
                     89:   "IPSEC_USER",
                     90:   "TIME_STAMPING",
                     91:   "STEP_UP_APPROVED"
                     92: );
                     93: 
                     94: my @valid_mozilla_trust_levels = (
                     95:   "TRUSTED_DELEGATOR",    # CAs
                     96:   "NOT_TRUSTED",          # Don't trust these certs.
                     97:   "MUST_VERIFY_TRUST",    # This explicitly tells us that it ISN'T a CA but is otherwise ok. In other words, this should tell the app to ignore any other sources that claim this is a CA.
                     98:   "TRUSTED"               # This cert is trusted, but only for itself and not for delegates (i.e. it is not a CA).
                     99: );
                    100: 
                    101: my $default_signature_algorithms = $opt_s = "MD5";
                    102: 
                    103: my @valid_signature_algorithms = (
                    104:   "MD5",
                    105:   "SHA1",
                    106:   "SHA256",
                    107:   "SHA384",
                    108:   "SHA512"
                    109: );
                    110: 
                    111: $0 =~ s@.*(/|\\)@@;
                    112: $Getopt::Std::STANDARD_HELP_VERSION = 1;
                    113: getopts('bd:fhiklmnp:qs:tuvw:');
                    114: 
                    115: if(!defined($opt_d)) {
                    116:     # to make plain "-d" use not cause warnings, and actually still work
                    117:     $opt_d = 'release';
                    118: }
                    119: 
                    120: # Use predefined URL or else custom URL specified on command line.
                    121: my $url;
                    122: if(defined($urls{$opt_d})) {
                    123:   $url = $urls{$opt_d};
                    124:   if(!$opt_k && $url !~ /^https:\/\//i) {
                    125:     die "The URL for '$opt_d' is not HTTPS. Use -k to override (insecure).\n";
                    126:   }
                    127: }
                    128: else {
                    129:   $url = $opt_d;
                    130: }
                    131: 
                    132: my $curl = `curl -V`;
                    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 whitespace
                    214: # 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 message which will exit
                    231:     print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join( ", ", map { "\"$_\"" } @invalid ), "\n";
                    232:     HELP_MESSAGE();
                    233:   }
                    234: 
                    235:   @values = @valid_values if ( is_in_list("ALL",@values) );
                    236: 
                    237:   return @values;
                    238: }
                    239: 
                    240: sub sha256 {
                    241:   my $result;
                    242:   if ($Digest::SHA::VERSION || $Digest::SHA::PurePerl::VERSION) {
                    243:     open(FILE, $_[0]) or die "Can't open '$_[0]': $!";
                    244:     binmode(FILE);
                    245:     $result = $MOD_SHA->new(256)->addfile(*FILE)->hexdigest;
                    246:     close(FILE);
                    247:   } else {
                    248:     # Use OpenSSL command if Perl Digest::SHA modules not available
                    249:     $result = `"$openssl" dgst -r -sha256 "$_[0]"`;
                    250:     $result =~ s/^([0-9a-f]{64}) .+/$1/is;
                    251:   }
                    252:   return $result;
                    253: }
                    254: 
                    255: 
                    256: sub oldhash {
                    257:   my $hash = "";
                    258:   open(C, "<$_[0]") || return 0;
                    259:   while(<C>) {
                    260:     chomp;
                    261:     if($_ =~ /^\#\# SHA256: (.*)/) {
                    262:       $hash = $1;
                    263:       last;
                    264:     }
                    265:   }
                    266:   close(C);
                    267:   return $hash;
                    268: }
                    269: 
                    270: if ( $opt_p !~ m/:/ ) {
                    271:   print "Error: Mozilla trust identifier list must include both purposes and levels\n";
                    272:   HELP_MESSAGE();
                    273: }
                    274: 
                    275: (my $included_mozilla_trust_purposes_string, my $included_mozilla_trust_levels_string) = split( ':', $opt_p );
                    276: my @included_mozilla_trust_purposes = parse_csv_param( "trust purpose", $included_mozilla_trust_purposes_string, @valid_mozilla_trust_purposes );
                    277: my @included_mozilla_trust_levels = parse_csv_param( "trust level", $included_mozilla_trust_levels_string, @valid_mozilla_trust_levels );
                    278: 
                    279: my @included_signature_algorithms = parse_csv_param( "signature algorithm", $opt_s, @valid_signature_algorithms );
                    280: 
                    281: sub should_output_cert(%) {
                    282:   my %trust_purposes_by_level = @_;
                    283: 
                    284:   foreach my $level (@included_mozilla_trust_levels) {
                    285:     # for each level we want to output, see if any of our desired purposes are included
                    286:     return 1 if ( defined( List::Util::first { is_in_list( $_, @included_mozilla_trust_purposes ) } @{$trust_purposes_by_level{$level}} ) );
                    287:   }
                    288: 
                    289:   return 0;
                    290: }
                    291: 
                    292: my $crt = $ARGV[0] || 'ca-bundle.crt';
                    293: (my $txt = $url) =~ s@(.*/|\?.*)@@g;
                    294: 
                    295: my $stdout = $crt eq '-';
                    296: my $resp;
                    297: my $fetched;
                    298: 
                    299: my $oldhash = oldhash($crt);
                    300: 
                    301: report "SHA256 of old file: $oldhash";
                    302: 
                    303: if(!$opt_n) {
                    304:   report "Downloading $txt ...";
                    305: 
                    306:   # If we have an HTTPS URL then use curl
                    307:   if($url =~ /^https:\/\//i) {
                    308:     if($curl) {
                    309:       if($curl =~ /^Protocols:.* https( |$)/m) {
                    310:         report "Get certdata with curl!";
                    311:         my $proto = !$opt_k ? "--proto =https" : "";
                    312:         my $quiet = $opt_q ? "-s" : "";
                    313:         my @out = `curl -w %{response_code} $proto $quiet -o "$txt" "$url"`;
                    314:         if(!$? && @out && $out[0] == 200) {
                    315:           $fetched = 1;
                    316:           report "Downloaded $txt";
                    317:         }
                    318:         else {
                    319:           report "Failed downloading via HTTPS with curl";
                    320:           if(-e $txt && !unlink($txt)) {
                    321:             report "Failed to remove '$txt': $!";
                    322:           }
                    323:         }
                    324:       }
                    325:       else {
                    326:         report "curl lacks https support";
                    327:       }
                    328:     }
                    329:     else {
                    330:       report "curl not found";
                    331:     }
                    332:   }
                    333: 
                    334:   # If nothing was fetched then use LWP
                    335:   if(!$fetched) {
                    336:     if($url =~ /^https:\/\//i) {
                    337:       report "Falling back to HTTP";
                    338:       $url =~ s/^https:\/\//http:\/\//i;
                    339:     }
                    340:     if(!$opt_k) {
                    341:       report "URLs other than HTTPS are disabled by default, to enable use -k";
                    342:       exit 1;
                    343:     }
                    344:     report "Get certdata with LWP!";
                    345:     if(!defined(${LWP::UserAgent::VERSION})) {
                    346:       report "LWP is not available (LWP::UserAgent not found)";
                    347:       exit 1;
                    348:     }
                    349:     my $ua  = new LWP::UserAgent(agent => "$0/$version");
                    350:     $ua->env_proxy();
                    351:     $resp = $ua->mirror($url, $txt);
                    352:     if($resp && $resp->code eq '304') {
                    353:       report "Not modified";
                    354:       exit 0 if -e $crt && !$opt_f;
                    355:     }
                    356:     else {
                    357:       $fetched = 1;
                    358:       report "Downloaded $txt";
                    359:     }
                    360:     if(!$resp || $resp->code !~ /^(?:200|304)$/) {
                    361:       report "Unable to download latest data: "
                    362:         . ($resp? $resp->code . ' - ' . $resp->message : "LWP failed");
                    363:       exit 1 if -e $crt || ! -r $txt;
                    364:     }
                    365:   }
                    366: }
                    367: 
                    368: my $filedate = $resp ? $resp->last_modified : (stat($txt))[9];
                    369: my $datesrc = "as of";
                    370: if(!$filedate) {
                    371:     # mxr.mozilla.org gave us a time, hg.mozilla.org does not!
                    372:     $filedate = time();
                    373:     $datesrc="downloaded on";
                    374: }
                    375: 
                    376: # get the hash from the download file
                    377: my $newhash= sha256($txt);
                    378: 
                    379: if(!$opt_f && $oldhash eq $newhash) {
                    380:     report "Downloaded file identical to previous run\'s source file. Exiting";
                    381:     if($opt_u && -e $txt && !unlink($txt)) {
                    382:         report "Failed to remove $txt: $!\n";
                    383:     }
                    384:     exit;
                    385: }
                    386: 
                    387: report "SHA256 of new file: $newhash";
                    388: 
                    389: my $currentdate = scalar gmtime($filedate);
                    390: 
                    391: my $format = $opt_t ? "plain text and " : "";
                    392: if( $stdout ) {
                    393:     open(CRT, '> -') or die "Couldn't open STDOUT: $!\n";
                    394: } else {
                    395:     open(CRT,">$crt.~") or die "Couldn't open $crt.~: $!\n";
                    396: }
                    397: print CRT <<EOT;
                    398: ##
                    399: ## Bundle of CA Root Certificates
                    400: ##
                    401: ## Certificate data from Mozilla ${datesrc}: ${currentdate} GMT
                    402: ##
                    403: ## This is a bundle of X.509 certificates of public Certificate Authorities
                    404: ## (CA). These were automatically extracted from Mozilla's root certificates
                    405: ## file (certdata.txt).  This file can be found in the mozilla source tree:
                    406: ## ${url}
                    407: ##
                    408: ## It contains the certificates in ${format}PEM format and therefore
                    409: ## can be directly used with curl / libcurl / php_curl, or with
                    410: ## an Apache+mod_ssl webserver for SSL client authentication.
                    411: ## Just configure this file as the SSLCACertificateFile.
                    412: ##
                    413: ## Conversion done with mk-ca-bundle.pl version $version.
                    414: ## SHA256: $newhash
                    415: ##
                    416: 
                    417: EOT
                    418: 
                    419: report "Processing  '$txt' ...";
                    420: my $caname;
                    421: my $certnum = 0;
                    422: my $skipnum = 0;
                    423: my $start_of_cert = 0;
                    424: my @precert;
                    425: my $cka_value;
                    426: my $valid = 1;
                    427: 
                    428: open(TXT,"$txt") or die "Couldn't open $txt: $!\n";
                    429: while (<TXT>) {
                    430:   if (/\*\*\*\*\* BEGIN LICENSE BLOCK \*\*\*\*\*/) {
                    431:     print CRT;
                    432:     print if ($opt_l);
                    433:     while (<TXT>) {
                    434:       print CRT;
                    435:       print if ($opt_l);
                    436:       last if (/\*\*\*\*\* END LICENSE BLOCK \*\*\*\*\*/);
                    437:     }
                    438:   }
                    439:   elsif(/^# (Issuer|Serial Number|Subject|Not Valid Before|Not Valid After |Fingerprint \(MD5\)|Fingerprint \(SHA1\)):/) {
                    440:       push @precert, $_;
                    441:       $valid = 1;
                    442:       next;
                    443:   }
                    444:   elsif(/^#|^\s*$/) {
                    445:       undef @precert;
                    446:       next;
                    447:   }
                    448:   chomp;
                    449: 
                    450:   # Example:
                    451:   # CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL
                    452:   # \062\060\060\066\061\067\060\060\060\060\060\060\132
                    453:   # END
                    454: 
                    455:   if (/^CKA_NSS_SERVER_DISTRUST_AFTER (CK_BBOOL CK_FALSE|MULTILINE_OCTAL)/) {
                    456:       if($1 eq "MULTILINE_OCTAL") {
                    457:           my @timestamp;
                    458:           while (<TXT>) {
                    459:               last if (/^END/);
                    460:               chomp;
                    461:               my @octets = split(/\\/);
                    462:               shift @octets;
                    463:               for (@octets) {
                    464:                   push @timestamp, chr(oct);
                    465:               }
                    466:           }
                    467:           # A trailing Z in the timestamp signifies UTC
                    468:           if($timestamp[12] ne "Z") {
                    469:               report "distrust date stamp is not using UTC";
                    470:           }
                    471:           # Example date: 200617000000Z
                    472:           # Means 2020-06-17 00:00:00 UTC
                    473:           my $distrustat =
                    474:             timegm($timestamp[10] . $timestamp[11], # second
                    475:                    $timestamp[8] . $timestamp[9],   # minute
                    476:                    $timestamp[6] . $timestamp[7],   # hour
                    477:                    $timestamp[4] . $timestamp[5],   # day
                    478:                    ($timestamp[2] . $timestamp[3]) - 1, # month
                    479:                    "20" . $timestamp[0] . $timestamp[1]); # year
                    480:           if(time >= $distrustat) {
                    481:               # not trusted anymore
                    482:               $skipnum++;
                    483:               report "Skipping: $caname is not trusted anymore" if ($opt_v);
                    484:               $valid = 0;
                    485:           }
                    486:           else {
                    487:               # still trusted
                    488:           }
                    489:       }
                    490:       next;
                    491:   }
                    492: 
                    493:   # this is a match for the start of a certificate
                    494:   if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
                    495:     $start_of_cert = 1
                    496:   }
                    497:   if ($start_of_cert && /^CKA_LABEL UTF8 \"(.*)\"/) {
                    498:     $caname = $1;
                    499:   }
                    500:   my %trust_purposes_by_level;
                    501:   if ($start_of_cert && /^CKA_VALUE MULTILINE_OCTAL/) {
                    502:     $cka_value="";
                    503:     while (<TXT>) {
                    504:       last if (/^END/);
                    505:       chomp;
                    506:       my @octets = split(/\\/);
                    507:       shift @octets;
                    508:       for (@octets) {
                    509:         $cka_value .= chr(oct);
                    510:       }
                    511:     }
                    512:   }
                    513:   if(/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/ && $valid) {
                    514:     # now scan the trust part to determine how we should trust this cert
                    515:     while (<TXT>) {
                    516:       last if (/^#/);
                    517:       if (/^CKA_TRUST_([A-Z_]+)\s+CK_TRUST\s+CKT_NSS_([A-Z_]+)\s*$/) {
                    518:         if ( !is_in_list($1,@valid_mozilla_trust_purposes) ) {
                    519:           report "Warning: Unrecognized trust purpose for cert: $caname. Trust purpose: $1. Trust Level: $2";
                    520:         } elsif ( !is_in_list($2,@valid_mozilla_trust_levels) ) {
                    521:           report "Warning: Unrecognized trust level for cert: $caname. Trust purpose: $1. Trust Level: $2";
                    522:         } else {
                    523:           push @{$trust_purposes_by_level{$2}}, $1;
                    524:         }
                    525:       }
                    526:     }
                    527: 
                    528:     if ( !should_output_cert(%trust_purposes_by_level) ) {
                    529:       $skipnum ++;
                    530:       report "Skipping: $caname" if ($opt_v);
                    531:     } else {
                    532:       my $data = $cka_value;
                    533:       $cka_value = "";
                    534: 
                    535:       if(!length($data)) {
                    536:           # if empty, skip
                    537:           next;
                    538:       }
                    539:       my $encoded = MIME::Base64::encode_base64($data, '');
                    540:       $encoded =~ s/(.{1,${opt_w}})/$1\n/g;
                    541:       my $pem = "-----BEGIN CERTIFICATE-----\n"
                    542:               . $encoded
                    543:               . "-----END CERTIFICATE-----\n";
                    544:       print CRT "\n$caname\n";
                    545:       print CRT @precert if($opt_m);
                    546:       my $maxStringLength = length(decode('UTF-8', $caname, Encode::FB_CROAK | Encode::LEAVE_SRC));
                    547:       if ($opt_t) {
                    548:         foreach my $key (keys %trust_purposes_by_level) {
                    549:            my $string = $key . ": " . join(", ", @{$trust_purposes_by_level{$key}});
                    550:            $maxStringLength = List::Util::max( length($string), $maxStringLength );
                    551:            print CRT $string . "\n";
                    552:         }
                    553:       }
                    554:       print CRT ("=" x $maxStringLength . "\n");
                    555:       if (!$opt_t) {
                    556:         print CRT $pem;
                    557:       } else {
                    558:         my $pipe = "";
                    559:         foreach my $hash (@included_signature_algorithms) {
                    560:           $pipe = "|$openssl x509 -" . $hash . " -fingerprint -noout -inform PEM";
                    561:           if (!$stdout) {
                    562:             $pipe .= " >> $crt.~";
                    563:             close(CRT) or die "Couldn't close $crt.~: $!";
                    564:           }
                    565:           open(TMP, $pipe) or die "Couldn't open openssl pipe: $!";
                    566:           print TMP $pem;
                    567:           close(TMP) or die "Couldn't close openssl pipe: $!";
                    568:           if (!$stdout) {
                    569:             open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!";
                    570:           }
                    571:         }
                    572:         $pipe = "|$openssl x509 -text -inform PEM";
                    573:         if (!$stdout) {
                    574:           $pipe .= " >> $crt.~";
                    575:           close(CRT) or die "Couldn't close $crt.~: $!";
                    576:         }
                    577:         open(TMP, $pipe) or die "Couldn't open openssl pipe: $!";
                    578:         print TMP $pem;
                    579:         close(TMP) or die "Couldn't close openssl pipe: $!";
                    580:         if (!$stdout) {
                    581:           open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!";
                    582:         }
                    583:       }
                    584:       report "Parsing: $caname" if ($opt_v);
                    585:       $certnum ++;
                    586:       $start_of_cert = 0;
                    587:     }
                    588:     undef @precert;
                    589:   }
                    590: 
                    591: }
                    592: close(TXT) or die "Couldn't close $txt: $!\n";
                    593: close(CRT) or die "Couldn't close $crt.~: $!\n";
                    594: unless( $stdout ) {
                    595:     if ($opt_b && -e $crt) {
                    596:         my $bk = 1;
                    597:         while (-e "$crt.~${bk}~") {
                    598:             $bk++;
                    599:         }
                    600:         rename $crt, "$crt.~${bk}~" or die "Failed to create backup $crt.~$bk}~: $!\n";
                    601:     } elsif( -e $crt ) {
                    602:         unlink( $crt ) or die "Failed to remove $crt: $!\n";
                    603:     }
                    604:     rename "$crt.~", $crt or die "Failed to rename $crt.~ to $crt: $!\n";
                    605: }
                    606: if($opt_u && -e $txt && !unlink($txt)) {
                    607:   report "Failed to remove $txt: $!\n";
                    608: }
                    609: report "Done ($certnum CA certs processed, $skipnum skipped).";

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>