diff --git a/bin/create-ca.sh b/bin/create-ca.sh new file mode 100755 index 0000000..123983b --- /dev/null +++ b/bin/create-ca.sh @@ -0,0 +1,163 @@ +#!/bin/bash +############################################################# +# Script name: create-CA.sh +# Author: Gilles Mouchet (gilles.mouchet@gmail.com +# Version: 1.0.0 +# Description: Script to create a own CA +# 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-19 +# Added: +# - CA creation +# Project initialization: +# - initialization by gilles.mouchet@gmail.com +# +############################################################ + +version=1.0.0 + +############################################################ +# FUNCTIONS +############################################################ +#----------------------------------------------------------- +# display usage +usage() { + cat << EOF +Usage: ./$(basename "$0") -n +Template script +Options: + -n, --common-name - CA common name [mandatory] + -h, --help - show this help + -v, --version - show script version + +Examples: + Show this help + ./$(basename "$0") -n "GMOLab CA" +EOF +} + +#----------------------------------------------------------- +############################################################ +# MAIN +############################################################ + +main(){ + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +# read library + source "$ROOT_DIR/lib/stdlib.sh" + +# check if param exist + if [ -z "$1" ]; then + usage + exit 1 + fi + +# init config + init_default + init_env + +# set color + set_color + +# check if script is runnibf with sudo + check_sudo + + # read cli parameters + while [[ "$#" -gt 0 ]]; do + case "$1" in + -n|--commonname) + # check if param $2 exist + if [ -z "$2" ]; then + echo -e "\n${RED}Error: Argument missing for option -n or --name${NC}\n" + usage + exit 1 + else + COMMON_NAME="$2" + fi + shift 2 + ;; + version|-v|--version) + cat << EOF +$(basename "$0") $version Copyright (C) 2003 - $(date +%Y) Gilles Mouchet +EOF + exit + ;; + *|help|-h|--help) + usage + exit + ;; + esac + done + + + # clean variable commonName + CA_NAME=$(clean_string "${COMMON_NAME}") + # summary + + printf " +${CYAN}Summary +----------------------------------------------------------------------------${NC} +CA private file name (key): ${GREEN}%s.key${NC} +CA public file name (crt): ${GREEN}%s.crt${NC} + +${CYAN}Informations${NC} +Country name: ${ORANGE}%s${NC} +State or province name: ${ORANGE}%s${NC} +Locality name: ${ORANGE}%s${NC} +Organization name: ${ORANGE}%s${NC} +Organizational unit name: ${ORANGE}%s${NC} +Common name: ${ORANGE}%s${NC} +Email address: ${ORANGE}%s${NC} + +${RED}IMPORTANT${NC} +You will be asked for a password. Choose a ${RED}STRONG PASSWORD${NC} +and KEEP IT SECURE. + +You will be asked for it when creating certificates. + +" "${KEY_CA_PATH}/${CA_NAME}" "${CRT_CA_PATH}/${CA_NAME}" \ + "$COUNTRY_NAME" "$STATE_OR_PROVINCE_NAME" "$LOCALITY_NAME" \ + "$ORGANIZATION_NAME" "$ORGANIZATIONAL_UNIT_NAME" "$COMMON_NAME" "$EMAIL_ADDRESS" + + yes_no "Is it ok" + +# check if CA files exist + if [ -f "$KEY_CA_PATH/$CA_NAME.key" ]; then + #echo -e "${RED}\n$KEY_CA_PATH/$CA_NAME.key already exists!\n" + msg_warn "\n$KEY_CA_PATH/$CA_NAME.key already exists!\n" + yes_no "Are you sure you want to delete it" + fi + +# config ca-conf file + sed -e "s|%COUNTRY_NAME%|$COUNTRY_NAME|" \ + -e "s|%STATE_OF_PROVINCE_NAME%|$STATE_OR_PROVINCE_NAME|" \ + -e "s|%LOCALITY_NAME%|$LOCALITY_NAME|" \ + -e "s|%ORGANIZITION_NAME%|$ORGANIZATION_NAME|" \ + -e "s|%ORGANiZATION_UNIT_NAME%|$ORGANIZATIONAL_UNIT_NAME|" \ + -e "s|%COMMON_NAME%|$COMMON_NAM|" \ + -e "s|%EMAIL_ADDRESS%|$EMAIL_ADDRESS|" < $ROOT_DIR/config/ca-config.tmpl > $ROOT_DIR/config/ca-config + +# create ca + openssl req -new -x509 -extensions v3_ca -days 1825 -newkey rsa:4096 \ + -keyout ${KEY_CA_PATH}/${CA_NAME}.key \ + -out ${CRT_CA_PATH}/${CA_NAME}.crt \ + -config $ROOT_DIR/config/ca-config \ + -batch > /dev/null 2>&1 + + if [ "$?" == "0" ]; then + echo -e "${RED}!! Keep your password safe !! ${NC}" + echo -e "${GREEN}CA created successfully ${NC}" + echo -e "Install ${ORANGE}${CRT_CA_PATH}/${CA_NAME}.crt${NC} in you browser." + else + echo -e "${RED}An error occured (rc: $?)${NC}" + fi +} +main "$@" diff --git a/bin/generate-cert.sh b/bin/generate-cert.sh new file mode 100755 index 0000000..7f39d0a --- /dev/null +++ b/bin/generate-cert.sh @@ -0,0 +1,348 @@ +#!/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 </dev/null; then + msg_error "Access denied: user $USER does not have sudo privileges or a password is required.." + exit 1 + fi + +# check if the effective user ID is 0 (root) + if [[ $EUID -ne 0 ]]; then + msg_error "\nThis script must be run as root or with sudo.\n" + exit 1 + fi + +# install sqlite + echo -n -e "Install ${ORANGE}sqlite${NC}. Please wait...: " + dnf install sqlite -y > /dev/null 2>&1 + check_rc $? + +# create paths + echo -n -e "Create path $BIN_PATH: " + if [ ! -d "$BIN_PATH" ]; then + mkdir -p "$BIN_PATH" 2>/dev/null + check_rc $? + else + msg_warn "$BIN_PATH already exists!" + fi + + echo -n -e "Create path $BIN_PATH/lib: " + if [ ! -d "$BIN_PATH/lib" ]; then + mkdir -p "$BIN_PATH/lib" 2>/dev/null + check_rc $? + else + msg_warn "$BIN_PATH/lib already exists!" + fi + + echo -e -n "Create $BIN_PATH/bin: " + if [ ! -d "$BIN_PATH/bin" ]; then + mkdir -p $BIN_PATH/bin 2>/dev/null + check_rc $? + else + msg_warn "$BIN_PATH/bin already exists!" + fi + + echo -e -n "Create $ETC_PATH: " + if [ ! -d "$ETC_PATH" ]; then + mkdir -p $ETC_PATH 2>/dev/null + check_rc $? + else + msg_warn "$ETC_PATH already exists!" + fi + +# copy config file + echo -e -n "Copy ${ORANGE}$ROOT_DIR/config/own-pki.conf${NC} to ${ETC_PATH}/: " + cp "$ROOT_DIR/config/own-pki.conf" "${ETC_PATH}/." + check_rc $? + +# create DB + echo -n -e "Create DB $DB_PATH: " + if [ -f "$DB_PATH" ]; then + msg_warn "$DB_PATH already exists!" + yes_no "Are you sure you want to recreate a database" + rm -rf "$DB_PATH" + init_db + check_rc $? + else + init_db + check_rc $? + fi + +# copy script file to opt + msg_info "Copy librairie scripts files" + files=( $ROOT_DIR/lib/* ) + for f in "${files[@]}"; do + echo -e -n " copy ${ORANGE}$f${NC} to ${BIN_PATH}/lib: " + cp "$f" "$BIN_PATH/lib/" + check_rc $? + done + + msg_info "Copy main scripts files" + files=( $ROOT_DIR/bin/* ) + for f in "${files[@]}"; do + # exclude install.sh + if [ "$f" != "$ROOT_DIR/bin/install.sh" ]; then + echo -e -n " copy ${ORANGE}$f${NC} to ${BIN_PATH}/bin: " + cp "$f" "$BIN_PATH/bin/" + check_rc $? + fi + done + + msg_info "Create link" + files=( $ROOT_DIR/bin/* ) + for f in "${files[@]}"; do + # exclude install.sh + if [ "$f" != "$ROOT_DIR/bin/install.sh" ]; then + SCRIPT_FILE=$(basename "$f") + echo -e -n " create link ${ORANGE}$BIN_PATH/bin/$SCRIPT_FILE${NC} to /usr/local/bin/: " + ln -f -s $BIN_PATH/bin/$SCRIPT_FILE /usr/local/bin/ #>"$out_tmp" 2>"$err_tmp" + check_rc $? + + fi + done +} +main "$@" diff --git a/config/ca-config b/config/ca-config new file mode 100644 index 0000000..b1b6c52 --- /dev/null +++ b/config/ca-config @@ -0,0 +1,102 @@ +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +[ ca ] +default_ca = CA_default # The default ca section +[ CA_default ] +dir = . # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/dbca/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial/serial # The current serial number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file +x509_extensions = usr_cert # The extentions to add to the cert +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = md5 # which md to use. +preserve = no # keep passed DN ordering +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert +string_mask = nombstr + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = CH +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Vaud + +localityName = Locality Name (eg, city) +localityName_default = Nyon + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = GMO Lab (gmolab) + +organizationalUnitName = Organizational Unit Name (eg, section) + +organizationalUnitName_default = ITCS (Information Technology and Communications Service) + +commonName = Common Name (eg, YOUR name) +commonName_default = +commonName_max = 64 + +emailAddress = Email Address +emailAddress_default = example@example.com +emailAddress_max = 40 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] +basicConstraints=CA:FALSE +nsComment = "OpenSSL Generated Certificate" +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always +basicConstraints = CA:true + +[ crl_ext ] +authorityKeyIdentifier=keyid:always,issuer:always \ No newline at end of file diff --git a/config/ca-config.tmpl b/config/ca-config.tmpl new file mode 100644 index 0000000..09674e1 --- /dev/null +++ b/config/ca-config.tmpl @@ -0,0 +1,102 @@ +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +[ ca ] +default_ca = CA_default # The default ca section +[ CA_default ] +dir = . # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/dbca/index.txt # database index file. +new_certs_dir = $dir/newcerts # default place for new certs. +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial/serial # The current serial number +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file +x509_extensions = usr_cert # The extentions to add to the cert +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = md5 # which md to use. +preserve = no # keep passed DN ordering +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert +string_mask = nombstr + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = %COUNTRY_NAME% +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = %STATE_OF_PROVINCE_NAME% + +localityName = Locality Name (eg, city) +localityName_default = %LOCALITY_NAME% + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = %ORGANIZITION_NAME% + +organizationalUnitName = Organizational Unit Name (eg, section) + +organizationalUnitName_default = %ORGANiZATION_UNIT_NAME% + +commonName = Common Name (eg, YOUR name) +commonName_default = %COMMON_NAME% +commonName_max = 64 + +emailAddress = Email Address +emailAddress_default = %EMAIL_ADDRESS% +emailAddress_max = 40 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] +basicConstraints=CA:FALSE +nsComment = "OpenSSL Generated Certificate" +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer:always + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer:always +basicConstraints = CA:true + +[ crl_ext ] +authorityKeyIdentifier=keyid:always,issuer:always \ No newline at end of file diff --git a/config/default.conf b/config/default.conf new file mode 100644 index 0000000..6b08636 --- /dev/null +++ b/config/default.conf @@ -0,0 +1,49 @@ +# Enables colorization (true) or disables it (false) +ENABLE_COLOR=true + +# Path to the certificate authority's private key file +KEY_CA_PATH=/etc/pki/tls/private + +# Path to the certificate authority's public key file +CRT_CA_PATH=/etc/pki/tls/certs + +# Default number of days for the certificate's validity +# This can be overridden with the `-t or --days` option +# when running the script. +DAYS=365 + +# Variables to use for create ca-config file +# Country Name (2 letter code) +COUNTRY_NAME=CH + +# State or province name (full name) +STATE_OR_PROVINCE_NAME=Vaud + +# Locality name (eg, city) +LOCALITY_NAME=Nyon + +# Organization name (eg, company) +ORGANIZATION_NAME="GMO Lab (gmolab)" + +# Organizational Unit Name (eg, section) +ORGANIZATIONAL_UNIT_NAME="ITCS (Information Technology and Communications Service)" + +# NOT USE. SET WITH PARAM -n from create-ca.sh script +# Common Name (eg, YOUR name) +COMMON_NAME="GMOLab CA" + +# Email address +EMAIL_ADDRESS=example@example.com + +# Debug +# false = debug inactive +# true = debug active +DEBUG=false + +# Automatically answers yes to the questions +# 0 = Ask confirmation +# 1 = does not ask confirmation +ASSUME_YES=0 + +# Temp path for certificates files +CERTS_TMP_PATH=/tmp/ca \ No newline at end of file diff --git a/config/own-pki.conf b/config/own-pki.conf new file mode 100644 index 0000000..b5fec24 --- /dev/null +++ b/config/own-pki.conf @@ -0,0 +1,39 @@ +# Enables colorization (true) or disables it (false) +#ENABLE_COLOR=true + +# Path to the certificate authority's private key file +#KEY_CA_PATH=/etc/pki/tls/private + +# Path to the certificate authority's public key file +#CRT_CA_PATH=/etc/pki/tls/certs + +# Default number of days for the certificate's validity +# This can be overridden with the `-t or --days` option +# when running the script. +#DAYS=365 + +# Variables to use for create ca-config file +# Country Name (2 letter code) +#COUNTRY_NAME=CH + +# State or province name (full name) +#STATE_OR_PROVINCE_NAME=Vaud + +# Locality name (eg, city) +#LOCALITY_NAME=Nyon + +# Organization name (eg, company) +#ORGANIZATION_NAME="GMO Lab (gmolab)" + +# Organizational Unit Name (eg, section) +#ORGANIZATIONAL_UNIT_NAME="ITCS (Information Technology and Communications Service)" + +# NOT USE. SET WITH PARAM -n from create-ca.sh script +# Common Name (eg, YOUR name) +#COMMON_NAME="GMOLab CA" + +# Email address +#EMAIL_ADDRESS=example@example.com + +# Temp path for certificates files +#CERTS_TMP_PATH=/tmp/ca \ No newline at end of file diff --git a/lib/check-rc.sh b/lib/check-rc.sh new file mode 100644 index 0000000..2349d90 --- /dev/null +++ b/lib/check-rc.sh @@ -0,0 +1,11 @@ +# check if run from script +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && exit 1 + +check_rc(){ + if [ "$1" != "0" ]; then + msg_error "Error (RC:$rc)" + exit 1 + else + msg_ok "OK" + fi +} \ No newline at end of file diff --git a/lib/core.sh b/lib/core.sh new file mode 100644 index 0000000..e05b844 --- /dev/null +++ b/lib/core.sh @@ -0,0 +1,51 @@ +# check if run from script +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && exit 1 + +init_default(){ +# read default conf file + DEFAULT_CONF="${ROOT_DIR}/config/default.conf" + [[ -f "$DEFAULT_CONF" ]] && source "$DEFAULT_CONF" + +} + +init_env() { +# read config file + CONFIG_FILE="/etc/own-pki/own-pki.conf" + [[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" || msg_error "File ${ORANGE}$CONFIG_FILE${RED} missing. Run 'sudo bin/install.sh'${NC}" + +# debug mode + if [[ "${DEBUG:-false}" == "true" ]]; then + set -x + fi + out_tmp=$(mktemp) + err_tmp=$(mktemp) +} + +clean_string() { + echo "$1" | \ + # translate special chars to closest ASCII (e.g., 'é' -> 'e') + iconv -f utf-8 -t ascii//TRANSLIT | \ + # convert to lowercase + tr '[:upper:]' '[:lower:]' | \ + # replace any non-alphanumeric character with an underscore + sed -E 's/[^a-z0-9]+/_/g' | \ + # replace multiple underscores into one + sed -E 's/(_)+/_/g' | \ + # remove underscores at the beginning or end + sed -E 's/^_|_$//g' +} + +check_sudo(){ +# check if user has sudo rigth + if sudo ! -n true 2>/dev/null; then + msg_error "\nAccess denied: user $USER does not have sudo privileges or a password is required.\n" + exit 1 + fi + +# check if the effective user ID is 0 (root) + if [[ $EUID -ne 0 ]]; then + msg_error "\nThis script must be run as root or with sudo.\n" + exit 1 + fi + +} diff --git a/lib/error.sh b/lib/error.sh new file mode 100644 index 0000000..aefb9f5 --- /dev/null +++ b/lib/error.sh @@ -0,0 +1,13 @@ +trap 'on_error $LINENO' ERR + +on_error() { + local exit_code=$? + local line="$1" + local cmd="${BASH_COMMAND}" + + log_error "Error (code: $exit_code)" + log_error "Line: $line" + log_error "Command: $cmd" + + exit "$exit_code" +} \ No newline at end of file diff --git a/lib/log.sh b/lib/log.sh new file mode 100644 index 0000000..4610468 --- /dev/null +++ b/lib/log.sh @@ -0,0 +1,23 @@ +# check if run from script +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && exit 1 + +LOG_FILE="" + +init_logs() { + LOG_DIR="${ROOT_DIR}/logs" + mkdir -p "$LOG_DIR" + LOG_FILE="${LOG_DIR}/app.log" +} + +_log() { + local level="$1" + local message="$2" + local timestamp + timestamp="$(date '+%Y-%m-%d %H:%M:%S')" + + echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" +} + +log_info() { _log "INFO" "$1"; } +log_warn() { _log "WARN" "$1"; } +log_error() { _log "ERROR" "$1"; } \ No newline at end of file diff --git a/lib/message.sh b/lib/message.sh new file mode 100644 index 0000000..c12fd49 --- /dev/null +++ b/lib/message.sh @@ -0,0 +1,28 @@ +# check if run from script +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && exit 1 + +_message(){ + case $1 in + INFO) + echo -e "${NC}$2${NC}" + ;; + OK) + echo -e "${GREEN}$2${NC}" + ;; + ASK) + echo -e -n "${CYAN}$2${NC}" + ;; + WARN) + echo -e "${ORANGE}$2${NC}" + ;; + ERROR) + echo -e "${RED}$2${NC}" + ;; + esac +} + +msg_info() { _message "INFO" "$1"; } +msg_ok() { _message "OK" "$1"; } +msg_ask() { _message "ASK" "$1"; } +msg_warn() { _message "WARN" "$1"; } +msg_error() { _message "ERROR" "$1"; } \ No newline at end of file diff --git a/lib/set-color.sh b/lib/set-color.sh new file mode 100644 index 0000000..89eec51 --- /dev/null +++ b/lib/set-color.sh @@ -0,0 +1,25 @@ +# check if run from script +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && exit 1 + +#----------------------------------------------------------- +# set colors +# RED Error +# ORANGE Attention or color for parameters when +# confirmation +# CYAN Ask to useer +# GREEN OK +set_color(){ + if [[ "$ENABLE_COLOR" == "true" ]]; then + RED='\e[0;31m' + ORANGE='\e[0;33m' + CYAN='\e[0;36m' + GREEN='\e[0;32m' + NC='\e[0m' + else + RED='' + ORANGE='' + CYAN='' + GREEN='' + NC='' + fi +} diff --git a/lib/stdlib.sh b/lib/stdlib.sh new file mode 100644 index 0000000..350e5e5 --- /dev/null +++ b/lib/stdlib.sh @@ -0,0 +1,11 @@ +# scripts and lib +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +#source "$ROOT_DIR/lib/log.sh" +#source "$ROOT_DIR/lib/error.sh" +source "$ROOT_DIR/lib/core.sh" +source "$ROOT_DIR/lib/set-color.sh" +source "$ROOT_DIR/lib/message.sh" +source "$ROOT_DIR/lib/check-rc.sh" +source "$ROOT_DIR/lib/yes-no.sh" \ No newline at end of file diff --git a/lib/yes-no.sh b/lib/yes-no.sh new file mode 100644 index 0000000..e3f96db --- /dev/null +++ b/lib/yes-no.sh @@ -0,0 +1,14 @@ +# check if run from script +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && exit 1 + +yes_no(){ + if [ "$ASSUME_YES" == "0" ]; then + echo -n -e "${CYAN}$1 [y/N]? ${NC}" + unset answer + read answer + if [ "${answer}" != "y" ]; then + echo -e "${ORANGE}Canceled!${NC}" + exit 1 + fi + fi +} \ No newline at end of file diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..b6dab44 --- /dev/null +++ b/logs/app.log @@ -0,0 +1,15 @@ +[2026-04-18 13:51:26] [ERROR] ❌ Erreur (code: 127) +[2026-04-18 13:51:26] [ERROR] 📍 Ligne: 77 +[2026-04-18 13:51:26] [ERROR] 💥 Commande: risky_function +[2026-04-18 13:54:55] [ERROR] ❌ Error (code: 127) +[2026-04-18 13:54:55] [ERROR] 📍 Line: 77 +[2026-04-18 13:54:55] [ERROR] 💥 Command: risky_function +[2026-04-18 13:55:28] [ERROR] ❌ Error (code: 1) +[2026-04-18 13:55:28] [ERROR] 📍 Line: 77 +[2026-04-18 13:55:28] [ERROR] 💥 Command: false +[2026-04-18 14:16:23] [ERROR] Error (code: 127) +[2026-04-18 14:16:23] [ERROR] Line: 77 +[2026-04-18 14:16:23] [ERROR] Command: message_info "info" +[2026-04-18 14:25:42] [ERROR] Error (code: 127) +[2026-04-18 14:25:42] [ERROR] Line: 87 +[2026-04-18 14:25:42] [ERROR] Command: 3echo -e "\n"