#!/bin/bash ############################################################# # Script name: create-cert.sh # Author: Gilles Mouchet (gilles.mouchet@gmail.com # Version: 1.0.0 # Description: Create a cert and save it on db # 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: # - adding cert into DB # - 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 (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 Show this help ./$(basename "$0") -h 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|--common-name) if [[ -z "$2" || "$2" == -* ]]; then msg_error "\nError: Argument missing for option -n or --common-name.\n" usage exit 1 else if [[ ! $2 =~ ^([a-z0-9]+(-[a-z0-9]+)*\.){2,}[a-z]{2,}$ ]]; then msg_error "\n$2 is not a commonName valid.\n" usage exit 1 else 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 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 if [[ ! $SAN_DNS =~ ^([a-z0-9]+(-[a-z0-9]+)*\.){2,}[a-z]{2,}$ ]]; then msg_error "\n$SAN_DNS is not a commonName valid.\n" exit 1 fi 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 if [[ ! $SAN_IP =~ ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]; then msg_error "\n$SAN_IP is not an address IP valid.\n" exit 1 fi 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 # 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 # create destination path if [ ! -d "$CERTS_TMP_PATH" ]; then echo "create $CERTS_TMP_PATH" mkdir -p $CERTS_TMP_PATH fi echo -e "\nPrepare the openSSL configuration file" cat > "$CERTS_TMP_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 #echo "DNS.$i = ${COMMON_NAME}" >> "$CERTS_TMP_PATH/${COMMON_NAME}_openssl.cnf" #((i++)) for SAN_DNS in "${DNS[@]}"; do echo "DNS.$i = $AN_DNS" >> "$CERTS_TMP_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_TMP_PATH/${COMMON_NAME}_openssl.cnf" ((i++)) done # create certificate echo -e "Generating the private key..." openssl genrsa -out "${CERTS_TMP_PATH}/${COMMON_NAME}.key" 4096 echo -e "Generating csr file..." openssl req -new -key "${CERTS_TMP_PATH}/${COMMON_NAME}.key" -out "${CERTS_TMP_PATH}/${COMMON_NAME}.csr" -config "$CERTS_TMP_PATH/${COMMON_NAME}_openssl.cnf" echo -e "Signing the certificate with the CA..." openssl x509 -req -in "${CERTS_TMP_PATH}/${COMMON_NAME}.csr" \ -CA "$CRT_CA_PATH/$CA_CRT" -CAkey "$KEY_CA_PATH/$CA_KEY" -CAcreateserial \ -out "${CERTS_TMP_PATH}/${COMMON_NAME}.crt" -days "$DAYS" \ -extensions req_ext -extfile "$CERTS_TMP_PATH/${COMMON_NAME}_openssl.cnf" \ > /dev/null 2>&1 #-passin pass:pa55w0rd > /dev/null 2>&1 echo -n "Result of certificate signing: " check_rc $? #echo -e "\nVerify certifcate" #echo -e "\nValidity" #openssl x509 -in $CERTS_TMP_PATH/${COMMON_NAME}.crt -noout -dates #echo -e "\nSubject Alternative Name" #openssl x509 -in $CERTS_TMP_PATH/${COMMON_NAME}.crt -noout -ext subjectAltName echo -e -n "\nVerify the validity of ${GREEN}$CERTS_TMP_PATH/${COMMON_NAME}.crt${NC} using the trust chain: " openssl verify -CAfile $CRT_CA_PATH/$CA_CRT $CERTS_TMP_PATH/$COMMON_NAME.crt > /dev/null 2>&1 check_rc $? # get validity date notBefore=$(openssl x509 -noout -in $CERTS_TMP_PATH/${COMMON_NAME}.crt -startdate | cut -d'=' -f2) notAfter=$(openssl x509 -noout -in $CERTS_TMP_PATH/${COMMON_NAME}.crt -enddate | cut -d'=' -f2) exit # check if commonName already exist on db echo -e -n "\nCheck if ${GREEN}${COMMON_NAME}${NC} exist in DB: " recordExist=$(sqlite3 $dbCertFile "SELECT EXISTS(SELECT 1 FROM certs WHERE common_name='${COMMON_NAME}');") rc="$?" [ "$rc" != "0" ] && { echo -e "${RED}Error reading the database (RC:$rc - $recordExist)${NC}"; exit; } || echo -e "${GREEN}Ok${NC}" if [ "$recordExist" == "1" ]; then echo -e -n "Update ${ORANGE}${COMMON_NAME}${NC} in DB: " sqlite3 $dbCertFile <