#!/usr/bin/perl # # Copyright (c) 2007 The Regents of the University of Illinois. All # rights reserved. # # Copyright (c) 2002 The Regents of the University of California. All # rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following # disclaimer. 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. 3. All advertising materials mentioning features or use # of this software must display the following acknowledgement: This # product includes software developed by the San Diego Supercomputer # Center and its contributors. 4. Neither the name of the Center nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # # Script name: ncsa-cert-request # Written by: John P Quinn (modified SDSC code written by William Link) # Date: June, 2003 # # $P12_CERT = "usercert.p12"; $REQ_TYPE=""; # Type of request: usercert, force, host, ldap... $CERT_NAME=""; # What to name cert file $KEY_NAME=""; # What to name key file $OPENSSL = "/usr/bin/openssl"; $CA = "ncsa-cert-request.ncsa.uiuc.edu"; $CA_ADMIN = "ca-admin\@ncsa.uiuc.edu"; $PORT = 3434; $MINPWDLEN = 12; $verbose=0; $debug=0; $loops=1; $PROGRAM = $0; $PROGRAM =~ s,^.*/,,; $RUNBY = `id --user --name 2>/dev/null`; chomp ($RUNBY); if ( $RUNBY !~ /^[a-z]/ ) { $RUNBY = `logname`; chomp ($RUNBY); } use Getopt::Long; GetOptions( 'debug!' => \$debug, 'dir=s' => \$alt_dir, 'force' => sub { my ($optname, $optval) = @_; $force = $optval; GetOptions('dir=s' => \$alt_dir); }, 'help' => sub { usage(); }, 'host=s' => sub { my ($optname, $optval) = @_; $host = $optval; GetOptions('dir=s' => \$alt_dir); }, 'hostrevoke=s' => sub { my ($optname, $optval) = @_; $hostrevoke = $optval; }, 'ldap=s' => sub { my ($optname, $optval) = @_; $ldap = $optval; GetOptions('dir=s' => \$alt_dir); }, 'ldaprevoke=s' => sub { my ($optname, $optval) = @_; $ldaprevoke = $optval; }, 'loops=i' => \$loops, 'port=i' => \$PORT, 'revoke' => \$revoke, 'server=s' => \$CA, 'verbose!' => \$verbose ) or usage(); $verbose = 1 if $debug; sub usage { print <] - Request a user certificate. If -dir is specified, place new user[cert|key] in , else place new user[cert|key] in ~/.globus directory. $PROGRAM [options] -force [-dir ] - Request an existing user certificate be revoked and a new certificate be issued. If -dir is specified, place new user[cert|key] in , else place new user[cert|key] in ~/.globus directory. $PROGRAM [options] -revoke - Request an existing user certificate be revoked. $PROGRAM [options] -host [-dir ] - Request a host certificate. If -dir is specified, place new host[cert|key] in , else place new host[cert|key] in ~/.globus directory. $PROGRAM [options] -hostrevoke - Request an existing host certificate be revoked. $PROGRAM [options] -ldap [-dir ] - Request an ldap certificate. If -dir is specified, place new ldap[cert|key] in , else place new ldap[cert|key] in ~/.globus directory. $PROGRAM [options] -ldaprevoke - Request an existing ldap certificate be revoked. $PROGRAM -help - Print this message. EOF exit; } if (defined($force)) { $REQ_TYPE='ForceCert'; $CERT_NAME="usercert.pem"; $KEY_NAME="userkey.pem"; } elsif (defined($revoke)) { $REQ_TYPE='Revoke'; } elsif (defined($host)) { # # Simple check for properties a host name should have # if (grep(/\s/, $host)) { printf("Hostname '$host' does not appear to be properly formatted.\n"); exit 1; } if ((! grep(/\./, $host)) || grep(/\s/, $host)) { printf("Hostname '$host' does not appear to be fully qualified.\n"); exit 1; } $REQ_TYPE='HostCert'; $CERT_NAME="hostcert.pem"; $KEY_NAME="hostkey.pem"; } elsif (defined($hostrevoke)) { # # Simple check for properties a host name should have # if (grep(/\s/, $hostrevoke)) { printf("Hostname '$hostrevoke' does not appear to be properly formatted.\n"); exit 1; } if ((! grep(/\./, $hostrevoke)) || grep(/\s/, $hostrevoke)) { printf("Hostname '$hostrevoke' does not appear to be fully qualified.\n"); exit 1; } $REQ_TYPE='HostCertRevoke'; $CERT_NAME="hostcert.pem"; $KEY_NAME="hostkey.pem"; } elsif (defined($ldap)) { # # Simple check for properties a host name should have # if (grep(/\s/, $ldap)) { printf("Hostname '$ldap' does not appear to be properly formatted.\n"); exit 1; } if ((! grep(/\./, $ldap)) || grep(/\s/, $ldap)) { printf("Hostname '$ldap' does not appear to be fully qualified.\n"); exit 1; } $REQ_TYPE='LdapCert'; $CERT_NAME="ldapcert.pem"; $KEY_NAME="ldapkey.pem"; } elsif (defined($ldaprevoke)) { # # Simple check for properties a host name should have # if (grep(/\s/, $ldaprevoke)) { printf("Hostname '$ldaprevoke' does not appear to be properly formatted.\n"); exit 1; } if ((! grep(/\./, $ldaprevoke)) || grep(/\s/, $ldaprevoke)) { printf("Hostname '$ldaprevoke' does not appear to be fully qualified.\n"); exit 1; } $REQ_TYPE='LdapCertRevoke'; $CERT_NAME="ldapcert.pem"; $KEY_NAME="ldapkey.pem"; } else { $REQ_TYPE='UserCert'; $CERT_NAME="usercert.pem"; $KEY_NAME="userkey.pem"; } print ("\n======================================================================\n\n"); print ("Welcome to the new \'$PROGRAM\'. The new \'$PROGRAM\'\n"); print ("provides an interactive certificate request and revoke capability for\n"); print ("NCSA users. Usage information is available via either: \n\n"); print ("\t$PROGRAM -help\n\n"); print ("\tor\n\n"); print ("\tman $PROGRAM\n\n"); print ("For further details about \'$PROGRAM\' and NCSA's Public Key\n"); print ("Infrastructure, please visit\n\n"); print ("\thttp://www.ncsa.uiuc.edu/UserInfo/Grid/Security/\n\n"); print ("\n======================================================================\n\n"); if (defined($alt_dir)) { # tilde expand if necessary if ($alt_dir =~ /^~/) { $alt_dir = tilde_expand($alt_dir); } $USER_CERT_DIR = $alt_dir; } else { $USER_CERT_DIR = $ENV{"HOME"} . "/.globus"; } # # Check for existence of certificates/keys of the kind for this # request in ~/.globus or the specified alternate directory. If # They exist, exit with a warning. # if (($REQ_TYPE ne "Revoke") && ($REQ_TYPE ne "ForceCert") && ($REQ_TYPE ne "HostCertRevoke") && ($REQ_TYPE ne "LdapCertRevoke")) { if ((-e "$USER_CERT_DIR/$CERT_NAME") && (-e "$USER_CERT_DIR/$KEY_NAME")) { print "\n\nThe files:\n\n"; print " $USER_CERT_DIR/$CERT_NAME\n"; print " $USER_CERT_DIR/$KEY_NAME\n\n"; print "exist and will not be overwritten. Please move or remove before continuing.\n\n"; exit 1; } } # # Check for other conditions which will prevent this script from running. # if (! -e $OPENSSL) { print ("\n"); print ("You can not request a cert from this machine because \n"); print ("$OPENSSL is not available. \n"); print ("\n"); exit 1; } # # Create directory, if necessary, and move to it # $created_user_cert_dir = 0; if (-e $USER_CERT_DIR) { chdir($USER_CERT_DIR) || die "Can't get to $USER_CERT_DIR. \n"; } else { if (mkdir($USER_CERT_DIR,0700)) { chdir($USER_CERT_DIR) || die "Can't get to $alt_dir. \n"; $created_user_cert_dir = 1; } else { print "Failed to create $USER_CERT_DIR\n"; exit 1; } } doit ("stty -echo"); print ("To continue, please enter the NCSA Kerberos password for $RUNBY: "); $LOGINPWD = ; chomp ($LOGINPWD); print "\n"; print ("For increased security, your NCSA default password is also needed.\n"); print ("To continue, please enter the NCSA default password for $RUNBY: "); $DEFAULTPWD = ; chomp ($DEFAULTPWD); print "\n"; $LOGINPWD = sprintf("%02d%s%s", length $LOGINPWD, $LOGINPWD, $DEFAULTPWD); # # Precede a backslash in either password with lots of escapes, precede # double quotes with a single escape character, and enclose the passwords # with double quotes to prevent "special characters" from being stripped # out when openssl creates the req.pem file. # $LOGINPWD=~ s/\\/\\\\/g; $LOGINPWD=~ s/"/\\"/g; $LOGINPWD= "\"$LOGINPWD\""; $DEFAULTPWD=~ s/\\/\\\\/g; $DEFAULTPWD=~ s/"/\\"/g; $DEFAULTPWD= "\"$DEFAULTPWD\""; if (($REQ_TYPE eq "UserCert") || ($REQ_TYPE eq "ForceCert")) { $ENCPWD = "a"; $ENCPWD2 = "b"; print ("\n \n"); print ("Next you will be prompted to enter a passphrase which will serve \n"); print ("as both the encryption key for your certificate's private key \n"); print ("and as the 'export passphrase' for your PKCS#12 converted \n"); print ("certificate. Please choose a passphrase that you can remember, \n"); print ("you will need to use it in the future. \n"); print ("\n"); while ($ENCPWD ne $ENCPWD2) { $ENCPWD = "pwd1"; $ENCPWD2 = "pwd2"; print ("\n"); print ("Please enter your private key encryption passphrase: "); $ENCPWD = ; chomp ($ENCPWD); if (length($ENCPWD) < $MINPWDLEN) { print ("\n\nPrivate key encryption passphrase must be at least twelve\ncharacters in length, please try again.\n"); } else { print ("\n"); print ("Verifying private key passphrase, please reenter passphrase: "); $ENCPWD2 = ; chomp ($ENCPWD2); print ("\n"); if ($ENCPWD ne $ENCPWD2) { print ("\nThe passphrases did not match, please try again.\n"); } } } doit ("stty echo"); print ("\n"); # # Write out the private key encryption password for use by openssl # when generating the key and certificate request. It will be deleted # after it has been used for the key encryption. # open(OUT, ">epwd"); print(OUT "$ENCPWD"); close(OUT); } else { doit ("stty echo"); print ("\n"); } #make the temporary host certificate file $TEMP_DIR=undef; foreach my $TEMP_DIR_TEST ( "/usr/tmp", "/tmp", "/var/tmp", "." ) { if ( -d $TEMP_DIR_TEST && -w $TEMP_DIR_TEST ) { $TEMP_DIR = $TEMP_DIR_TEST; last; } } die "Cannot find a directory for writing temporary files" if ! defined $TEMP_DIR; $CERT = "$TEMP_DIR/$PROGRAM.$$.crt"; print "using temporary cert file $CERT\n" if $verbose; open CACERT, ">", $CERT || die "Cannot write temporary cert file to $CERT"; print CACERT <conf" || die "Can't open $USER_CERT_DIR/conf file for writing. \n"); foreach my $line ( @CONFIG_DATA ) { $line =~ s/CommonName/$RUNBY/; $line =~ s/ChallengePwd/$LOGINPWD/; $line =~ s/ReqType/$REQ_TYPE/; $line =~ s/HostName/$host/ if defined($host); #host should contain hostname $line =~ s/HostName/$hostrevoke/ if defined($hostrevoke); #host revoke should ... $line =~ s/HostName/$ldap/ if defined($ldap); #ldap should contain hostname $line =~ s/HostName/$ldaprevoke/ if defined($ldaprevoke); #ldap revoke should ... print CONF "$line\n"; } close(CONF); # # Generate a .rnd file to be used as a randon number seed, create the cert # request, and encrypt it using the CA's cert. # $RNUM = rand; doit ("echo $RNUM | /bin/cat > rnseed"); doit ("echo `ps -el` | gzip >> rnseed"); doit ("echo `/bin/date` | /bin/cat >> rnseed"); doit ("$OPENSSL genrsa -rand rnseed > /dev/null 2>&1"); if (($REQ_TYPE eq "UserCert") || ($REQ_TYPE eq "ForceCert")) { doit ("$OPENSSL genrsa -des3 -out $KEY_NAME -passout file:./epwd 2048 > /dev/null 2>&1"); doit ("$OPENSSL req -new -config conf -passin file:./epwd -key $KEY_NAME -out req.pem > /dev/null 2>&1"); chmod (0600,$KEY_NAME); } elsif ($REQ_TYPE eq "Revoke") { doit ("$OPENSSL req -new -config conf -out req.pem -nodes > /dev/null 2>&1"); unlink ("privkey.pem"); } else { doit ("$OPENSSL genrsa -out $KEY_NAME 2048 > /dev/null 2>&1"); doit ("$OPENSSL req -new -key $KEY_NAME -config conf -out req.pem > /dev/null 2>&1"); chmod (0600,$KEY_NAME); } if ( $debug ) { print "encrypting:\n"; system ("cat req.pem"); } doit("$OPENSSL smime -encrypt -des3 -in req.pem -out ereq $CERT"); if ( $debug ) { print "encrypted:\n"; system ("cat ereq"); } unlink ("conf","epwd","req.pem","rnseed"); # # Set up a socket connection to the CA daemon running on # $CA and send the certificate request. # use IO::Socket; my $loops_counter = 0; my $loops_start_time; $loops_start_time = time() if $loops > 1; LOOP: print "opening socket on server $CA port $PORT ...\n" if $debug; $SOCKET = new IO::Socket::INET (PeerAddr => $CA, PeerPort => $PORT, Proto => 'tcp'); if (! $SOCKET) { unlink ("ereq"); if ($REQ_TYPE eq "UserCert") { unlink("userkey.pem"); } elsif ($REQ_TYPE eq "HostCert") { unlink("hostkey.pem"); } elsif ($REQ_TYPE eq "LdapCert") { unlink("ldapkey.pem"); } chdir($ENV{"HOME"}) || die "Can't cd back to home directory. \n"; if ($created_user_cert_dir) { doit ("rm -rf $USER_CERT_DIR"); } print ("Can not connect to $CA: $! \n"); unlink $CERT if ! $debug; exit 1; } $MSG=`/bin/cat ereq`; select($SOCKET); send($SOCKET,$MSG,0); send($SOCKET,"All done\n",0); unlink ("ereq") if $loops_counter >= ( $loops - 1 ); # # Get a reply from the CA daemon. If the first eight bytes is "Success " # the rest of the message is the cert, which can be written out to disk # in the current directory as [user|host|ldap]cert.pem. Is a user cert, # a PKCS#12 copy of the cert is made. If the first eight bytes is "Error " # then something went # wrong, print the rest of the message, which is an # error message, to STDOUT. # recv($SOCKET,$BUFF,8,0); if ("$BUFF" eq "Error ") { select (STDOUT); print ($BUFF); recv($SOCKET,$BUFF,5000,0); print ("$BUFF \n"); close($SOCKET); if ($REQ_TYPE eq "UserCert") { unlink("userkey.pem"); } elsif ($REQ_TYPE eq "HostCert") { unlink("hostkey.pem"); } elsif ($REQ_TYPE eq "LdapCert") { unlink("ldapkey.pem"); } chdir($ENV{"HOME"}) || die "Can't cd back to home directory. \n"; if ($created_user_cert_dir) { doit ("rm -rf $USER_CERT_DIR"); } unlink ("ereq") if $loops > 1; unlink $CERT if ! $debug; exit 1; } elsif ("$BUFF" eq "Success ") { if (($REQ_TYPE ne "Revoke") && ($REQ_TYPE ne "HostCertRevoke") && ($REQ_TYPE ne "LdapCertRevoke")) { open (CERTFILE, ">$CERT_NAME"); select (CERTFILE); local $/; undef ($/); while (<$SOCKET>) { print ($_); } close(CERTFILE); } else { select (STDOUT); recv($SOCKET,$BUFF,5000,0); print ("$BUFF \n"); } close($SOCKET); select (STDOUT); print ("\n"); if (($REQ_TYPE eq "UserCert") || ($REQ_TYPE eq "ForceCert")) { open(OUT, ">epwd"); print(OUT "$ENCPWD"); close(OUT); doit ("$OPENSSL rsa -passin file:./epwd -in $KEY_NAME -outform PEM -out key.pem > /dev/null 2>&1"); doit ("$OPENSSL pkcs12 -export -in $CERT_NAME -inkey key.pem -passout file:./epwd -out $P12_CERT -name \"NCSA OpenSSL Certificate\""); chmod (0600, $P12_CERT, $CERT_NAME); unlink ("epwd","key.pem"); print ("You now have a $USER_CERT_DIR directory containing the following files: \n"); print ("\n"); print ("$P12_CERT - Your digital certificate and private key in \n"); print (" a PKCS#12 format certificate \n"); print ("\n"); print ("$CERT_NAME - Your digital certificate, signed by the CA \n"); print (" daemon \n"); print ("\n"); print ("$KEY_NAME - Your encrypted private key matching the \n"); print (" public key contained in your $CERT_NAME \n"); print ("\n"); print ("\n"); print ("********************************************************** \n"); print ("\n"); print (" YOUR CERTIFICATE IS VALID FOR ONE YEAR, DO NOT FORGET \n"); print (" YOUR PRIVATE KEY ENCRYPTION PASSWORD AND, DO NOT DELETE \n"); print (" YOUR $USER_CERT_DIR DIRECTORY. \n"); print ("\n"); print ("********************************************************** \n"); print ("\n"); } elsif (($REQ_TYPE ne "Revoke") && ($REQ_TYPE ne "HostCertRevoke") && ($REQ_TYPE ne "LdapCertRevoke")) { print ("Your $USER_CERT_DIR directory now contains the following files: \n"); print ("\n"); print ("$CERT_NAME - The digital $CERT_NAME service certificate, signed by the CA \n"); print (" daemon \n"); print ("\n"); print ("$KEY_NAME - The encrypted private key matching the \n"); print (" public key contained in $CERT_NAME \n"); print ("\n"); } } else { select (STDOUT); print ("Can't recognize reply from CA daemon. \n"); close($SOCKET); chdir($ENV{"HOME"}) || die "Can't cd back to home directory. \n"; if ($created_user_cert_dir) { doit ("rm -rf $USER_CERT_DIR"); } unlink ("ereq") if $loops > 1; unlink $CERT if ! $debug; exit 1; } if ( $loops > 1 ) { $loops_counter++; my $loops_total_time = time() - $loops_start_time; my $loops_average_time = $loops_total_time / $loops_counter; print "\n$loops_counter loops took $loops_total_time seconds or $loops_average_time per loop\n"; goto LOOP if $loops_counter < $loops; unlink ("ereq"); } sub tilde_expand { my $alt_dir = shift; ($homedir, @rest) = split (/\//, $alt_dir); my $real_homedir = `/bin/echo $homedir`; chomp($real_homedir); my $expanded_path = $real_homedir; for (my $i=0; $i<@rest; $i++) { $expanded_path = "$expanded_path/$rest[$i]"; } return $expanded_path; } sub doit { my $cmd = shift; print "$cmd\n" if $verbose; system("$cmd"); } unlink $CERT if ! $debug; exit 0;