Files
simple-ca/README.md
T

9.4 KiB

Simple CA

shell python go

Tools for creating and managing a simple Certificate Authority for testing and development. Three implementations are provided — pick whichever fits your environment:

simple-ca.sh simple-ca.py Go binary
Usage sourced function library CLI compiled CLI
Config file none simple-ca.json none
AIA base URL aia_base_url.txt simple-ca.json aia_base_url.txt
Certificate history no yes no
CRL generation no yes no
Revocation no yes no
PFX (--apple-openssl) yes yes yes
Requires bash/zsh + openssl Python 3.8+ + openssl none (native crypto)

All variants share the same directory layout and produce interchangeable files.

Directory layout

CA_DIR/
  ca_cert.pem              root CA certificate
  ca_key.pem               root CA private key
  ca_bundle.pem            root + all issuing CA certs concatenated
  aia_base_url.txt         base URL for AIA extension (shell/Go only)
  {name}_cert.pem          certificate issued by root CA
  {name}_key.pem           key for certificate issued by root CA
  {issuing_ca}/
    ca_cert.pem            issuing CA certificate
    ca_key.pem             issuing CA private key
    {name}_cert.pem        certificate issued by issuing CA
    {name}_key.pem         key for certificate issued by issuing CA

Python additionally writes:

CA_DIR/
  simple-ca.json           config + certificate history
  crl.pem                  root CA CRL (make-crl)
  {issuing_ca}/
    crl.pem                issuing CA CRL (make-crl)

Use ca_bundle.pem with openssl verify -CAfile CA_DIR/ca_bundle.pem — this works identically on Linux, macOS, and Windows without relying on hash symlinks.


Shell — simple-ca.sh

Source the file and call the functions directly. SIMPLE_CA_DIR is set by the first --ca-dir call and inherited by all subsequent calls in the same session. It can also be set in the environment before sourcing.

source simple-ca.sh

make_ca

make_ca [--ca-dir DIR] [--days N] [--aia-base-url URL] [--issuing-ca NAME] CA_NAME

Without --issuing-ca: creates the root CA in DIR.
With --issuing-ca NAME: creates an issuing CA in DIR/NAME/, signed by the root CA.

make_cert

make_cert [--ca-dir DIR] [--cert-dir DIR] [--days N] [--issuing-ca NAME] SUBJECT [SAN ...]

Creates a TLS certificate (serverAuth + clientAuth). SUBJECT is used as the CN and first SAN. Additional SANs can be DNS names or IPv4 addresses. Without --cert-dir, files are written to the signing CA's directory.

make_pfx

make_pfx [--ca-dir DIR] [--issuing-ca NAME] [--password PASS] [--apple-openssl] CERT_PATH

Creates a PKCS#12 bundle from an existing certificate. --apple-openssl uses /usr/bin/openssl to produce a PFX that Apple Keychain and iOS accept (legacy RC2/3DES encryption instead of modern PBES2).

Example — two-level CA

source simple-ca.sh

make_ca --ca-dir /tmp/ca "My Root CA"
make_ca --issuing-ca servers "My Servers CA"

make_cert --issuing-ca servers web.example.com web.example.com 192.168.1.1
make_pfx  --issuing-ca servers --password s3cr3t /tmp/ca/servers/web_cert.pem

Python — simple-ca.py

A standalone CLI with the same commands as the shell variant plus CRL generation and revocation. Configuration and certificate history are stored in simple-ca.json inside the CA directory.

python3 simple-ca.py COMMAND [OPTIONS] [ARGS]

Commands

make-ca   [--ca-dir DIR] [--days N] [--ca-publish-base-url URL] [--issuing-ca NAME] CA_NAME
make-cert [--ca-dir DIR] [--cert-dir DIR] [--days N] [--issuing-ca NAME] SUBJECT [SAN ...]
make-pfx  [--ca-dir DIR] [--issuing-ca NAME] [--password PASS] [--apple-openssl] CERT_PATH
make-crl  [--ca-dir DIR] [--issuing-ca NAME] [--days N]
revoke-cert [--ca-dir DIR] [--issuing-ca NAME] CERT_PATH

--ca-publish-base-url embeds both AIA (Authority Information Access) and CRL Distribution Point extensions in issued certificates, pointing to the published location of the CA cert and CRL file.

Example — two-level CA with CRL

python3 simple-ca.py make-ca  --ca-dir /tmp/ca "My Root CA"
python3 simple-ca.py make-ca  --ca-dir /tmp/ca --issuing-ca servers "My Servers CA"

python3 simple-ca.py make-cert --ca-dir /tmp/ca --issuing-ca servers web.example.com web.example.com 192.168.1.1
python3 simple-ca.py make-cert --ca-dir /tmp/ca --issuing-ca servers alice.example.com alice.example.com

python3 simple-ca.py revoke-cert --ca-dir /tmp/ca --issuing-ca servers /tmp/ca/servers/alice_cert.pem
python3 simple-ca.py make-crl    --ca-dir /tmp/ca --issuing-ca servers

Go binary

Equivalent to the shell variant in features. Uses native Go crypto — no openssl subprocess required except for --apple-openssl PFX generation.

cd src/simple-ca && go build -o simple-ca .

Commands

simple-ca make-ca   [--ca-dir DIR] [--days N] [--aia-base-url URL] [--issuing-ca NAME] CA_NAME
simple-ca make-cert [--ca-dir DIR] [--cert-dir DIR] [--days N] [--issuing-ca NAME] SUBJECT [SAN ...]
simple-ca make-pfx  [--ca-dir DIR] [--issuing-ca NAME] [--password PASS] [--apple-openssl] CERT_PATH

SIMPLE_CA_DIR environment variable is used as the default CA directory when --ca-dir is not specified.

Example — two-level CA

export SIMPLE_CA_DIR=/tmp/ca

simple-ca make-ca  "My Root CA"
simple-ca make-ca  --issuing-ca servers "My Servers CA"

simple-ca make-cert --issuing-ca servers web.example.com web.example.com 192.168.1.1
simple-ca make-pfx  --issuing-ca servers --password s3cr3t /tmp/ca/servers/web_cert.pem

generate-mobileconfig.py

Generates Apple .mobileconfig profiles for distributing CA certificates and optionally client certificates and IKEv2 VPN configuration to Apple devices (macOS / iOS / iPadOS).

Modes

Arguments supplied Profile content
--ca-cert only CA trust anchor
--ca-cert + --client-cert + --client-key CA trust anchor + PKCS#12 client certificate
All of the above + --remote-address + --dns + --match-domains CA + client cert + IKEv2 VPN

Usage

generate-mobileconfig.py --ca-cert CA.pem --output profile.mobileconfig \
    --identifier com.example.vpn \
    [--client-cert CLIENT.pem --client-key CLIENT_KEY.pem] \
    [--remote-address vpn.example.com --dns 10.0.0.1 --match-domains example.com] \
    [--profile-name "My VPN"] [--ca-name "My CA"] \
    [--client-name "My Cert"] [--vpn-name "My VPN Connection"]

Required arguments

  • --ca-cert PEM — CA certificate PEM file to embed as a trust anchor.
  • --output FILE — Output .mobileconfig path.
  • --identifier ID — Reverse-DNS profile identifier (e.g. com.example.vpn). Derived automatically from --remote-address when a VPN profile is generated.

Client certificate (optional)

  • --client-cert PEM — Client certificate PEM file.
  • --client-key PEM — Client private key PEM file (required together with --client-cert).

VPN (requires client certificate)

  • --remote-address FQDN — VPN gateway hostname.
  • --dns IP [IP …] — DNS server(s) for split DNS.
  • --match-domains DOMAIN [DOMAIN …] — Split-DNS domains routed through the VPN.

Display name overrides (all optional)

  • --profile-name NAME — Profile display name (default: VPN or Certificates).
  • --ca-name NAME — CA payload display name (default: certificate CN).
  • --client-name NAME — Client cert payload display name (default: certificate CN).
  • --vpn-name NAME — VPN connection display name (default: profile name).

Examples

CA trust profile only:

python3 generate-mobileconfig.py \
    --ca-cert ca/ca_cert.pem \
    --identifier com.example.ca \
    --output ca-trust.mobileconfig

CA + client certificate:

python3 generate-mobileconfig.py \
    --ca-cert ca/ca_cert.pem \
    --client-cert certs/alice_cert.pem \
    --client-key  certs/alice_key.pem \
    --identifier com.example.certs \
    --output alice.mobileconfig

Full IKEv2 VPN profile:

python3 generate-mobileconfig.py \
    --ca-cert ca/ca_cert.pem \
    --client-cert certs/alice_cert.pem \
    --client-key  certs/alice_key.pem \
    --remote-address vpn.example.com \
    --dns 10.0.0.1 \
    --match-domains example.com internal.example.com \
    --output alice-vpn.mobileconfig

Self-signed certificate

The following command creates a full-featured self-signed certificate usable as a CA and for client/server authentication:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -keyout "key.pem" \
    -out "cert.pem" \
    -subj "/CN=<user name>/O=<user organization>/C=<CC>" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign,cRLSign" \
    -addext "extendedKeyUsage=serverAuth,clientAuth" \
    -addext "subjectAltName=DNS:<computer_name>"