Annotation of loncom/CrGenerate.pl, revision 1.9
1.1 foxr 1: #!/usr/bin/perl
2: # The LearningOnline Network
3: # CrGenerate - Generate a loncapa certificate request.
4: #
1.9 ! schafran 5: # $Id: CrGenerate.pl,v 1.8 2004/07/05 11:36:52 foxr Exp $
1.1 foxr 6: #
7: # Copyright Michigan State University Board of Trustees
8: #
9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
10: #
11: # LON-CAPA is free software; you can redistribute it and/or modify
12: # it under the terms of the GNU General Public License as published by
13: # the Free Software Foundation; either version 2 of the License, or
14: # (at your option) any later version.
15: #
16: # LON-CAPA is distributed in the hope that it will be useful,
17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19: # GNU General Public License for more details.
20: #
21: # You should have received a copy of the GNU General Public License
22: # along with LON-CAPA; if not, write to the Free Software
23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24: #
25: # /home/httpd/html/adm/gpl.txt
26: #
27:
28:
29: # http://www.lon-capa.org/
30: #
31: #
32: # This script:
33: # 1. Generates a private host key and certificate request/
34: # 2. Decodes the private host key
35: # 3. Installs the private host key with appropriate permissions
36: # in the appropriate directory (sorry to be vague about this, but
37: # the installation directory is determined by external configuration
38: # info).
39: # 4. Constructs an email to the loncapa cluster administrator
40: # consisting of a generic heading and the certificate request as a MIME
41: # attachment.
42: # 5. Sends the email and
43: # 6. Cleans up after itself by removing any temp files generated.
44: #
45: #
46:
47:
48: # Import section:
49:
50: use strict;
1.6 foxr 51: use lib '/home/httpd/lib/perl';
1.1 foxr 52: use MIME::Entity;
53: use LONCAPA::Configuration;
1.4 foxr 54: use File::Copy;
1.1 foxr 55:
1.4 foxr 56: # Global variable declarations:4
1.1 foxr 57:
1.2 foxr 58: my $SSLCommand; # Full path to openssl command.
59: my $CertificateDirectory; # LONCAPA Certificate directory.
60: my $KeyFilename; # Key filename (within CertificateDirectory).
61: my $RequestEmail; # Email address of loncapa cert admin.
1.4 foxr 62: my $WebUID; # UID of web user.
63: my $WebGID; # GID of web user.
1.1 foxr 64:
1.4 foxr 65: my $Passphrase="loncapawhatever"; # Initial passphrase for keyfile
66: my $RequestFile="loncapaRequest.pem"; # Name of Certificate request file.
67: my $EncodedKey="hostkey.pem"; # Name of encoded key file.
1.3 foxr 68:
1.4 foxr 69: my $WebUser="www"; # Username running the web server.
70: my $WebGroup="www"; # Group name running the web server.
1.3 foxr 71:
1.1 foxr 72: # Debug/log support:
73: #
1.7 foxr 74: my $DEBUG = 0; # 1 for on, 0 for off.
1.1 foxr 75:
76: # Send debugging to stderr.
77: # Parameters:
78: # msg - Message to send to stderr.
79: # Implicit Inputs:
80: # $DEBUG - message is only written if this is true.
81: #
82: sub Debug {
1.8 foxr 83: my ($msg) = @_;
1.1 foxr 84: if($DEBUG) {
85: print STDERR "$msg\n";
86: }
87: }
88:
1.3 foxr 89: #
1.5 foxr 90: # Decodes the email address from a textual certificate request
91: # file:
92: # Parameters:
93: # $RequestFile - Name of the file containing the textual
94: # version of the certificate request.
95: # Returns:
96: # Email address contained in the request.
97: # Failure:
98: # If unable to open or unable to fine an email address in the file,
99: # dies with a message.
100: #
101: sub DecodeEmailFromRequest {
102: Debug("DecodeEmailFromRequest");
103:
1.8 foxr 104: my ($RequestFile) = @_;
1.5 foxr 105: Debug("Request file is called $RequestFile");
106:
107: # We need to look for the line that has a "/Email=" in it.
108:
109: Debug("opening $RequestFile");
110: open REQUEST, "< $RequestFile" or
1.9 ! schafran 111: die "Unable to open $RequestFile to parse return e-mail address";
1.5 foxr 112:
113: Debug("Parsing request file");
114: my $line;
115: my $found = 0;
116: while($line = <REQUEST>) {
117: chomp($line); # Never a bad idea.
118: if($line =~ /\/Email=/) {
119: $found = 1;
120: last;
121: }
122: }
123: if(!$found) {
1.9 ! schafran 124: die "There does not appear to be an e-mail address in $RequestFile";
1.5 foxr 125: }
126:
127: close REQUEST;
128:
129: Debug("Found /Email in $line");
130:
131: # $line contains a bunch of comma separated key=value pairs.
132: # The problem is that after these is a /Email=<what-we-want>
133: # first we'll split the line up at the commas.
134: # Then we'll look for the entity with the /Email in it.
135: # That line will get split at the / and then the Email=<what-we-want>
136: # gets split at the =. I'm sure there's some clever regular expression
137: # substitution that will get it all in a single line, but I think
138: # this approach is gonna be much easier to understand than punctuation
139: # sneezed all over the page:
140:
141: my @commalist = split(/,/, $line);
142: my $item;
143: my $emailequals = "";
144: foreach $item (@commalist) {
145: if($item =~ /\/Email=/) { # gotcha...
146: $emailequals = $item;
147: last;
148: }
149: }
150:
151: Debug("Pulled out $emailequals from $line");
152: my ($trash, $addressequals) = split(/\//, $emailequals);
153: Debug("Futher pulled out $addressequals");
154:
155: my ($junk, $address) = split(/=/, $addressequals);
1.9 ! schafran 156: Debug("Parsed final e-mail address as $address");
1.5 foxr 157:
158:
159:
160: return $address;
161: }
162:
163: #
1.3 foxr 164: # Read the LonCAPA web config files to get the values of the
165: # configuration global variables we need:
166: # Implicit inputs:
167: # loncapa.conf - configuration file to read (user specific).
168: # Implicit outputs (see global variables section):
169: # SSLCommand,
170: # CertificateDirectory
171: # KeyfileName
172: # RequestEmail
173: # Side-Effects:
174: # Exit with error if cannot complete.
175: #
176: sub ReadConfig {
177:
178: Debug("Reading configuration");
179: my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
180:
181: # Name of the SSL Program
182:
183: if($perlvarref->{SSLProgram}) {
184: $SSLCommand = $perlvarref->{SSLProgram};
185: Debug("SSL Command: $SSLCommand");
186: }
187: else {
188: die "Unable to read the SSLCommand configuration option\n";
189: }
190:
191: # Where the certificates, and host key are installed:
1.1 foxr 192:
1.3 foxr 193: if($perlvarref->{lonCertificateDirectory}) {
194: $CertificateDirectory = $perlvarref->{lonCertificateDirectory};
195: Debug("Local certificate Directory: $CertificateDirectory");
196: }
197: else {
198: die "Unable to read SSLDirectory configuration option\n";
199: }
200: # The name of the host key file (to be installed in SSLDirectory).
201: #
202: if($perlvarref->{lonnetPrivateKey}) {
203: $KeyFilename = $perlvarref->{lonnetPrivateKey};
204: Debug("Private key will be installed as $KeyFilename");
205: }
206: else {
207: die "Unable to read lonnetPrivateKey conrig paraemter\n";
208: }
209: # The email address to which the certificate request is sent:
210:
211: if($perlvarref->{SSLEmail}) {
212: $RequestEmail = $perlvarref->{SSLEmail};
213: Debug("Certificate request will be sent to $RequestEmail");
214: }
215: else {
216: die "Could not read SSLEmail coniguration key";
217: }
1.4 foxr 218: # The UID/GID of the web user: It's possible the web user's
219: # GID is not its primary, so we'll translate that form the
220: # group file separately.
221:
222: my ($login, $pass, $uid, $gid) = getpwnam($WebUser);
223: if($uid) {
224: $WebUID = $uid;
225: Debug("Web user: $WebUser -> UID: $WebUID");
226: }
227: else {
228: die "Could not translate web user: $WebUser to a uid.";
229: }
230: my $gid = getgrnam($WebGroup);
231: if($gid) {
232: $WebGID = $gid;
233: Debug("Web group: $WebGroup -> GID $WebGID");
234: }
235: else {
236: die "Unable to translate web group $WebGroup to a gid.";
237: }
238: }
239: #
240: # Generate a certificate request.
241: # The openssl command is issued to create a local host key and
242: # a certificate request. The key is initially encoded.
243: # We will eventually decode this, however, since the key
244: # passphrase is open source we'll protect even the initial
245: # encoded key file too. We'll need to decode the keyfile since
246: # otherwise, openssl will need a passphrase everytime an ssl connection
247: # is created (ouch).
248: # Implicit Inputs:
249: # Passphrase - Initial passphrase for the encoded key.
250: # RequestFile - Filename of the certificate request.
251: # EncodedKey - Filename of the encoded key file.
252: #
253: # Side-Effects:
254: #
255: sub GenerateRequest {
256: Debug("Generating the request and key");
257:
258: print "We are now going to generate the certificate request\n";
259: print "You will be prompted by openssl for several pieces of \n";
260: print "information. Most of this information is for documentation\n";
261: print "purposes only, so it's not critical if you make a mistake.\n";
262: print "However: The generated certificate will be sent to the \n";
1.9 ! schafran 263: print "E-mail address you provide, and you should leave the optional\n";
1.4 foxr 264: print "Challenge password blank.\n";
265:
266: my $requestcmd = $SSLCommand." req -newkey rsa:1024 "
267: ." -keyout hostkey.pem "
268: ." -keyform PEM "
269: ." -out request.pem "
270: ." -outform PEM "
271: ." -passout pass:$Passphrase";
272: my $status = system($requestcmd);
273: if($status) {
274: die "Certificate request generation failed: $status";
275: }
276:
277: chmod(0600, "hostkey.pem"); # Protect key since passphrase is opensrc.
278:
279: Debug("Decoding the key");
280: my $decodecmd = $SSLCommand." rsa -in hostkey.pem"
281: ." -out hostkey.dec"
282: ." -passin pass:$Passphrase";
1.5 foxr 283: $status = system($decodecmd);
1.4 foxr 284: if($status) {
285: die "Host key decode failed";
286: }
287:
288: chmod(0600, "hostkey.dec"); # Protect the decoded hostkey.
1.5 foxr 289:
290: # Create the textual version of the request too:
291:
292: Debug("Creating textual version of the request for users.");
293: my $textcmd = $SSLCommand." req -in request.pem -text "
294: ." -out request.txt";
295: $status = system($textcmd);
296: if($status) {
297: die "Textualization of the certificate request failed";
298: }
299:
300:
1.4 foxr 301: Debug("Done");
302: }
303: #
304: # Installs the decoded host key (hostkey.dec) in the
305: # certificate directory with the correct permissions.
306: #
307: # Implicit Inputs:
308: # hostkey.dec - the name of the host key file.
309: # $CertificateDirectory - where the key file gets installed
310: # $KeyFilename - Final name of the key file.
311: # $WebUser - User who should own the key file.
312: # $WebGroup - Group who should own the key file.
313: # 0400 - Permissions to give to the installed key
314: # file.
315: # 0700 - Permissions given to the certificate
316: # directory if created.
317: # Side-Effects:
318: # If necessary, $CertificateDirectory is created.
319: # $CertificateDirectory/$KeyFilename is ovewritten with the
320: # contents of hostkey.dec in the cwd.
321: #
322: sub InstallKey {
323: Debug("InstallKey");
324:
325: Debug("Need to create certificate directory?");
326: if(!(-d $CertificateDirectory)) {
327:
328: Debug("Creating");
329: mkdir($CertificateDirectory, 0700);
330: chown($WebUID, $WebGID, $CertificateDirectory);
331: }
332: else {
333: Debug("Exists");
334: }
335:
336: Debug("Installing the key file:");
337: my $FullKeyPath = $CertificateDirectory."/".$KeyFilename;
338: copy("hostkey.dec", $FullKeyPath);
339:
340: Debug("Setting ownership and permissions");
341: chmod(0400, $FullKeyPath);
342: chown($WebUID, $WebGID, $FullKeyPath);
343:
344: Debug("Done");
1.3 foxr 345: }
1.5 foxr 346: #
347: # Package up a certificate request and email it to the loncapa
348: # admin. The email sent:
349: # - Has the subject: "LonCAPA certificate request for hostname
350: # - Has, as the body, the text version of the certificate.
351: # This can be inspected by the human issuing the certificate
352: # to decide if they want to really grant it... it will
353: # have the return email and all the documentation fields.
354: # - Has a text attachment that consists of the .pem version of the
355: # request. This is extracted by the human granting the
356: # certificate and used as input to the CrGrant.pl script.
357: #
358: #
359: # Implicit inputs:
360: # request.pem - The certificate request file.
361: # request.txt - Textual version of the request file.
362: # $RequestEmail - Email address to which the key is sent.
363: #
364: sub MailRequest {
365: Debug("Mailing request");
366:
367: # First we need to pull out the return address from the textual
368: # form of the certificate request:
369:
370: my $FromEmail = DecodeEmailFromRequest("request.txt");
371: if(!$FromEmail) {
1.9 ! schafran 372: die "From e-mail address cannot be decoded from certificate request";
1.5 foxr 373: }
374: Debug("Certificate will be sent back to $FromEmail");
375:
376: # Create the email message headers and all:
377: #
378: Debug("Creating top...level...");
379: my $top = MIME::Entity->build(Type => "multipart/mixed",
380: From => $FromEmail,
381: To => $RequestEmail,
382: Subject => "LonCAPA certificate request");
383: if(!$top) {
384: die "Unable to create top level mime document";
385: }
386: Debug("Attaching Text formatted certificate request");
387: $top->attach(Path => "request.txt");
388:
389:
390: Debug("Attaching PEM formatted certificate request...");
391: $top->attach(Type => "text/plain",
392: Path => "request.pem");
393:
394: # Now send the email via sendmail this should work as long as
395: # sendmail or postfix are configured properly. Most other mailers
396: # define the sendmail command too for compatibility with what
397: # we're trying to do. I decided to use sendmail directly because
398: # otherwise I'm not sure the mail headers I created in $top
399: # will get properly passed as headers to other mailer thingies.
400: #
401:
402: Debug("Mailing..");
403:
404: open MAILPIPE, "| /usr/lib/sendmail -t -oi -oem" or
405: die "Failed to open pipe to sendmail: $!";
406: $top->print(\*MAILPIPE);
407: close MAILPIPE;
408:
409:
410:
411: Debug("Done");
412: }
1.6 foxr 413:
414: #
415: # Cleans up the detritus that's been created by this
416: # script (see Implicit inputs below).
417: # Implicit inputs:
418: # request.pem - Name of certificate request file in PEM format
419: # which will be deleted.
420: # request.txt - Name of textual equivalent of request file
421: # which will also be deleted.
422: # hostkey.pem - Encrypted host key which will be deleted.
423: # hostkey.dec - Decoded host key, which will be deleted.
424: #
425: sub Cleanup {
426: Debug("Cleaning up generated, temporary files");
427: unlink("request.pem", "request.txt", "hostkey.pem", "hostkey.dec");
428: Debug("done!");
429: }
1.1 foxr 430:
431:
432:
433: # Entry point:
434:
435: Debug("Starting program");
436: ReadConfig; # Read loncapa apache config file.
437: GenerateRequest; # Generate certificate request.
438: InstallKey; # Install the user's key.
439: MailRequest; # Mail certificate request to loncapa
440: Cleanup; # Cleanup temp files created.
441:
442: Debug("Done");
1.7 foxr 443:
444: #---------------------- POD documentatio --------------------
445:
446: =head1 NAME
447:
448: CrGenerate - Generate a loncapa certificate request.
449:
450: =head1 SYNOPSIS
451:
452: Usage: B<CrGenerate>
453:
454: This should probably be run automatically at system
455: installation time. Root must run this as write access is
456: required to /home/httpd.
457:
458: This is a command line script that:
459:
460: - Generates a hostkey and certificate request.
461: - Installs the protected/decoded host key where
462: secure lond/lonc can find it.
463: - Emails the certificate request to the loncapa certificate
464: manager.
465:
466: In due course if all is legitimate, the loncapa certificate
467: manager will email a certificate installation script to
468: the local loncapa system administrator.
469:
470: =head1 DESCRIPTION
471:
472: Using the default openssl configuration file, a certificate
473: request and local hostkey are created in the current working
474: directory. The local host key is decoded and installed in the
475: loncapa certificate directory. This allows the secure versions
476: of lonc and lond to locate them when attempting to form
477: external connections. The key file is given mode
478: 0400 to secure it from prying eyes.
479:
480: The certificate request in PEM form is attached to an email that
481: contains the textual equivalent of the certificate request
482: and sent to the loncapa certificate manager. All temporary
483: files (certificate request, keys etc.) are removed from the
484: current working directory.
485:
486: It is recommended that the directory this script is run in have
487: permission mask 0700 to ensure that there are no timing holes
488: during which the decoded host key file can be stolen.
489:
490: During certificate generation, the user will receive several
491: prompts. For the default LonCAPA openssl configuration,
492: these prompts, and documentation and sample responses
493: in angle brackets (<>) are shown below:
494:
495: Country Name (2 letter code) [GB]: <your country e.g. US>
496: State or Province Name (full name) [Berkshire]: <State, province prefecture etc. e.g. Michigan>
497: Locality Name (eg, city) [Newbury]: <City township or municipality e.g. East Lansing>
498: Organization Name (eg, company) [My Company Ltd]: <corporate entity e.g. Michigan State University>
499: Organizational Unit Name (eg, section) []: <unit within Organization e.g. LITE lab>
500: Common Name (eg, your name or your server's host name) [] <server's hostname e.g. myhost.university.edu>
501: Email Address []: <Address to which the granted certificate should be sent e.g. me@university.edu>
502:
503: Please enter the following 'extra' attributes
504: to be sent with your certificate request
505: A challenge password []: <leave this blank!!!!!>
506: An optional company name []: <Put whatever you want or leave blank>
507:
508:
509: =head1 DEPENDENCIES
510:
511: - MIME::Entity Used to create the email message.
512: - LONCAPA::Configuration Used to parse the loncapa configuration files.
513: - File::Copy Used to install the key file.
514: - /usr/lib/sendmail Properly configured sendmail, used to send the
515: certificate request email to the loncapa
516: certificate administrator.
517: - /etc/httpd/conf/* Loncapa configuration files read to locate
518: the certificate directory etc.
519:
520: =head1 FILES
521:
522: The following temporary files are created in the cwd
523:
524: hostkey.pem - PEM formatted version of the encrypted host key.
525: hostkey.dec - PEM formatted decrypted version of the host key.
526: request.pem - PEM formatted certificate request.
527: request.txt - Textual rendering of the certificate request.
528:
529: The following permanent file is created:
530:
531: $CertDir/$Keyfile - The installed decoded host key file. $CertDir
532: is defined by the Perl variable lonCertificateDirectory
533: in /etc/loncapa_apache.conf while $Keyfile is
534: defined by the perl variable lonnetPrivateKey in the
535: same configuration file.
536:
537: =head1 COPYRIGHT:
538:
539: Copyright Michigan State University Board of Trustees
540:
541: This file is part of the LearningOnline Network with CAPA (LON-CAPA).
542:
543: LON-CAPA is free software; you can redistribute it and/or modify
544: it under the terms of the GNU General Public License as published by
545: the Free Software Foundation; either version 2 of the License, or
546: (at your option) any later version.
547:
548: LON-CAPA is distributed in the hope that it will be useful,
549: but WITHOUT ANY WARRANTY; without even the implied warranty of
550: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
551: GNU General Public License for more details.
552:
553: You should have received a copy of the GNU General Public License
554: along with LON-CAPA; if not, write to the Free Software
555: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
556:
557: /home/httpd/html/adm/gpl.txt
558:
559:
560: =cut
561:
562:
563:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>