#!/bin/sh
#
# Copyright (c) FOM-Nikhef 2014-
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Authors:
# 2014-
#    Mischa Sall\'e <msalle@nikhef.nl>
#    NIKHEF Amsterdam, the Netherlands
#    <grid-mw-security@nikhef.nl>
#

# cmdline adaptable defaults:
VERB=0
user=""
PREFIX="user:"
X509_USER_CERT=${X509_USER_CERT:-$HOME/.globus/usercert.pem}
X509_USER_KEY=${X509_USER_KEY:-$HOME/.globus/userkey.pem}
PROXY_FILE=/tmp/x509up_u$(id -u)
PROXY_PATHLENGTH=-1			# PROXY_PATHLENGTH -1 for infinite
PROXY_INFO=""				# set automatically based on pcPathLen
PROXY_POLICY=normal_policy		# Proxy type: normal or limited

# Other defaults:
BITS=1024				# RSA keylength
HASH=sha256				# hash algorithm

# Programs
OD=$(which od)
TR=$(which tr)
RM=$(which rm)
CAT=$(which cat)
SED=$(which sed)
MKTEMP=$(which mktemp)
BASENAME=$(which basename)
OPENSSL=$(which openssl)

# Script name
prog=$($BASENAME $0)

########################################################################

# Usage function
usage()	{
    echo "Usage: $($BASENAME $0) <options>"
    echo "Options:"
    echo " -h          print this help text"
    echo " -v          be verbose"
    echo " -u <user>   username, added after \"/CN=${PREFIX}\", mandatory option"
    echo " -P <prefix> prefix in /CN field, default: \"$PREFIX\""
    echo " -c <cert>   robot certificate, default: \"$X509_USER_CERT\""
    echo " -k <key>    robot key, default: \"$X509_USER_KEY\""
    echo " -p <proxy>  PUSP proxy filename, default: \"$PROXY_FILE\""
    echo " -l          create limited proxy, default: normal (impersonation) proxy"
    if [ "$PROXY_PATHLENGTH" = "-1" ];then
	echo " -L <length> PUSP proxy pathlength constraint, default: infinite"
    else
	echo " -L <length> PUSP proxy pathlength constraint, default: $PROXY_PATHLENGTH"
    fi
    exit 0
}

# Called for invalid options
illoption() {
    case $1 in
        :)  echo "$prog: option \`-$2' requires an argument" >&2 ;;
        ?)  echo "$prog: invalid option -- $2" >&2 ;;
    esac
    echo "Try \`$prog -h' for more information" >&2
    exit $3
}

# Verbose log
verb()	{
    if [ $VERB -eq 1 ];then
	echo "$*"
    fi
}

# Get command line options
while getopts ":hvu:c:k:p:P:lL:" i $*;do
    case $i in
	h)  usage ;;
	v)  VERB=1 ;;
	u)  user=$OPTARG ;;
	c)  X509_USER_CERT=$OPTARG ;;
	k)  X509_USER_KEY=$OPTARG ;;
	p)  PROXY_FILE=$OPTARG ;;
	P)  PREFIX=$OPTARG ;;
	l)  PROXY_POLICY=limited_policy ;;
	L)  PROXY_PATHLENGTH=$OPTARG ;;
	:)  illoption $i $OPTARG 2 ;;
	?)  illoption $i $OPTARG 2 ;;
    esac
done

# Check mandatory arguments
if [ -z "$user" ];then
    echo "$prog: Missing mandatory option -u <user>" >&2
    echo "Try \`$prog -h' for more information" >&2
    exit 1
fi

# Set correct pcPathLen parameters
if [ "$PROXY_PATHLENGTH" = "-1" ];then
    PROXY_INFO=rfc3820_seq_sect_infinite
elif [ "$PROXY_PATHLENGTH" -ge 0 ] 2> /dev/null ;then
    PROXY_INFO=rfc3820_seq_sect
else
    echo "$prog: Invalid proxy pathlength constraint: \"$PROXY_PATHLENGTH\"" >&2
    echo "Try \`$prog -h' for more information" >&2
    exit 1
fi
    

# Subject of payload proxy
PROXY_CN="/CN=${PREFIX}$user"

# Set nameopts for getting subject from proxy
NAMEOPTS="esc_2253,esc_ctrl,utf8,dump_nostr,dump_der,sep_multiline,sname"

# New serial number: use 4 random bytes
SERIAL=$($OPENSSL rand 4|$OD -t u4 -A n|$TR -d '[:space:]')

# Enforce umask
umask 077

# Different tempfiles: put in separate directory in $TMPDIR or /tmp
PROXYTMPDIR=$($MKTEMP -d --tmpdir create_pusp_XXXXXX)
OPENSSL_CONF=$($MKTEMP --tmpdir=$PROXYTMPDIR openssl.cnf.XXXXXX)
PROXYREQ=$($MKTEMP --tmpdir=$PROXYTMPDIR proxyrequest.XXXXXX)
PROXYKEY=$($MKTEMP --tmpdir=$PROXYTMPDIR proxykey.XXXXXX)
PROXYCERT=$($MKTEMP --tmpdir=$PROXYTMPDIR proxycert.XXXXXX)
LOGFILE=$($MKTEMP --tmpdir=$PROXYTMPDIR logfile.XXXXXX)

cleanup()   {
    # Don't do a rm -rf for safety 
    for f in "$OPENSSL_CONF" "$PROXYREQ" "$PROXYKEY" "$PROXYCERT" "$LOGFILE";do
	if [ -n "$f" -a -f "$f" ];then
	    $RM "$f"
	fi
    done
    rmdir $PROXYTMPDIR || {
	echo "Cleanup of $PROXYTMPDIR failed" >&2
    }
}

myexit()    {
    cleanup
    exit $1
}

# Create OpenSSL config file on the fly. Need RFC compliant proxy.
$CAT > $OPENSSL_CONF << EOF
extensions = rfc3820_proxy

[ rfc3820_proxy ]
keyUsage = critical,digitalSignature,keyEncipherment
1.3.6.1.5.5.7.1.14 = critical,ASN1:SEQUENCE:$PROXY_INFO

[ rfc3820_seq_sect ]
field1 = INTEGER:$PROXY_PATHLENGTH
field2 = SEQUENCE:$PROXY_POLICY

[ rfc3820_seq_sect_infinite ]
field1 = SEQUENCE:$PROXY_POLICY

[ normal_policy ]
p1 = OID:1.3.6.1.5.5.7.21.1

[ limited_policy ]
p1 = OID:1.3.6.1.4.1.3536.1.1.1.9
EOF

# Get subject from input proxy
SUBJ=$($OPENSSL x509 -in $X509_USER_CERT -noout -subject -nameopt $NAMEOPTS|\
       $SED 's+/+\\/+g'|$SED '1d;s:^ *:/:'|$TR -d '\n')
if [ -z "$SUBJ" ];then
    echo "Getting subject of $X509_USER_CERT failed" >&2
    myexit 1
fi
verb "Got subject \"$SUBJ\""

# Create certificate signing request
verb "Generating $BITS bits RSA key and request"
$OPENSSL req \
    -utf8 -new -nodes -newkey rsa:$BITS -subj "${SUBJ}${PROXY_CN}" \
    -keyout $PROXYKEY -out $PROXYREQ 2> $LOGFILE || {
	echo "Creating request failed, logfile:" >&2
	$CAT $LOGFILE >&2
	myexit 1
    }

# Sign certificate signing request, creating proxy certificate
verb "Signing key and request to create proxy cert"
$OPENSSL x509 \
    -req -CAkeyform pem -in $PROXYREQ -out $PROXYCERT \
    -CA $X509_USER_CERT -CAkey $X509_USER_KEY \
    -set_serial $SERIAL -days 1 -$HASH \
    -extfile $OPENSSL_CONF 2> $LOGFILE || {
	echo "Signing request failed, logfile:" >&2
	$CAT $LOGFILE >&2
	myexit 1
    }

# Add new cert and key to proxy file
$CAT $PROXYCERT $PROXYKEY > $PROXY_FILE

# Append certificate only parts of input certificate
doprint=0
$CAT $X509_USER_CERT | while read line ; do
    if [ "$line" = "-----BEGIN CERTIFICATE-----" ];then
	echo "$line"
	doprint=1
    elif [ "$line" = "-----END CERTIFICATE-----" ];then
	echo "$line"
	doprint=0
    elif [ $doprint -eq 1 ];then
	echo "$line"
    fi
done >> $PROXY_FILE

# Cleanup temp files
cleanup

verb "Proxy is left in $PROXY_FILE"
