#!/bin/bash
#############################################################################
#
# Copyright Avaya Inc., All Rights Reserved.
#
# THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Avaya Inc.
#
# The copyright notice above does not evidence any actual or intended
# publication of such source code.
#
# Some third-party source code components may have  been modified from their
# original versions by Avaya Inc.
#
# The modifications are Copyright Avaya Inc., All Rights Reserved.
#
#############################################################################

#
# What: csrmanage
#
# DESCRIPTION:
#       this is a shell script to manage Certificate Signing Requests
#
# Dependencies:
#
# The script uses a modified version of the standard CM "csrmanage" script from
# "/opt/ecs/bin/csrmanage" that has been moved to "/opt/util/bin/csrmanage" to
# create/print/decode/delete CSR.

# Aliases for commands
OPENSSL_CMD='/usr/bin/openssl'
BASENAME_CMD='/bin/basename'
DATE_CMD='/bin/date'
FIND_CMD='/usr/bin/find'
LOGGER_CMD='/bin/logger'
RM_CMD='/bin/rm'
GREP_CMD='/usr/bin/grep'
LS_CMD='/usr/bin/ls'
COLUMN_CMD='/usr/bin/column'
CP_CMD='/usr/bin/cp'
CAT_CMD='/usr/bin/cat'

# Define script name
SCRIPTNAME=$($BASENAME_CMD $0)

# definition of boolean values
TRUE=0
FALSE=1

# definition of error codes
RC_SUCCESS=0
RC_ERROR=1

# Definition of log levels
#LOG_DEBUG="debug"
#LOG_INFO="info"
#LOG_ERROR="error"

CSR_DIR="/etc/opt/certs/signing_requests"
CSR_EXT="csr"
KEY_EXT="key"
SSL_CFG="/etc/opt/openssl.cnf"
CSR_LIST="/etc/opt/certs/.CSRLIST"

################################################################################
# Logging and debug output
#function log
#{
#level=$1
#message=$2

# No debug output withou debug option
#if [ $level = $LOG_DEBUG ] && [ $debug -eq $FALSE ]
#then
#	return
#fi	

# Write to stderr only with debug option
#if [ $debug -eq $TRUE ]
#then
	logger="$LOGGER_CMD -p auth.$level -s -t $SCRIPTNAME"
#else
	logger="$LOGGER_CMD -p auth.$level -t $SCRIPTNAME"
#fi

#$logger "($level) $message"
#}

################################################################################
# print usage if given the wrong arguments or if help was requested
function usage
{
USAGE=\
"Usage: csrmanage [ create -subj 'string' -keysize number -hash digest 
                    -name string [-ca] ]
                 [ print | decode | delete -name string ]
                 [ generate | list ]
                 [ -h | -d ]
       create   : Create a new key and CSR with given keysize and subj
       generate : Interactive mode to create CSR
       list     : Print a colon sperated list of all CSRs names
       print    : Print the Base64 content of the CSR file of a given name
       decode   : Print the decoded content of the CSR file of a given name
       delete   : Delete CSR file and key of a given name
       -subj    : New CSR subject line. Use quotes to protect the string
       -keysize : New CSR key size (2048, 3072 or 4096)
       -hash    : Type of message digest to use (sha256, sha384, or sha512)
       -ca      : New CSR is capable for a certificate authority 
       -name  : CSR name for print, decode and delete
       -h       : Usage (this)
"
echo "$USAGE" >&2
}

################################################################################
# Validate length of  CSR fields/attributes
function lenCheck
{
 varname="$1"
 varval="$2"
 varval="$(echo -e "${varval}" | tr -d '[:space:]')"
 len=${#varval}
 if [ "$varname" = "country" ] && [ $len -ne 2 ];then
   echo "Invalid Country Name value. The value does not have exactly 2 characters or contains invalid characters ('&\`^$;*\"%#"'!'").CSR creation failed."
   exit $RC_ERROR
 elif [ $len -gt 30 ];then
   echo "Invalid $varname. Value of more than 30 characters is not allowed.CSR creation failed."
   exit $RC_ERROR
 fi
}

################################################################################
# Check CSR fields/attributes values are CLI safe
function cliSafe
{
 varname="$1"
 varval="$2"
 if [[ "$varval" == *[''\&``\^\!$\;*\"\%#]* ]] || [[ "$varval" =~ [[:cntrl:]] ]] || [[ ! "$varval" =~ [[:print:]] ]]
 then
   echo "$varname contains invalid character(s) ('&\`^$;*\"%#"'!'").CSR creation failed."
   exit $RC_ERROR
 fi
 
 if [[ "$varname" == "name" ]] && [[ "$varval" =~ [[:space:]] ]];then
   echo "Space not allowed in $varname. CSR creation failed."
   exit $RC_ERROR
 fi
}

################################################################################
# Create a private key and a CSR
function create
{
usr_handle="$name"
# Create new random name and define file names
name=$($DATE_CMD +%s)_$RANDOM
csrfile="$CSR_DIR/$name.$CSR_EXT"
keyfile="$CSR_DIR/$name.$KEY_EXT"

#log $LOG_DEBUG "Creating new CSR with name: $name"

# Extension for a CA capable request
if [ $ca -eq $TRUE ]; then
	extension="avaya_req_ca"
else
	extension="avaya_req"
fi	

# Generate key
$OPENSSL_CMD genrsa -rand /dev/urandom -out $keyfile $keysize &> /dev/null
rc=$?

# Report errors
if [ $rc -ne $TRUE ]; then
#	log $LOG_ERROR "command $OPENSSL_CMD genrsa returned $rc"
	echo "Privake key creation failed."
	return $RC_ERROR
fi

chmod 600 $keyfile

# Generate request
$OPENSSL_CMD req -new -batch -nodes -config $SSL_CFG \
-keyform PEM -outform PEM -$digest -reqexts $extension \
-key $keyfile -out $csrfile -subj "$subject"

# Report errors
rc=$?
if [ $rc -ne $TRUE ]; then
	#log $LOG_ERROR "command $OPENSSL_CMD req returned $rc"
	echo "CSR creation failed."
	$RM_CMD -f $keyfile
	return $RC_ERROR
fi

$CP_CMD -f $csrfile /var/tmp/"$usr_handle".$CSR_EXT
rc=$?
chmod 644 /var/tmp/"$usr_handle".$CSR_EXT
if [ $rc -ne $TRUE ]; then
	echo "Unable to copy generated CSR at /var/tmp."
	$RM_CMD -f $csrfile $keyfile
        return $RC_ERROR
else
	echo "CSR created successfully and available at /var/tmp/"$usr_handle".$CSR_EXT ."
fi

generated_date=`$LS_CMD -l --time-style="+%d-%b-%Y" $csrfile | cut -d' ' -f6`
echo -e "$usr_handle:$name:$generated_date" >> $CSR_LIST

return $RC_SUCCESS
}

################################################################################
# List the handles of outstanding CSRs
function list
{

list_exists=`sed '/^\s*$/d' $CSR_LIST | wc -l`
if [ $list_exists -le 1 ];then
	echo "No CSR available at present."
	return $RC_ERROR
fi

if [ -f /tmp/csrlist.out ]
then
	$RM_CMD -f /tmp/csrlist.out
fi
echo -e "Name:Common Name:Organization Name:Organization Unit:Generated On" > /tmp/csrlist.out

# Get name of all certificate files
for csrfile in $($FIND_CMD $CSR_DIR -name "*.$CSR_EXT"); do
	name=$(basename $csrfile .$CSR_EXT)

	# Check if key file with same name is existing and add to CSR list
	# otherwise log an error
	if [ -e $CSR_DIR/$name.$KEY_EXT ]; then
		csrlist="$csrlist:$name"
		subject=`$OPENSSL_CMD req -in $CSR_DIR/$name.csr -noout -subject`
		common_name=`echo $subject | cut -d'/' -f7`
		if [ -n "$common_name" ];then
			common_name="${common_name##*=}"		
		fi
		org_name=`echo "$subject" | cut -d'/' -f5 | sed 's/.*=//'`
		org_unit=`echo "$subject" | cut -d'/' -f6 | sed 's/.*=//'`
		usr_handle_info=`$GREP_CMD -w "$name" $CSR_LIST`
		usr_handle=`echo $usr_handle_info | cut -d':' -f1`
		generated_date=`echo $usr_handle_info | cut -d':' -f3`
		echo -e "$usr_handle:$common_name:$org_name:$org_unit:$generated_date\n" >> /tmp/csrlist.out
			
	#else
	#	log $LOG_ERROR "no matching key file for CSR $name"
	fi
done

$COLUMN_CMD -t -s: /tmp/csrlist.out
$RM_CMD -f /tmp/csrlist.out >/dev/null 2>&1
#log $LOG_DEBUG "listing CSRs: ${csrlist:1}"

#echo "${csrlist:1}"

return $RC_SUCCESS
}

################################################################################
# Print the plain content of a CSR file
function print
{
#log $LOG_DEBUG "printing CSR with name: $name"

csr_exists=`$GREP_CMD -w "$name" $CSR_LIST`
if [ -n "$csr_exists" ];then
        name=`echo $csr_exists | cut -d':' -f2`
else
        echo "CSR $name doesnot exist.Print operation failed."
        return $RC_ERROR
fi

csrfile="$CSR_DIR/$name.$CSR_EXT"

$CAT_CMD $csrfile

return $RC_SUCCESS
}

################################################################################
# Decode a CSR file and print the output
function decode
{
#log $LOG_DEBUG "decoding CSR with name: $name"

csr_exists=`$GREP_CMD -w "$name" $CSR_LIST`
if [ -n "$csr_exists" ];then
        name=`echo $csr_exists | cut -d':' -f2`
else
        echo "CSR $name doesnot exist.Decode operation failed."
        return $RC_ERROR
fi

csrfile="$CSR_DIR/$name.$CSR_EXT"

# Decode the CSR file
$OPENSSL_CMD req -noout -text -in $csrfile -verify

# Report errors
rc=$?
if [ $rc -ne $TRUE ]; then
	echo "Unable to decode CSR."
	return $RC_ERROR	
fi

return $RC_SUCCESS
}

################################################################################
# Delete a CSR
function delete
{

#log $LOG_DEBUG "deleting CSR and KEY with name: $name"
#echo "Deleting a signing request will prevent installation of the corresponding signed certificate when it is returned from the CA."
#echo -p "Do you want to proceed? (Yes/No)" RESPONSE
#RESPONSE=${RESPONSE,,}
#if [[ "$RESPONSE" == "n" ]] || [[ "$RESPONSE" == "no" ]]
#then
#   echo "Aborting..."
#   return $RC_SUCCESS
#fi

usr_name="$name"
csr_exists=`$GREP_CMD -w "$name" $CSR_LIST`
if [ -n "$csr_exists" ];then
	name=`echo $csr_exists | cut -d':' -f2`
else
	echo "CSR $name doesnot exist.Delete operation failed."
	return $RC_ERROR
fi

csrfile="$CSR_DIR/$name.$CSR_EXT"
keyfile="$CSR_DIR/$name.$KEY_EXT"

$RM_CMD -f /var/tmp/$usr_name.$CSR_EXT $csrfile $keyfile >/dev/null 2>&1
sed -i "/$name/d" $CSR_LIST

echo "Successfully deleted CSR $usr_name."

return $RC_SUCCESS
}

function generate
{

  read -p "Country Name (2 letter code) : " country
  read -p "State or Province Name (full name) : " state
  read -p "Locality name (eg: city) : " locality
  read -p "Organization name (eg; company) : " org
  read -p "Organization Unit Name (eg: section) : " orgunit
  read -p "Common Name (eg:your name or server\'s hostname ) : " cname
  read -p "Unique CSR identifier (any unique name) : " name
  read -p "Key size(2048, 3072 or 4096) : " keysize
  read -p "Digest algorithm(sha256, sha384, or sha512) : " digest
  read -p "CA capable?(yes/no) : " CA
  
  subject="/C=$country/ST=$state/L=$locality/O=$org/OU=$orgunit/CN=$cname"
  mode=create
}


#############################################################################
# MAIN

# name the options and parameters
ca=$FALSE
debug=$FALSE

if [ $# -eq 0 ]
then
	usage
	exit $RC_ERROR
fi	

while [ $# -gt 0 ]; do
	case "$1" in
	generate | create | list | print | decode | delete)
			mode="$1"
			;;

	-subj)		subject="$2"
			shift
			;;
					
	-keysize)	keysize="$2"
			shift
			;;

	-hash)		digest="$2"
			shift
			;;

	-name)	name="$2"
			shift
			;;
				
	-ca)		ca=$TRUE
			;;
			
	-d)		debug=$TRUE
			;;

	-v)		version
			exit $RC_SUCCESS
			;;	
	
	-h)		usage
			exit $RC_SUCCESS
			;;
				
	*)		echo "Invalid argument(s).Check usage."
			exit $RC_ERROR
			;;
	esac
	shift	
done


# Check for a valid mode.
if ( [ "$mode" != "create" ] &&
     [ "$mode" != "generate" ] &&
     [ "$mode" != "print" ] &&
     [ "$mode" != "list" ] &&
     [ "$mode" != "decode" ] &&
     [ "$mode" != "delete" ] )
then
	echo >&2
	echo "Missing mode of operation.Check usage." >&2
	echo >&2
	exit $RC_ERROR
fi


# No arguments should be specified with the list mode.
if ( [ "$mode" = "list" ] && ( [ -n "$subject" ] || [ -n "$keysize" ] || \
     [ -n "$digest" ] || [ -n "$name" ] ) )
then
	echo "List mode do not require further arguments. Remove extra arguments."
	exit $RC_ERROR
fi

# Check for valid mode and parameters
if ( [ "$mode" = "create" ] && ( [ -z "$subject" ] || [ -z "$keysize" ] || \
     [ -z "$digest" ] || [ -z "$name" ] ) ) || \
   ( ( [ "$mode" = "print" ] || [ "$mode" = "decode" ] || \
       [ "$mode" = "delete" ] ) && [ -z "$name" ] )
then
	echo "Valid arguments and corresponding values are not provided.Check usage."
	exit $RC_ERROR
fi

#Check for valid subject
if ( [ "$mode" = "create" ] && [ -n "$subject" ] ) || [ "$mode" = "generate" ]
then
	if [ "$mode" = "generate" ];then
		generate
	fi
i=2
  while [ $i -le 7 ];
  do
	field=`echo $subject | cut -d/ -f${i}`
	if [ -z "$field" ];then
		break
	fi
	fname=`echo "$field" | cut -d= -f1`
	fval=`echo "$field" | cut -d= -f2-`
	if [ "$fname" = "C" ];then
		country="$fval"
	#	lenCheck country $country
	#	cliSafe  country $country
	elif [ "$fname" = "ST" ];then
		state="$fval"
	elif [ "$fname" = "L" ];then
		locality="$fval"
	elif [ "$fname" = "O" ];then
		org="$fval"
	elif [ "$fname" = "OU" ];then
		orgunit="$fval"
	elif [ "$fname" = "CN" ];then
		cname="$fval"
	fi
	i=$( expr $i + 1 )
  done	

declare -A SUBMAP=( [Country]=$country [State]=$state [Locality]=$locality \
			[Organization]=$org [Organization_unit]=$orgunit [Common_name]=$cname [name]=$name )
for KEY in "${!SUBMAP[@]}"; do
  # Print the KEY value
  #echo "Key: $KEY"
  # Print the VALUE attached to that KEY
  #echo "Value: ${ARRAY[$KEY]}"
  lenCheck "$KEY" "${SUBMAP[$KEY]}"
  cliSafe "$KEY" "${SUBMAP[$KEY]}"
done

  if [ -n "$cname" ];then
	subject="/C=$country/ST=$state/L=$locality/O=$org/OU=$orgunit/CN=$cname"
  else
	subject="/C=$country/ST=$state/L=$locality/O=$org/OU=$orgunit"
  fi

fi


# Check for valid keysize
if ( [ "$mode" = "create" ] && ( [ "$keysize" != "3072" ] && \
     [ "$keysize" != "2048" ] && [ "$keysize" != "4096" ] ) )
then
	echo >&2
	echo "Invalid keysize.Check usage." >&2
	echo >&2
	exit $RC_ERROR
fi

# Check for valid digest algorithm
if ( [ "$mode" = "create" ] && ( [ "$digest" != "sha256" ] && \
     [ "$digest" != "sha384" ] && [ "$digest" != "sha512" ] ) )
then
	echo >&2
	echo "Invalid digest type.Check usage." >&2
	echo >&2
	exit $RC_ERROR
fi

# Check for valid name
if [ "$mode" = "create" ] || [ "$mode" = "generate" ] ;then
	total_csr=`sed '/^\s*$/d' $CSR_LIST | wc -l`
	handle_already_exists=`$GREP_CMD -w "$name" $CSR_LIST`
	if [ -n "$handle_already_exists" ];then
		echo "CSR with $name name already exists.Duplicate names are not allowed."
		exit $RC_ERROR
	elif [ $total_csr -ge 7 ];then
		echo "A maximum of 6 CSR can be present at a time.Please delete unused CSR first."
		exit $RC_ERROR
	fi
fi

# Call the function for the selected mode
$mode

exit
