#!/bin/bash ############################################################# # Script name: create-cert.sh # Author: Gilles Mouchet (gilles.mouchet@gmail.com # Version: 1.0.0 # Description: Create a cert # License: GNU GPL v3 # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # Changelog # [1.0.0] - 2026-04-12 # Added: # - generating private key # - generating Certificate Signing Request (csr) file # - signing the certificate with the CA # Project initialization # - initialization by gilles.mouchet@gmail.com # ############################################################ # version=1.0.0 ############################################################ # FUNCTIONS ############################################################ #----------------------------------------------------------- # display usage usage() { cat << EOF Usage: $0 -c -k -n [-d ] [-i ] [-t ]" Template script Options: -c, --ca-name - ca name (./info-cert.sh -c get list of CA name) -n, --commonName - common name fqdn format (server.domain.ext) -d, --dns - subject alternative name (multiple SAN separated by commas) -i, --ip - ip address to add to the certificate (multiple IPs separated by commas) -t, --days - validity period of the certificate in days (defaults $days days) -y, --assumeyes - automatically answer yes for all questions. -h, --help - show this help -v, --version - show script version Examples: Generate a certificate for myweb.gmolab.net ./$(basename "$0") -c gmolab_ca -n myweb.gmolab.net Generate a certifciate for myweb.gmolab.net with dns alias and ip ./$(basename "$0") -c gmolab_ca -n myweb.gmolab.net -i 92.168.1.10,10.0.0.5,10.10.34.25 --dns www.gmolab.net,qual-myweb.gmolab.net -t 49 EOF } ############################################################ # MAIN ############################################################ main(){ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(dirname "$SCRIPT_DIR")" # read library source "$ROOT_DIR/lib/stdlib.sh" # init config init_default init_env # set color set_color # check if script is runnibf with sudo check_sudo # check if param exist if [ -z "$1" ]; then usage exit 1 fi # set color set_color # parse cli parameters while [[ "$#" -gt 0 ]]; do case "$1" in -c|--ca-name) if [[ -z "$2" || "$2" == -* ]]; then msg_error "\nError: Argument missing for option -c or --ca-name.\n" usage exit 1 else CA_CRT=$2.crt CA_KEY=$2.key fi shift 2 ;; -n|--cn) if [[ -z "$2" || "$2" == -* ]]; then msg_error "\nError: Argument missing for option -n or --cn.\n" usage exit 1 else check_format_fqdn "$2" COMMON_NAME=$2 # record for db if no option -d or --dns exist. # some browser needed the commName in Subject Alternative Name DNS_LINE=$2 # record for db if no option -d or --dns exist. Some browser needed the commName in Subject Alternative Name fi shift 2 ;; -d|--dns) if [[ -z "$2" || "$2" == -* ]]; then msg_error "\nError: Argument missing for option -d or --dns.\n" usage exit 1 else # add commonName and parameters -d --dns value in dnsLine variable. # some browser needed the commName in Subject Alternative Name DNS_LINE="$COMMON_NAME,$2" # put dnsLine value in dns array IFS=',' read -r -a DNS <<< "$DNS_LINE" # check dns format for SAN_DNS in "${DNS[@]}"; do check_format_fqdn $SAN_DNS done fi shift 2 ;; -i|--ip) if [[ -z "$2" || "$2" == -* ]]; then msg_error "\nError: Argument missing for option -i or --ip.\n" usage exit 1 else # add parameters -i --ip values in IP_LINE variable. IP_LINE=$2 # put ipLine value in ipAddrs array IFS=',' read -r -a IP_ADDRS <<< "$2" # check ip format for SAN_IP in "${IP_ADDRS[@]}"; do check_format_ip $SAN_IP done fi shift 2 ;; -t|--days) if [[ -z "$2" || "$2" == -* ]]; then msg_err "\nError: Argument missing for option -t or --days.\n" usage exit 1 else DAYS=$2 fi shift 2 ;; -y|--assumeyes) ASSUME_YES="1" shift ;; -v|--version) cat << EOF $(basename "$0") $version Copyright (C) 2003 - $(date +%Y) Gilles Mouchet EOF exit ;; *|-h|--help) usage exit ;; esac done # check if file doesn't exists if [ -f "$CERTS_PATH/$COMMON_NAME.key" ]; then echo -e "The ${ORANGE}$CERTS_PATH/$COMMON_NAME.key${NC} file exists!" yes_no "Are you sure you want to continue" fi # if array dns is empty we add commonName if [ "${#DNS[@]}" -eq 0 ]; then DNS+="$COMMON_NAME" fi # check if mandatory variables exists if [[ -z "$CA_CRT" || -z "$CA_KEY" || -z "$COMMON_NAME" ]]; then usage exit 1 fi # check if ca key and ca crt file exist if [[ ! -f "$CRT_CA_PATH/$CA_CRT" || ! -f "$KEY_CA_PATH/$CA_KEY" ]]; then msg_error "One or both of the following files are missing:" msg_warn " - $CRT_CA_PATH/$CA_CRT" msg_warn " - $KEY_CA_PATH/$CA_KEY" exit 1 fi # summary printf " ${CYAN}Summary ---------------------------------------------------------------------${NC} Certifcate authority: ${GREEN}%s${NC} Common name: ${GREEN}%s${NC} " "$CRT_CA_PATH/$CA_CRT" "$COMMON_NAME" echo -e "SAN List:" for SAN_DNS in "${DNS[@]}"; do msg_ok " - $SAN_DNS" done echo -e "IP List:" for SAN_IP in "${IP_ADDRS[@]}"; do msg_ok " - $SAN_IP" done echo -e "Validity:" echo -e " Not before: ${GREEN}$(date +"%b %d %H:%M:%S %Y GMT")${NC}" echo -e " Not After : ${GREEN}$(date -u -d "+$DAYS days" +"%b %d %H:%M:%S %Y GMT")${NC}" if [ "$ASSUME_YES" == "0" ]; then yes_no "Is it ok" fi echo -e "\nPrepare the openSSL configuration file" cat > "$CERTS_PATH/${COMMON_NAME}_openssl.cnf" << EOF [ req ] default_bits = 2048 distinguished_name = req_distinguished_name req_extensions = req_ext prompt = no [ req_distinguished_name ] CN = $COMMON_NAME [ req_ext ] subjectAltName = @alt_names [ alt_names ] EOF echo -e " Add SAN" # add dns i=1 for SAN_DNS in "${DNS[@]}"; do echo "DNS.$i = $SAN_DNS" >> "$CERTS_PATH/${COMMON_NAME}_openssl.cnf" ((i++)) done echo -e " Add IP" # add ip i=1 for SAN_IP in "${IP_ADDRS[@]}"; do echo "IP.$i = $SAN_IP" >> "$CERTS_PATH/${COMMON_NAME}_openssl.cnf" ((i++)) done # create certificate echo -e "Generating the private key..." openssl genrsa -out "${CERTS_PATH}/${COMMON_NAME}.key" 4096 echo -e "Generating csr file..." openssl req -new -key "${CERTS_PATH}/${COMMON_NAME}.key" -out "${CERTS_PATH}/${COMMON_NAME}.csr" -config "$CERTS_PATH/${COMMON_NAME}_openssl.cnf" echo -e "Signing the certificate with the CA..." openssl x509 -req -in "${CERTS_PATH}/${COMMON_NAME}.csr" \ -CA "$CRT_CA_PATH/$CA_CRT" -CAkey "$KEY_CA_PATH/$CA_KEY" -CAcreateserial \ -out "${CERTS_PATH}/${COMMON_NAME}.crt" -days "$DAYS" \ -extensions req_ext -extfile "$CERTS_PATH/${COMMON_NAME}_openssl.cnf" \ #-passin pass:$KEY_CA_PASS \ > /dev/null 2>&1 rc=$? echo -n "Result of certificate signing: " check_rc $rc #echo -e "\nVerify certifcate" #echo -e "\nValidity" #openssl x509 -in $CERTS_PATH/${COMMON_NAME}.crt -noout -dates #echo -e "\nSubject Alternative Name" #openssl x509 -in $CERTS_PATH/${COMMON_NAME}.crt -noout -ext subjectAltName echo -e -n "\nVerify the validity of ${GREEN}$CERTS_PATH/${COMMON_NAME}.crt${NC} using the trust chain: " openssl verify -CAfile $CRT_CA_PATH/$CA_CRT $CERTS_PATH/$COMMON_NAME.crt > /dev/null 2>&1 check_rc $? # get validity date #NOT_BEFORE=$(openssl x509 -noout -in $CERTS_PATH/${COMMON_NAME}.crt -startdate | cut -d'=' -f2) #NOT_AFTER=$(openssl x509 -noout -in $CERTS_PATH/${COMMON_NAME}.crt -enddate | cut -d'=' -f2) # check if commonName already exist on db #echo -e -n "\nCheck if ${GREEN}${COMMON_NAME}${NC} exist in DB: " #recordExist=$(sqlite3 $DB_PATH "SELECT EXISTS(SELECT 1 FROM certs WHERE common_name='${COMMON_NAME}');") #check_rc $? #if [ "$recordExist" == "1" ]; then # echo -e -n "Update ${ORANGE}${COMMON_NAME}${NC} in DB: " ## cert_crt_64=$(base64 -w0 ${CERTS_TMP_PATH}/${COMMON_NAME}.crt) ## echo $cert_crt_64 # sqlite3 $DB_PATH <