Refactor simple-ca: Remove JSON config and streamline AIA URL handling
/ test-shell (push) Successful in 11s
/ test-python (push) Successful in 25s
/ test-go (push) Successful in 41s

- Removed the JSON configuration structure and related functions.
- Introduced plain text file for AIA base URL management.
- Updated CA and certificate creation functions to directly read/write AIA URL.
- Simplified CA bundle rebuilding logic by directly reading subdirectories.
- Enhanced test coverage for CA and certificate creation, including PFX generation.
- Adjusted test cases to reflect changes in directory structure and file handling.
This commit is contained in:
2026-05-24 21:40:06 +02:00
parent 04d8dab9bc
commit 935167ca8c
7 changed files with 440 additions and 316 deletions
+129 -40
View File
@@ -26,12 +26,15 @@
# before sourcing this file, or overridden per-call with --ca-dir. Once set by any
# call, subsequent calls in the same session inherit it.
#
# Issuing CAs live in subdirectories of SIMPLE_CA_DIR:
# Directory layout:
# $SIMPLE_CA_DIR/ca_cert.pem — root CA certificate
# $SIMPLE_CA_DIR/ca_key.pem — root CA private key
# $SIMPLE_CA_DIR/{name}_cert.pem — certificates issued by the root CA
# $SIMPLE_CA_DIR/{issuing_ca}/ca_cert.pem — issuing CA certificate
# $SIMPLE_CA_DIR/{issuing_ca}/ca_key.pem — issuing CA private key
# $SIMPLE_CA_DIR/{issuing_ca}/{name}_cert.pem — certificates issued by that issuing CA
#
# Certificates are always written to the directory of the CA that signs them.
# Any subdirectory containing ca_cert.pem is treated as an issuing CA.
SIMPLE_CA_DIR="${SIMPLE_CA_DIR:-}"
@@ -65,17 +68,32 @@ function make_ca() {
while [[ $# -gt 0 ]]; do
case "$1" in
--days)
[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { echo "ERROR: --days requires a positive integer." >&2; return 1; }
if [[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]]; then
echo "ERROR: --days requires a positive integer." >&2
return 1
fi
CA_DAYS="$2"; shift 2 ;;
--issuing-ca)
[[ -z "$2" ]] && { echo "ERROR: --issuing-ca requires a value." >&2; return 1; }
[[ "$2" == "ca" ]] && { echo "ERROR: --issuing-ca cannot be 'ca'." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --issuing-ca requires a value." >&2
return 1
fi
if [[ "$2" == "ca" ]]; then
echo "ERROR: --issuing-ca cannot be 'ca'." >&2
return 1
fi
ISSUING_CA="$2"; shift 2 ;;
--aia-base-url)
[[ -z "$2" ]] && { echo "ERROR: --aia-base-url requires a value." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --aia-base-url requires a value." >&2
return 1
fi
AIA_BASE_URL="$2"; shift 2 ;;
--ca-dir)
[[ -z "$2" ]] && { echo "ERROR: --ca-dir requires a value." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --ca-dir requires a value." >&2
return 1
fi
SIMPLE_CA_DIR="$2"; shift 2 ;;
*) break ;;
esac
@@ -84,10 +102,14 @@ function make_ca() {
local CA_NAME="$1"
_require_ca_dir || return 1
[[ -z "$CA_NAME" ]] && { echo "ERROR: CA name is required." >&2; return 1; }
if [[ -z "$CA_NAME" ]]; then
echo "ERROR: CA name is required." >&2
return 1
fi
[[ -z "$AIA_BASE_URL" && -f "$SIMPLE_CA_DIR/aia_base_url.txt" ]] \
&& AIA_BASE_URL="$(cat "$SIMPLE_CA_DIR/aia_base_url.txt")"
if [[ -z "$AIA_BASE_URL" && -f "$SIMPLE_CA_DIR/aia_base_url.txt" ]]; then
AIA_BASE_URL="$(cat "$SIMPLE_CA_DIR/aia_base_url.txt")"
fi
local ROOT_CA_CERT="$SIMPLE_CA_DIR/ca_cert.pem"
local ROOT_CA_KEY="$SIMPLE_CA_DIR/ca_key.pem"
@@ -114,7 +136,9 @@ function make_ca() {
return 1
fi
_rebuild_ca_bundle
[[ -n "$AIA_BASE_URL" ]] && echo "$AIA_BASE_URL" > "$SIMPLE_CA_DIR/aia_base_url.txt"
if [[ -n "$AIA_BASE_URL" ]]; then
echo "$AIA_BASE_URL" > "$SIMPLE_CA_DIR/aia_base_url.txt"
fi
return 0
fi
@@ -167,38 +191,62 @@ function make_cert() {
while [[ $# -gt 0 ]]; do
case "$1" in
--ca-dir)
[[ -z "$2" ]] && { echo "ERROR: --ca-dir requires a value." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --ca-dir requires a value." >&2
return 1
fi
SIMPLE_CA_DIR="$2"; shift 2 ;;
--cert-dir)
[[ -z "$2" ]] && { echo "ERROR: --cert-dir requires a value." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --cert-dir requires a value." >&2
return 1
fi
CERT_DIR="$2"; shift 2 ;;
--issuing-ca)
[[ -z "$2" ]] && { echo "ERROR: --issuing-ca requires a value." >&2; return 1; }
[[ "$2" == "ca" ]] && { echo "ERROR: --issuing-ca cannot be 'ca'." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --issuing-ca requires a value." >&2
return 1
fi
if [[ "$2" == "ca" ]]; then
echo "ERROR: --issuing-ca cannot be 'ca'." >&2
return 1
fi
ISSUING_CA="$2"; shift 2 ;;
--days)
[[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]] && { echo "ERROR: --days requires a positive integer." >&2; return 1; }
if [[ -z "$2" || ! "$2" =~ ^[0-9]+$ ]]; then
echo "ERROR: --days requires a positive integer." >&2
return 1
fi
CERT_DAYS="$2"; shift 2 ;;
*) break ;;
esac
done
local CERT_SUBJECT_NAME="$1"
[[ $# -gt 0 ]] && shift
if [[ $# -gt 0 ]]; then
shift
fi
_require_ca_dir || return 1
[[ -z "$CERT_DIR" ]] && { echo "ERROR: --cert-dir is required." >&2; return 1; }
[[ ! -d "$CERT_DIR" ]] && { echo "ERROR: Certificate directory '$CERT_DIR' does not exist." >&2; return 1; }
[[ -z "$CERT_SUBJECT_NAME" ]] && { echo "ERROR: Subject name is required." >&2; return 1; }
_is_dns "$CERT_SUBJECT_NAME" || { echo "ERROR: Invalid subject name '$CERT_SUBJECT_NAME'. Must be a valid DNS name." >&2; return 1; }
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
local SIGNING_DIR="$SIMPLE_CA_DIR${ISSUING_CA:+/$ISSUING_CA}"
local SIGNING_CERT="$SIGNING_DIR/ca_cert.pem"
local SIGNING_KEY="$SIGNING_DIR/ca_key.pem"
CERT_DIR="${CERT_DIR:-$SIGNING_DIR}"
[[ ! -f "$SIGNING_CERT" || ! -f "$SIGNING_KEY" ]] \
&& { echo "ERROR: Signing CA certificate and key not found in $SIGNING_DIR." >&2; return 1; }
if [[ ! -f "$SIGNING_CERT" || ! -f "$SIGNING_KEY" ]]; then
echo "ERROR: Signing CA certificate and key not found in $SIGNING_DIR." >&2
return 1
fi
local AIA_URL=""
if [[ -f "$SIMPLE_CA_DIR/aia_base_url.txt" ]]; then
@@ -211,9 +259,13 @@ function make_cert() {
local SANS=("DNS:${CERT_SUBJECT_NAME}")
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; }
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
@@ -255,19 +307,34 @@ function make_cert() {
function make_pfx() {
local ISSUING_CA=""
local PFX_PASSWORD=""
local APPLE_OPENSSL=0
while [[ $# -gt 0 ]]; do
case "$1" in
--ca-dir)
[[ -z "$2" ]] && { echo "ERROR: --ca-dir requires a value." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --ca-dir requires a value." >&2
return 1
fi
SIMPLE_CA_DIR="$2"; shift 2 ;;
--issuing-ca)
[[ -z "$2" ]] && { echo "ERROR: --issuing-ca requires a value." >&2; return 1; }
[[ "$2" == "ca" ]] && { echo "ERROR: --issuing-ca cannot be 'ca'." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --issuing-ca requires a value." >&2
return 1
fi
if [[ "$2" == "ca" ]]; then
echo "ERROR: --issuing-ca cannot be 'ca'." >&2
return 1
fi
ISSUING_CA="$2"; shift 2 ;;
--password)
[[ -z "$2" ]] && { echo "ERROR: --password requires a value." >&2; return 1; }
if [[ -z "$2" ]]; then
echo "ERROR: --password requires a value." >&2
return 1
fi
PFX_PASSWORD="$2"; shift 2 ;;
--apple-openssl)
APPLE_OPENSSL=1; shift ;;
*) break ;;
esac
done
@@ -275,24 +342,44 @@ function make_pfx() {
local CERT_PATH="$1"
_require_ca_dir || return 1
[[ -z "$CERT_PATH" ]] && { echo "ERROR: Certificate path is required." >&2; return 1; }
if [[ -z "$CERT_PATH" ]]; then
echo "ERROR: Certificate path is required." >&2
return 1
fi
local CERT_DIR CERT_NAME KEY_PATH
CERT_DIR="$(dirname "$CERT_PATH")"
CERT_NAME="$(basename "$CERT_PATH" _cert.pem)"
KEY_PATH="$CERT_DIR/${CERT_NAME}_key.pem"
[[ ! -d "$CERT_DIR" ]] && { echo "ERROR: Certificate directory '$CERT_DIR' does not exist." >&2; return 1; }
[[ ! -f "$CERT_PATH" || ! -f "$KEY_PATH" ]] && { echo "ERROR: Server certificate or key not found." >&2; return 1; }
[[ ! -f "$SIMPLE_CA_DIR/ca_cert.pem" ]] && { echo "ERROR: Root CA certificate not found in $SIMPLE_CA_DIR." >&2; return 1; }
[[ -n "$ISSUING_CA" && ! -f "$SIMPLE_CA_DIR/$ISSUING_CA/ca_cert.pem" ]] \
&& { echo "ERROR: Issuing CA certificate not found in $SIMPLE_CA_DIR/$ISSUING_CA." >&2; return 1; }
[[ -f "$CERT_DIR/${CERT_NAME}.pfx" ]] && { echo "PKCS#12 (PFX) file already exists, aborting generation." >&2; return 1; }
if [[ ! -d "$CERT_DIR" ]]; then
echo "ERROR: Certificate directory '$CERT_DIR' does not exist." >&2
return 1
fi
if [[ ! -f "$CERT_PATH" || ! -f "$KEY_PATH" ]]; then
echo "ERROR: Server certificate or key not found." >&2
return 1
fi
if [[ ! -f "$SIMPLE_CA_DIR/ca_cert.pem" ]]; then
echo "ERROR: Root CA certificate not found in $SIMPLE_CA_DIR." >&2
return 1
fi
if [[ -n "$ISSUING_CA" && ! -f "$SIMPLE_CA_DIR/$ISSUING_CA/ca_cert.pem" ]]; then
echo "ERROR: Issuing CA certificate not found in $SIMPLE_CA_DIR/$ISSUING_CA." >&2
return 1
fi
if [[ -f "$CERT_DIR/${CERT_NAME}.pfx" ]]; then
echo "PKCS#12 (PFX) file already exists, aborting generation." >&2
return 1
fi
PFX_PASSWORD="${PFX_PASSWORD:-changeit}"
local OPENSSL_BIN="openssl"
if [[ "$APPLE_OPENSSL" -eq 1 ]]; then
OPENSSL_BIN="/usr/bin/openssl"
fi
echo -n "Generating PKCS#12 (PFX) file..."
local CHAIN_FILE
@@ -300,9 +387,11 @@ function make_pfx() {
trap "rm -f '$CHAIN_FILE'" EXIT QUIT KILL INT HUP
cat "$SIMPLE_CA_DIR/ca_cert.pem" > "$CHAIN_FILE"
[[ -n "$ISSUING_CA" ]] && cat "$SIMPLE_CA_DIR/$ISSUING_CA/ca_cert.pem" >> "$CHAIN_FILE"
if [[ -n "$ISSUING_CA" ]]; then
cat "$SIMPLE_CA_DIR/$ISSUING_CA/ca_cert.pem" >> "$CHAIN_FILE"
fi
if ! openssl pkcs12 \
if ! "$OPENSSL_BIN" pkcs12 \
-export \
-out "$CERT_DIR/${CERT_NAME}.pfx" \
-inkey "$KEY_PATH" \