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