#!/usr/bin/perl # The LearningOnline Network # CrGrant.pl - Grant a loncapa SSL certificate. # # $Id: CrGrant.pl,v 1.3 2004/07/06 11:05:45 foxr 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/ # # This script operates on a certificate request that has been # extracted from the attachment sent to the loncapa certificate # administrator and: # # 1. Creates an ssl certificate corresponding to the request. # 2. Constructs an installation script that will install # the certificate along with the certificate authority's # certificate in a loncapa system. # 3. Constructs an email which contains a cover letter # describing what to do with the attachment, and an # attachment that consists of the installation script # created in step 2. # 4. Emails the message to the email address in the certificate # request. # # There are some assumptions we need to make in order to # get this all to work: # - The certificate authority is installed on a # loncapa system with configuration files that specify # the same certificate directory and certificate filenames # as the target system (otherwise we can't generate the # installation script). # - The loncapa certificate authority configuration file is # $SSLDir/loncapaca.cnf and that it specifies that: # o The certificate authority files are in $SSLDir/loncapaca # o The certificate authority certificate is in: # $SSLDir/loncapaca/cacert.pem # o Only one instance of this script will be run at a time in # this directory. # o The person that runs this script knows the passphrase # for the loncapa certificate authority's private key # which remains encrypted for security reasons. # # # Import section: use strict; use lib '/home/httpd/lib/perl'; # An assumption!!! use MIME::Entity; use LONCAPA::Configuration; # Global variable declarations my $ssl_dir = "/usr/share/ssl"; # Where ssl config files etc. live my $ca_cert_file = $ssl_dir."/loncapaca/cacert.pem"; # CA's certificate file. my $ca_config_file= $ssl_dir."/loncapaca.cnf"; # CA's config file. # LONCAPA Configuration global variables: # Items read from our configuration file. my $ssl_command = "/usr/bin/openssl "; # Command to run openssl. my $loncapa_cert_dir; # Name of target cert dir (from config) my $loncapa_hostcert_name; # Name of host's signed cert file (config) my $loncapa_cacert_name; # Name of the CA's certificate file (config) # Items I just need to know: my $loncapa_config = "loncapa.conf"; # User's override config file. my $loncapa_apache_user = 'www'; # Name of apache daemon's user my $loncapa_apache_group = 'www'; # Name of apache daemon's group # Debug/log support my $DEBUG=1; sub Debug { my $msg = shift; if($DEBUG) { print STDERR "$msg\n"; } } # Support subs: # # Print out program usage. # # Side effects: # Output goes to stderr. # sub Usage { print STDERR << "USAGE"; Usage: CrGrant.pl requestfile.pem Where: requestfile.pem is a PEM formatted certificate extracted from an email to the LonCAPA certificate manager. USAGE } # # Read the loncapa configuration file and pull out the items # we need: # # Implicit inputs: # $loncapa_config - The name of the auxilliary config file. # Side effects: # - On failure exits with an error message. # - On success set the following variables: # o loncapa_cert_dir - Path to certificates. # o loncapa_hostcert_name - Name of host's cert file in that dir # o loncapa_cacert_name - Name of CA's cert file in that dir. # o ssl_command - Name of ssl utility command. sub ReadConfig { Debug("Reading the config files"); my $perlvarref = LONCAPA::Configuration::read_conf($loncapa_config); # Pull out the individual variables or die: # SSL Command: if($perlvarref->{SSLProgram}) { $ssl_command = $perlvarref->{SSLProgram}; Debug("SSL utility program is $ssl_command"); } else { die "LonCAPA configuration errror: Can't read SSLProgram variable"; } # Certificate directory: if($perlvarref->{lonCertificateDirectory}) { $loncapa_cert_dir = $perlvarref->{lonCertificateDirectory}; Debug("Certificates will be installed in $loncapa_cert_dir"); } else { die "LonCAPA configuration error can't read lonCertificateDirectory variable"; } # Get the name of the host's certificate: if($perlvarref->{lonnetCertificate}) { $loncapa_hostcert_name = $perlvarref->{lonnetCertificate}; Debug("Host's certificate will be $loncapa_hostcert_name"); } else { die "LonCAPA configuration error: Can't read lonnetCertificate variable"; } # Get the name of the certificate authority's certificate. if($perlvarref->{lonnetCertificateAuthority}) { $loncapa_cacert_name = $perlvarref->{lonnetCertificateAuthority}; Debug("CA's certificate will be $loncapa_cacert_name"); } else { die "LonCAPA configuration error: Can't read lonnetCertificateAuthority variable"; } } # Create a certificate from the request file. The certificate # is used, in conjunction with the openssl command with the # certificate authority configuration to produce a certificate # file. # # The certificate is parsed to determine the email address # of the requestor, which is returned to the caller. # #Parameters: # request_file - Name of the file containing the certificate request. #Returns: # If the request file exists and is able to produce a certificate # the email address of the requester is returned to the caller. # If not, undef is returned. # sub CreateCertificate { my ($request_file) = @_; Debug("CreateCertificate"); if(!(-e $request_file)) { Debug("Certificate file $request_file does not exist"); return undef; } Debug("Certificate file $request_file exists"); # Create the certificate: The status of the openssl command # is used to determine if the certificate succeeded: my $create_command = $ssl_command." ca -config ".$ca_config_file ." -in ".$request_file ." -out hostCertificate.pem"; my $status = system($create_command); if($status) { Debug("openssl ca failed"); print STDERR "Certificate generation failed... probably bad"; print STDERR " request file!\n"; return undef; } Debug("openssl ca succeeded"); # Now we have a shining new signed certificate in ./hostCertificate.pem # we parse it to get the email address to which the certificate should # be emailed. # The certificate's return email address will be in the Subject line: # Debug("Parsing certificate file for Subject:"); open CERTIFICATE, ") { Debug("Line = $line"); if($line =~ /Subject:/) { Debug("Found Subject: in $line"); $subject_found =1; last; } } close CERTIFICATE; if(!$subject_found) { Debug("Did not find Subject line in cert"); print STDERR "Output certificate parse failed: no Subject:\n"; return undef; } # The subject line contains an Email= string amidst the other stuff. # First break in to comma separated stuff, then locate the piece that # contains /Email= my @subject_fields = split(/,/, $line); my $email_found = 0; my $element; my $email_element; Debug("Parsing subject line for Email="); foreach $element (@subject_fields) { $email_element = $element; Debug("Parsing $element"); if($element =~ /\/Email=/) { Debug("Found /Email="); $email_found = 1; last; } } if(!$email_found) { Debug("Failed to fine Email="); print STDERR "Unable to find line with /Email= in cert. Subject\n"; return undef; } # The piece we found must first be split at the / # to isolate the Email= part and then that part at the = to isolate # the address: Debug("Splitting $email_element at /"); my ($junk, $email) = split(/\//, $email_element); Debug("Email part is $email"); my ($junk, $address) = split(/=/, $email); Debug("CreateCertificate Returning $address to caller"); return $address; } # # Create the installation script. This will be bash script # that will install the certifiate and the CA's certificate with ownership # WebUser:WebGroup and permissions 0400. I thought about using a perl # script in order to be able to get the certificate file/directory from # the configuration files. Unfortunately this is not as easy as it looks. # Root has a chicken and egg problem. In order to read the config file # you need to have added the ..../lib/perl to the perl lib path. To do # that correctly, you need to have read the config file to know where # it is...What we will do is read our local configuration file and # assume that our configuration is the same as the target's system in # all respects we care about. # Implicit Inputs: # - Bash is in /bin/bash # - $loncapa_cert_dir - install target directory. # - $loncapa_hostcert_name - Name of installed host cert file. # - $loncapa_cacert_name - Name of installed ca cert file. # - $loncapa_apache_user - username under which httpd runs. # - $loncapa_apache_group - group under which httpd runs. # - 0400 - install permissions. # - The host's certificate is now in ./hostCertificate.pem # - The CA's certificate is now in $ca_cert_file # # Implicit Outputs: # A file named CertInstall.sh # sub CreateInstallScript { open INSTALLER,">CertInstall.sh"; print INSTALLER <\$HOSTCERT BASH_HEADER # Now copy the host certificate into the script: open HOSTCERT, ") { print INSTALLER $line; # Line presumably has a \n. } close HOSTCERT; # Close the here doc, and start up the cat of the ca cert: print INSTALLER "HOSTCERTTEXT\n"; print INSTALLER "echo unpacking CA certificate\n"; print INSTALLER "cat <<-CACERTTEXT >\$CACERT\n"; open CACERT, "<$ca_cert_file"; while(my $line = ) { print INSTALLER $line; } close CACERT; print INSTALLER "CACERTTEXT\n"; # Ok, the script can create the two files, now it must install # install them >and< clean up after itself. print INSTALLER <