# Simple CA [![shell](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions/workflows/test-shell.yaml/badge.svg)](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions?workflow=test-shell.yaml) [![python](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions/workflows/test-python.yaml/badge.svg)](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions?workflow=test-python.yaml) [![go](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions/workflows/test-go.yaml/badge.svg)](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions?workflow=test-go.yaml) 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. ```bash source simple-ca.sh ``` ### `make_ca` ```bash 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` ```bash 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` ```bash 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 ```bash 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. ```bash python3 simple-ca.py COMMAND [OPTIONS] [ARGS] ``` ### Commands ```bash 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 ```bash 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. ```bash cd src/simple-ca && go build -o simple-ca . ``` ### Commands ```bash 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 ```bash 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 `.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. - `--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). #### Other - `--openssl PATH` — Path to the `openssl` binary (default: `/usr/bin/openssl`). ### Examples **CA trust profile only:** ```bash python3 generate-mobileconfig.py \ --ca-cert ca/ca_cert.pem \ --identifier com.example.ca \ --output ca-trust.mobileconfig ``` **CA + client certificate:** ```bash 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:** ```bash 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: ```bash openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout "key.pem" \ -out "cert.pem" \ -subj "/CN=/O=/C=" \ -addext "basicConstraints=critical,CA:TRUE" \ -addext "keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign,cRLSign" \ -addext "extendedKeyUsage=serverAuth,clientAuth" \ -addext "subjectAltName=DNS:" ```