From 79ac590ba6e6597e2b7d2826b87bd45e96904a28 Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Wed, 4 Mar 2026 08:01:06 +0100 Subject: [PATCH] Enhance CA functions and testing script with improved error handling and verification steps --- cert-functions.sh | 146 ++++++++++++++++++++++++++++++++++++++++------ run-tests.sh | 82 +++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 21 deletions(-) mode change 100644 => 100755 cert-functions.sh diff --git a/cert-functions.sh b/cert-functions.sh old mode 100644 new mode 100755 index e2afdd9..3a99da8 --- a/cert-functions.sh +++ b/cert-functions.sh @@ -1,29 +1,107 @@ function make_ca() { - # Use the provided directory argument or default to AZURITE_DIR if not provided - local CERT_DIR="$1" - local CA_NAME="$2" + local CA_DAYS=3650 # Default validity period for CA certificates - if [[ -z "$CERT_DIR" || -z "$CA_NAME" || ! -d "$CERT_DIR" ]]; then - echo "ERROR: Certificate directory $CERT_DIR does not exist." + # 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 "$CERT_DIR/ca_cert.pem" || ! -f "$CERT_DIR/ca_key.pem" ]]; then + 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 "$CERT_DIR/ca_key.pem" \ - -out "$CERT_DIR/ca_cert.pem" \ - -days 3650 \ - -nodes \ + -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:0"; then + -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 @@ -46,10 +124,42 @@ function _is_dns() { } 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 @@ -65,8 +175,8 @@ function make_server_cert() { 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 + 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 @@ -101,9 +211,10 @@ function make_server_cert() { 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" \ - -nodes \ + -noenc \ -subj "/CN=${CERT_SUBJECT_NAME}" \ -addext "basicConstraints=critical,CA:FALSE" \ -addext "keyUsage=digitalSignature,keyEncipherment" \ @@ -111,11 +222,10 @@ function make_server_cert() { -addext "$SANS_EXT" \ | openssl x509 \ -req \ - -CA "$CERT_DIR/ca_cert.pem" \ - -CAkey "$CERT_DIR/ca_key.pem" \ - -set_serial "0x$(openssl rand -hex 16)" \ + -CA "$CERT_DIR/$CA_CERT" \ + -CAkey "$CERT_DIR/$CA_KEY" \ -copy_extensions copyall \ - -days 365 \ + -days $CERT_DAYS \ -text \ -out "$CERT_DIR/${CERT_NAME}_cert.pem"; then echo "ERROR: Failed to generate server certificate and key." >&2 diff --git a/run-tests.sh b/run-tests.sh index b38b8b3..10de028 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -6,13 +6,89 @@ set -e # Load the certificate functions source "$(dirname "$BASH_SOURCE[0]")/cert-functions.sh" +function clean_up_test_dir() { + local TEST_DIR="$1" + + if [[ -d "$TEST_DIR" ]]; then + echo "Cleaning up test directory $TEST_DIR..." + rm -rf "$TEST_DIR"/* + fi + + echo "Creating test directory $TEST_DIR and ca subdirectory..." + mkdir -p "$TEST_DIR/ca" +} + +function display_certificate() { + local CERT_DIR="$1" + local CERT_FILE="$2" + + echo -e "\nDisplaying generated certificate for verification ($CERT_FILE):" + + # Display the certificate details for verification + openssl x509 -in "$CERT_DIR/$CERT_FILE" -noout -subject -issuer -serial -hash -fingerprint + echo +} + # Create a temporary directory for the test certificates -TEMP_CERT_DIR=$(mktemp -d) -# Ensure the temporary directory is cleaned up on exit or interruption -trap "rm -rf $TEMP_CERT_DIR" EXIT KILL HUP INT QUIT +TEMP_CERT_DIR="$(dirname "$BASH_SOURCE[0]")/tests" +# Clean up any existing files in the temporary directory +clean_up_test_dir "$TEMP_CERT_DIR" + +echo +echo "Running tests for standalone CA..." +echo "----------------------------------" +echo # Create a standalone CA for testing purposes if ! make_ca "$TEMP_CERT_DIR" "Test CA"; then echo "ERROR: Failed to create CA." >&2 exit 1 fi + +# List the generated certificates and keys for verification +display_certificate "$TEMP_CERT_DIR" "ca_cert.pem" + +# Make a server certificate signed by the CA +if ! make_server_cert "$TEMP_CERT_DIR" "test" "test.example.com" "127.0.0.1"; then + echo "ERROR: Failed to create server certificate." >&2 + exit 1 +fi + +# List the generated server certificate and key for verification +display_certificate "$TEMP_CERT_DIR" "test_cert.pem" + +# Remove all files from the directory +clean_up_test_dir "$TEMP_CERT_DIR" + +echo +echo "Running tests for two-level CA..." +echo "---------------------------------" +echo + +# Create a new CA with pathlen 1 +if ! make_ca --path-len 1 "$TEMP_CERT_DIR" "Test Two Level CA"; then + echo "ERROR: Failed to create CA." >&2 + exit 1 +fi + +# List the generated certificates and keys for verification +display_certificate "$TEMP_CERT_DIR" "ca_cert.pem" + +# Create an issuing CA signed by the first CA +if ! make_ca --issuing-ca "issuing_ca" "$TEMP_CERT_DIR" "Issuing CA"; then + echo "ERROR: Failed to create issuing CA." >&2 + exit 1 +fi + +# List the generated certificates and keys for verification +display_certificate "$TEMP_CERT_DIR" "issuing_ca_cert.pem" + +# Make a server certificate signed by the CA +if ! make_server_cert --issuing-ca "issuing_ca" "$TEMP_CERT_DIR" "test" "test.example.com" "127.0.0.1"; then + echo "ERROR: Failed to create server certificate." >&2 + exit 1 +fi + +# List the generated server certificate and key for verification +display_certificate "$TEMP_CERT_DIR" "test_cert.pem" +