Diff for /loncom/CrCA.pl between versions 1.1 and 1.4

version 1.1, 2019/01/01 04:43:47 version 1.4, 2019/07/18 00:28:04
Line 1 Line 1
 #!/usr/bin/perl  #!/usr/bin/perl
   # The LearningOnline Network with CAPA
   # Script to create a Certificate Authority (CA) for a LON-CAPA cluster.
   #
   # $Id$
   #
   # Copyright Michigan State University Board of Trustees
   #
   # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   # LON-CAPA is free software; you can redistribute it and/or modify
   # it under the terms of the GNU General Public License as published by
   # the Free Software Foundation; either version 2 of the License, or
   # (at your option) any later version.
   #
   # LON-CAPA is distributed in the hope that it will be useful,
   # but WITHOUT ANY WARRANTY; without even the implied warranty of
   # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   # GNU General Public License for more details.
   #
   # You should have received a copy of the GNU General Public License
   # along with LON-CAPA; if not, write to the Free Software
   # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   #
   # /home/httpd/html/adm/gpl.txt
   #
   # http://www.lon-capa.org/
   
 use strict;  use strict;
 use Sys::Hostname::FQDN();  
 use Term::ReadKey;  
 use Locale::Country;  
 use Crypt::OpenSSL::X509;  
 use DateTime::Format::x509;  
 use File::Slurp;  
 use Cwd;  
   
 #  #
 # Expected structure  # Expected structure
Line 53  Term::ReadKey Line 72  Term::ReadKey
 Sys::Hostname::FQDN  Sys::Hostname::FQDN
 Locale::Country  Locale::Country
 Crypt::OpenSSL::X509  Crypt::OpenSSL::X509
   Crypt::X509::CRL
   MIME::Base64
 DateTime::Format::x509  DateTime::Format::x509
 File::Slurp  File::Slurp
   
Line 71  for use in the cluster. Line 92  for use in the cluster.
   
 END  END
   
 #Proceed?    print ('Continue? [Y/n]');
     my $go_on = &get_user_selection(1);
     if (!$go_on) {
         exit;
     }
   
     eval { require Sys::Hostname::FQDN; };
     if ($@) {
         print "Could not find required perl module: Sys::Hostname::FQDN. Exiting.\n";
         exit;
     }
     eval { require Term::ReadKey; };
     if ($@) {
         print "Could not find required perl module: Term::ReadKey. Exiting\n";
         exit;
     }
     eval { require Locale::Country; };
     if ($@) {
         print "Could not find required perl module: Locale::Country. Exiting\n";
         exit;
     }
     eval { require Crypt::OpenSSL::X509; };
     if ($@) {
         print "Could not find required perl module: Crypt::OpenSSL::X509. Exiting\n";
         exit;
     }
     eval { require Crypt::X509::CRL; };
     if ($@) {
         print "Could not find required perl module: Crypt::X509::CRL. Exiting\n";
         exit;
     }
     eval { require DateTime::Format::x509; };
     if ($@) {
         print "Could not find required perl module: DateTime::Format::x509. Exiting\n";
         exit;
     }
     eval { require File::Slurp; };
     if ($@) {
         print "Could not find required perl module: File::Slurp. Exiting\n";
         exit;
     }
     eval { require MIME::Base64; };
     if ($@) {
         print "Could not find required perl core module: MIME::Base64\n";
         exit;
     }
     eval { require Cwd; };
     if ($@) {
         print "Could not find required perl core module: Cwd\n";
         exit;
     }
   
   my ($dir,$hostname,%data);    my ($dir,$hostname,%data);
   
Line 126  END Line 197  END
 A configuration file: $dir/lonca/opensslca.conf will be created.  A configuration file: $dir/lonca/opensslca.conf will be created.
   
 The following information will be included:   The following information will be included: 
 Country, State/Province, City, Cluster Name, Organizational Name, E-mail address, CA certificate lifetime (days), Default certificate lifetime (days), CRL re-creation interval (days)  Country, State/Province, City, Cluster Name, Organizational Name, E-mail address, Default certificate lifetime (days), CRL re-creation interval (days)
   
 END  END
       $hostname = Sys::Hostname::FQDN::fqdn();        $hostname = Sys::Hostname::FQDN::fqdn();
Line 145  END Line 216  END
                         clustername => 'Cluster name',                          clustername => 'Cluster name',
                         organization => 'Organization name',                          organization => 'Organization name',
                       );                        );
       my ($clustername,$organization,$country,$state,$city,$email,$cadays,$clusterhostname,$days,$crldays);        my ($clustername,$organization,$country,$state,$city,$email,$clusterhostname,$days,$crldays);
       $clusterhostname =  $hostname;        $clusterhostname =  $hostname;
       $country = &get_country($hostname);        $country = &get_country($hostname);
       print "Enter state or province name\n";        print "Enter state or province name\n";
Line 157  END Line 228  END
             'This name will be included as the Common Name for the CA certificate.'."\n";              'This name will be included as the Common Name for the CA certificate.'."\n";
       $clustername = &get_info($fieldname{'clustername'});        $clustername = &get_info($fieldname{'clustername'});
       print 'Enter the organization name for this LON-CAPA cluster, e.g., "Lon CAPA certification authority"'."\n".        print 'Enter the organization name for this LON-CAPA cluster, e.g., "Lon CAPA certification authority"'."\n".
             'This name will be included as the Oraganization for the CA certificate.'."\n";                  'This name will be included as the Organization for the CA certificate.'."\n";    
       $organization = &get_info($fieldname{'organization'});        $organization = &get_info($fieldname{'organization'});
       print "Enter the lifetime (in days) for the CA root certificate distributed to all nodes, e.g., 3650\n";  
       $cadays = &get_days();  
       print "Enter the default lifetime (in days) for each certificate created/signed by the CA for individual nodes, e.g., 3650\n";        print "Enter the default lifetime (in days) for each certificate created/signed by the CA for individual nodes, e.g., 3650\n";
       $days = &get_days();        $days = &get_days();
       print "Enter the re-creation interval (in days) for the CA's certificate revocation list (CRL), e.g., 180\n";        print "Enter the re-creation interval (in days) for the CA's certificate revocation list (CRL), e.g., 180\n";
Line 198  organizationalUnitName = optional Line 267  organizationalUnitName = optional
 [ certificate_extensions ]  [ certificate_extensions ]
   
 basicConstraints   = CA:false  basicConstraints   = CA:false
 crlDistributionPoints = URI:http://$clusterhostname/adm/dns/loncapaCAcrl  crlDistributionPoints = URI:http://$clusterhostname/adm/dns/loncapaCRL
   
 [ req ]  [ req ]
   
Line 287  END Line 356  END
           exit;            exit;
       }        }
   }    }
     my $makecacert;
   if (-e "$dir/lonca/cacert.pem") {    if (-e "$dir/lonca/cacert.pem") {
       print "A CA certificate exists\n";        print "A CA certificate exists\n";
       open(PIPE,"openssl pkey -in $dir/lonca/private/cakey.pem -passin pass:$sslkeypass -pubout -outform der | sha256sum |");        open(PIPE,"openssl pkey -in $dir/lonca/private/cakey.pem -passin pass:$sslkeypass -pubout -outform der | sha256sum |");
Line 297  END Line 367  END
       my $hashfromcert = <PIPE>;        my $hashfromcert = <PIPE>;
       close(PIPE);        close(PIPE);
       chomp($hashfromcert);        chomp($hashfromcert);
         my $defsel = 0;
       if ($hashfromkey eq $hashfromcert) {        if ($hashfromkey eq $hashfromcert) {
           my ($now,$starttime,$endtime,$status,%cert);            my ($now,$starttime,$endtime,$status,%cert);
           my $x509 = Crypt::OpenSSL::X509->new_from_file("$dir/lonca/cacert.pem");            my $x509 = Crypt::OpenSSL::X509->new_from_file("$dir/lonca/cacert.pem");
Line 325  END Line 396  END
               if ($endtime <= $now) {                if ($endtime <= $now) {
                   $status = 'previous';                    $status = 'previous';
                   print "Current CA certificate expired $cert{'end'}\n";                     print "Current CA certificate expired $cert{'end'}\n"; 
                     print 'Create a new certificate? [Y/n]';
                     $defsel = 1;
               } elsif ($starttime > $now) {                } elsif ($starttime > $now) {
                   $status = 'future';                    $status = 'future';
                   print "Current CA certificate will be valid after $cert{'start'}\n";                     print "Current CA certificate will be valid after $cert{'start'}\n";
                     print 'Create a new certificate? [y/N]';
               } else {                } else {
                   $status eq 'active';                    $status eq 'active';
                   print "Current CA certificate valid until $cert{'end'}".' '.                    print "Current CA certificate valid until $cert{'end'}".' '.
                         "Signature Algorithm: $cert{'alg'}; Public Key size: $cert{'size'}\n";                           "Signature Algorithm: $cert{'alg'}; Public Key size: $cert{'size'}\n"; 
               }                    print 'Create a new certificate? [y/N]';
               if ($status eq 'previous') {  
                   print 'Create a new certificate? [Y/n]';  
                   if (&get_user_selection(1)) {  
                       unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) {  
                           print "Failed to create CA cert\n";  
                           exit;  
                       }  
                   }  
               }                }
           } else {            } else {
               print "Could not determine validity of current CA certificate\n";                print "Could not determine validity of current CA certificate\n";
               exit;                print 'Create a new certificate? [Y/n]';
                 $defsel = 1;
           }            }
         } else {
             print "Current CA certificate does not match key.\n";
             print 'Create a new certificate? [Y/n]';
             $defsel = 1;
         }
         if (&get_user_selection($defsel)) {
             $makecacert = 1;
       }        }
   } else {    } else {
       unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) {        $makecacert = 1;
           print "Failed to create CA cert\n";    }
     if ($makecacert) {
         print "Enter the lifetime (in days) for the CA root certificate distributed to all nodes, e.g., 3650\n";
         my $cadays = &get_days();
         unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass,$cadays)) {
             print "Failed to create CA certificate\n";
           exit;            exit;
       }        }
   }    }
Line 364  END Line 443  END
       print "lonca/index.txt file is missing\n";        print "lonca/index.txt file is missing\n";
       exit;         exit; 
   }        }    
 # echo 1000 > serial  
   
   
   unless (-e "$dir/lonca/crl/loncapaCAcrl.pem") {    my $defcrlsel = 1;
       open(PIPE,"openssl ca -gencrl -keyfile $dir/lonca/private/cakey.pem -cert $dir/lonca/cacert.pem -out $dir".    if (!-e "$dir/lonca/crl/loncapaCAcrl.pem") {
                 "/lonca/crl/loncapaCAcrl.pem -config $dir/lonca/opensslca.conf -passin pass:$sslkeypass |");        print "No Revocation Certificate List found.\n";
       close(PIPE);        print 'Create Certificate Revocation List [Y/n]';
       if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {    } else {
           print "Certificate Revocation List created\n";        if (open(PIPE,"openssl crl -in $dir/lonca/crl/loncapaCAcrl.pem -inform pem -CAfile $dir/lonca/cacert.pem  -noout 2>&1 |")) {
             my $crlstatus = <PIPE>;
             close(PIPE);
             chomp($crlstatus);
             my $failmsg = "Could not determine 'valid from' and 'valid to' dates for Certificate Revocation List.\n";
             if ($crlstatus =~ /OK/) {
                 print "Current Certficate Revocation List is consistent with current CA certificate.\n";
                 if (open(my $fh,'<',"$dir/lonca/crl/loncapaCAcrl.pem")) {
                     my $pem_crl = '';
                     while (my $line=<$fh>) {
                         chomp($line);
                         next if ($line eq '-----BEGIN X509 CRL-----');
                         next if ($line eq '-----END X509 CRL-----');
                         $pem_crl .= $line;
                     }
                     close($fh);
                     my $der_crl = MIME::Base64::decode_base64($pem_crl);
                     if ($der_crl ne '') {
                         my $decoded = Crypt::X509::CRL->new( crl => $der_crl );
                         if (ref($decoded)) {
                             if ($decoded->error) {
                                 print $failmsg; 
                             } else {
                                 my $starttime = $decoded->this_update;
                                 my $endtime = $decoded->next_update;
                                 if (($endtime ne '') && ($endtime < time)) {
                                     print "Certificate Revocation List is no longer valid.\n";
                                 } elsif ($starttime > time) {
                                     print "Certificate Revocation List will become valid in the future.\n";
                                 } elsif (($starttime ne '') && ($endtime ne '')) {
                                     my $showstart = localtime($starttime);
                                     my $showend = localtime($endtime);
                                     print "Certificate Revocation List valid from: $showstart to: $showend\n";
                                     $defcrlsel = 0;
                                 } else {
                                     print $failmsg;
                                 }
                             }
                         } else {
                             print $failmsg; 
                         }
                     } else {
                         print $failmsg;
                     }
                 } else {
                     print $failmsg;
                 }
             } else {
                 print "Current Certificate Revocation List is not consistent with current CA certificate.\n";
             }
             if ($defcrlsel) {
                 print 'Create Certificate Revocation List [Y/n]';
             } else {
                 print 'Create Certificate Revocation List [y/N]';
             }
         } else {
             print "Could not check Certificate Revocation List status.\n";
             print 'Create Certificate Revocation List [Y/n]';
       }        }
   }    }
   if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {    if (&get_user_selection($defcrlsel)) {
       open(PIPE,"openssl crl -in $dir/lonca/crl/loncapaCAcrl.pem -inform pem -CAfile $dir/lonca/cacert.pem  -noout 2>&1 |");        if (open(PIPE,"openssl ca -gencrl -keyfile $dir/lonca/private/cakey.pem -cert $dir/lonca/cacert.pem -out $dir".
       my $revoked = <PIPE>;                      "/lonca/crl/loncapaCAcrl.pem -config $dir/lonca/opensslca.conf -passin pass:$sslkeypass |")) {
       close(PIPE);            close(PIPE);
       chomp($revoked);            if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {
       print "Revocation certificate status: $revoked\n";                if (open(PIPE,"openssl crl -in $dir/lonca/crl/loncapaCAcrl.pem -inform pem -CAfile $dir/lonca/cacert.pem  -noout 2>&1 |")) {
 # Create a new one?                     my $revoked = <PIPE>;
                     close(PIPE);
                     chomp($revoked);
                     if ($revoked eq 'verify OK') {
                         print "Certificate Revocation List created\n";
                     } else {
                         print "Certificate Revocation List status: $revoked\n";
                     }
                 } else {
                     print "Could not check Certificate Revocation List status\n";
                 }
             } else {
                 print "Failed to create Certificate Revocation List\n";
             }
         } else {
             print "Failed to create Certificate Revocation List\n";
         }
   }    }
     exit(0);
   
   
 sub cafield_to_key {  sub cafield_to_key {
     my %mapping = (      my %mapping = (
Line 588  sub get_password { Line 740  sub get_password {
     local $| = 1;      local $| = 1;
     print $prompt.': ';      print $prompt.': ';
     my $newpasswd = '';      my $newpasswd = '';
     ReadMode 'raw';      Term::ReadKey::ReadMode('raw');
     my $key;      my $key;
     while(ord($key = ReadKey(0)) != 10) {      while(ord($key = Term::ReadKey::ReadKey(0)) != 10) {
         if(ord($key) == 127 || ord($key) == 8) {          if(ord($key) == 127 || ord($key) == 8) {
             chop($newpasswd);              chop($newpasswd);
             print "\b \b";              print "\b \b";
Line 599  sub get_password { Line 751  sub get_password {
             print '*';              print '*';
         }          }
     }      }
     ReadMode 'normal';      Term::ReadKey::ReadMode('normal');
     print "\n";      print "\n";
     return $newpasswd;      return $newpasswd;
 }  }
Line 635  sub make_key { Line 787  sub make_key {
 #  #
   
 sub make_ca_cert {  sub make_ca_cert {
     my ($keydir,$certdir,$sslkeypass) = @_;      my ($keydir,$certdir,$sslkeypass,$cadays) = @_;
 # generate SSL cert for CA  # generate SSL cert for CA
     my $created;      my $created;
     if ((-d $keydir) && (-d $certdir) && ($sslkeypass ne ''))  {      if ((-d $keydir) && (-d $certdir) && ($sslkeypass ne '') && ($cadays =~ /^\d+$/) && ($cadays > 0))  {
         my $cmd = "openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem";          open(PIPE,"openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -days $cadays -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem |");
         print "Calling ||$cmd||\n";  
         open(PIPE,"openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem |");  
         close(PIPE);          close(PIPE);
         if (-f "$certdir/cacert.pem") {          if (-f "$certdir/cacert.pem") {
             my $mode = 0600;              my $mode = 0600;
             chmod $mode, "$certdir/cacert.pem";              chmod $mode, "$certdir/cacert.pem";
 #            chmod $mode, "$certdir/careq.pem";  
 #            open(PIPE,"openssl ca -create_serial -out $certdir/cacert.pem -days 3650 -keyfile $keydir/cakey.pem -selfsign -config ./openssl.cnf -infiles $certdir/careq.pem |");  
 #            close(PIPE);  
 #            if (-f "$certdir/cacert.pem") {  
 #                my $mode = 0600;  
 #                chmod $mode, "$certdir/cacert.pem";  
 #            }  
             $created= 1;              $created= 1;
         }          }
     } else {      } else {
         print "Creation of CA root certificate failed.  Missing one or more of: CA directory, CA key directory, or CA passphrase.\n";          print "Creation of CA root certificate failed.  Missing one or more of: CA directory, CA key directory, CA passphrase, or certificate lifetime (number of days).\n";
     }      }
     return $created;      return $created;
 }  }
Line 711  sub get_country { Line 854  sub get_country {
         ($posscountry) = ($desiredhostname =~ /\.(a-z){2}$/);          ($posscountry) = ($desiredhostname =~ /\.(a-z){2}$/);
     }      }
     if ($posscountry) {      if ($posscountry) {
         my $countrydesc = &Locale::Country::code2country($posscountry);          my $countrydesc = Locale::Country::code2country($posscountry);
         if ($countrydesc eq '') {          if ($countrydesc eq '') {
             undef($posscountry);              undef($posscountry);
         }          }
Line 728  sub get_country { Line 871  sub get_country {
         my $choice=<STDIN>;          my $choice=<STDIN>;
         chomp($choice);          chomp($choice);
         if ($choice ne '') {          if ($choice ne '') {
             if (&Locale::Country::code2country(lc($choice))) {              if (Locale::Country::code2country(lc($choice))) {
                 $country=uc($choice);                  $country=uc($choice);
                 $flag=1;                  $flag=1;
             } else {              } else {
Line 788  sub confirm_config { Line 931  sub confirm_config {
         print(<<END);          print(<<END);
   
 The cluster name, organization name, country, state and city will be   The cluster name, organization name, country, state and city will be 
 included in the CA certificate  included in the CA certificate, and in signed certificate(s) issued to
   node(s) in the cluster (which will receive the default certficate lifetime).
   
 1) Cluster Name: $data{'clustername'}  1) Cluster Name: $data{'clustername'}
 2) Organization Name: $data{'organization'}  2) Organization Name: $data{'organization'}
Line 796  included in the CA certificate Line 940  included in the CA certificate
 4) State or Province: $data{'state'}  4) State or Province: $data{'state'}
 5) City: $data{'city'}  5) City: $data{'city'}
 6) E-mail: $data{'email'}  6) E-mail: $data{'email'}
 7) CA certificate lifetime (days): $data{'cadays'}  7) Default certificate lifetime for issued certs (days): $data{'days'}
 8) Default certificate lifetime for issued certs (days): $data{'days'}  8) CRL recreation interval (days): $data{'crldays'}
 9) CRL recreation interval (days): $data{'crldays'}  9) Everything is correct up above
 10) Everything is correct up above  
   
 Enter a choice of 1-9 to change, otherwise enter 10:   Enter a choice of 1-8 to change, otherwise enter 9:
 END  END
         my $choice=<STDIN>;          my $choice=<STDIN>;
         chomp($choice);          chomp($choice);
Line 849  END Line 992  END
             $data{'email'}=$choice2;              $data{'email'}=$choice2;
         } elsif ($choice == 7) {          } elsif ($choice == 7) {
 print(<<END);  print(<<END);
 7) CA Root Certificate lifetime: $data{'cadays'}  7) Default certificate lifetime: $data{'days'}
 Enter new value:  
 END  
             my $choice2=<>;  
             chomp($choice2);  
             $choice2 =~ s/\D//g;  
             $data{'cadays'}=$choice2;  
         } elsif ($choice == 8) {  
 print(<<END);  
 8) Default certificate lifetime: $data{'days'}  
 Enter new value:  Enter new value:
 END  END
             my $choice2=<>;              my $choice2=<>;
             chomp($choice2);              chomp($choice2);
             $choice2 =~ s/\D//g;              $choice2 =~ s/\D//g;
             $data{'days'}=$choice2;              $data{'days'}=$choice2;
         } elsif ($choice == 9) {          } elsif ($choice == 8) {
 print(<<END);  print(<<END);
 9) CRL re-creation interval: $data{'crldays'}  8) CRL re-creation interval: $data{'crldays'}
 Enter new value:  Enter new value:
 END  END
             my $choice2=<>;              my $choice2=<>;
             chomp($choice2);              chomp($choice2);
             $choice2 =~ s/\D//g;              $choice2 =~ s/\D//g;
             $data{'crldays'}=$choice2;              $data{'crldays'}=$choice2;
         } elsif ($choice == 10) {          } elsif ($choice == 9) {
             $flag=1;              $flag=1;
             foreach my $key (keys(%data)) {               foreach my $key (keys(%data)) { 
                 $data{$key} =~ s{/}{ }g;                  $data{$key} =~ s{/}{ }g;

Removed from v.1.1  
changed lines
  Added in v.1.4


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