Annotation of loncom/CrCA.pl, revision 1.1

1.1     ! raeburn     1: #!/usr/bin/perl
        !             2: use strict;
        !             3: use Sys::Hostname::FQDN();
        !             4: use Term::ReadKey;
        !             5: use Locale::Country;
        !             6: use Crypt::OpenSSL::X509;
        !             7: use DateTime::Format::x509;
        !             8: use File::Slurp;
        !             9: use Cwd;
        !            10: 
        !            11: #
        !            12: # Expected structure
        !            13: #
        !            14: #  /lonca
        !            15: #          opensslca.cnf
        !            16: #          cacert.pem
        !            17: #          index.txt
        !            18: #          /certs
        !            19: #          /crl
        !            20: #          /private
        !            21: #          /requests
        !            22: #
        !            23: 
        !            24:   print(<<END);
        !            25: 
        !            26: ****** SSL Certificate Authority *****
        !            27: 
        !            28: If you are running your own cluster of LON-CAPA nodes you will need to
        !            29: create a Certificate Authority (CA) for your cluster. You will then use
        !            30: the CA to sign LON-CAPA SSL certificate signing requests generated by
        !            31: the nodes in your cluster.
        !            32: 
        !            33: LON-CAPA SSL Certificates can be used in two different contexts:
        !            34: (a) if you configure LON-CAPA to use a secure channel for exchange of
        !            35: the shared encryption key when establishing an "internal" LON-CAPA
        !            36: connection between nodes in your cluster, and (b) if you configure
        !            37: LON-CAPA to use client SSL certificate validation when one node replicates
        !            38: content from library node(s) in your cluster.
        !            39: 
        !            40: Although a LON-CAPA cluster may contain multiple domains and/or multiple
        !            41: library nodes, there will only be one LON-CAPA certificate authority (CA)
        !            42: for the cluster.  The CA certificate signing infrastructure need not be 
        !            43: housed on a LON-CAPA node; it can instead be installed on a separate
        !            44: Linux instance.  The instance housing the CA infrastructure needs to
        !            45: have the following Linux packages installed:
        !            46: 
        !            47: openssl
        !            48: perl
        !            49: 
        !            50: and the following perl modules from CPAN installed:
        !            51: 
        !            52: Term::ReadKey
        !            53: Sys::Hostname::FQDN
        !            54: Locale::Country
        !            55: Crypt::OpenSSL::X509
        !            56: DateTime::Format::x509
        !            57: File::Slurp
        !            58: 
        !            59: You need to decide on a directory you wish to use to hold the
        !            60: CA infrastructure. If necessary you should create a new directory.
        !            61: Then move this script (CrCA.pl) to that directory, and run it with
        !            62: the command: perl CrCA.pl
        !            63: 
        !            64: The script will create any required subdirectories (and files) 
        !            65: within that directory, if they do not already exist.
        !            66: 
        !            67: You will need to provide a password to be used for the openssl CA key 
        !            68: which will be stored in the /private subdirectory, and will be used
        !            69: when signing certificate signing requests to create LON-CAPA certificates 
        !            70: for use in the cluster.
        !            71: 
        !            72: END
        !            73: 
        !            74: #Proceed?
        !            75: 
        !            76:   my ($dir,$hostname,%data);
        !            77: 
        !            78: # Check if required subdirectories exist in current directory.
        !            79:   $dir = Cwd::getcwd();
        !            80: 
        !            81:   if (-e "$dir/lonca") {
        !            82:       if ((!-d "$dir/lonca") && (-f "$dir/lonca")) {
        !            83:           print "A lonca directory is required, but there is an existing file of that name.\n".
        !            84:                 "Please either delete the lonca file, or change to a different directory, and ".
        !            85:                 "create the CA infrastructure there.\n";
        !            86:           exit;
        !            87:       }
        !            88:   } else {
        !            89:       mkdir("$dir/lonca",0700);
        !            90:       system('chown root:root '."$dir/lonca");
        !            91:   }
        !            92:   if (-d "$dir/lonca") {
        !            93:       foreach my $subdir ('certs','crl','private','requests') {
        !            94:           if (!-d "$dir/lonca/$subdir") {
        !            95:               if (-f "$dir/lonca/$subdir") {
        !            96:                   print "A $subdir sub-directory is required, but there is an existing file of that name.\n".
        !            97:                         "Please either delete or move the $subdir file, then run this script again.\n";
        !            98:                   exit;
        !            99:               } else {
        !           100:                   mkdir("$dir/lonca/$subdir",0700);
        !           101:                   system('chown root:root '."$dir/lonca/$subdir");
        !           102:               }
        !           103:           }
        !           104:       }
        !           105:   } else {
        !           106:       print "A lonca directory is required, but no directory exists\n";
        !           107:       exit;
        !           108:   }  
        !           109:   if (-e "$dir/lonca/opensslca.conf") {
        !           110:       # retrieve existing config file and verify that if contains the required fields.
        !           111:       %data = &parse_config("$dir/lonca/opensslca.conf");
        !           112:       my %update = &confirm_config(%data);
        !           113:       my %changes;
        !           114:       foreach my $field ('clustername','organization','email','country','state','city','days','crldays') {
        !           115:           if ($data{$field} ne $update{$field}) {
        !           116:               $changes{$field} = $update{$field};
        !           117:           }
        !           118:       }
        !           119:       if (keys(%changes)) {
        !           120:           &save_config_changes("$dir/lonca/opensslca.conf",\%changes);
        !           121:       }
        !           122:   } else {
        !           123:       print(<<END);
        !           124: ****** Certificate Authority Configuration File *****
        !           125: 
        !           126: A configuration file: $dir/lonca/opensslca.conf will be created.
        !           127: 
        !           128: The following information will be included: 
        !           129: Country, State/Province, City, Cluster Name, Organizational Name, E-mail address, CA certificate lifetime (days), Default certificate lifetime (days), CRL re-creation interval (days)
        !           130: 
        !           131: END
        !           132:       $hostname = Sys::Hostname::FQDN::fqdn();
        !           133:       if ($hostname eq '') {
        !           134:           $hostname =&get_hostname();
        !           135:       } else {
        !           136:           print "Hostname detected: $hostname. Is that correct? [Y/n]";
        !           137:           if (!&get_user_selection(1)) {
        !           138:               $hostname =&get_hostname();
        !           139:           }
        !           140:       }
        !           141: 
        !           142:       my %fieldname = (
        !           143:                         city => 'City',
        !           144:                         state => 'State or Province',
        !           145:                         clustername => 'Cluster name',
        !           146:                         organization => 'Organization name',
        !           147:                       );
        !           148:       my ($clustername,$organization,$country,$state,$city,$email,$cadays,$clusterhostname,$days,$crldays);
        !           149:       $clusterhostname =  $hostname;
        !           150:       $country = &get_country($hostname);
        !           151:       print "Enter state or province name\n";
        !           152:       $state = &get_info($fieldname{'state'});
        !           153:       print "Enter city name\n";
        !           154:       $city = &get_info($fieldname{'city'});
        !           155:       $email = &get_camail();
        !           156:       print 'Enter a name for this LON-CAPA cluster, e.g., "Lon-CAPA learning network"'."\n".
        !           157:             'This name will be included as the Common Name for the CA certificate.'."\n";
        !           158:       $clustername = &get_info($fieldname{'clustername'});
        !           159:       print 'Enter the organization name for this LON-CAPA cluster, e.g., "Lon CAPA certification authority"'."\n".
        !           160:             'This name will be included as the Oraganization for the CA certificate.'."\n";    
        !           161:       $organization = &get_info($fieldname{'organization'});
        !           162:       print "Enter the lifetime (in days) for the CA root certificate distributed to all nodes, e.g., 3650\n";
        !           163:       $cadays = &get_days();
        !           164:       print "Enter the default lifetime (in days) for each certificate created/signed by the CA for individual nodes, e.g., 3650\n";
        !           165:       $days = &get_days();
        !           166:       print "Enter the re-creation interval (in days) for the CA's certificate revocation list (CRL), e.g., 180\n";
        !           167:       $crldays = &get_days();
        !           168: 
        !           169:       if (open(my $fh,'>',"$dir/lonca/opensslca.conf")) {
        !           170:           print $fh <<"END";
        !           171: [ ca ]
        !           172: default_ca       =  loncapa
        !           173: 
        !           174: [ loncapa ]
        !           175: dir              = $dir/lonca
        !           176: certificate      = $dir/lonca/cacert.pem
        !           177: database         = $dir/lonca/index.txt
        !           178: new_certs_dir    = $dir/lonca/certs
        !           179: private_key      = $dir/lonca/private/cakey.pem
        !           180: serial           = $dir/lonca/serial
        !           181: 
        !           182: default_crl_days = $crldays
        !           183: default_days     = $days
        !           184: default_md       = sha256
        !           185: 
        !           186: policy           = loncapa_policy
        !           187: x509_extensions  = certificate_extensions
        !           188: 
        !           189: [ loncapa_policy ]
        !           190: 
        !           191: commonName           = supplied
        !           192: stateOrProvinceName  = supplied
        !           193: countryName          = supplied
        !           194: emailAddress         = supplied
        !           195: organizationName     = supplied
        !           196: organizationalUnitName = optional
        !           197: 
        !           198: [ certificate_extensions ]
        !           199: 
        !           200: basicConstraints   = CA:false
        !           201: crlDistributionPoints = URI:http://$clusterhostname/adm/dns/loncapaCAcrl
        !           202: 
        !           203: [ req ]
        !           204: 
        !           205: default_bits       = 2048
        !           206: distinguished_name = loncapa_ca
        !           207: 
        !           208: x509_extensions    = loncapa_ca_extensions
        !           209: 
        !           210: [ loncapa_ca ]
        !           211: 
        !           212: commonName           = $clustername
        !           213: localityName         = $city
        !           214: stateOrProvinceName  = $state
        !           215: countryName          = $country
        !           216: emailAddress         = $email
        !           217: organizationName     = $organization
        !           218: 
        !           219: [ loncapa_ca_extensions ]
        !           220: basicConstraints  = CA:true
        !           221: 
        !           222: [ crl_ext ]
        !           223: 
        !           224: authorityKeyIdentifier=keyid:always,issuer:always
        !           225: 
        !           226: 
        !           227: END
        !           228: 
        !           229:       } else {
        !           230:           print 'Error: failed to wtite to '."$dir/lonca/opensslca.conf. Exiting.\n";
        !           231:           exit;
        !           232:       }
        !           233:       %data = &parse_config("$dir/lonca/opensslca.conf");
        !           234:       my %update = &confirm_config(%data);
        !           235:       my %changes;
        !           236:       foreach my $field ('clustername','organization','email','country','state','city','days','crldays') {
        !           237:           if ($data{$field} ne $update{$field}) {
        !           238:               $changes{$field} = $update{$field};
        !           239:           }
        !           240:       }
        !           241:       if (keys(%changes)) {
        !           242:           &save_config_changes("$dir/lonca/opensslca.conf",\%changes);
        !           243:       }
        !           244:   }
        !           245: 
        !           246:   my $sslkeypass;
        !           247:   if (-e "$dir/lonca/private/cakey.pem") {
        !           248:       my ($keyok,$try);
        !           249:       print "CA key aleady exists\n";
        !           250:       $try = 1;
        !           251:       while (!$keyok && $try) {
        !           252:           $sslkeypass = &get_password('Enter the password for the CA key');
        !           253:           if ($sslkeypass ne '') {
        !           254:               open(PIPE,"openssl rsa -noout -in lonca/private/cakey.pem -passin pass:$sslkeypass -check |");
        !           255:               my $check = <PIPE>;
        !           256:               close(PIPE);
        !           257:               chomp($check);
        !           258:               if ($check eq 'RSA key ok') {
        !           259:                   $keyok = 1;
        !           260:                   last;
        !           261:               } else {
        !           262:                   print "CA key check failed. Try again? [Y/n]";
        !           263:                   if (!&get_user_selection(1)) {
        !           264:                       $try = 0;
        !           265:                   }
        !           266:               }
        !           267:           }
        !           268:       }
        !           269:       unless ($keyok) {
        !           270:           print "CA key check failed. Create a new key? [Y/n]";
        !           271:           if (&get_user_selection(1)) {
        !           272:               $sslkeypass = &get_new_sslkeypass();
        !           273:               # generate SSL key
        !           274:               unless (&make_key("$dir/lonca/private",$sslkeypass)) {
        !           275:                   print "Failed to create CA key\n";
        !           276:                   exit;
        !           277:               }
        !           278:           } else {
        !           279:               exit;
        !           280:           }
        !           281:       } 
        !           282:   } else {
        !           283:       $sslkeypass = &get_new_sslkeypass();
        !           284:       # generate SSL key
        !           285:       unless (&make_key("$dir/lonca/private",$sslkeypass)) {
        !           286:           print "Failed to create CA key\n";
        !           287:           exit;
        !           288:       }
        !           289:   }
        !           290:   if (-e "$dir/lonca/cacert.pem") {
        !           291:       print "A CA certificate exists\n";
        !           292:       open(PIPE,"openssl pkey -in $dir/lonca/private/cakey.pem -passin pass:$sslkeypass -pubout -outform der | sha256sum |");
        !           293:       my $hashfromkey = <PIPE>;
        !           294:       close(PIPE);
        !           295:       chomp($hashfromkey);
        !           296:       open(PIPE,"openssl x509 -in $dir/lonca/cacert.pem -pubkey | openssl pkey -pubin -pubout -outform der | sha256sum |");
        !           297:       my $hashfromcert = <PIPE>;
        !           298:       close(PIPE);
        !           299:       chomp($hashfromcert);
        !           300:       if ($hashfromkey eq $hashfromcert) {
        !           301:           my ($now,$starttime,$endtime,$status,%cert);
        !           302:           my $x509 = Crypt::OpenSSL::X509->new_from_file("$dir/lonca/cacert.pem");
        !           303:           my @items = split(/,\s+/,$x509->subject());
        !           304:           foreach my $item (@items) {
        !           305:               my ($name,$value) = split(/=/,$item);
        !           306:               if ($name eq 'CN') {
        !           307:                   $cert{'cn'} = $value;
        !           308:               }
        !           309:           }
        !           310:           $cert{'start'} = $x509->notBefore();
        !           311:           $cert{'end'} = $x509->notAfter();
        !           312:           $cert{'alg'} = $x509->sig_alg_name();
        !           313:           $cert{'size'} = $x509->bit_length();
        !           314:           $cert{'email'} = $x509->email();
        !           315:           my $dt = DateTime::Format::x509->parse_datetime($cert{'start'});
        !           316:           if (ref($dt)) {
        !           317:               $starttime = $dt->epoch;
        !           318:           }
        !           319:           $dt =  DateTime::Format::x509->parse_datetime($cert{'end'});
        !           320:           if (ref($dt)) {
        !           321:               $endtime = $dt->epoch;
        !           322:           }
        !           323:           $now = time;
        !           324:           if (($starttime ne '') && ($endtime ne '')) {
        !           325:               if ($endtime <= $now) {
        !           326:                   $status = 'previous';
        !           327:                   print "Current CA certificate expired $cert{'end'}\n"; 
        !           328:               } elsif ($starttime > $now) {
        !           329:                   $status = 'future';
        !           330:                   print "Current CA certificate will be valid after $cert{'start'}\n"; 
        !           331:               } else {
        !           332:                   $status eq 'active';
        !           333:                   print "Current CA certificate valid until $cert{'end'}".' '.
        !           334:                         "Signature Algorithm: $cert{'alg'}; Public Key size: $cert{'size'}\n"; 
        !           335:               }
        !           336:               if ($status eq 'previous') {
        !           337:                   print 'Create a new certificate? [Y/n]';
        !           338:                   if (&get_user_selection(1)) {
        !           339:                       unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) {
        !           340:                           print "Failed to create CA cert\n";
        !           341:                           exit;
        !           342:                       }
        !           343:                   }
        !           344:               }
        !           345:           } else {
        !           346:               print "Could not determine validity of current CA certificate\n";
        !           347:               exit;
        !           348:           }
        !           349:       }
        !           350:   } else {
        !           351:       unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) {
        !           352:           print "Failed to create CA cert\n";
        !           353:           exit;
        !           354:       }
        !           355:   }
        !           356: 
        !           357:   if (!-e "$dir/lonca/index.txt") {
        !           358:       File::Slurp::write_file("$dir/lonca/index.txt");
        !           359:   }
        !           360:   if (-e "$dir/lonca/index.txt") {
        !           361:       my $mode = 0600;
        !           362:       chmod $mode, "$dir/lonca/index.txt";
        !           363:   } else {
        !           364:       print "lonca/index.txt file is missing\n";
        !           365:       exit; 
        !           366:   }    
        !           367: # echo 1000 > serial
        !           368: 
        !           369: 
        !           370:   unless (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
        !           371:       open(PIPE,"openssl ca -gencrl -keyfile $dir/lonca/private/cakey.pem -cert $dir/lonca/cacert.pem -out $dir".
        !           372:                 "/lonca/crl/loncapaCAcrl.pem -config $dir/lonca/opensslca.conf -passin pass:$sslkeypass |");
        !           373:       close(PIPE);
        !           374:       if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
        !           375:           print "Certificate Revocation List created\n";
        !           376:       }
        !           377:   }
        !           378:   if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
        !           379:       open(PIPE,"openssl crl -in $dir/lonca/crl/loncapaCAcrl.pem -inform pem -CAfile $dir/lonca/cacert.pem  -noout 2>&1 |");
        !           380:       my $revoked = <PIPE>;
        !           381:       close(PIPE);
        !           382:       chomp($revoked);
        !           383:       print "Revocation certificate status: $revoked\n";
        !           384: # Create a new one? 
        !           385:   }
        !           386: 
        !           387: sub cafield_to_key {
        !           388:     my %mapping = (
        !           389:                     city         => 'localityName',
        !           390:                     state        => 'stateOrProvinceName',
        !           391:                     country      => 'countryName',
        !           392:                     email        => 'emailAddress',
        !           393:                     organization => 'organizationName',
        !           394:                     clustername  => 'commonName',
        !           395:                   );
        !           396:     return %mapping;
        !           397: }
        !           398: 
        !           399: sub field_to_key {
        !           400:     my %mapping = (
        !           401:                     days    => 'default_days',
        !           402:                     crldays => 'default_crl_days',
        !           403:                   );
        !           404: }
        !           405: 
        !           406: sub parse_config {
        !           407:     my ($filepath) = @_;
        !           408:     my (%fields,%data);
        !           409:     if (open(my $fh,'<',$filepath)) {
        !           410:         my $currsection;
        !           411:         while(<$fh>) {
        !           412:             chomp();
        !           413:             s/(^\s+|\s+$)//g;
        !           414:             if (/^\[\s*([^\s]+)\s*\]/) {
        !           415:                 $currsection = $1;
        !           416:             } elsif (/^([^=]+)=([^=]+)$/) {
        !           417:                 my ($key,$value) = ($1,$2);
        !           418:                 $key =~ s/\s+$//;
        !           419:                 $value =~ s/^\s+//;
        !           420:                 if ($currsection ne '') {
        !           421:                     $fields{$currsection}{$key} = $value;
        !           422:                 }
        !           423:             }
        !           424:         }
        !           425:         close($fh); 
        !           426:     }
        !           427:     if (ref($fields{'loncapa_ca'}) eq 'HASH') {
        !           428:         my %ca_mapping = &cafield_to_key();
        !           429:         foreach my $key (keys(%ca_mapping)) {
        !           430:             $data{$key} = $fields{'loncapa_ca'}{$ca_mapping{$key}};
        !           431:         }
        !           432:     }
        !           433:     if (ref($fields{'loncapa'}) eq 'HASH') {
        !           434:         my %mapping = &field_to_key();
        !           435:         foreach my $key (keys(%mapping)) {
        !           436:             $data{$key} = $fields{'loncapa'}{$mapping{$key}};
        !           437:         }
        !           438:     }
        !           439:     return %data; 
        !           440: }
        !           441: 
        !           442: sub save_config_changes {
        !           443:     my ($filepath,$updated) = @_;
        !           444:     return unless (ref($updated) eq 'HASH');
        !           445:     my %mapping = &field_to_key();
        !           446:     my %ca_mapping = &cafield_to_key();
        !           447:     my %revmapping = reverse(%mapping);
        !           448:     my %rev_ca_mapping = reverse(%ca_mapping);
        !           449:     my $lines;
        !           450:     if (open(my $fh,'<',$filepath)) {
        !           451:         my $currsection;
        !           452:         while(<$fh>) {
        !           453:             my $line = $_;
        !           454:             chomp();
        !           455:             s/(^\s+|\s+$)//g;
        !           456:             my $newline;
        !           457:             if (/^\[\s*([^\s]+)\s*\]/) {
        !           458:                 $currsection = $1;
        !           459:             } elsif (/^([^=]+)=([^=]*)$/) {
        !           460:                 my ($origkey,$origvalue) = ($1,$2);
        !           461:                 my ($key,$value) = ($origkey,$origvalue);
        !           462:                 $key =~ s/\s+$//;
        !           463:                 $value =~ s/^\s+//;
        !           464:                 if ($currsection eq 'loncapa_ca') {
        !           465:                     if ((exists($rev_ca_mapping{$key})) && (exists($updated->{$rev_ca_mapping{$key}}))) {
        !           466:                         if ($value eq '') {
        !           467:                             if ($origvalue eq '') {
        !           468:                                 $origvalue = ' ';
        !           469:                             }
        !           470:                             $origvalue .= $updated->{$rev_ca_mapping{$key}};
        !           471:                         } else {
        !           472:                             $origvalue =~ s/\Q$value\E/$updated->{$rev_ca_mapping{$key}}/;
        !           473:                         }
        !           474:                         $newline = $origkey.'='.$origvalue."\n";
        !           475:                     }
        !           476:                 } elsif ($currsection eq 'loncapa') {
        !           477:                     if ((exists($revmapping{$key})) && (exists($updated->{$revmapping{$key}}))) {
        !           478:                         if ($value eq '') {
        !           479:                             if ($origvalue eq '') {
        !           480:                                 $origvalue = ' ';
        !           481:                             }
        !           482:                             $origvalue .= $updated->{$revmapping{$key}};
        !           483:                         } else {
        !           484:                             $origvalue =~ s/\Q$value\E/$updated->{$revmapping{$key}}/;
        !           485:                         }
        !           486:                         $newline = $origkey.'='.$origvalue."\n";
        !           487:                     }
        !           488:                 }
        !           489:             }
        !           490:             if ($newline) {
        !           491:                 $lines .= $newline;
        !           492:             } else {
        !           493:                 $lines .= $line;
        !           494:             }
        !           495:         }
        !           496:         close($fh);
        !           497:         if (open(my $fout,'>',$filepath)) {
        !           498:             print $fout $lines;
        !           499:             close($fout);
        !           500:         } else {
        !           501:             print "Error: failed to open '$filepath' for writing\n"; 
        !           502:         }
        !           503:     }
        !           504:     return;
        !           505: }
        !           506: 
        !           507: #
        !           508: # get_hostname() prompts the user to provide the server's hostname.
        !           509: #
        !           510: # If invalid input is provided, the routine is called recursively
        !           511: # until, a valid hostname is provided.
        !           512: #
        !           513: 
        !           514: sub get_hostname {
        !           515:     my $hostname;
        !           516:     print 'Enter the hostname of this server, e.g., loncapa.somewhere.edu'."\n";
        !           517:     my $choice = <STDIN>;
        !           518:     chomp($choice);
        !           519:     $choice =~ s/(^\s+|\s+$)//g;
        !           520:     if ($choice eq '') {
        !           521:         print "Hostname you entered was either blank or contanied only white space.\n";
        !           522:     } elsif ($choice =~ /^[\w\.\-]+$/) {
        !           523:         $hostname = $choice;
        !           524:     } else {
        !           525:         print "Hostname you entered was invalid --  a hostname may only contain letters, numbers, - and .\n";
        !           526:     }
        !           527:     while ($hostname eq '') {
        !           528:         $hostname = &get_hostname();
        !           529:     }
        !           530:     print "\n";
        !           531:     return $hostname;
        !           532: }
        !           533: 
        !           534: sub get_new_sslkeypass {
        !           535:     my $sslkeypass;
        !           536:     my $flag=0;
        !           537: # get password for SSL key
        !           538:     while (!$flag) {
        !           539:         $sslkeypass = &make_passphrase();
        !           540:         if ($sslkeypass) {
        !           541:             $flag = 1;
        !           542:         } else {
        !           543:             print "Invalid input (a password is required for the CA key).\n";
        !           544:         }
        !           545:     }
        !           546:     return $sslkeypass;
        !           547: }
        !           548: 
        !           549: sub make_passphrase {
        !           550:     my ($got_passwd,$firstpass,$secondpass,$passwd);
        !           551:     my $maxtries = 10;
        !           552:     my $trial = 0;
        !           553:     while ((!$got_passwd) && ($trial < $maxtries)) {
        !           554:         $firstpass = &get_password('Enter a password for the CA key (at least 6 characters long)');
        !           555:         if (length($firstpass) < 6) {
        !           556:             print('Password too short.'."\n".
        !           557:               'Please choose a password with at least six characters.'."\n".
        !           558:               'Please try again.'."\n");
        !           559:         } elsif (length($firstpass) > 30) {
        !           560:             print('Password too long.'."\n".
        !           561:                   'Please choose a password with no more than thirty characters.'."\n".
        !           562:                   'Please try again.'."\n");
        !           563:         } else {
        !           564:             my $pbad=0;
        !           565:             foreach (split(//,$firstpass)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
        !           566:             if ($pbad) {
        !           567:                 print('Password contains invalid characters.'."\n".
        !           568:                       'Password must consist of standard ASCII characters.'."\n".
        !           569:                       'Please try again.'."\n");
        !           570:             } else {
        !           571:                 $secondpass = &get_password('Enter password a second time');
        !           572:                 if ($firstpass eq $secondpass) {
        !           573:                     $got_passwd = 1;
        !           574:                     $passwd = $firstpass;
        !           575:                 } else {
        !           576:                     print('Passwords did not match.'."\n".
        !           577:                           'Please try again.'."\n");
        !           578:                 }
        !           579:             }
        !           580:         }
        !           581:         $trial ++;
        !           582:     }
        !           583:     return $passwd;
        !           584: }
        !           585: 
        !           586: sub get_password {
        !           587:     my ($prompt) = @_;
        !           588:     local $| = 1;
        !           589:     print $prompt.': ';
        !           590:     my $newpasswd = '';
        !           591:     ReadMode 'raw';
        !           592:     my $key;
        !           593:     while(ord($key = ReadKey(0)) != 10) {
        !           594:         if(ord($key) == 127 || ord($key) == 8) {
        !           595:             chop($newpasswd);
        !           596:             print "\b \b";
        !           597:         } elsif(!ord($key) < 32) {
        !           598:             $newpasswd .= $key;
        !           599:             print '*';
        !           600:         }
        !           601:     }
        !           602:     ReadMode 'normal';
        !           603:     print "\n";
        !           604:     return $newpasswd;
        !           605: }
        !           606: 
        !           607: #
        !           608: # make_key() generates CA root key
        !           609: #
        !           610: 
        !           611: sub make_key {
        !           612:     my ($keydir,$sslkeypass) = @_;
        !           613: # generate SSL key
        !           614:     my $created;
        !           615:     if (($keydir ne '') && ($sslkeypass ne '')) {
        !           616:         if (-f "$keydir/cakey.pem") {
        !           617:             my $mode = 0600;
        !           618:             chmod $mode, "$keydir/cakey.pem";
        !           619:         }
        !           620:         open(PIPE,"openssl genrsa -aes256 -passout pass:$sslkeypass -out $keydir/cakey.pem 2048 2>&1 |");
        !           621:         close(PIPE);
        !           622:         if (-f "$keydir/cakey.pem") {
        !           623:             my $mode = 0400;
        !           624:             chmod $mode, "$keydir/cakey.pem";
        !           625:             $created= 1;
        !           626:         }
        !           627:     } else {
        !           628:         print "Key creation failed.  Missing one or more of: certificates directory, key name\n";
        !           629:     }
        !           630:     return $created;
        !           631: }
        !           632: 
        !           633: #
        !           634: # make_ca_cert() generates CA root certificate
        !           635: #
        !           636: 
        !           637: sub make_ca_cert {
        !           638:     my ($keydir,$certdir,$sslkeypass) = @_;
        !           639: # generate SSL cert for CA
        !           640:     my $created;
        !           641:     if ((-d $keydir) && (-d $certdir) && ($sslkeypass ne ''))  {
        !           642:         my $cmd = "openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem";
        !           643:         print "Calling ||$cmd||\n";
        !           644:         open(PIPE,"openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem |");
        !           645:         close(PIPE);
        !           646:         if (-f "$certdir/cacert.pem") {
        !           647:             my $mode = 0600;
        !           648:             chmod $mode, "$certdir/cacert.pem";
        !           649: #            chmod $mode, "$certdir/careq.pem";
        !           650: #            open(PIPE,"openssl ca -create_serial -out $certdir/cacert.pem -days 3650 -keyfile $keydir/cakey.pem -selfsign -config ./openssl.cnf -infiles $certdir/careq.pem |");
        !           651: #            close(PIPE);
        !           652: #            if (-f "$certdir/cacert.pem") {
        !           653: #                my $mode = 0600;
        !           654: #                chmod $mode, "$certdir/cacert.pem";
        !           655: #            }
        !           656:             $created= 1;
        !           657:         }
        !           658:     } else {
        !           659:         print "Creation of CA root certificate failed.  Missing one or more of: CA directory, CA key directory, or CA passphrase.\n";
        !           660:     }
        !           661:     return $created;
        !           662: }
        !           663: 
        !           664: sub get_camail {
        !           665:     my $camail;
        !           666:     my $flag=0;
        !           667: # get Certificate Authority E-mail
        !           668:     while (!$flag) {
        !           669:         print(<<END);
        !           670: 
        !           671: Enter e-mail address of Certificate Authority. 
        !           672: END
        !           673: 
        !           674:         my $choice=<>;
        !           675:         chomp($choice);
        !           676:         if (($choice ne '') && ($choice =~ /^[^\@]+\@[^\@]+$/)) {
        !           677:             $camail=$choice;
        !           678:             $flag=1;
        !           679:         } else {
        !           680:             print "Invalid input (a valid email address is required).\n";
        !           681:         }
        !           682:     }
        !           683:     return $camail;
        !           684: }
        !           685: 
        !           686: sub ssl_info {
        !           687:     print(<<END);
        !           688: 
        !           689: ****** Information about Country, State or Province and City *****
        !           690: 
        !           691: A two-letter country code, e.g., US, CA, DE etc. as defined by ISO 3166,
        !           692: is required. A state or province, and a city are also required.
        !           693: This locality information is included in two SSL certificates used internally
        !           694: by LON-CAPA, unless you are running standalone.
        !           695: 
        !           696: If your server will be part of either the production or development
        !           697: clusters, then the certificate will need to be signed by the official
        !           698: LON-CAPA Certificate Authority (CA).  If you will be running your own
        !           699: cluster then the cluster will need to create its own CA.
        !           700: 
        !           701: END
        !           702: }
        !           703: 
        !           704: sub get_country {
        !           705:     my ($desiredhostname) = @_;
        !           706: # get Country
        !           707:     my ($posscountry,$country);
        !           708:     if ($desiredhostname =~ /\.(edu|com|org)$/) {
        !           709:         $posscountry = 'us';
        !           710:     } else {
        !           711:         ($posscountry) = ($desiredhostname =~ /\.(a-z){2}$/);
        !           712:     }
        !           713:     if ($posscountry) {
        !           714:         my $countrydesc = &Locale::Country::code2country($posscountry);
        !           715:         if ($countrydesc eq '') {
        !           716:             undef($posscountry);
        !           717:         }
        !           718:     }
        !           719: 
        !           720:     my $flag=0;
        !           721:     while (!$flag) {
        !           722:         if ($posscountry) {
        !           723:             $posscountry = uc($posscountry);
        !           724:             print "Enter Two-Letter Country Code [$posscountry]:\n";
        !           725:         } else {
        !           726:             print "Enter the Two-Letter Country Code:\n";
        !           727:         }
        !           728:         my $choice=<STDIN>;
        !           729:         chomp($choice);
        !           730:         if ($choice ne '') {
        !           731:             if (&Locale::Country::code2country(lc($choice))) {
        !           732:                 $country=uc($choice);
        !           733:                 $flag=1;
        !           734:             } else {
        !           735:                 print "Invalid input -- a valid two letter country code is required\n";
        !           736:             }
        !           737:         } elsif (($choice eq '') && ($posscountry ne '')) {
        !           738:             $country = $posscountry;
        !           739:             $flag = 1;
        !           740:         } else {
        !           741:             print "Invalid input -- a country code is required\n";
        !           742:         }
        !           743:     }
        !           744:     return $country;
        !           745: }
        !           746: 
        !           747: sub get_info {
        !           748:     my ($typename) = @_;
        !           749:     my $value;
        !           750:     my $choice = <STDIN>;
        !           751:     chomp($choice);
        !           752:     $choice =~ s/(^\s+|\s+$)//g;
        !           753:     if ($choice eq '') {
        !           754:         print "$typename you entered was either blank or contained only white space.\n";
        !           755:     } else {
        !           756:         $value = $choice;
        !           757:     }
        !           758:     while ($value eq '') {
        !           759:         $value = &get_info($typename);
        !           760:     }
        !           761:     print "\n";
        !           762:     return $value;
        !           763: }
        !           764: 
        !           765: sub get_days {
        !           766:     my $value;
        !           767:     my $choice = <STDIN>;
        !           768:     chomp($choice);
        !           769:     $choice =~ s/(^\s+|\s+$)//g;
        !           770:     if ($choice eq '') {
        !           771:         print "The value you entered was either blank or contained only white space.\n";
        !           772:     } elsif ($choice !~ /^\d+$/) {
        !           773:         print "The value you entered contained invalid characters -- you must enter just an integer.\n";
        !           774:     } else {
        !           775:         $value = $choice;
        !           776:     }
        !           777:     while ($value eq '') {
        !           778:         $value = &get_days();
        !           779:     }
        !           780:     print "\n";
        !           781:     return $value;
        !           782: }
        !           783: 
        !           784: sub confirm_config {
        !           785:     my (%data) = @_;
        !           786:     my $flag = 0;
        !           787:     while (!$flag) {
        !           788:         print(<<END);
        !           789: 
        !           790: The cluster name, organization name, country, state and city will be 
        !           791: included in the CA certificate
        !           792: 
        !           793: 1) Cluster Name: $data{'clustername'}
        !           794: 2) Organization Name: $data{'organization'}
        !           795: 3) Country: $data{'country'}
        !           796: 4) State or Province: $data{'state'}
        !           797: 5) City: $data{'city'}
        !           798: 6) E-mail: $data{'email'}
        !           799: 7) CA certificate lifetime (days): $data{'cadays'}
        !           800: 8) Default certificate lifetime for issued certs (days): $data{'days'}
        !           801: 9) CRL recreation interval (days): $data{'crldays'}
        !           802: 10) Everything is correct up above
        !           803: 
        !           804: Enter a choice of 1-9 to change, otherwise enter 10: 
        !           805: END
        !           806:         my $choice=<STDIN>;
        !           807:         chomp($choice);
        !           808:         if ($choice == 1) {
        !           809:             print(<<END);
        !           810: 1) Cluster Name: $data{'clustername'}
        !           811: Enter new value:
        !           812: END
        !           813:             my $choice2=<STDIN>;
        !           814:             chomp($choice2);
        !           815:             $data{'clustername'}=$choice2;
        !           816:             chomp($choice2);
        !           817:             $data{'organization'}=$choice2;
        !           818:         } elsif ($choice == 3) {
        !           819:             print(<<END);
        !           820: 3) Country: $data{'country'}
        !           821: Enter new value (this should be a two-character code, e,g, US, CA, DE):
        !           822: END
        !           823:             my $choice2=<STDIN>;
        !           824:             chomp($choice2);
        !           825:             $data{'country'} = uc($choice2);
        !           826:         } elsif ($choice == 4) {
        !           827:             print(<<END);
        !           828: 4) State or Province: $data{'state'}
        !           829: Enter new value:
        !           830: END
        !           831:             my $choice2=<>;
        !           832:             chomp($choice2);
        !           833:             $data{'state'}=$choice2;
        !           834:         } elsif ($choice == 5) {
        !           835:             print(<<END);
        !           836: 5) City: $data{'city'}
        !           837: Enter new value:
        !           838: END
        !           839:             my $choice2=<>;
        !           840:             chomp($choice2);
        !           841:             $data{'city'}=$choice2;
        !           842:         } elsif ($choice == 6) {
        !           843:             print(<<END);
        !           844: 6) E-mail: $data{'email'}
        !           845: Enter new value:
        !           846: END
        !           847:             my $choice2=<>;
        !           848:             chomp($choice2);
        !           849:             $data{'email'}=$choice2;
        !           850:         } elsif ($choice == 7) {
        !           851: print(<<END);
        !           852: 7) CA Root Certificate lifetime: $data{'cadays'}
        !           853: Enter new value:
        !           854: END
        !           855:             my $choice2=<>;
        !           856:             chomp($choice2);
        !           857:             $choice2 =~ s/\D//g;
        !           858:             $data{'cadays'}=$choice2;
        !           859:         } elsif ($choice == 8) {
        !           860: print(<<END);
        !           861: 8) Default certificate lifetime: $data{'days'}
        !           862: Enter new value:
        !           863: END
        !           864:             my $choice2=<>;
        !           865:             chomp($choice2);
        !           866:             $choice2 =~ s/\D//g;
        !           867:             $data{'days'}=$choice2;
        !           868:         } elsif ($choice == 9) {
        !           869: print(<<END);
        !           870: 9) CRL re-creation interval: $data{'crldays'}
        !           871: Enter new value:
        !           872: END
        !           873:             my $choice2=<>;
        !           874:             chomp($choice2);
        !           875:             $choice2 =~ s/\D//g;
        !           876:             $data{'crldays'}=$choice2;
        !           877:         } elsif ($choice == 10) {
        !           878:             $flag=1;
        !           879:             foreach my $key (keys(%data)) { 
        !           880:                 $data{$key} =~ s{/}{ }g;
        !           881:             }  
        !           882:         } else {
        !           883:             print "Invalid input.\n";
        !           884:         }
        !           885:     }
        !           886:     return %data; 
        !           887: }
        !           888: 
        !           889: sub get_user_selection {
        !           890:     my ($defaultrun) = @_;
        !           891:     my $do_action = 0;
        !           892:     my $choice = <STDIN>;
        !           893:     chomp($choice);
        !           894:     $choice =~ s/(^\s+|\s+$)//g;
        !           895:     my $yes = 'y';
        !           896:     if ($defaultrun) {
        !           897:         if (($choice eq '') || ($choice =~ /^\Q$yes\E/i)) {
        !           898:             $do_action = 1;
        !           899:         }
        !           900:     } else {
        !           901:         if ($choice =~ /^\Q$yes\E/i) {
        !           902:             $do_action = 1;
        !           903:         }
        !           904:     }
        !           905:     return $do_action;
        !           906: }
        !           907: 

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