--- loncom/CrCA.pl 2019/01/01 04:43:47 1.1 +++ loncom/CrCA.pl 2019/07/08 23:00:16 1.3 @@ -1,12 +1,31 @@ #!/usr/bin/perl +# The LearningOnline Network with CAPA +# Script to create a Certificate Authority (CA) for a LON-CAPA cluster. +# +# $Id: CrCA.pl,v 1.3 2019/07/08 23:00:16 raeburn Exp $ +# +# 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 Sys::Hostname::FQDN(); -use Term::ReadKey; -use Locale::Country; -use Crypt::OpenSSL::X509; -use DateTime::Format::x509; -use File::Slurp; -use Cwd; # # Expected structure @@ -71,7 +90,19 @@ for use in the cluster. END -#Proceed? + print ('Continue? [Y/n]'); + my $go_on = &get_user_selection(1); + if (!$go_on) { + exit; + } + + require Sys::Hostname::FQDN; + require Term::ReadKey; + require Locale::Country; + require Crypt::OpenSSL::X509; + require DateTime::Format::x509; + require File::Slurp; + require Cwd; my ($dir,$hostname,%data); @@ -126,7 +157,7 @@ END A configuration file: $dir/lonca/opensslca.conf will be created. 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 $hostname = Sys::Hostname::FQDN::fqdn(); @@ -145,7 +176,7 @@ END clustername => 'Cluster 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; $country = &get_country($hostname); print "Enter state or province name\n"; @@ -157,10 +188,8 @@ END 'This name will be included as the Common Name for the CA certificate.'."\n"; $clustername = &get_info($fieldname{'clustername'}); 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'}); - 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"; $days = &get_days(); print "Enter the re-creation interval (in days) for the CA's certificate revocation list (CRL), e.g., 180\n"; @@ -198,7 +227,7 @@ organizationalUnitName = optional [ certificate_extensions ] basicConstraints = CA:false -crlDistributionPoints = URI:http://$clusterhostname/adm/dns/loncapaCAcrl +crlDistributionPoints = URI:http://$clusterhostname/adm/dns/loncapaCRL [ req ] @@ -287,6 +316,7 @@ END exit; } } + my $makecacert; if (-e "$dir/lonca/cacert.pem") { print "A CA certificate exists\n"; open(PIPE,"openssl pkey -in $dir/lonca/private/cakey.pem -passin pass:$sslkeypass -pubout -outform der | sha256sum |"); @@ -297,6 +327,7 @@ END my $hashfromcert = ; close(PIPE); chomp($hashfromcert); + my $defsel = 0; if ($hashfromkey eq $hashfromcert) { my ($now,$starttime,$endtime,$status,%cert); my $x509 = Crypt::OpenSSL::X509->new_from_file("$dir/lonca/cacert.pem"); @@ -325,30 +356,38 @@ END if ($endtime <= $now) { $status = 'previous'; print "Current CA certificate expired $cert{'end'}\n"; + print 'Create a new certificate? [Y/n]'; + $defsel = 1; } elsif ($starttime > $now) { $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 { $status eq 'active'; print "Current CA certificate valid until $cert{'end'}".' '. "Signature Algorithm: $cert{'alg'}; Public Key size: $cert{'size'}\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; - } - } + print 'Create a new certificate? [y/N]'; } } else { 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 { - unless (&make_ca_cert("$dir/lonca/private","$dir/lonca",$sslkeypass)) { + $makecacert = 1; + } + 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 cert\n"; exit; } @@ -588,9 +627,9 @@ sub get_password { local $| = 1; print $prompt.': '; my $newpasswd = ''; - ReadMode 'raw'; + Term::ReadKey::ReadMode('raw'); my $key; - while(ord($key = ReadKey(0)) != 10) { + while(ord($key = Term::ReadKey::ReadKey(0)) != 10) { if(ord($key) == 127 || ord($key) == 8) { chop($newpasswd); print "\b \b"; @@ -599,7 +638,7 @@ sub get_password { print '*'; } } - ReadMode 'normal'; + Term::ReadKey::ReadMode('normal'); print "\n"; return $newpasswd; } @@ -635,28 +674,19 @@ sub make_key { # sub make_ca_cert { - my ($keydir,$certdir,$sslkeypass) = @_; + my ($keydir,$certdir,$sslkeypass,$cadays) = @_; # generate SSL cert for CA my $created; - if ((-d $keydir) && (-d $certdir) && ($sslkeypass ne '')) { - my $cmd = "openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -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 |"); + if ((-d $keydir) && (-d $certdir) && ($sslkeypass ne '') && ($cadays =~ /^\d+$/) && ($cadays > 0)) { + open(PIPE,"openssl req -x509 -key $keydir/cakey.pem -passin pass:$sslkeypass -new -days $cadays -batch -config $certdir/opensslca.conf -out $certdir/cacert.pem |"); close(PIPE); if (-f "$certdir/cacert.pem") { my $mode = 0600; 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; } } 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; } @@ -711,7 +741,7 @@ sub get_country { ($posscountry) = ($desiredhostname =~ /\.(a-z){2}$/); } if ($posscountry) { - my $countrydesc = &Locale::Country::code2country($posscountry); + my $countrydesc = Locale::Country::code2country($posscountry); if ($countrydesc eq '') { undef($posscountry); } @@ -728,7 +758,7 @@ sub get_country { my $choice=; chomp($choice); if ($choice ne '') { - if (&Locale::Country::code2country(lc($choice))) { + if (Locale::Country::code2country(lc($choice))) { $country=uc($choice); $flag=1; } else { @@ -796,12 +826,11 @@ included in the CA certificate 4) State or Province: $data{'state'} 5) City: $data{'city'} 6) E-mail: $data{'email'} -7) CA certificate lifetime (days): $data{'cadays'} -8) Default certificate lifetime for issued certs (days): $data{'days'} -9) CRL recreation interval (days): $data{'crldays'} -10) Everything is correct up above +7) Default certificate lifetime for issued certs (days): $data{'days'} +8) CRL recreation interval (days): $data{'crldays'} +9) 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 my $choice=; chomp($choice); @@ -849,32 +878,23 @@ END $data{'email'}=$choice2; } elsif ($choice == 7) { print(<; - chomp($choice2); - $choice2 =~ s/\D//g; - $data{'cadays'}=$choice2; - } elsif ($choice == 8) { -print(<; chomp($choice2); $choice2 =~ s/\D//g; $data{'days'}=$choice2; - } elsif ($choice == 9) { + } elsif ($choice == 8) { print(<; chomp($choice2); $choice2 =~ s/\D//g; $data{'crldays'}=$choice2; - } elsif ($choice == 10) { + } elsif ($choice == 9) { $flag=1; foreach my $key (keys(%data)) { $data{$key} =~ s{/}{ }g;