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

version 1.2, 2019/01/01 04:55:00 version 1.4, 2019/07/18 00:28:04
Line 26 Line 26
 # http://www.lon-capa.org/  # 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 79  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 97  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 152  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 171  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 183  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 224  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 313  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 323  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 351  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 390  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  
   
     my $defcrlsel = 1;
   unless (-e "$dir/lonca/crl/loncapaCAcrl.pem") {    if (!-e "$dir/lonca/crl/loncapaCAcrl.pem") {
       open(PIPE,"openssl ca -gencrl -keyfile $dir/lonca/private/cakey.pem -cert $dir/lonca/cacert.pem -out $dir".        print "No Revocation Certificate List found.\n";
                 "/lonca/crl/loncapaCAcrl.pem -config $dir/lonca/opensslca.conf -passin pass:$sslkeypass |");        print 'Create Certificate Revocation List [Y/n]';
       close(PIPE);    } else {
       if (-e "$dir/lonca/crl/loncapaCAcrl.pem") {        if (open(PIPE,"openssl crl -in $dir/lonca/crl/loncapaCAcrl.pem -inform pem -CAfile $dir/lonca/cacert.pem  -noout 2>&1 |")) {
           print "Certificate Revocation List created\n";            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 614  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 625  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 661  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 737  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 754  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 814  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 822  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 875  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.2  
changed lines
  Added in v.1.4


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