File:  [LON-CAPA] / loncom / CrCA.pl
Revision 1.1: download - view: text, annotated - select for diffs
Tue Jan 1 04:43:47 2019 UTC (5 years, 3 months ago) by raeburn
Branches: MAIN
CVS tags: HEAD
- Script to create a Certificate Authority (CA) for a LON-CAPA cluster.

    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>