342 lines
11 KiB
Bash
Executable File
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 "$@" |