#!/bin/bash
# DATE 10/11/2024
# AUTEUR Cbillet
# Objectif: Script de génération de CSR certificat avec options avancées
# version : 2.0

################################################################################
# Functions
################################################################################

# Fonction d'affichage de l'aide
Help() {
   echo -e "\n\e[1;36mScript de génération de CSR pour certificat SSL/TLS avec options avancées\e[0m"
   echo
   echo -e "\e[1;32mUSAGE\e[0m"
   echo "  $0 [options]"
   echo

   # Affiche la descriptions 
   echo -e "\e[1;32mDESCRIPTION\e[0m"
   echo "  Ce script génère un CSR (Certificate Signing Request) et une clé privée pour SSL/TLS"
   echo "  avec des options avancées, telles que le choix de l'algorithme de chiffrement, la taille"
   echo "  de clé, la courbe elliptique, et des noms alternatifs."
   echo

   # Affiche les listes valides pour certaines options, au format : [Option1, Option2, ...]
   local valid_cities="[ $(IFS=','; echo "${VALID_CITIES[*]}" | sed 's/,/, /g') ]"
   local valid_locations="[ $(IFS=','; echo "${VALID_LOCATIONS[*]}" | sed 's/,/, /g') ]"
   local valid_ciphers="[ $(IFS=','; echo "${VALID_CIPHERS[*]}" | sed 's/,/, /g') ]"
   local valid_digests="[ $(IFS=','; echo "${VALID_DIGESTS[*]}" | sed 's/,/, /g') ]"
   local valid_curves="[ $(IFS=','; echo "${VALID_CURVES[*]}" | sed 's/,/, /g') ]"

   # Liste les options et leurs descriptions
   echo -e "\e[1;32mOPTIONS\e[0m"

   # Options obligatoires
   echo -e "\e[1;31mObligatoires :\e[0m"
   echo -e "  \e[1;33m-d, --domain DOMAIN\e[0m          Domaine du certificat (ex : --domain example.com)"
   echo -e "  \e[1;33m-s, --server_name NAME\e[0m       Nom court du serveur (ex : --server_name www)"
   echo -e "  \e[1;33m-c, --city CITY\e[0m              Ville (ex : --city Paris)"
   echo -e "                                     \e[2mOptions :\e[0m $valid_cities"
   echo -e "  \e[1;33m-l, --location LOC\e[0m           Région (ex : --location Ile-de-France)"
   echo -e "                                     \e[2mOptions :\e[0m $valid_locations"
   echo -e "  \e[1;33m-O, --organisation ORG\e[0m       Nom de l'organisation (ex : --organisation 'My Org')"

   # Options avec des valeurs par défaut
   echo -e "\n\e[1;34mAvec valeurs par défaut :\e[0m"
   echo -e "  \e[1;35m-o, --output_dir DIR\e[0m         Répertoire de sortie (défaut : ./)"
   echo -e "                                     \e[2mExemple :\e[0m --output_dir /etc/ssl/csr"
   echo -e "  \e[1;35m-u, --ou UNIT\e[0m                Unité organisationnelle (défaut : DSI)"
   echo -e "  \e[1;35m-C, --country CODE\e[0m           Code pays ISO (défaut : FR)"
   
   # Options supplémentaires
   echo -e "\n\e[1;34mOptions supplémentaires :\e[0m"
   echo -e "  \e[1;33m-a, --alternate_name AN\e[0m      Noms alternatifs (ex : --alternate_name www.example.net)"
   echo -e "  \e[1;35m--cipher CIPHER\e[0m              Algorithme de chiffrement (défaut : rsa)"
   echo -e "                                     \e[2mOptions :\e[0m $valid_ciphers"
   echo -e "  \e[1;35m--keysize SIZE\e[0m               Taille de la clé RSA (≥ 512, défaut : 4096)"
   echo -e "  \e[1;35m--digest DIGEST\e[0m              Algorithme de hachage (défaut : sha256)"
   echo -e "                                     \e[2mOptions :\e[0m $valid_digests"
   echo -e "  \e[1;33m--curve CURVE\e[0m                Courbe elliptique pour EC (si --cipher ec)"
   echo -e "                                     \e[2mOptions :\e[0m $valid_curves"

   # Exemples d'utilisation
   echo -e "\n\e[1;32mEXEMPLES D'UTILISATION\e[0m"
   echo -e "  CSR avec RSA et nom alternatif :"
   echo "    $0 -d example.com -s www -c Paris -l Ile-de-France -O 'My Organisation' \\"
   echo "       --cipher rsa --keysize 2048 --digest sha512 -a www.example.net"
   echo
   echo -e "  CSR avec EC et SHA-256 :"
   echo "    $0 -d example.com -s www -c Paris -l Ile-de-France -O 'My Organisation' \\"
   echo "       --cipher ec --curve prime256v1 --digest sha256"

   echo -e "\nUtilisez ce script pour créer des CSR et des clés privées adaptées aux standards modernes de sécurité SSL/TLS.\n"
}

# Fonction pour demander une valeur manquante à l'utilisateur
prompt_for_value() {
   local var_name=$1
   local prompt_message=$2
   local value=""
   while [[ -z "$value" ]]; do
      read -p "$prompt_message : " value
      if [[ -z "$value" ]]; then
         echo -e "\e[1;31mErreur : La variable $var_name ne peut pas être vide.\e[0m"
      fi
   done
   echo "$value"
}

# Fonction pour valider qu'une valeur est dans une liste de valeurs autorisées
validate_value() {
   local value=$1
   shift
   local valid_values=("$@")
   for valid_value in "${valid_values[@]}"; do
      if [[ "$value" == "$valid_value" ]]; then
         return 0
      fi
   done
   return 1
}

# Fonction pour générer les fichiers de clé et de CSR en fonction des options sélectionnées
generate_files() {
   local server_name_short=$1
   local server_name_long=$2
   local location=$3
   local city=$4
   local organisation=$ORGANISATION
   local country=$COUNTRY
   local organizational_unit=$ORGANIZATIONAL_UNIT
   local path_out=$PATH_OUT
   local cipher=$CIPHER
   local keysize=$KEYSIZE
   local digest=$DIGEST
   local curve=$CURVE
   local valid_curves="[ $(IFS=','; echo "${VALID_CURVES[*]}" | sed 's/,/, /g') ]"
   
   # Configuration des noms alternatifs (Subject Alternative Names - SAN)
   local alt_names="DNS.1=$server_name_short\nDNS.2=$server_name_long"
   local i=3
   for alt_name in "${ALTERNATE_NAMES[@]}"; do
      alt_names="${alt_names}\nDNS.${i}=${alt_name}"
      ((i++))
   done

   

	# Génération des fichiers clé et CSR pour l'algorithme EC
   if [[ "$cipher" == "ec" ]]; then
      if [[ "$keysize_specified" == true ]]; then
         echo -e "\e[1;31mErreur : L'algorithme EC ne doit pas être utilisé avec --keysize. Utilisez --curve.\e[0m"
         exit 1
      elif [[ "$curve_specified" != true ]]; then
         echo -e "\e[1;31mErreur : La courbe elliptique doit être spécifiée avec --curve pour EC (choix : ${valid_curves[*]}).\e[0m"
         exit 1
      elif ! validate_value "$curve" "${VALID_CURVES[@]}"; then
         echo -e "\e[1;31mErreur : La courbe elliptique spécifiée ($curve) n'est pas valide (choix : ${valid_curves[*]}).\e[0m"
         exit 1
      fi
	  echo -e "\e[1;34mDébut génération CSR et KEY pour le serveur ${server_name_long}\e[0m"
      openssl ecparam -name "$curve" -genkey -noout -out "${path_out}/${server_name_long}.key" && \
      openssl req -new -key "${path_out}/${server_name_long}.key" -"$digest" -out "${path_out}/${server_name_long}.csr" -config <(
      cat <<- EOF
      [req]
      prompt             = no
      default_md         = $digest
      req_extensions     = extensions
      x509_extensions    = extensions
      distinguished_name = dn
      string_mask        = utf8only

      [ dn ]
      C=$country
      ST=$location
      L=$city
      O=$organisation
      OU=$organizational_unit
      CN=${server_name_long}

      [ extensions ]
      subjectKeyIdentifier    = hash
      basicConstraints        = CA:TRUE
      keyUsage                = nonRepudiation, digitalSignature, keyEncipherment
      extendedKeyUsage        = serverAuth
      subjectAltName          = @alternate_names

      [ alternate_names ]
      ${alt_names}
EOF
      )
   elif [[ "$cipher" == "rsa" ]]; then
      if [[ "$curve_specified" == true ]]; then
         echo -e "\e[1;31mErreur : L'algorithme RSA ne doit pas être utilisé avec --curve. Utilisez --keysize.\e[0m"
         exit 1
      elif [[ "$keysize_specified" != true ]]; then
         keysize=4096  # Valeur par défaut pour RSA si non spécifiée
      elif ! [[ "$keysize" =~ ^[0-9]+$ ]] || [[ "$keysize" -lt 512 ]]; then
         echo -e "\e[1;31mErreur : La taille de clé pour RSA doit être un entier positif >= 512.\e[0m"
         exit 1
      fi
	  echo -e "\e[1;34mDébut génération CSR et KEY pour le serveur ${server_name_long}\e[0m"
      openssl req -new -"$digest" -newkey "${cipher}:${keysize}" -nodes -keyout "${path_out}/${server_name_long}.key" -out "${path_out}/${server_name_long}.csr" -config <(
      cat <<- EOF
      [req]
      default_bits       = $keysize
      prompt             = no
      default_md         = $digest
      req_extensions     = extensions
      x509_extensions    = extensions
      distinguished_name = dn
      string_mask        = utf8only

      [ dn ]
      C=$country
      ST=$location
      L=$city
      O=$organisation
      OU=$organizational_unit
      CN=${server_name_long}

      [ extensions ]
      subjectKeyIdentifier    = hash
      basicConstraints        = CA:TRUE
      keyUsage                = nonRepudiation, digitalSignature, keyEncipherment
      extendedKeyUsage        = serverAuth
      subjectAltName          = @alternate_names

      [ alternate_names ]
      ${alt_names}
EOF
      )
   fi

   # Vérification de la création des fichiers
   if [ -f "${path_out}/${server_name_long}.csr" ] && [ -f "${path_out}/${server_name_long}.key" ]; then
      echo -e "\e[1;32mLes fichiers ${server_name_long}.csr et ${server_name_long}.key ont été générés.\e[0m"
   else
      echo -e "\e[1;31mErreur : Au moins l'un des fichiers ${server_name_long}.csr ou ${server_name_long}.key n'existe pas\e[0m"
      exit 1
   fi
}

################################################################################
# Execution  
################################################################################

# Initialisation des options et valeurs par défaut
DOMAIN=""
SNAME=""
CITY=""
LOCATION=""
ORGANISATION=""
ORGANIZATIONAL_UNIT="DSI"
COUNTRY="FR"
PATH_OUT="."
CIPHER="rsa"
KEYSIZE=4096
DIGEST="sha256"
CURVE=""
ALTERNATE_NAMES=()
keysize_specified=false
curve_specified=false

# Listes des valeurs valides
VALID_CIPHERS=("rsa" "ec")
VALID_DIGESTS=("sha256" "sha384" "sha512")
VALID_CURVES=("prime256v1" "secp384r1" "secp521r1")
VALID_LOCATIONS=(
    "Auvergne-Rhone-Alpes"
    "Bourgogne-Franche-Comte"
    "Bretagne"
    "Centre-Val de Loire"
    "Corse"
    "Grand Est"
    "Hauts-de-France"
    "Ile-de-France"
    "Normandie"
    "Nouvelle-Aquitaine"
    "Occitanie"
    "Pays de la Loire"
    "Provence-Alpes-Cote d'Azur"
    "Guadeloupe"
    "Martinique"
    "Guyane"
    "La Reunion"
    "Mayotte"
)
VALID_CITIES=(
    "Paris"                # Ile-de-France
    "Rennes"               # Bretagne
    "Toulouse"             # Occitanie
    "Bordeaux"             # Nouvelle-Aquitaine
    "Lyon"                 # Auvergne-Rhone-Alpes
    "Dijon"                # Bourgogne-Franche-Comte
    "Orleans"              # Centre-Val de Loire
    "Ajaccio"              # Corse
    "Strasbourg"           # Grand Est
    "Lille"                # Hauts-de-France
    "Rouen"                # Normandie
    "Nantes"               # Pays de la Loire
    "Marseille"            # Provence-Alpes-Cote d'Azur
    "Basse-Terre"          # Guadeloupe
    "Fort-de-France"       # Martinique
    "Cayenne"              # Guyane
    "Saint-Denis"          # La Reunion
    "Mamoudzou"            # Mayotte
    "Ollioules"            # Specific needs
    "Ivry-sur-Seine"       # Specific needs	
    "Fontenay-sous-Bois"   # Specific needs	
    "Levallois-Perret"     # Specific needs	
)


# Traitement des options de ligne de commande
while [[ "$#" -gt 0 ]]; do
    case "$1" in
        -h|--help) Help; exit;;
        -d|--domain) DOMAIN="$2"; shift;;
        -s|--server_name) SNAME="$2"; shift;;
        -c|--city) CITY="$2"; shift;;
        -l|--location) LOCATION="$2"; shift;;
        -O|--organisation) ORGANISATION="$2"; shift;;
        -u|--ou) ORGANIZATIONAL_UNIT="$2"; shift;;
        -C|--country) COUNTRY="$2"; shift;;
        -o|--output_dir) PATH_OUT="$2"; shift;;
        -a|--alternate_name) ALTERNATE_NAMES+=("$2"); shift;;
        --cipher) CIPHER="$2"; shift;;
        --keysize) KEYSIZE="$2"; keysize_specified=true; shift;;
        --digest) DIGEST="$2"; shift;;
        --curve) CURVE="$2"; curve_specified=true; shift;;
        *) echo -e "\e[1;31mErreur : Option inconnue : $1\e[0m"; Help; exit 1;;
    esac
    shift
done

# Validation des options obligatoires et invite si manquantes
MISSING_VARS=()
for var in DOMAIN SNAME CITY LOCATION ORGANISATION; do
    if [[ -z "${!var}" ]]; then
        MISSING_VARS+=("$var")
    fi
done


if [[ "${#MISSING_VARS[@]}" -gt 0 ]]; then
    echo -e "\e[1;33mCertaines informations obligatoires sont manquantes.\e[0m"
    for var in "${MISSING_VARS[@]}"; do
        case "$var" in
            "DOMAIN") DOMAIN=$(prompt_for_value "DOMAIN" "Veuillez entrer le domaine");;
            "SNAME") SNAME=$(prompt_for_value "SNAME" "Veuillez entrer le nom court");;
            "CITY") CITY=$(prompt_for_value "CITY" "Veuillez entrer la ville");;
            "LOCATION") LOCATION=$(prompt_for_value "LOCATION" "Veuillez entrer la localisation");;
            "ORGANISATION") ORGANISATION=$(prompt_for_value "ORGANISATION" "Veuillez entrer le nom de l'organisation");;
        esac
    done
fi

# Validation des valeurs pour les options CITY, LOCATION, CIPHER, et DIGEST
# Convertir les tableaux en chaînes formatées avec des virgules et un espace
valid_cities="[ $(IFS=','; echo "${VALID_CITIES[*]}" | sed 's/,/, /g') ]"
valid_locations="[ $(IFS=','; echo "${VALID_LOCATIONS[*]}" | sed 's/,/, /g') ]"
valid_ciphers="[ $(IFS=','; echo "${VALID_CIPHERS[*]}" | sed 's/,/, /g') ]"
valid_digests="[ $(IFS=','; echo "${VALID_DIGESTS[*]}" | sed 's/,/, /g') ]"
if ! validate_value "$CITY" "${VALID_CITIES[@]}"; then
   echo -e "\e[1;31mErreur : La ville spécifiée ($CITY) n'est pas valide. Choix possibles : ${valid_cities[*]}\e[0m"
   exit 1
fi
if ! validate_value "$LOCATION" "${VALID_LOCATIONS[@]}"; then
   echo -e "\e[1;31mErreur : La localisation spécifiée ($LOCATION) n'est pas valide. Choix possibles : ${valid_locations[*]}\e[0m"
   exit 1
fi
if ! validate_value "$CIPHER" "${VALID_CIPHERS[@]}"; then
   echo -e "\e[1;31mErreur : L'algorithme de chiffrement spécifié ($CIPHER) n'est pas valide. Options possibles : ${valid_ciphers[*]}\e[0m"
   exit 1
fi
if ! validate_value "$DIGEST" "${VALID_DIGESTS[@]}"; then
   echo -e "\e[1;31mErreur : L'algorithme de hachage spécifié ($DIGEST) n'est pas valide. Options possibles : ${valid_digests[*]}\e[0m"
   exit 1
fi

# Génération des fichiers CSR et clé
ServerNameShort=$(echo "$SNAME")
ServerNameLong="${ServerNameShort}.${DOMAIN}"
generate_files "$ServerNameShort" "$ServerNameLong" "$LOCATION" "$CITY"

# Log script completion
logger -t "$0" "$(date +'%Y-%m-%d %H:%M:%S') - Le script a terminé avec succès pour le serveur ${ServerNameLong}"
