#!/usr/bin/perl
## Program:
# 	openssl_generate_user_req.pl
#
## Purpose:
#  This script generates user certificate requests if OpenSSL is avalable.
## Options:
#	Mandatory: 	-u "<FirstName> <LastName>" -i <institute>
#	Advised:	-r <local RA email address>
#	Other:		-f (overwrite existing files); -h (help)
#
#  See a list of Organizational Units (OU) supported by GridKa-CA at http://grid.fzk.de/ca/RA.html
#  Default: config-files are created in /tmp/certs and removed after successful run
#  Default: user-dir ~/.globus and all user*pem are created in $HOME/.globus
#
## History:
#  Version  1.03 261006 IN Even more verbose output, link to RA page included etc., some fixes
#  Version 1.02 25102006 H. Enke Enke /AIP - removed the interative prompt for settings already there
#  Version 1.01 beta 251006 Iliya Nickelt GAVO /AIP - improvements, work in progress
#  Version 1.0 October 2006 Harry Enke Astrogrid-D / AIP

# TO DO [251006 IN]
# - improve _LOCAL_RA_ handling (automatic?)

use Getopt::Std;
getopts('u:i:r:hf');
#verify, on openssl is available in the path"
my $ossl="openssl";
my $cmd =`openssl version`;
my $ra_email_default="GridKA-CA\@iwr.fzk.de";

if($cmd ){
print "\nUsing $cmd \n";
}else{
	die "*** ERROR: No openssl found in path!\nPlease make sure openssl is installed properly. Try \"locate openssl\"\n";
} 
my $pwd=$ENV{'PWD'};

if ($opt_h) 
 {
	print "openssl_generate_user_req.pl -- a certificate request generation script using openssl.\n";
	print "Options: -f (overwrites existing keys!); -u \"<user name>\"; -r \"<RA-Email-address>\"; -d <directory for files>; -h (help)\n";	
	print "Example: openssl_generate_user_req.pl -u \"Karl Schwarzschild\" -i AIP\ -r henke\@aip.de\n";
	print "Remember to put quotes around your name.\n";
	print "For a list of OUs and RAs see  http://grid.fzk.de/ca/RA.html\n";
	die "\n";
 }


my $loc_ra=($opt_r)?$opt_r:$ra_email_default;
my $hdr=$ENV{'HOME'};
my $dr="$hdr/.globus";
my $tdr="/tmp/certs";
my $usr=$ENV{'LOGNAME'};
my $inst =$opt_i;
if(! $opt_f){ 
	my $pemcheck=&_check_pems($dr);
	if (! $pemcheck) {
	print "*** ERROR: A certificate request and a key file already exist.\n";
	print "           To overwrite your current files, please call the program again using the \"-f\" option\n";
	die "\n";
	}
} 

if(!($opt_i && $opt_u))
{
  print "Insufficient input: \"-u\" (user) and \"-i\" (institute) are mandatory and must be specified. Please use:\n";
  print "        openssl_generate_user_req.pl -u \"\<FirstName LastName\>\" -i <Institute_Acronym>\n";
  print "Remember to put quotes around your name.\n";
  print "For details use \'openssl_generate_user_req.pl -h\' or contact your local Registration Authority\n";
  die "\n";
 }

if (! $opt_r){
	print "\aWarning: RA's email address was not specified. Using $ra_email_default as default.\n";
	print "\nWe advise you to look up your local RA's email address at  http://grid.fzk.de/ca/RA.html\n";
	print "and call the script using the additional option \"-r <RA-mail-address>\"\n";
	print "\nTo interrupt you can press CTRL-C\n";
	sleep 3;
	}


$pwd=(-d $opt_d)?$opt_d:$pwd;
my $CN=$opt_u;
my $sw=($opt_f)?" -force":"";	

#first: generate config for the user
if(! -d $dr){
my $rtv = mkdir($dr);
die "*** ERROR: Not allowed to create $dr, cannot proceed\n" if(!$rtv);
}
if(! -d $tdr){
my $rtv = mkdir($tdr);
die "Not allowed to create $tdr, cannot proceed\n" if(!$rtv);
}
my $userconf=&gen_conf_usr($CN,$inst);
#my $hostcnf=&gen_conf_host($inst);

#second: generate request with openssl
my $rtv= &create_certs($dr,$tdr,$userconf,$ossl,$loc_ra);

#third: collect request to $dir with prefix usr
$CN=~ s/\ /\_/g;
my $reqf="$dr/$CN\_usercert_request.pem";
system("cp $dr/usercert_request.pem $reqf");
print "\n\n*************************************************************************************\n";
print "Please check if your NAME (CN) and ORGANISATION (OU) are correct in your name string:\n\n";
system("grep \/O=GermanGrid $reqf");
print "\n*************************************************************************************\n";
print "\nIf there are any errors, call the script again with the \"-f\" option and supply your correct username.\n";
print "For more information, try \"openssl_generate_user_req.pl -h\" or contact your local Registration Authority\n";
print "\nIf everything is correct, please email the new request file\n     \"$reqf\"\nas an attachment to your local Registration Authority: \n";

if ($loc_ra eq $ra_email_default) {print "     ** Look up the email address at  http://grid.fzk.de/ca/RA.html **\n\n";}
else {print "$loc_ra.\n\n";}

print "In that mail you must also include your office phone number.\n\n";
print "In a few days you will recieve an email from the Root Certificate Authority with your signed certificate.\n\n";

# print "Please email the request \n $reqf  \nto your local Registration Authority. \nIt will be then forwarded (signed) to the root-CA\n";
1;
sub _check_pems{
my ($dr)=@_;
chdir($dr);
my $flist=`ls *.pem 2>/dev/null`;
my @ar=split(/\n/,$flist);
return 1 if $#ar < 1;
my $c=0;
for my $f (@ar){
	$c++;
	$c++ if(-s $f);
	next;
}	

# return 0 for all files nonzero(likely a valid certpair), otherwise return 1
#return ($c % 2);
return 0;
}

sub gen_conf_usr{
my ($cn,$inst)=@_;
my $conf='
#
# SSLeay example configuration file.
# This is mostly being used for generation of certificate requests.
#

RANDFILE		= $ENV::HOME/.rnd

####################################################################
[ ca ]
default_ca	= CA_default		# The default ca section

####################################################################
[ CA_default ]

dir		= ./demoCA		# Where everything is kept
certs		= $dir/certs		# Where the issued certs are kept
crl_dir		= $dir/crl		# Where the issued crl are kept
database	= $dir/index.txt	# database index file.
new_certs_dir	= $dir/newcerts		# default place for new certs.

certificate	= $dir/cacert.pem 	# The CA certificate
serial		= $dir/serial 		# The current serial number
crl		= $dir/crl.pem 		# The current CRL
private_key	= $dir/private/cakey.pem# The private key
RANDFILE	= $dir/private/.rand	# private random number file

x509_extensions	= x509v3_extensions	# The extentions to add to the cert
default_days	= 365			# how long to certify for
default_crl_days= 365 # DEE 30	# how long before next CRL
default_md	= sha1			# which md to use.
preserve	= no			# keep passed DN ordering

# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy		= policy_match

# For the CA policy
[ policy_match ]
countryName		= optional
stateOrProvinceName	= optional
organizationName	= match
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

# For the \'anything\' policy
# At this point in time, you must list all acceptable \'object\'
# types.
[ policy_anything ]
countryName		= optional
stateOrProvinceName	= optional
localityName		= optional
organizationName	= optional
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

####################################################################
[ req ]
default_bits		= 2048
default_keyfile 	= privkey.pem
distinguished_name	= req_distinguished_name
req_extensions          = v3_req

# TO DO:  [IN 251006]
# - Set org name default GermanGrid without user input
# - Suppress org name level 1 and 2
# - Auto-detect common name?

[ req_distinguished_name ]
# BEGIN CONFIG
0.organizationName               = Level 0 Organization
0.organizationName_default       = GermanGrid
0.organizationalUnitName          = Institute Acronym
0.organizationalUnitName_default = __INST__
commonName                      = Name (First Last)
commonName_default              = __CN__
commonName_max                  = 64
# END CONFIG

[ v3_req ]
nsCertType                      = objsign,email,server,client
basicConstraints                = critical,CA:false
';

$conf=~ s/__INST__/$inst/;
$conf=~ s/__CN__/$cn/;
my $cf="$tdr/_usr_cert_conf";
open(FO,">$cf");
print FO $conf;
close(FO);
return $cf;
}

###########################################################
# create_request_header
###########################################################
#shamelessly changed to perl-syntax from globus-cert-request
sub create_request_header {
my ($loc_ra,$certf,$subject,$ossl)=@_;
my $hdr='  
This is a Certificate Request file:

It should be mailed to _LOCAL_RA_


=========================================================================
Certificate Subject:

    __SUBJECT__

The above string is known as your user certificate subject, and it
uniquely identifies this user.

To install this user certificate, please save this e-mail message
into the following file.


__CERT_FILE__


      You need not edit this file in any way. Simply
      send an e-mail with this file attached to _LOCAL_RA_



If you have any questions about the certificate contact
the LOCAL_RA at _LOCAL_RA_.

';
$hdr=~ s/_LOCAL_RA_/$loc_ra/g;
$hdr=~ s/__CERT_FILE__/$cert/g;
$hdr=~ s/__SUBJECT__/$subject/g;
return $hdr;
}


###########################################################
# create_certs
#   Create the certificate, key, and certificate request
#   files
###########################################################
#shamelessly changed to perl-syntax from globus-cert-request
sub create_certs {
my ($dr,$tdr,$userconf,$ossl,$loc_ra)=@_;

my $KEY_FILE="$dr/userkey.pem";
my $CERT_FILE="$dr/usercert.pem";
my $REQU_FILE="$dr/usercert_request.pem";
my $REQU_OUT="$tdr/usercert_request.out";
my $RAND_TEMP="$tdr/RandFile";
my $SSL_EXEC ="$ossl";

# commented out IN 251006
#print ' 
#         "A certificate request and private key is being created."
#         "You will be asked to enter a PEM pass phrase."
#         "This pass phrase is akin to your account password, "
#         "and is used to protect your key file."
#         "If you forget your pass phrase, you will need to"
#         "obtain a new certificate."
#';        


    #------------------------
    # Create the Certificate File
my $rtv =  system("umask 022; touch $CERT_FILE");


    #------------------------
    # Create some semi random data for key generation 
	my $rtv =  system("umask 066; touch $RAND_TEMP");
    if ( -e "/dev/urandom" ){
       my $cmd="head -c 1000 /dev/urandom >> $RAND_TEMP 2>&1";
	   system($cmd);
	 }
	 my $cmd ="date >> $RAND_TEMP 2>&1
    	netstat -in >> $RAND_TEMP 2>&1
    	ps -ef >> $RAND_TEMP 2>&1
    	ls -ln $ENV{HOME} >> $RAND_TEMP 2>&1
    	ls -ln /tmp >> $RAND_TEMP 2>&1
    	umask 266
		";
		$rtv=system($cmd);	
    #------------------------
    # Create the Key and Request Files
	# Trick to avoid interactive questions for fields already filled
	my $REQUA = "$tdr/req_answer";
	system("echo \"\n\n\n\n\"$CN\"\" > $REQUA");
	my $cmd="
        $SSL_EXEC req -new -keyout $KEY_FILE -out $REQU_OUT -rand $RAND_TEMP  -config $userconf < $REQUA
		umask 266
	";	
	$rtv = system($cmd);

    if ($rtv){
		print "Error number $rtv was returned by $SSL_EXEC\n";                    
        die "exiting, something wrong with your ssl\n";
    }

    
    
    #------------------------
    # Insert instructions into the request file


    my $SUBJECT=`${SSL_EXEC} req -text -noout < '$REQU_OUT' 2>&1 | grep 'Subject:' | awk -F: '{print $2}' | cut -c2- `;
	chomp($SUBJECT);
	$SUBJECT =~ s/.*\:\ //;
	$SUBJECT =~ s/,\ /\//g;
	$SUBJECT = "/".$SUBJECT;
	print "DN:>$SUBJECT<\n";
#	die "\n";
    #Convert the subject to the correct form.

    my $REQU_HEAD = &create_request_header($loc_ra,$certf,$SUBJECT, $ossl);

    # Finalize the Request file.
	open(FI,"<$REQU_OUT");
	my @ar=<FI>;
	close(FI);
	open(FO,">$REQU_FILE");
	print FO $REQU_HEAD;
	print FO (join " ",@ar);
	close(FO);
	$rtv=system("rm -rf $tdr");
}


