#!/usr/bin/perl 

#  renewUserCert.pl - automatically get a new certificate issued based on the
#  valid X.509 certificate that is used to authenticate the connection.
#
# Generates a certficate request based on a current X.509 certificate and
# submits it to an iPlanet CA server. The current key is used for the
# new certificate unless the -newkey option is specified.

# any parameters (except debug, help and dir) that are omitted
# will be promted for.
#
#  Sample Usage:
#       renewUserCert  - will renew your usercert.pem in your .globus directory
#                    it will prompt for the passphrase that was used to
#                    encypt userkey.pem
# -email           - the user's email address
# -phone           - the user's 10 digit phone number
# -passin pass     - file containing password for the currrent private key
#                      if omitted, openssl will prompt for a passphrase
# -passout pass   - file containing password for the new private key
#                      if omitted, openssl will prompt for a passphrase
#                      only used if the -newkey switch is given.
# -cert            - Certificate that is to be replaced
#                       Defaults to usercert.pem
# -key             - private key corresponsding to the certificate
#                       Defaults to userkey.pem
# -dir             - the directory  in which to find the key and cert to be
#                     replaced and to store the new one.
#                     defaults to $HOME/.globus
# -prefix          - Causes the certificate to be renewed to be
#                     <prefix>cert.pem and <prefix>key.pem
#                     instead of usercert.pem and userkey.pem
# -newkey          - if given a  new key will be generated.
#                    if omitted the current key will be used for the new cert
# -debug           - sets the debug flag, causing lots of debugging info
#                    to be spewed to the console
# -help            - will print a usage message

# modified version - Dhiva sep 16, 2004 dhiva@es.net
#                      Modified the requestCert script to talk to the replace
#                      cert interface.
# change History  : HIDDEN parameters - more parameters have been included
#                   removed $OPTFIELDS, $SUBJECT, $USERID 
#                   no need to ask for the Full Name ot generate random number
#                   because this interface preserves the 
#                   subjectDN from the current certificate  
# 
#  version 1.0 - Oct, 2004 mrthompson@lbl.gov
# based on Dhiva's UserGridEnrollment and Steve Chan's makecert.pl scripts
#
use Getopt::Long;

# url for the CA server's user certificate request interface

#use the following two lines for the fusionGrid CA
#my $baseURL = "https://jade.es.net:9002/enrollment";  # fusion CA
#my $dc2 = "FusionGrid";

#use the following two lines for the DOEGrids CA
my $baseURL = "https://pki1.doegrids.org/enrollment"; # doegrids CA
my $dc2 = "doegrids";

# files used for openssl certificate request

my $credDir =  $ENV{HOME}."/.globus";
my $usercert = "usercert.pem";
my $userkey = "userkey.pem";
my $newkey = "newuserkey.pem";
my $newcert = "newusercert.pem";
my $reqfile = "/tmp/".$$."req.pem";
my $tempCertKey = "/tmp/certkey.$$";
my $passinarg ="";
my $passoutarg ="";
my $password ="";
my $prefix="";
my $randfile = "/tmp/random.$$";
my $sslconf = "/tmp/sslconf.$$";
my $verbose = "-s";
my $insecure ="-k";
my $gennewkey = 0;

# file for output from the certificate request
my $output_file = "/tmp/response.$$.html";

my $curlVersion = `curl --version | awk \'{ print \$2 }\'`;

###########################################################
# SUBSCRIBER PERSONAL INFORMATION
###########################################################
my $csrRequestorName="NOTUSED";
my $csrRequestorEmail="";
my $csrRequestorPhone="";
my $csrRequestorAffiliation="FusionGrid";

#############OPTIONAL-FIELDS###############################
###########################################################
my $COMMENT="csrRequestorComments=None";
my $PWDFLDS="challengePassword=''&confirmChallengePassword=''";

$PWDFLDS =~ s/ //g;
$COMMENT =~ s/ /+/g;

$OPTFIELDS="-d \"$COMMENT&$PWDFLDS\"";

###########################################################
# Hidden form fields - These are all defaults
# Standard Parameters required by the Service 
#  ********** DO NOT CHANGE THESE VALUES ******************
###########################################################
my $HIDDEN = "requestFormat=clientAuth&
certType=client&
certauthEnrollType=single&
certauthEnroll=on&
ssl_client=true&
ssl_server=true&
digital_signature=true&
non_repudiation=true&
key_encipherment=true&
data_encipherment=true&
AuthServerRequest=false&
doSslAuth=on&
authenticator=UidPwdDirAuth&
uid=doegridsuser&
pwd=doegridsuser&
email=true";

###########################################################
# replace the spaces with + symbol
###########################################################

$HIDDEN =~ s/\s//g;
$HIDDEN = "-d \"$HIDDEN\"";

# hash for the command line args initialized to null strings
my @keys = qw(email phone cert key passin passout dir prefix );
my %args;
@args{@keys} = ("") x @keys;

GetOptions( \%args,"email=s","phone=s","cert=s","key=s","passin=s","passout=s","dir=s", "prefix=s", "newkey+","debug+","help+");

if ( $args{help}) {
  usage();
  exit 1;
}
if ($args{debug}) {
  $verbose = "-v";
}
if ($args{newkey}) {
  $gennewkey = 1;
}
if ( $args{prefix} ) {
  $usercert = $args{prefix} . "cert.pem";
  $userkey = $args{prefix} . "key.pem";
  $newcert = $args{prefix} . "newcert.pem";
  $newkey = $args{prefix} . "newkey.pem";
}
if ($args{key}) {
  $userkey = $args{key};
  $newkey = "new" . $userkey;
}
if ($args{cert} ) {
  $usercert = $args{cert};
  $newcert = "new" .  $usercert
}
if ($args{dir}) {
  $credDir = $args{dir};
  if ( ! -d $credDir) {
    printf "$credDir does not exist\n";
    exit 1;
  }
}
$newkey = $credDir."/". $newkey;
$newcert = $credDir."/" . $newcert;
$usercert = $credDir . "/" . $usercert;
$userkey = $credDir . "/" . $userkey;

if (! -r $usercert) {
  print "$usercert does not exist\n";
  exit 1;
}
if ( ! -r $userkey) {
  print "$userkey does not exist\n";
  exit 1;
}
if ( $args{passin}){
  $passinarg = "-passin file:$args{passin}";
} else {
   print "Enter passphrase for $userkey: ";
   system "stty -echo";
   chomp( $password = <STDIN>);
   print "\n";
   system "stty echo";
   $passinarg = "-passin pass:$password";
}
if ($args{passout}) {
  $passoutarg = "passout file:$args{passout}";
}
else {
  if ($gennewkey){
    print "you will be  prompted for a passphrase to encyrpt the private key that is being generated.\n";
  }
}

#combine cert and  key into one file for use by curl for https client-side auth
`cat $usercert $userkey > $tempCertKey`;
`chmod 600 $tempCertKey`;

# curl started requiring the server CA cert in verion 7.10, the -k switch overides the check
@vers = split /\./, $curlVersion;
if ($vers[0] <= 7) {
  if ($vers[1] <= 9 ) {
    $insecure = "";
  }
}

# setup config file for the openssl stuff
umask 0x600;

open SSLCONF, ">$sslconf" || die "Could not setup openssl config file $sslconf";
    
print SSLCONF <<EOT1;
#
# SSLeay configuration file.
# Dynamically generated for certificate issuing script
#

RANDFILE         = $randfile
####################################################################
[ req ]
default_bits            = 2048
default_keyfile         = $newkey
distinguished_name      = req_distinguished_name
prompt                  = no
encrypt_key             = yes
[ req_distinguished_name ]
# BEGIN CONFIG
1.DC = org
2.DC = $dc2
OU = People
CN = NOTUSED

# END CONFIG
EOT1

close SSLCONF;
  

# get some entropy for the random seed

`date > $randfile`;
`head -n1000 /dev/urandom >> $randfile` if ( -e "/dev/urandom" );
`ps -aux >> $randfile`;
`ls -ln /tmp >> $randfile`;

# Generate certificate request
my $cmd;
if ($gennewkey) {
  ($cmd) = sprintf("openssl req -new $passoutarg -out %s -config %s",$reqfile,$sslconf);
} else {
  ($cmd) = sprintf( "openssl req -new $passinarg -key %s -out %s -config %s",$userkey,$reqfile, $sslconf);
}
  if ($args{debug}) {
    print $cmd."\n";
  }
  $res = system( $cmd );

  if ($res) {
    die "Error while executing $cmd";
  }
if ($gennewkey){
  `chmod 600 $newkey`;
}

unlink $sslconf;

# Get the rest of the input arguments

while  (! grep /@/, $args{email} ) {
  print "input your email address: ";
  $args{email} = <STDIN>;
  $args{email} =~ s/^\s*(.*?)\s*$/$1/;
}
$csrRequestorEmail = $args{email};
while ( length( $args{phone}) < 10 ) {
  print "input your 10 digit phone number: ";
  $args{phone} = <STDIN>;
  $args{phone} =~ s/^\s*(.*?)\s*$/$1/;
}
$csrRequestorPhone = $args{phone};

;

my $FORMFIELDS="csrRequestorName=$csrRequestorName&
csrRequestorEmail=$csrRequestorEmail&
csrRequestorPhone=$csrRequestorPhone&
csrRequestorAffiliation=$csrRequestorAffiliation&
SponsorEmail= &
SponsorPhone= &
SponsorName= ";

$FORMFIELDS =~ s/\&\s/\&/g;
$FORMFIELDS =~ s/\s/+/g;
$FORMFIELDS = "-d \"$FORMFIELDS\"";

# Build a command line for curl

# XXX Solaris grep does not take -A switch
$CR=`grep -A100 BEG $reqfile | grep -B100 END`;

$CR =~ s/\n/%0d%0a/g;
$CR =~ s/\+/%2B/g;
$CR =~ s/\//%2F/g;
$CR =~ s/=/%3D/g;
$CR =~ s/ /+/g;

$CR = "-d pkcs10Request=$CR";

# the -k (insecure) switch turns off curl's check for a trusted CA on the server
# certificate when using https. curl looks in .../share/curl/cur-ca-bundle.crt 
# by default.
# you can give it a different ca bundle by using the -cacert option.
# if it can't find a bundle at all, it will skip the check.

$passinarg ="";
if ($password ne ""){
  $passinarg = "--pass $password";
} elsif ( $args{passin}){
  if (open PD, $args{passin}) {
    chomp( $password = <PD>);
    $passinarg = "--pass $password";
    close PD;
  } else {
    print "unable to open $args{passin}. Curl will prompt for password\n";
  }
} else {
  print "Connecting to the CA. You will be prompted for the passphrase
for $userkey.\n";
}

$curlcmd = "curl $verbose $insecure -S --random-file $randfile --cert $tempCertKey $passinarg $HIDDEN $CR $FORMFIELDS $baseURL > $output_file";

if ($args{debug}) {
    print $curlcmd."\n";
}

$res = system($curlcmd);

unlink $tempCertKey, $randfile;

if ($res) {
    die "Error executing $curlcmd";
}

open OF, $output_file or die "can't open $output_file $!\n"; 
@output = <OF>;

if ($args{debug}) {
    print @output;
}

unlink $reqfile, $output_file;

###########################################################
# Copy out the certificate if the call was successful
# otherwise print the error message
###########################################################
@CMS_error = grep /^fixed\.unexpectedError/, @output;
if ( @CMS_error == 1) {
  print "Unable to retrieve certificate. Please check the certificate number.\n";
  $CMS_error[0] =~ s/^fixed\.unexpectedError = //;
  print $CMS_error[0] . "\n";
  exit 1;
}
@CMS_Policy_Error_Info = grep /^record\.policyMessage/, @output;
if ( @CMS_Policy_Error_Info == 1){
  $CMS_Policy_Error = $CMS_Policy_Error_Info[0];
  $CMS_Policy_Error =~  s/record.policyMessage.*?=//;
  $CMS_Policy_Error =~ s/<.*?>//g;    # get rid of html tags
  $CMS_Policy_Error =~ s/;//g ;
  print "$CMS_Policy_Error\n";
  exit 1;
}
@retCert = grep /^record\.base64Cert/, @output;
if ( @retCert == 0 ) {
  print "Request to CA failed.\nDumping reply html page\n\n";
  if (! $args{debug}) {
    print @output;
  }
  exit 1;
}
$retCert[0] =~ s/.*\"(.+)\".*/$1/;
$retCert[0] =~ s/\\n/\n/g;

open CERTFILE, ">$newcert"
         || die "Could not write to $newcert\n";
print CERTFILE $retCert[0] . "\n";
close CERTFILE;

print "your renewed certificate is in $newcert
You can move it to $usercert to replace your 
current one\n";
if ($gennewkey){
  print "Your new key is in $newkey and should be moved to 
$userkey\n";
}
print "\nPlease ignore the mail that you will receive from the CA. It
only applies to certificate requests that were made via a web browser\n";

################### Usage ###############################
sub usage {
  print "usage is :
       -cert <certificate to be replaced>
                 defaults to usercert.pem
       -key  <private key corresponding to certificate>
                 defaults to userkey.pem
       -passin <optional file containing passphrase for current private key>
       -passout <optional file containing passphrase for nes private key>
                only used if the -newkey switch is given
       -email <user's email> required
       -phone <user's phone> required
       -prefix Causes the certificate to be renewed to be 
                 <prefix>cert.pem and <prefix>key.pem
                 instead of usercert.pem and userkey.pem
       -dir  <optional directory in which to find and store the 
                 certificate and key>
                 defaults to $ENV{HOME}/.globus
       -newkey generate a new key for the new certificate
       -debug spews lots of output to the terminal
       -help  prints this message
       any omitted and required arguments will be prompted for\n";
}