9.5 KiB
Simple CA
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 + --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 --match-domains example.com] \
[--profile-name "My VPN"] [--ca-name "My CA"] \
[--client-name "My Cert"] [--vpn-name "My VPN Connection"] \
[--openssl /usr/bin/openssl]
Required arguments
--ca-cert PEM— CA certificate PEM file to embed as a trust anchor.--output FILE— Output.mobileconfigpath.--identifier ID— Reverse-DNS profile identifier (e.g.com.example.vpn). Derived automatically from--remote-addresswhen 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.--match-domains DOMAIN [DOMAIN …]— Split-DNS domains routed through the VPN.
Display name overrides (all optional)
--profile-name NAME— Profile display name (default:VPNorCertificates).--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).
Other
--openssl PATH— Path to theopensslbinary (default:/usr/bin/openssl).
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 \
--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>"