feat: Implement JSON configuration loading and saving for CA settings
This commit is contained in:
+54
-37
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user