From ee2072bf5c61b71624151742867d856979de37be Mon Sep 17 00:00:00 2001 From: Whatchawnt <143775469+Whatchawnt@users.noreply.github.com> Date: Mon, 14 Jul 2025 01:06:38 -0400 Subject: [PATCH 1/3] Create generate-p12.sh Script 1. Added ability to generate .p12 file (fullPKCS12.p12) from the certificates, private keys, and certificate chain. The script downloads the root certificate from the copy of the intermediate certificate. 2. Added simlink for /etc/letsencrypt/live/npm-10/fullpkcs12.sh to /etc/letsencrypt/live/npm-10/fullpkcs12-#.sh (Where -# is the number appended based on the number that comes after the private key in the same directory, so if privkey1.pem is in the folder then the script will generate fullpkcs12-1.sh) Still Yet To do: 1. Add ability for changing "npm-10" by taking in an argument by the script. --- backend/scripts/generate-p12.sh | 405 ++++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) create mode 100644 backend/scripts/generate-p12.sh diff --git a/backend/scripts/generate-p12.sh b/backend/scripts/generate-p12.sh new file mode 100644 index 000000000..448ab2bbf --- /dev/null +++ b/backend/scripts/generate-p12.sh @@ -0,0 +1,405 @@ +#!/bin/bash +# +# +# Selected Cipher would be passed in from the GUI but this is an example of what the GUI could provide for a selected CIPHER +SELECTED_CIPHER="aes-256-cbc" +SELECTED_CIPHER="${SELECTED_CIPHER^^}" + +# Selected Digest would be passed in from the GUI but this is an example of what the GUI could provide for a selected DIGEST +SELECTED_DIGEST="sha256" +SELECTED_DIGEST="${SELECTED_DIGEST^^}" + +#Docker Path to live p12 files. Live is a SimLink to Archived +PATH_TO_LIVE="/etc/letsencrypt/live" + +#Docker Path to archive p12 files. +PATH_TO_ARCHIVE="/etc/letsencrypt/archive" + + +#The Specific Folder Name for the NPM cert, Should be the text "npm" followe by a hyphen and numbers (I.E. npm-3) +NPM_FOLDER="npm-10" + +#The Directory containing the certificate and privcate key for a npm configuration +NPM_PATH="$PATH_TO_LIVE/$NPM_FOLDER" + +#Private Key File Name (with extension) +PRIVATE_KEY="privkey.pem" +PRIVATE_KEY_FULL_PATH=$(realpath "$NPM_PATH/$PRIVATE_KEY") +echo "PRIVATE KEY FULL PATH: $PRIVATE_KEY_FULL_PATH" + +PRIVATE_KEY_FILENAME=$(basename "$PRIVATE_KEY_FULL_PATH") +INCREMENT_NUMBER="" +#If the private key has an incremented number after 'privkey' then save the number in a variable to use to increment the pkcs12 file that will be generated. +if [[ ! -z "$PRIVATE_KEY_FILENAME" ]] +then + INCREMENTED_FILE_NAME_CHECK='^.*[0-9]*\.pem' + INCREMENTED_NUMBER_CHECK='[0-9]*' + echo "PRIVATE KEY FILENAME: $PRIVATE_KEY_FILENAME" + #If the private key ends in numbers followed by the extension (I.E. privkey1.pem) then obtain the number portion, it will be used to increment the pkcs12 file as well. + #If the file does not end with an incremented number followed by the extension then the INCREMENT_NUMBER will be empty. + INCREMENT_NUMBER=$(echo "$PRIVATE_KEY_FILENAME" | grep -oP "$INCREMENTED_FILE_NAME_CHECK" | grep -oP "$INCREMENTED_NUMBER_CHECK") + echo "INCREMENT NUMBER: $INCREMENT_NUMBER" +fi + +#Certificate File Name (with extension) +CERTIFICATE_FILE="cert.pem" +#Find the real path +CERTIFICATE_FILE_FULL_PATH=$(realpath "$NPM_PATH/$CERTIFICATE_FILE") +echo "CERTIFICATE FILE FULL PATH: $CERTIFICATE_FILE_FULL_PATH" + +#Chain of Certificates (with extension). The certificate file containing your issued cert and any intermediate CAs +CERTIFICATE_CHAIN_FILE="fullchain.pem" +#Find the real path +CERTIFICATE_CHAIN_FILE_FULL_PATH=$(realpath "$NPM_PATH/$CERTIFICATE_CHAIN_FILE") +echo "CERTIFICATE CHAIN FILE FULL PATH: $CERTIFICATE_CHAIN_FILE_FULL_PATH" + +# if INCREMENT_NUMBER is not empty then use the filename for pkcs12 without incrementing to match the number for the private key. +if [[ ! -z "$INCREMENT_NUMBER" ]] +then + #The Name to give the PKCS12 file generated by the script (with extension). It will be placd in the same file as the private key file. + PKCS12_FILE_NAME="fullpkcs12-$INCREMENT_NUMBER.p12" + echo "PKCS12 FILE NAME: $PKCS12_FILE_NAME" + PKCS12_SIMLINK_NAME="fullpkcs12.p12" +else + #The Name to give the PKCS12 file generated by the script (with extension). It will be placd in the same file as the private key file. + PKCS12_FILE_NAME="fullpkcs12.p12" + echo "PKCS12 FILE NAME: $PKCS12_FILE_NAME" +fi + + +#The directory that the pkcs12 file will be saved in is the same as the private key file +GET_PATH=$(realpath "$NPM_PATH/$PRIVATE_KEY") +PKCS12_FULL_PATH=$(dirname "$GET_PATH")"/$PKCS12_FILE_NAME" +echo "PKCS12 FULL PATH: $PKCS12_FULL_PATH" +PKCS12_SIMLINK_PATH="$NPM_PATH/$PKCS12_SIMLINK_NAME" +echo "PKCS12 SIMLINK PATH: $PKCS12_SIMLINK_PATH" + +#Root Certificate File Name (with extension) the root file does not exist so will need to create it later from the fullchain +ROOT_CERTIFICATE="root$INCREMENT_NUMBER.pem" +ROOT_FULL_PATH=$(dirname "$GET_PATH")"/$ROOT_CERTIFICATE" +echo "ROOT_FULL_PATH: $ROOT_FULL_PATH" + +#The Password Provided by the user (Default: password) +PKCS12_PASSWORD="password" + +#Create an Array of CIPHER ALGORITHMS +#Remove the lines "Legacy:" and "Provided:" from the created List of Cipher Algorithms +LIST_OF_CIPHER_ALGORITHMS=$(openssl list -cipher-algorithms) +ARRAY_OF_CIPHER_ALGORITHMS=($(echo "${LIST_OF_CIPHER_ALGORITHMS}" | sed -E '/^Legacy:|^Provided:/d')) + +#Create an Array of DIGEST ALGORITHMS +LIST_OF_DIGEST_ALGORITHMS=$(openssl list -digest-algorithms) +ARRAY_OF_DIGEST_ALGORITHMS=($(echo "${LIST_OF_DIGEST_ALGORITHMS}" | sed -E '/^Legacy:|^Provided:/d')) + +#ARRAY of Temp files that are generated by the script. At the end they will be removed +declare -a TEMP_FILES_ARRAY=() + + +#Start This script only if The Certificate Chain File exist, and the private key exist, and both are also readable. +if [[ -z "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] && [[ -f "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] && [[ -r "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] +then + echo -e "\nERROR - Certificate Chain is empty or does not exist." + exit 1 +fi +if [[ -z "$PRIVATE_KEY_FULL_PATH" ]] && [[ -f "$PRIVATE_KEY_FULL_PATH" ]] && [[ -r "$PRIVATE_KEY_FULL_PATH" ]] +then + echo -e "\nERROR - Private Key is empty or does not exist." + exit 1 +fi + +#Search through the certificate chain and find the root certificate, if it does not exist in the chain file thewn download it from the intermediate certificate +if [[ -f "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] && [[ -r "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] +then + # Split the chain into separate certificates and process each one to try and find the root certificate + awk 'BEGIN{c=0} + /-----BEGIN CERTIFICATE-----/ {in_cert=1; c++; fname=sprintf("cert_temp_%02d.pem", c)} + in_cert { print > fname } + /-----END CERTIFICATE-----/ {in_cert=0}' "$CERTIFICATE_CHAIN_FILE_FULL_PATH" + + TEMP_FILES_ARRAY+=("cert_temp_01.pem") + TEMP_FILES_ARRAY+=("cert_temp_02.pem") + + FOUND_ROOT_CERTIFICATE=false + # Loop through each extracted certificate + for cert_temp_file in cert_temp_*.pem + do + echo -e "\nProcessing $cert_temp_file ..." + + # Get subject and issuer + issuer_subject=$(openssl x509 -noout -subject -issuer -in "$cert_temp_file" 2>/dev/null) + subject=$(echo "$issuer_subject" | grep 'subject=' | cut -d= -f2-) + issuer=$(echo "$issuer_subject" | grep 'issuer=' | cut -d= -f2-) + + echo " Subject: $subject" + echo " Issuer : $issuer" + + # Check if the certificate is self-signed + if [[ "$subject" == "$issuer" ]] + then + echo -e "\n Found root certificate: $cert_temp_file" + FOUND_ROOT_CERTIFICATE=true + #Save the certificate file in the same directory as the private key file + cp "$cert_temp_file" "$CERTIFICATE_FILE_FULL_PATH" + break + fi + done + + #If the root certificate was not found in the certificate chain + if [[ "$FOUND_ROOT_CERTIFICATE" == false ]] + then + #If the Root Certificate was not found within the certificate chain then obtain it from the intermediate chain + # Extract each certificate into its own file + awk 'BEGIN{c=0} + /-----BEGIN CERTIFICATE-----/ {in_cert=1; c++; fname=sprintf("cert_temp_%02d.pem", c)} + in_cert { print > fname } + /-----END CERTIFICATE-----/ {in_cert=0}' "$CERTIFICATE_CHAIN_FILE_FULL_PATH" + + # Identify the last certificate in the chain (presumably the intermediate) + last_cert=$(ls cert_temp_*.pem | sort | tail -n 1) + + echo " Attempting to download the root certificate from the Intermediate Certificate..." + #Assuming the last certificate in the chain is the Intermediate Certificate + echo -e " Using last certificate in chain: $last_cert" + + # Try to extract the AIA (issuer URL) from the intermediate cert + issuer_url=$(openssl x509 -in "$last_cert" -noout -text 2>/dev/null | \ + grep -A1 "Authority Information Access" | \ + grep "CA Issuers - URI:" | \ + sed 's/.*URI://') + + if [[ -z "$issuer_url" ]] + then + echo " No issuer URL found in the AIA field. Cannot fetch root certificate." + else + echo " Found issuer URL: $issuer_url" + + # Download as binary + curl -s -o issuer_cert_temp.der "$issuer_url" + + # Try converting to PEM + if openssl x509 -inform DER -in issuer_cert_temp.der -out "$ROOT_FULL_PATH" 2>/dev/null + then + echo " Successfully saved root certificate as PEM to: $ROOT_FULL_PATH" + TEMP_FILES_ARRAY+=("issuer_cert_temp.der") + else + echo " Conversion from DER to PEM failed. Trying to read as PEM directly..." + + if openssl x509 -in issuer_cert_temp.der -out "$ROOT_FULL_PATH" 2>/dev/null + then + #Save the certificate file in the same directory as the private key file + echo " Root certificate was already in PEM. Saved to: $ROOT_FULL_PATH" + else + echo " Failed to convert or parse the downloaded certificate." + #rm -f "$ROOT_FULL_PATH" + fi + + TEMP_FILES_ARRAY+=("issuer_cert_temp.der") + fi + + echo -e "\nProcessing $ROOT_CERTIFICATE ..." + + # Get subject and issuer + issuer_subject=$(openssl x509 -noout -subject -issuer -in "$ROOT_FULL_PATH" 2>/dev/null) + subject=$(echo "$issuer_subject" | grep 'subject=' | cut -d= -f2-) + issuer=$(echo "$issuer_subject" | grep 'issuer=' | cut -d= -f2-) + + echo " Subject: $subject" + echo -e " Issuer : $issuer" + + # Check if the certificate is self-signed + if [[ "$subject" == "$issuer" ]] + then + echo " Successfully found root certificate" + echo " Successfully dowenloaded root certificate to: $ROOT_FULL_PATH" + fi + fi + fi + +fi + + +echo -e "\nVerifying if the selected cipher is supported by the system..." + +for CIPHER in "${ARRAY_OF_CIPHER_ALGORITHMS[@]}" +do + FOUND_SELECTED_CIPHER=$(echo "$CIPHER" | grep -x "$SELECTED_CIPHER") + + if [[ ! -z "$FOUND_SELECTED_CIPHER" ]] + then + break; + fi +done + + +#If the Selected Cipher does not exist +if [[ -z "$FOUND_SELECTED_CIPHER" ]] +then + echo -e "\nThe selected Cipher '$SELECTED_CIPHER' does not exist" +else + #Generate the P12 + echo "Selected Cipher: $SELECTED_CIPHER" + echo "Found Selected Cipher: $FOUND_SELECTED_CIPHER" + echo "[*] The selected Cipher will be used for -certpbe and -keypbe inputs [*]" +fi + + +echo -e "\nVerifying if the selected digest is supported by the system..." +for DIGEST in "${ARRAY_OF_DIGEST_ALGORITHMS[@]}" +do + FOUND_SELECTED_DIGEST=$(echo "$DIGEST" | grep -x "$SELECTED_DIGEST") + + if [[ ! -z "$FOUND_SELECTED_DIGEST" ]] + then + break + fi +done + + +#If the Selected Digest does not exist +if [[ -z "$FOUND_SELECTED_DIGEST" ]] +then + echo -e "\nThe selected Digest '$SELECTED_DIGEST' does not exist" +else + #Generate the P12 + echo "Selected Digest: $SELECTED_DIGEST" + echo "Found Selected Digest: $FOUND_SELECTED_DIGEST" +fi + + +#If Selected Cipher and Digest exist exist, as well as password, certificate, root certificate, private key, password, etc. +if [[ ! -z "$FOUND_SELECTED_CIPHER" ]] && [[ ! -z "$FOUND_SELECTED_DIGEST" ]] && [[ ! -z "$PRIVATE_KEY_FULL_PATH" ]] && [[ ! -z "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] && [[ ! -z "$ROOT_FULL_PATH" ]] && [[ ! -z "$NPM_FOLDER" ]] && [[ ! -z "$PKCS12_PASSWORD" ]] +then + #The Private Key does not exist or is not readable + if [[ ! -f "$PRIVATE_KEY_FULL_PATH" ]] || [[ ! -r "$PRIVATE_KEY_FULL_PATH" ]] + then + echo -e "\nERROR - Private Key File '$NPM_PATH/$PRIVATE_KEY' does not exist or is not readable" + exit 1 + fi + #The Certificate Chain does not exist or is not readable + if [[ ! -f "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] || [[ ! -r "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] + then + echo -e "\nERROR - Certificate Chain '$CERTIFICATE_CHAIN_FILE_FULL_PATH' does not exist or is not readable" + exit 1 + fi + if [[ ! -f "$ROOT_FULL_PATH" ]] || [[ ! -r "$ROOT_FULL_PATH" ]] + then + echo -e "\nERROR - Root Certificate '$ROOT_FULL_PATH' does not exist or is not readable" + exit 1 + fi + + + echo -e "\n---------------------------------------------------------------------------------" + echo -e "\nGenerating PKCS12 ..." + + #Run the openssl command to generate the p12 file + openssl pkcs12 -export -out "$PKCS12_FULL_PATH" -certpbe "$FOUND_SELECTED_CIPHER" -keypbe "$FOUND_SELECTED_CIPHER" -macalg "$FOUND_SELECTED_DIGEST" -inkey "$PRIVATE_KEY_FULL_PATH" -in "$CERTIFICATE_CHAIN_FILE_FULL_PATH" -certfile "$ROOT_FULL_PATH" -name "$NPM_FOLDER" -password "pass:$PKCS12_PASSWORD" + + + #If the last command completed successfully + if [[ $? -eq 0 ]] + then + echo -e "\nSuccessfully generated PKCS12" + echo -e "\nPKCS12 Full Path: $PKCS12_FULL_PATH" + + #Change the ownership to npm:npm + chown npm:npm "$PKCS12_FULL_PATH" + + if [[ $? -eq 0 ]] + then + echo -e "\nSuccessfully changed $PKCS12_FULL_PATH User and Group ownership to npm" + else + echo "\nERROR - Unable to change $PKCS12_FULL_PATH User and Group ownership to npm" + exit 1 + fi + + echo -e "\nCreating simlink for PKCS12 File ..." + echo " from: $PKCS12_SIMLINK_PATH" + echo " to: $PKCS12_FULL_PATH" + + # Get just the filename (basename) from the destination path + SYMLINK_NAME="$(basename "$PKCS12_SIMLINK_PATH")" + + # Get the directory where the symlink will be created + SYMLINK_DIR="$(dirname "$PKCS12_SIMLINK_PATH")" + + # Get the relative path from the symlink destination to the source file + RELATIVE_SOURCE="$(realpath --relative-to="$SYMLINK_DIR" "$PKCS12_FULL_PATH")" + + if [[ -z "$RELATIVE_SOURCE" ]] + then + echo -e "\nERROR - Relative Path variable is empty." + fi + + + #ln -s "to-here" <- "from-here". The from-here should not exist yet, it is to be created, while the to-here should already exist. + #Create the links relative to the path + ln -sf "$RELATIVE_SOURCE" "$SYMLINK_DIR/$SYMLINK_NAME" + + if [[ $? -eq 0 ]] + then + echo -e "\nSuccessfully created SimLink" + else + echo "\nERROR - Unable to create SimLink" + exit 1 + fi + + #Change the ownership to npm:npm for SimLink + chown -h npm:npm "$PKCS12_SIMLINK_PATH" + + if [[ $? -eq 0 ]] + then + echo -e "\nSuccessfully changed $PKCS12_SIMLINK_PATH User and Group ownership to npm" + else + echo "\nERROR - Unable to change $PKCS12_SIMLINK_PATH User and Group ownership to npm" + exit 1 + fi + + #The Root Certificate that was created by this script is no longer needed after generating the .p12 file so add it to the array of temporary files to delete + TEMP_FILES_ARRAY+=("$ROOT_FULL_PATH") # Comment out if you want to keep the Root Certificate geenrated by this script (Generated from downloaing the root from the Intermediate CA Certificate). + + echo -e "\n\nCleaning up temporary files..." + for temp_file in "${TEMP_FILES_ARRAY[@]}" + do + echo -e "\n deleting $temp_file ..." + rm -f "$temp_file" + + if [[ $? -ne 0 ]] + then + echo -e "\n ERROR - Unable to delete '$temp_file'" + else + echo -e " Successfully deleted '$temp_file'" + fi + done + echo "" + else + echo -e "\nERROR - Unable to Generate PKCS12 '$PKCS12_FULL_PATH'\n" + fi + echo -e "---------------------------------------------------------------------------------\n" +else + #If one of the variables are empty + if [[ -z "$FOUND_SELECTED_CIPHER" ]] + then + echo -e "\nERROR - Selected Cipher is empty." + exit 1 + fi + if [[ -z "$FOUND_SELECTED_DIGEST" ]] + then + echo -e "\nERROR - Selected Digest is empty." + exit 1 + fi + if [[ -z "$ROOT_FULL_PATH" ]] || [[ -f "$ROOT_FULL_PATH" ]] + then + echo -e "\nERROR - Root Certificate is empty or does not exist." + exit 1 + fi + #The $NPM_FOLDER is used to create a friendly name in the openssl command. If its blank the friendly name that is displayed will be blank + if [[ -z "$NPM_FOLDER" ]] + then + echo -e "\nWARNING: Friendly Name value is empty." + fi + if [[ -z "$PKCS12_PASSWORD" ]] + then + echo -e "\nERROR - Password is empty." + exit 1 + fi +fi From 00a5008771deb242b7c2e1f899f849a6c863e242 Mon Sep 17 00:00:00 2001 From: Whatchawnt <143775469+Whatchawnt@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:41:46 -0400 Subject: [PATCH 2/3] Update generate-p12.sh 1. Added Option to parse in the npm value (I.E. npm-10) as a required variable which is the npm value that the certificate files are stored in (I.E. '/etc/letsencrypt/live/npm-10/' and '/etc/letsencrypt/archive/npm-10/' ) 2. Added the option to parse a password into the script to be used as the p12 password for securing the private key within the generated PKCS12 file. 3. Added Variable in script to allow the user to keep the root certificate that the script generates ($KEEP_ROOT_CERTIFICATE which it is set to true by default). 4. Added Simlinks for the .p12 file (follows the current convention) 5. Added Simlink for the root certificate, if the user chooses to keep the root certificate by setting the variable $KEEP_ROOT_CERTIFICATE to true (follows the current convention) 4. Added automatic cleanup for temporary files as well as the root file when the variable $KEEP_ROOT_CERTIFICATE is set to false. --- backend/scripts/generate-p12.sh | 193 ++++++++++++++++++++++++++++---- 1 file changed, 169 insertions(+), 24 deletions(-) diff --git a/backend/scripts/generate-p12.sh b/backend/scripts/generate-p12.sh index 448ab2bbf..58c1c6bd4 100644 --- a/backend/scripts/generate-p12.sh +++ b/backend/scripts/generate-p12.sh @@ -1,6 +1,95 @@ #!/bin/bash # # + +SCRIPT_NAME="$(basename "$0")" + +print_help() { + cat < [--password ] + +Options: + --npm Required. Name or identifier used for the certificate. Must be in the form "npm-#" + --password Optional. Password to secure the PKCS#12 (.p12) file. If not provided the scipt will use the script default. + -h, --help Show this help message and exit. + +Example: + ./$SCRIPT_NAME --npm npm-123 + ./$SCRIPT_NAME --npm npm-123 --password secret123 +EOF +} + +# Initialize Required Input variables +NPM_NAME="" +PKCS12_PASSWORD="" + +# Exit early and show help if no arguments were provided +if [[ $# -eq 0 ]]; then + echo -e "\nERROR - No arguments provided.\n" + print_help + exit 1 +fi + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --npm) + NPM_NAME="$2" + NPM_NAME="${NPM_NAME,,}" + REGEX_FOR_NPM_MATCH='^(N|n)(P|p)(M|m)\-[0-9]+$' + VALIDATE_NPM_NAME=$(echo "$NPM_NAME" | grep -P "$REGEX_FOR_NPM_MATCH") + + #Validate the NPM_NAME + if [[ -z "$2" || "$2" == --* ]] + then + echo -e "\nERROR: --npm requires a value." + exit 1 + fi + if [[ -z "$NPM_NAME" ]] + then + echo -e "\nERROR: --npm requires a value." + exit 1 + elif [[ -z "$VALIDATE_NPM_NAME" ]] + then + echo -e "\nERROR: --npm input must be in the form npm-#\n" + exit 1 + fi + + shift 2 + ;; + --password) + #Verify the argument data was not passed in as password instead if the user did not supply a password. + if [[ -z "$2" || "$2" == --* ]] + then + echo -e "\nERROR: --password requires a value." + exit 1 + fi + PKCS12_PASSWORD="$2" + shift 2 + ;; + -h|--help) + print_help + exit 0 + ;; + *) + echo -e "\nError: Unsupported argument: $1\n" + print_help + exit 1 + ;; + esac +done + + +#Use the default password if the user did not supply one. +#If the Password Variable is passed in this script will use it for encrypting the p12 private key when generating the p12. +#If the password was not provided then the default password (below) will be used. +if [[ -z "$PKCS12_PASSWORD" ]] +then + #The Password Provided by the user (Default: password) + PKCS12_PASSWORD="password" +fi + + # Selected Cipher would be passed in from the GUI but this is an example of what the GUI could provide for a selected CIPHER SELECTED_CIPHER="aes-256-cbc" SELECTED_CIPHER="${SELECTED_CIPHER^^}" @@ -17,7 +106,7 @@ PATH_TO_ARCHIVE="/etc/letsencrypt/archive" #The Specific Folder Name for the NPM cert, Should be the text "npm" followe by a hyphen and numbers (I.E. npm-3) -NPM_FOLDER="npm-10" +NPM_FOLDER="$NPM_NAME" #The Directory containing the certificate and privcate key for a npm configuration NPM_PATH="$PATH_TO_LIVE/$NPM_FOLDER" @@ -59,11 +148,16 @@ then #The Name to give the PKCS12 file generated by the script (with extension). It will be placd in the same file as the private key file. PKCS12_FILE_NAME="fullpkcs12-$INCREMENT_NUMBER.p12" echo "PKCS12 FILE NAME: $PKCS12_FILE_NAME" + ROOT_CERTIFICATE_FILENAME="root$INCREMENT_NUMBER.pem" PKCS12_SIMLINK_NAME="fullpkcs12.p12" + ROOT_CERTIFICATE_SIMLINK_NAME="root.pem" else #The Name to give the PKCS12 file generated by the script (with extension). It will be placd in the same file as the private key file. PKCS12_FILE_NAME="fullpkcs12.p12" echo "PKCS12 FILE NAME: $PKCS12_FILE_NAME" + ROOT_CERTIFICATE_FILENAME="root$INCREMENT_NUMBER.pem" + PKCS12_SIMLINK_NAME="fullpkcs12.p12" + ROOT_CERTIFICATE_SIMLINK_NAME="root.pem" fi @@ -74,13 +168,13 @@ echo "PKCS12 FULL PATH: $PKCS12_FULL_PATH" PKCS12_SIMLINK_PATH="$NPM_PATH/$PKCS12_SIMLINK_NAME" echo "PKCS12 SIMLINK PATH: $PKCS12_SIMLINK_PATH" -#Root Certificate File Name (with extension) the root file does not exist so will need to create it later from the fullchain -ROOT_CERTIFICATE="root$INCREMENT_NUMBER.pem" -ROOT_FULL_PATH=$(dirname "$GET_PATH")"/$ROOT_CERTIFICATE" -echo "ROOT_FULL_PATH: $ROOT_FULL_PATH" -#The Password Provided by the user (Default: password) -PKCS12_PASSWORD="password" +#Root Certificate File Name (with extension) the root file does not exist so will need to create it later from the fullchain +#ROOT_CERTIFICATE_FILENAME="root$INCREMENT_NUMBER.pem" +ROOT_FULL_PATH=$(dirname "$GET_PATH")"/$ROOT_CERTIFICATE_FILENAME" +echo "ROOT FULL PATH: $ROOT_FULL_PATH" +ROOT_CERTIFICATE_SIMLINK_PATH="$NPM_PATH/$ROOT_CERTIFICATE_SIMLINK_NAME" +echo "ROOT CERTIFICATE SIMLINK PATH: $ROOT_CERTIFICATE_SIMLINK_PATH" #Create an Array of CIPHER ALGORITHMS #Remove the lines "Legacy:" and "Provided:" from the created List of Cipher Algorithms @@ -94,6 +188,13 @@ ARRAY_OF_DIGEST_ALGORITHMS=($(echo "${LIST_OF_DIGEST_ALGORITHMS}" | sed -E '/^Le #ARRAY of Temp files that are generated by the script. At the end they will be removed declare -a TEMP_FILES_ARRAY=() +#owner, used when running chown +OWNER="npm" +#Group, used when running chown +GROUP="npm" + +#Keep the root Certificate generated by this script, or delete it after using it temporarily +KEEP_ROOT_CERTIFICATE=true #Start This script only if The Certificate Chain File exist, and the private key exist, and both are also readable. if [[ -z "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] && [[ -f "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] && [[ -r "$CERTIFICATE_CHAIN_FILE_FULL_PATH" ]] @@ -119,7 +220,7 @@ then TEMP_FILES_ARRAY+=("cert_temp_01.pem") TEMP_FILES_ARRAY+=("cert_temp_02.pem") - FOUND_ROOT_CERTIFICATE=false + FOUND_ROOT_CERTIFICATE_FILENAME=false # Loop through each extracted certificate for cert_temp_file in cert_temp_*.pem do @@ -137,7 +238,7 @@ then if [[ "$subject" == "$issuer" ]] then echo -e "\n Found root certificate: $cert_temp_file" - FOUND_ROOT_CERTIFICATE=true + FOUND_ROOT_CERTIFICATE_FILENAME=true #Save the certificate file in the same directory as the private key file cp "$cert_temp_file" "$CERTIFICATE_FILE_FULL_PATH" break @@ -145,7 +246,7 @@ then done #If the root certificate was not found in the certificate chain - if [[ "$FOUND_ROOT_CERTIFICATE" == false ]] + if [[ "$FOUND_ROOT_CERTIFICATE_FILENAME" == false ]] then #If the Root Certificate was not found within the certificate chain then obtain it from the intermediate chain # Extract each certificate into its own file @@ -196,7 +297,7 @@ then TEMP_FILES_ARRAY+=("issuer_cert_temp.der") fi - echo -e "\nProcessing $ROOT_CERTIFICATE ..." + echo -e "\nProcessing $ROOT_CERTIFICATE_FILENAME ..." # Get subject and issuer issuer_subject=$(openssl x509 -noout -subject -issuer -in "$ROOT_FULL_PATH" 2>/dev/null) @@ -301,14 +402,14 @@ then echo -e "\nSuccessfully generated PKCS12" echo -e "\nPKCS12 Full Path: $PKCS12_FULL_PATH" - #Change the ownership to npm:npm - chown npm:npm "$PKCS12_FULL_PATH" + #Change the ownership to "$OWNER:$GROUP" + chown "$OWNER:$GROUP" "$PKCS12_FULL_PATH" if [[ $? -eq 0 ]] then - echo -e "\nSuccessfully changed $PKCS12_FULL_PATH User and Group ownership to npm" + echo -e "\nSuccessfully changed $PKCS12_FULL_PATH User and Group ownership to $OWNER:$GROUP" else - echo "\nERROR - Unable to change $PKCS12_FULL_PATH User and Group ownership to npm" + echo "\nERROR - Unable to change $PKCS12_FULL_PATH User and Group ownership to $OWNER:$GROUP" exit 1 fi @@ -329,33 +430,35 @@ then then echo -e "\nERROR - Relative Path variable is empty." fi - - + #ln -s "to-here" <- "from-here". The from-here should not exist yet, it is to be created, while the to-here should already exist. #Create the links relative to the path ln -sf "$RELATIVE_SOURCE" "$SYMLINK_DIR/$SYMLINK_NAME" if [[ $? -eq 0 ]] then - echo -e "\nSuccessfully created SimLink" + echo " Successfully created SimLink" else echo "\nERROR - Unable to create SimLink" exit 1 fi - #Change the ownership to npm:npm for SimLink - chown -h npm:npm "$PKCS12_SIMLINK_PATH" + #Change the ownership to "$OWNER:$GROUP" for SimLink + chown -h "$OWNER:$GROUP" "$PKCS12_SIMLINK_PATH" if [[ $? -eq 0 ]] then - echo -e "\nSuccessfully changed $PKCS12_SIMLINK_PATH User and Group ownership to npm" + echo " Successfully changed $PKCS12_SIMLINK_PATH User and Group ownership to $OWNER:$GROUP" else - echo "\nERROR - Unable to change $PKCS12_SIMLINK_PATH User and Group ownership to npm" + echo "\nERROR - Unable to change $PKCS12_SIMLINK_PATH User and Group ownership to $OWNER:$GROUP" exit 1 fi - #The Root Certificate that was created by this script is no longer needed after generating the .p12 file so add it to the array of temporary files to delete - TEMP_FILES_ARRAY+=("$ROOT_FULL_PATH") # Comment out if you want to keep the Root Certificate geenrated by this script (Generated from downloaing the root from the Intermediate CA Certificate). + if [[ "$KEEP_ROOT_CERTIFICATE" == false ]] + then + #The Root Certificate that was created by this script is no longer needed after generating the .p12 file so add it to the array of temporary files to delete + TEMP_FILES_ARRAY+=("$ROOT_FULL_PATH") # When $KEEP_ROOT_CERTIFICATE is set to true the script will keep the file and create a simlink for it as well + fi echo -e "\n\nCleaning up temporary files..." for temp_file in "${TEMP_FILES_ARRAY[@]}" @@ -370,6 +473,48 @@ then echo -e " Successfully deleted '$temp_file'" fi done + + #If the Root Path still exist then the user must have comented out the deletion of the root.pem file. So generate a simlink for it as well. + if [[ -f "$ROOT_FULL_PATH" ]] + then + echo -e "\nCreating SimLink for root certificate..." + #Change the ownership to "$OWNER:$GROUP" + chown "$OWNER:$GROUP" "$ROOT_FULL_PATH" + + # Get the directory where the symlink will be created + SYMLINK_DIR="$(dirname "$ROOT_CERTIFICATE_SIMLINK_PATH")" + + # Get the relative path from the symlink destination to the source file + RELATIVE_SOURCE="$(realpath --relative-to="$SYMLINK_DIR" "$ROOT_FULL_PATH")" + + if [[ -z "$RELATIVE_SOURCE" ]] + then + echo -e "\nERROR - Relative Path variable is empty." + fi + + #ln -s "to-here" <- "from-here". The from-here should not exist yet, it is to be created, while the to-here should already exist. + #Create the links relative to the path + ln -sf "$RELATIVE_SOURCE" "$SYMLINK_DIR/$ROOT_CERTIFICATE_SIMLINK_NAME" + + if [[ $? -eq 0 ]] + then + echo " Successfully created SimLink" + else + echo "\nERROR - Unable to create SimLink" + exit 1 + fi + + #Change the ownership to "$OWNER:$GROUP" for SimLink + chown -h "$OWNER:$GROUP" "$ROOT_CERTIFICATE_SIMLINK_PATH" + + if [[ $? -eq 0 ]] + then + echo " Successfully changed $ROOT_CERTIFICATE_SIMLINK_PATH User and Group ownership to $OWNER:$GROUP" + else + echo "\nERROR - Unable to change $ROOT_CERTIFICATE_SIMLINK_PATH User and Group ownership to $OWNER:$GROUP" + exit 1 + fi + fi echo "" else echo -e "\nERROR - Unable to Generate PKCS12 '$PKCS12_FULL_PATH'\n" From da7c0d59639674d32b2c80e1ea55045aad56b9bd Mon Sep 17 00:00:00 2001 From: Whatchawnt <143775469+Whatchawnt@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:52:07 -0400 Subject: [PATCH 3/3] Update generate-p12.sh 1. Added Option to parse in the npm value (I.E. npm-10) as a required variable which is the npm value that the certificate files are stored in (I.E. '/etc/letsencrypt/live/npm-10/' and '/etc/letsencrypt/archive/npm-10/' ) 2. Added the option to parse a password into the script to be used as the p12 password for securing the private key within the generated PKCS12 file. 3. Added Variable in script to allow the user to keep the root certificate that the script generates ($KEEP_ROOT_CERTIFICATE which it is set to true by default). 4. Added Simlinks for the .p12 file (follows the current convention) 5. Added Simlink for the root certificate, if the user chooses to keep the root certificate by setting the variable $KEEP_ROOT_CERTIFICATE to true (follows the current convention) 6. Added automatic cleanup for temporary files as well as the root file when the variable $KEEP_ROOT_CERTIFICATE is set to false. 7. Changed Print Help Text to use echo command. --- backend/scripts/generate-p12.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/scripts/generate-p12.sh b/backend/scripts/generate-p12.sh index 58c1c6bd4..f899c67d3 100644 --- a/backend/scripts/generate-p12.sh +++ b/backend/scripts/generate-p12.sh @@ -5,18 +5,17 @@ SCRIPT_NAME="$(basename "$0")" print_help() { - cat < [--password ] +echo -e "\nUsage: $SCRIPT_NAME --npm [--password ] Options: - --npm Required. Name or identifier used for the certificate. Must be in the form "npm-#" + --npm Required. Name or identifier used for the certificate. Must be in the form 'npm-#' --password Optional. Password to secure the PKCS#12 (.p12) file. If not provided the scipt will use the script default. -h, --help Show this help message and exit. Example: ./$SCRIPT_NAME --npm npm-123 ./$SCRIPT_NAME --npm npm-123 --password secret123 -EOF +" } # Initialize Required Input variables @@ -25,7 +24,7 @@ PKCS12_PASSWORD="" # Exit early and show help if no arguments were provided if [[ $# -eq 0 ]]; then - echo -e "\nERROR - No arguments provided.\n" + echo -e "\n\nERROR - No arguments provided." print_help exit 1 fi