feat: Implement JSON configuration loading and saving for CA settings
/ test-bash (push) Successful in 13s
/ test-python (push) Successful in 58s
/ test-go (push) Successful in 9m36s

This commit is contained in:
2026-05-24 13:13:39 +02:00
parent 215bd25fc7
commit 93fc382f9a
+54 -37
View File
@@ -24,6 +24,7 @@
# This module requires Python 3.8+ and OpenSSL to be installed on the system.
import argparse
import json
import os
import re
import subprocess
@@ -31,11 +32,34 @@ import sys
OPENSSL = "/usr/bin/openssl"
CONFIG_FILE = "simple-ca.json"
def _err(msg):
print(f"ERROR: {msg}", file=sys.stderr)
def _load_config(ca_dir) -> dict:
path = os.path.join(ca_dir, CONFIG_FILE)
if not os.path.isfile(path):
return {}
try:
with open(path, "r") as f:
return json.load(f)
except (json.JSONDecodeError, OSError) as e:
print(f"WARNING: could not read {path}: {e}", file=sys.stderr)
return {}
def _save_config(ca_dir, patch: dict):
path = os.path.join(ca_dir, CONFIG_FILE)
cfg = _load_config(ca_dir)
cfg.update(patch)
with open(path, "w") as f:
json.dump(cfg, f, indent=2)
f.write("\n")
def _rebuild_ca_bundle(ca_dir):
"""Write ca_bundle.pem = root cert + any issuing CA certs in this dir."""
bundle_path = os.path.join(ca_dir, "ca_bundle.pem")
@@ -68,11 +92,6 @@ def make_ca(ca_dir, ca_name, days=3650, issuing_ca=None, aia_base_url=None):
_err("CA name is required.")
return False
aia_file = os.path.join(ca_dir, "aia_base_url.txt")
if not aia_base_url and os.path.isfile(aia_file):
with open(aia_file, "r") as f:
aia_base_url = f.read().strip()
ca_file_prefix = issuing_ca or "ca"
root_ca_cert = "ca_cert.pem"
root_ca_key = "ca_key.pem"
@@ -114,11 +133,7 @@ def make_ca(ca_dir, ca_name, days=3650, issuing_ca=None, aia_base_url=None):
return False
_rebuild_ca_bundle(ca_dir)
if aia_base_url:
with open(aia_file, "w") as f:
f.write(aia_base_url)
_save_config(ca_dir, {"aia_base_url": aia_base_url} if aia_base_url else {})
return True
if not os.path.isfile(ca_cert_path) or not os.path.isfile(ca_key_path):
@@ -152,11 +167,7 @@ def make_ca(ca_dir, ca_name, days=3650, issuing_ca=None, aia_base_url=None):
return False
_rebuild_ca_bundle(ca_dir)
if aia_base_url:
with open(aia_file, "w") as f:
f.write(aia_base_url)
_save_config(ca_dir, {"aia_base_url": aia_base_url} if aia_base_url else {})
return True
@@ -182,7 +193,7 @@ def _pipe(cmd1, cmd2):
def make_cert(cert_dir, cert_subject_name, sans=None, ca_dir=None,
issuing_ca=None, days=365):
issuing_ca=None, days=365, aia_base_url=None):
if issuing_ca == "ca":
_err("--issuing-ca cannot be 'ca' as it is reserved for the root CA.")
return False
@@ -190,11 +201,7 @@ def make_cert(cert_dir, cert_subject_name, sans=None, ca_dir=None,
ca_file_prefix = issuing_ca or "ca"
ca_dir = ca_dir or cert_dir
aia_base_url_file = os.path.join(ca_dir, "aia_base_url.txt")
aia_url = ""
if os.path.isfile(aia_base_url_file):
with open(aia_base_url_file, "r") as f:
aia_url = f"{f.read().strip()}/{ca_file_prefix}_cert.crt"
aia_url = f"{aia_base_url}/{ca_file_prefix}_cert.crt" if aia_base_url else ""
ca_cert = f"{ca_file_prefix}_cert.pem"
ca_key = f"{ca_file_prefix}_key.pem"
@@ -370,29 +377,29 @@ def _build_parser():
sub = parser.add_subparsers(dest="command", required=True)
p_ca = sub.add_parser("make-ca", help="Create a root or issuing CA.")
p_ca.add_argument("--days", type=int, default=3650, help="Validity period in days (default: 3650)")
p_ca.add_argument("--issuing-ca", help="Specify the issuing CA")
p_ca.add_argument("--aia-base-url", help="Specify the AIA base URL")
p_ca.add_argument("--days", type=int, default=None, help="Validity period in days (default: 3650)")
p_ca.add_argument("--issuing-ca", default=None, help="Specify the issuing CA")
p_ca.add_argument("--aia-base-url", default=None, help="Specify the AIA base URL")
p_ca.add_argument("--ca-dir", help="Directory to store the CA files")
p_ca.add_argument("--openssl", default=OPENSSL, metavar="PATH",
p_ca.add_argument("--openssl", default=None, metavar="PATH",
help=f"Path to the openssl binary (default: {OPENSSL})")
p_ca.add_argument("ca_name", help="Name of the CA")
p_cert = sub.add_parser("make-cert", help="Create a server/client certificate.")
p_cert.add_argument("--ca-dir", help="Directory of the CA")
p_cert.add_argument("--issuing-ca", help="Specify the issuing CA")
p_cert.add_argument("--days", type=int, default=365, help="Validity period in days (default: 365)")
p_cert.add_argument("--issuing-ca", default=None, help="Specify the issuing CA")
p_cert.add_argument("--days", type=int, default=None, help="Validity period in days (default: 365)")
p_cert.add_argument("--cert-dir", help="Directory to store the certificate files")
p_cert.add_argument("--openssl", default=OPENSSL, metavar="PATH",
p_cert.add_argument("--openssl", default=None, metavar="PATH",
help=f"Path to the openssl binary (default: {OPENSSL})")
p_cert.add_argument("subject_name", help="Subject name for the certificate")
p_cert.add_argument("sans", nargs="*", help="Subject Alternative Names (SANs) for the certificate")
p_pfx = sub.add_parser("make-pfx", help="Create a PKCS#12 (PFX) bundle.")
p_pfx.add_argument("--issuing-ca", help="Specify the issuing CA")
p_pfx.add_argument("--issuing-ca", default=None, help="Specify the issuing CA")
p_pfx.add_argument("--ca-dir", help="Directory of the CA")
p_pfx.add_argument("--password", help="Password for the PFX file")
p_pfx.add_argument("--openssl", default=OPENSSL, metavar="PATH",
p_pfx.add_argument("--openssl", default=None, metavar="PATH",
help=f"Path to the openssl binary (default: {OPENSSL})")
p_pfx.add_argument("path", help="Path to the certificate file")
@@ -404,28 +411,38 @@ def main(argv=None):
parser = _build_parser()
args = parser.parse_args(argv)
OPENSSL = args.openssl
ca_dir = args.ca_dir or os.environ.get("SIMPLE_CA_DIR") or os.getcwd()
cfg = _load_config(ca_dir)
OPENSSL = args.openssl or cfg.get("openssl", OPENSSL)
issuing_ca = args.issuing_ca or cfg.get("issuing_ca")
aia_base_url = getattr(args, "aia_base_url", None) or cfg.get("aia_base_url")
days_cfg = cfg.get("days", {})
if args.command == "make-ca":
days = args.days or days_cfg.get("ca", 3650)
ok = make_ca(
ca_dir, args.ca_name,
days=args.days,
issuing_ca=args.issuing_ca,
aia_base_url=args.aia_base_url,
days=days,
issuing_ca=issuing_ca,
aia_base_url=aia_base_url,
)
elif args.command == "make-cert":
days = args.days or days_cfg.get("cert", 365)
ok = make_cert(
args.cert_dir, args.subject_name,
sans=args.sans,
ca_dir=ca_dir,
issuing_ca=args.issuing_ca,
days=args.days,
issuing_ca=issuing_ca,
days=days,
aia_base_url=aia_base_url,
)
elif args.command == "make-pfx":
ok = make_pfx(
args.path, ca_dir,
issuing_ca=args.issuing_ca,
issuing_ca=issuing_ca,
password=args.password,
)
else: