#!/usr/bin/env bash
set -e
CA_CERT=""
CA_KEY=""
CN=""
KEY_PASS=""
ENCRYPT_KEY=false
OUT_DIR="./output"
DNS_LIST=()
IP_LIST=()
DAYS=365
usage() {
cat <<EOF
Usage:
$0 --ca-cert <ca.crt> --ca-key <ca.key> --cn <common_name> [options]
Required:
--ca-cert CA certificate path
--ca-key CA private key path
--cn Certificate Common Name
Optional:
--dns Add DNS SAN (can repeat)
--ip Add IP SAN (can repeat)
--encrypt-key Enable private key encryption (AES-256, RSA 2048-bit)
Password will be prompted interactively (no echo)
--out-dir Output directory (default: ./output)
--days Valid days (default: 365)
Example:
# With encrypted private key
$0 \\
--ca-cert ca.crt \\
--ca-key ca.key \\
--cn compute01 \\
--dns compute01 \\
--ip 90.91.185.56 \\
--encrypt-key
# Without encryption
$0 \\
--ca-cert ca.crt \\
--ca-key ca.key \\
--cn compute01
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--ca-cert)
CA_CERT="$2"
shift 2
;;
--ca-key)
CA_KEY="$2"
shift 2
;;
--cn)
CN="$2"
shift 2
;;
--encrypt-key)
ENCRYPT_KEY=true
shift
;;
--dns)
DNS_LIST+=("$2")
shift 2
;;
--ip)
IP_LIST+=("$2")
shift 2
;;
--out-dir)
OUT_DIR="$2"
shift 2
;;
--days)
DAYS="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1"
usage
exit 1
;;
esac
done
if [[ -z "$CA_CERT" || -z "$CA_KEY" || -z "$CN" ]]; then
echo "ERROR: missing required arguments"
usage
exit 1
fi
if [[ "$ENCRYPT_KEY" == true ]]; then
read -rs -p "Enter key password: " KEY_PASS
echo
read -rs -p "Confirm key password: " KEY_PASS_CONFIRM
echo
if [[ "$KEY_PASS" != "$KEY_PASS_CONFIRM" ]]; then
echo "ERROR: Passwords do not match"
exit 1
fi
if [[ -z "$KEY_PASS" ]]; then
echo "ERROR: Key password cannot be empty"
exit 1
fi
fi
mkdir -p "$OUT_DIR"
KEY_FILE="${OUT_DIR}/${CN}.key"
CSR_FILE="${OUT_DIR}/${CN}.csr"
CRT_FILE="${OUT_DIR}/${CN}.crt"
PEM_FILE="${OUT_DIR}/${CN}.pem"
CONF_FILE="${OUT_DIR}/${CN}_openssl.cnf"
echo "Generating OpenSSL config..."
cat > "$CONF_FILE" <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
CN = ${CN}
[ req_ext ]
subjectAltName = @alt_names
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
[ alt_names ]
EOF
DNS_INDEX=1
for dns in "${DNS_LIST[@]}"; do
echo "DNS.${DNS_INDEX} = ${dns}" >> "$CONF_FILE"
DNS_INDEX=$((DNS_INDEX + 1))
done
IP_INDEX=1
for ip in "${IP_LIST[@]}"; do
echo "IP.${IP_INDEX} = ${ip}" >> "$CONF_FILE"
IP_INDEX=$((IP_INDEX + 1))
done
echo "Generating private key (RSA 2048-bit)..."
if [[ "$ENCRYPT_KEY" == true ]]; then
openssl genrsa -aes256 -passout stdin -out "$KEY_FILE" 2048 <<< "$KEY_PASS"
else
openssl genrsa -out "$KEY_FILE" 2048
fi
chmod 600 "$KEY_FILE"
echo "Generating CSR..."
if [[ "$ENCRYPT_KEY" == true ]]; then
openssl req \
-new \
-key "$KEY_FILE" \
-passin stdin \
-out "$CSR_FILE" \
-config "$CONF_FILE" <<< "$KEY_PASS"
else
openssl req \
-new \
-key "$KEY_FILE" \
-out "$CSR_FILE" \
-config "$CONF_FILE"
fi
echo "Signing certificate with CA..."
openssl x509 \
-req \
-in "$CSR_FILE" \
-CA "$CA_CERT" \
-CAkey "$CA_KEY" \
-CAcreateserial \
-out "$CRT_FILE" \
-days "$DAYS" \
-sha256 \
-extensions req_ext \
-extfile "$CONF_FILE"
echo "Generating combined PEM..."
cat "$CRT_FILE" "$KEY_FILE" > "$PEM_FILE"
chmod 600 "$PEM_FILE"
KEY_PASS=""
KEY_PASS_CONFIRM=""
echo
echo "================================================="
echo "Certificate generation completed"
echo "================================================="
echo "Key: $KEY_FILE"
echo "Certificate: $CRT_FILE"
echo "PEM: $PEM_FILE"
if [[ "$ENCRYPT_KEY" == true ]]; then
echo "Key encrypted: AES-256 (RSA 2048-bit)"
fi
echo
echo "Verify SAN:"
echo "openssl x509 -in $CRT_FILE -text -noout"
echo "================================================="