268 lines
9.5 KiB
Markdown
268 lines
9.5 KiB
Markdown
# Simple CA
|
|
|
|
[](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions?workflow=test-shell.yaml)
|
|
[](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions?workflow=test-python.yaml)
|
|
[](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=<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>"
|
|
```
|