function make_ca() { local CA_DAYS=3650 # Default validity period for CA certificates # CA defaults to the main CA if not specified, but can be overridden with --issuing-ca local CA_FILE_PREFIX="ca" local PATHLEN=0 while [[ $# -gt 0 ]]; do case "$1" in --days) if [[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]]; then echo "ERROR: Invalid value for --days. Must be a positive integer." >&2 return 1 fi CA_DAYS="$2" shift 2 ;; --issuing-ca) if [[ -z "$2" ]]; then echo "ERROR: Missing value for --issuing-ca." >&2 return 1 fi if [[ "$2" == "ca" ]]; then echo "ERROR: --issuing-ca cannot be 'ca' as it is reserved for the root CA." >&2 return 1 fi CA_FILE_PREFIX="$2" shift 2 ;; --path-len) if [[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]]; then echo "ERROR: Invalid value for --path-len. Must be a non-negative integer." >&2 return 1 fi PATHLEN="$2" shift 2 ;; *) break ;; esac done # Use the provided directory argument local CA_DIR="$1" local CA_NAME="$2" shift 2 if [[ -z "$CA_DIR" || -z "$CA_NAME" || ! -d "$CA_DIR" ]]; then echo "ERROR: Certificate directory $CA_DIR does not exist." return 1 fi local ROOT_CA_CERT="ca_cert.pem" local ROOT_CA_KEY="ca_key.pem" local CA_CERT="${CA_FILE_PREFIX}_cert.pem" local CA_KEY="${CA_FILE_PREFIX}_key.pem" # Generate CA certificate and key if they don't exist if [[ ! -f "$CA_DIR/$ROOT_CA_CERT" || ! -f "$CA_DIR/$ROOT_CA_KEY" ]]; then # Check, if the user requested a non-root CA without an existing root CA, which is not possible. if [[ "$ROOT_CA_CERT" != "$CA_CERT" ]]; then echo "ERROR: Cannot create issuing CA '$CA_NAME' without existing root CA certificate and key. Please create the root CA first." >&2 return 1 fi echo "Generating CA certificate '$CA_NAME' and key..." if ! openssl req \ -quiet \ -x509 \ -newkey rsa:4096 \ -keyout "$CA_DIR/$ROOT_CA_KEY" \ -out "$CA_DIR/$ROOT_CA_CERT" \ -days "$CA_DAYS" \ -noenc \ -subj "/CN=${CA_NAME}" \ -text \ -addext "basicConstraints=critical,CA:TRUE,pathlen:${PATHLEN}"; then echo "ERROR: Failed to generate CA certificate and key." >&2 return 1 fi return 0 fi if [[ ! -f "$CA_DIR/$CA_CERT" || ! -f "$CA_DIR/$CA_KEY" ]]; then echo "Generating issuing CA certificate '$CA_NAME' and key..." if ! openssl req \ -quiet \ -newkey rsa:4096 \ -keyout "$CA_DIR/${CA_KEY}" \ -noenc \ -subj "/CN=${CA_NAME}" \ -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \ | openssl x509 \ -req \ -CA "$CA_DIR/$ROOT_CA_CERT" \ -CAkey "$CA_DIR/$ROOT_CA_KEY" \ -copy_extensions copyall \ -days "$CA_DAYS" \ -text \ -out "$CA_DIR/${CA_CERT}"; then echo "ERROR: Failed to generate issuing CA certificate and key." >&2 return 1 fi fi return 0 } function _is_ip() { if [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then return 0 else return 1 fi } function _is_dns() { if [[ "$1" =~ ^[a-z0-9-]+(\.[a-z0-9-]+)*$ ]]; then return 0 else return 1 fi } function make_server_cert() { local CA_FILE_PREFIX="ca" # Default to CA if no issuing CA is used local CERT_DAYS=365 # Default validity period for leaf certificates while [[ $# -gt 0 ]]; do case "$1" in --issuing-ca) if [[ -z "$2" ]]; then echo "ERROR: Missing value for --issuing-ca." >&2 return 1 fi if [[ "$2" == "ca" ]]; then echo "ERROR: --issuing-ca cannot be 'ca' as it is reserved for the root CA." >&2 return 1 fi CA_FILE_PREFIX="$2" shift 2 ;; --days) if [[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]]; then echo "ERROR: Invalid value for --days. Must be a positive integer." >&2 return 1 fi CERT_DAYS="$2" shift 2 ;; *) break ;; esac done local CERT_DIR="$1" local CERT_SUBJECT_NAME="$2" shift 2 local CA_CERT="${CA_FILE_PREFIX}_cert.pem" local CA_KEY="${CA_FILE_PREFIX}_key.pem" if [[ -z "$CERT_DIR" || ! -d "$CERT_DIR" ]]; then echo "ERROR: Certificate directory $CERT_DIR does not exist." return 1 fi if [[ -z "$CERT_SUBJECT_NAME" ]]; then echo "ERROR: Subject name is required." >&2 return 1 fi if ! _is_dns "$CERT_SUBJECT_NAME"; then echo "ERROR: Invalid subject name '$CERT_SUBJECT_NAME'. Must be a valid DNS name." >&2 return 1 fi if [[ ! -f "$CERT_DIR/$CA_CERT" || ! -f "$CERT_DIR/$CA_KEY" ]]; then echo "ERROR: Signing CA certificate and key not found in $CERT_DIR. Please call setup a signing CA first." >&2 return 1 fi # Calculate the "account" name from the subject name, the hostname part before the first dot local CERT_NAME="${CERT_SUBJECT_NAME%%.*}" # Start with the subjectAltName extension containing the main DNS name local SANS=("DNS:${CERT_SUBJECT_NAME}") # Combine the remaining arguments into a single string for the subjectAltName extension while [[ $# -gt 0 ]]; do if _is_ip "$1"; then SANS+=("IP:$1") elif _is_dns "$1"; then SANS+=("DNS:$1") else echo "ERROR: Invalid SAN entry '$1'" >&2 return 1 fi shift done # Join the SAN entries with commas for the OpenSSL command local SANS_EXT="subjectAltName=$(IFS=,; echo "${SANS[*]}")" echo "Generating server certificate for '$CERT_SUBJECT_NAME' with SANs:" for san in "${SANS[@]}"; do echo " - $san" done # Generate server certificate and key if they don't exist if [[ ! -f "$CERT_DIR/${CERT_NAME}_cert.pem" || ! -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then echo "Generating server certificate and key..." if ! openssl req \ -quiet \ -newkey rsa:4096 \ -keyout "$CERT_DIR/${CERT_NAME}_key.pem" \ -noenc \ -subj "/CN=${CERT_SUBJECT_NAME}" \ -addext "basicConstraints=critical,CA:FALSE" \ -addext "keyUsage=digitalSignature,keyEncipherment" \ -addext "extendedKeyUsage=serverAuth,clientAuth" \ -addext "$SANS_EXT" \ | openssl x509 \ -req \ -CA "$CERT_DIR/$CA_CERT" \ -CAkey "$CERT_DIR/$CA_KEY" \ -copy_extensions copyall \ -days $CERT_DAYS \ -text \ -out "$CERT_DIR/${CERT_NAME}_cert.pem"; then echo "ERROR: Failed to generate server certificate and key." >&2 return 1 fi fi return 0 } function make_pfx() { local CERT_DIR="$1" local CERT_NAME="$2" local PFX_PASSWORD="${3:-}" if [[ -z "$CERT_DIR" || ! -d "$CERT_DIR" ]]; then echo "ERROR: Certificate directory $CERT_DIR does not exist." return 1 fi if [[ -z "$CERT_NAME" ]]; then echo "ERROR: Certificate name is required." >&2 return 1 fi if [[ ! -f "$CERT_DIR/${CERT_NAME}_cert.pem" || ! -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then echo "ERROR: Server certificate and key not found in $CERT_DIR. Please call make_server_cert first." >&2 return 1 fi if [[ ! -f "$CERT_DIR/ca_cert.pem" || ! -f "$CERT_DIR/ca_key.pem" ]]; then echo "ERROR: CA certificate and key not found in $CERT_DIR. Please call make_ca first." >&2 return 1 fi if [[ -z "$PFX_PASSWORD" ]]; then PFX_PASSWORD="changeit" fi if [[ ! -f "$CERT_DIR/${CERT_NAME}.pfx" ]]; then echo -n "Generating PKCS#12 (PFX) file..." if ! openssl pkcs12 \ -export -out "$CERT_DIR/${CERT_NAME}.pfx" \ -inkey "$CERT_DIR/${CERT_NAME}_key.pem" \ -in "$CERT_DIR/${CERT_NAME}_cert.pem" \ -certfile "$CERT_DIR/ca_cert.pem" \ -password pass:"$PFX_PASSWORD"; then echo "ERROR: Failed to generate PKCS#12 (PFX) file." >&2 return 1 fi echo "done." else echo "PKCS#12 (PFX) file already exists, aborting generation." return 1 fi return 0 }