own-pki/bin/generate-cert.sh
2026-04-20 20:44:13 +02:00

342 lines
11 KiB
Bash
Executable File

#!/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 <ca_cert> -k <ca_key> -n <common_name> [-d <dns1,dns2>] [-i <ip1,ip2>] [-t <days>]"
Template script
Options:
-c, --ca-name <ca_name> - ca name (./info-cert.sh -c get list of CA name)
-n, --commonName <cn> - common name (server.domain.ext)
-d, --dns <dns1,dns2,dnsx> - subject alternative name (multiple SAN separated by commas)
-i, --ip <ip1,ipx> - ip address to add to the certificate (multiple IPs separated by commas)
-t, --days <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
NOT_BEFORE=$(openssl x509 -noout -in $CERTS_TMP_PATH/${COMMON_NAME}.crt -startdate | cut -d'=' -f2)
NOT_AFTER=$(openssl x509 -noout -in $CERTS_TMP_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: "
sqlite3 $DB_PATH <<EOF
UPDATE certs SET san_dns = '$DNS_LINE',
san_ip = '$IP_LINE',
cert_key = readfile('${CERTS_TMP_PATH}/${COMMON_NAME}.key'),
cert_csr = readfile('${CERTS_TMP_PATH}/${COMMON_NAME}.csr'),
cert_crt = readfile('${CERTS_TMP_PATH}/${COMMON_NAME}.crt'),
not_valid_before = '$NOT_BEFORE',
not_valid_after = '$NOT_AFTER'
WHERE common_name = '${COMMON_NAME}';
EOF
check_rc $?
else
echo -e -n "Add ${ORANGE}${COMMON_NAME}${NC} in DB: "
sqlite3 $DB_PATH <<EOF
INSERT INTO certs (common_name,san_dns,san_ip,cert_key,cert_csr,cert_crt,not_valid_before,not_valid_after)
VALUES ('${COMMON_NAME}', '$DNS_LINE','$IP_LINE',
readfile('${CERTS_TMP_PATH}/${COMMON_NAME}.key'),
readfile('${CERTS_TMP_PATH}/${COMMON_NAME}.csr'),
readfile('${CERTS_TMP_PATH}/${COMMON_NAME}.crt'),
'$NOT_BEFORE',
'$NOT_AFTER')
EOF
check_rc $?
fi
}
main "$@"