#!/bin/bash
# csr.sh: Certificate Signing Request Generator
# Copyright(c) 2005 Evaldo Gardenali <evaldo@gardenali.biz>
# 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.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "ASIS"
# 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 COPYRIGHT OWNER 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.
#
# ChangeLog:
# Mon May 23 00:14:37 BRT 2005 - evaldo - Initial Release
# ---------------------------------------------------------------------------- #
#
# Changelog
#
# 2005     - Evaldo Gardenali - Initial Release
# 20120508 - Antonio David Pérez Morales (RedIRIS)
# 2013     - Javi Masa (RedIRIS)
# 20150217 - Rafael Varela (USC)
#            Adaptación a UTF-8 por defecto
#
# ---------------------------------------------------------------------------- #

# Variables globales necesarias
# Nos aseguramos de que no habrá problemas con los permisos
LASTUMASK=`umask`
umask 077

# Definimos el path donde se guardará la CSR y la clave privada.
# Por defecto es el directorio de trabajo desde donde se llama al script
CURRENT_DIR=$(pwd -P)

# Definimos el binario de OpenSSL en nuestro sistema
OPENSSL_BIN=`which openssl`

if [ $? != 0 ]; then
	echo "No se ha encontrado openssl instalado en el sistema. Por favor instale openssl para continuar."
	exit ${?}
fi

# OpenSSL necesita un fichero donde leer y escribir datos aleatorios
RANDOMFILE=$HOME/.rnd

# Creamos el fichero de configuracion para OpenSSL
CONFIG=`mktemp -q /tmp/openssl-conf.XXXXXXXX`
if [ ! $? -eq 0 ]; then
	echo "No puedo crear un fichero de configuración temporal. Abortando..."
	exit 1
fi

# Mantiene el paso en el que nos encontramos
STEP=1

# Almacena el tipo de certificado seleccionado. Por defecto es 1 (Certificado Simple)
CERTTYPE="1"

# Almacena el CN
COMMONNAME=""

# Almacena el nombre de la organización O
ORGANIZATIONNAME=""

# Almacena los subjectALternativeNames
SANAMES=""

# Almacena el conjunto de caracteres
STRING_MASK=""


#Funciones
##########################################################################################
# Funcion waiting: Atrapa las interrupciones evitando cancelar la ejecución directamente #
##########################################################################################
function waiting()
{
	echo ""
	echo ""
	echo  "Generación de la CSR en curso, por favor no aborte el proceso. Abortar el proceso puede causar problemas durante la generación de la solicitud"
	echo -n "¿Estás seguro de que desea abortar el proceso? [s/n]:"
	read option
	if [ "$option" = "s" ] || [ "$option" = "S" ]
	then
		stty echo
		echo "Abortando ejecución..."
		exit 1;
	else
		echo ""
	fi
}

##################################################################################
# Funcion selectCertType: Permite seleccionar el tipo de certificado a solicitar #
##################################################################################
function selectCertType()
{
echo "¿Para que tipo de certificado desea generar la solicitud?"
echo "    1. Certificado simple (sólo CN)"
echo "    2. Certificado con múltiples dominios (CN y Subject Alternative Names)"
echo "    3. Certificado Wildcard"	
echo ""

echo -n "Seleccione una opcion [$CERTTYPE]: "
read option
CERTTYPE=${option:-$CERTTYPE}

case $CERTTYPE in
		1) echo "Certificado simple (sólo CN) seleccionado"
		;;
		2) echo "Certificado con múltiples dominios (CN y Subject Alternative Names) seleccionado"
		;;
		3) echo "Certificado Wildcard seleccionado"
		;;
		*) echo "Tipo no soportado. Abortando."
		   exit 2;
		;;
esac
}

#########################################################################
# Funcion selectCN: Permite seleccionar el CN a incluir en la solicitud #
#########################################################################
function selectCN()
{
while [ "1" ]; do
	echo -n "FQDN/CommonName (p. ej. "
	if [ $CERTTYPE = "3" ]
	then
		echo -n "*.level3.example.com): "
	else
		echo -n "www.example.com): "
	fi
	read COMMONNAME
	if [ -z $COMMONNAME ]
	then
		echo "El campo FQDN/CommonName es obligatorio."
	else
		if [ $CERTTYPE = "3" ]
		then
		#Check whether the COMMONNAME is a wildcard COMMONNAME
			if [[ "$COMMONNAME" =~ ^\*(\.[^.*]+){3,} ]]
			then
				break
			else
				echo "$COMMONNAME no es un nombre de dominio válido para certificados wildcard. Debe contener un wildcard y cumplir la regla de los 3 puntos."
			fi
		else
			if [[ "$COMMONNAME" =~ ^[^.*]+(\.[^.*]+){2,} ]]
			then
				break
			else
				echo "$COMMONNAME no es un nombre de dominio válido. No debe contener wildcard y tener una profundidad adecuada"
			fi
		fi
	fi
done
}

#############################################################################################
# Funcion selectOrganization: Permite seleccionar la organización a incluir en la solicitud #
#############################################################################################
function selectOrganization()
{
printf "OrganisationName (p. ej. RedIRIS): "
read ORGANIZATIONNAME
while [ "$ORGANIZATIONNAME" = "" ]; do
	echo "El campo OrganisationName es obligatorio.";
	printf "OrganisationName (p. ej. RedIRIS): "
	read ORGANIZATIONNAME
done
}

##########################################################################
# Funcion selectCharacterSet: Permite seleccionar el juego de caracteres #
##########################################################################
function selectCharacterSet()
{
echo "Juego de caracteres para el DN del certificado"
printf "  (p. ej. default, pkix, utf8only, nombstr) [utf8only]: "
read STRING_MASK
if [ "$STRING_MASK" = "" ]; then
	STRING_MASK="utf8only"
elif [ $STRING_MASK != "default" ] && [ $STRING_MASK != "pkix" ] && [ $STRING_MASK != "utf8only" ] && [ $STRING_MASK != "nombstr" ]; then
	echo "Mascara de tipo de cadena no válida $STRING_MASK."
	exit 1
fi

# Si no escoge UTF8, mostramos mensaje de aviso
if [ $STRING_MASK != "utf8only" ]; then
	echo
	echo "##################################################################"
	echo "AVISO: No has escogido utf8only."
	echo "Si vas a usar caracteres internacionales, es recomendable hacerlo."
	echo "(Ver https://www.openssl.org/docs/apps/req.html y"
	echo " http://www.ietf.org/rfc/rfc2459.txt)"
	echo "##################################################################"
	echo
fi
}

#################################################################################################
# Funcion selectSANs: Permite seleccionar los subjectAlternativeNames a incluir en la solicitud #
# (Sólo para solicitudes de certificados tipo 2 Multidominio									#
#################################################################################################
function selectSANs()
{
if [ $CERTTYPE = "2" ]
then
	echo
	echo "Introducas SubjectAltName para el certificado, uno por línea."
	echo "Introduzca una línea en blanco para finalizar"
	SAN=1        # inicializamos el bucle con cualquier valor. 
	SANAMES=""   # limpiamos los SANAMES
	while [ ! "$SAN" = "" ]; do
    	printf "SubjectAltName: DNS:"
	    read SAN
    	if [ "$SAN" = "" ]; then break; fi # condicion de salida
	    if [ "$SANAMES" = "" ]; then
    	    SANAMES="DNS:$SAN"
	    else
    	    SANAMES="$SANAMES,DNS:$SAN"
	    fi
	done
fi
}

#############################################################################################
# Funcion generateConfigFile: Genera el fichero de configuración de openssl					#
# con los datos de la solicitud: CN, O, SANs...												#
#############################################################################################
function generateConfigFile()
{
# Generamos el fichero de configuración
cat <<EOF > $CONFIG
# -------------- BEGIN custom openssl.cnf -----
 HOME                    = $HOME
EOF

# Si estamos con un HP-UX añadimos el fichero aleatorio.
if [ "`uname -s`" = "HP-UX" ]; then
    echo " RANDFILE                = $RANDOMFILE" >> $CONFIG
fi


cat <<EOF >> $CONFIG
 oid_section             = new_oids
 [ new_oids ]
 dc=0.9.2342.19200300.100.1.25
 
 [ req ]
 default_bits = 2048
 default_keyfile = $CURRENT_DIR/${COMMONNAME}_privatekey.pem
 oid_section = new_oids
 encrypt_key = no
 string_mask = $STRING_MASK
 default_md = sha256
 distinguished_name	= req_distinguished_name
 
EOF

# Se especifica utf8only, indicamos que las cadenas son utf8
if [ $STRING_MASK = "utf8only" ]; then
cat << EOF >> $CONFIG
 utf8 = yes	
EOF
fi

if [ ! "$SANAMES" = "" ]; then
	echo " req_extensions = req_v3 # Extensiones para añadir al certificado" >> $CONFIG
fi


# Los valores que se declaran en la política son:
#  C   country of the Organization
#  ST  State of the Organization (optional)
#  L   Locality of the Organisation (optional)
#  O   Organisation Name
#  OU  Organisational Unit Name (optional)
#  CN  Contains a domain name
#  unstructuredName Contains a domain name (optional)

cat <<EOF >> $CONFIG
 [ req_distinguished_name ]
 countryName		= Country Name (Código ISO 3166)
 countryName_default	= ES
 countryName_min = 2
 countryName_max = 2
 
 stateOrProvinceName = State or Province of the Organization (Guadalajara) [opcional]
 stateOrProvinceName_default = 
 stateOrProvinceName_max = 64
 
 localityName = Locality of the Organisation (Villabado de arriba) [opcional]
 localityName_default =
 
 organizationName	= Organization Name (p. ej. RedIRIS)
 organizationName_default = $ORGANIZATIONNAME
 organizationName_max = 64
 
 organizationalUnitName = Organisational Unit Name (Departamento de ciencias aplicadas) [optional]
 organizationalUnitName_ = 
 
 commonName = Common Name (eg, www.example.com)
 commonName_default = $COMMONNAME
 commonName_max = 64
 
 unstructuredName = unstructuredName MUST contains a FQDN/CommonName (eg, www.example.com) [opcional]
 unstructuredName_default =
 unstructuredName_max = 64
 
 [ req_v3 ]
EOF

if [ ! "$SANAMES" = "" ]; then
    echo "subjectAltName=$SANAMES" >> $CONFIG
fi

echo "# -------------- END custom openssl.cnf -----" >> $CONFIG
}

#############################################################################################
# Funcion generateCSR: Genera la solicitud utilizando openssl y almacena la solicitud		#
# y la clave privada en el directorio de trabajo actual										#
#############################################################################################
function generateCSR()
{
echo "Running OpenSSL..."
#openssl req -batch -config $CONFIG -newkey rsa:2048 -out $HOME/${HOST}_csr.pem
$OPENSSL_BIN req -new -config $CONFIG -out "$CURRENT_DIR/${COMMONNAME}_csr.pem"
}

###################################################################
# Funcion showCSR: Muestra la CSR en un formato legible y también #
# muestra la CSR codificada para ser utilizada en el ISC		  #
###################################################################
function showCSR()
{
echo "Contenido de la CSR:" 
$OPENSSL_BIN req -text -noout -in "$CURRENT_DIR/${COMMONNAME}_csr.pem"
echo "Copie y pegue el contenido de la siguiente CSR en ISC:"
echo
cat "$CURRENT_DIR/${COMMONNAME}_csr.pem"
}

#Fin Funciones

#Script de generación de solicitudes de firma de certificados (CSR)

# Bindeamos la funcion waiting a las señales de interrupción SIGINT y SIGKILL
trap waiting SIGINT
trap waiting SIGKILL

echo ""
echo "Generador de clave privada y CSR (Certificate Signing Request)"
echo "--------------------------------------------------------------"
echo
echo "Este programa ha sido diseñado para cumplir los formatos requeridos por"
echo "el ISC del servicio de certificados de RedIRIS."
echo "http://www.rediris.es/scs/isc"
echo 

while [ $STEP != "END" ]
do
	case $STEP in
		1) selectCertType
		;;
		2) selectCN
		;;
		3) selectOrganization
		;;	
		4) selectCharacterSet
		;;
		5) selectSANs
		;;
		6) generateConfigFile
		;;
		7) generateCSR
		;;
		8) showCSR
		;;
		9)
		#Eliminamos el fichero de configuracion
		if [ -n "$CONFIG" ];
		then
			rm $CONFIG
		fi
		#Restauramos la mascara anterior
		umask $LASTUMASK
		echo "La clave privada está almacenada en $CURRENT_DIR/${COMMONNAME}_privatekey.pem"
		exit 0;;

		*) exit 1
		;;
	 esac

	STEP=`expr $STEP + 1`

done
