Compare commits
6 Commits
935167ca8c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 27461a0fbe | |||
| c537ae5dd7 | |||
| ad6af575dc | |||
| e8b5241a54 | |||
| 4845aae7f5 | |||
| 2da0c7428d |
@@ -0,0 +1,20 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/simple-ca/**'
|
||||
|
||||
jobs:
|
||||
test-go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Run Go tests
|
||||
run: go test -v ./...
|
||||
working-directory: src/simple-ca
|
||||
@@ -0,0 +1,23 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'simple-ca.py'
|
||||
- 'test_simple_ca.py'
|
||||
|
||||
jobs:
|
||||
test-python:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install pytest
|
||||
run: pip install pytest
|
||||
|
||||
- name: Run python tests
|
||||
run: python3 -m pytest test_simple_ca.py -v
|
||||
@@ -0,0 +1,15 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'simple-ca.sh'
|
||||
- 'run-tests.sh'
|
||||
|
||||
jobs:
|
||||
test-shell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run shell tests
|
||||
run: bash run-tests.sh
|
||||
@@ -1,51 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'simple-ca.sh'
|
||||
- 'simple-ca.py'
|
||||
- 'run-tests.sh'
|
||||
- 'test_simple_ca.py'
|
||||
- 'src/simple-ca/**'
|
||||
- '.gitea/workflows/test.yaml'
|
||||
|
||||
jobs:
|
||||
test-python:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install pytest
|
||||
run: pip install pytest
|
||||
|
||||
- name: Run python tests
|
||||
run: python3 -m pytest test_simple_ca.py -v
|
||||
|
||||
test-shell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run shell tests
|
||||
run: bash run-tests.sh
|
||||
|
||||
test-go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Run Go tests
|
||||
run: go test -v ./...
|
||||
working-directory: src/simple-ca
|
||||
@@ -1,64 +1,172 @@
|
||||
# Simple CA
|
||||
|
||||
[](https://gitea.koszewscy.waw.pl/slawek/simple-ca/actions?workflow=test.yaml)
|
||||
[](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)
|
||||
|
||||
`simple-ca.sh` is a Bash script that provides functions for creating and managing a simple Certificate Authority (CA) and generating certificates. It can create a single or two-level CA hierarchy, and generate client-server TLS certificates. The script is designed to be simple and easy to use, making it suitable for testing and development purposes, where a self-signed certificate is not sufficient.
|
||||
Tools for creating and managing a simple Certificate Authority for testing and development. Three implementations are provided — pick whichever fits your environment:
|
||||
|
||||
All certificates generated by this script have a random serial number.
|
||||
| | `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) |
|
||||
|
||||
## Functions
|
||||
All variants share the same directory layout and produce interchangeable files.
|
||||
|
||||
### `make_ca()`
|
||||
## Directory layout
|
||||
|
||||
This function creates a root CA certificate and private key. It can optionally create an intermediate CA certificate and private key, which is signed by the root CA. The function takes several parameters to customize the CA creation process, such as the CA name, validity period, and whether to create an intermediate CA.
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
make_ca [--days <validity_days>] [--issuing-ca <name>] <ca_directory> <ca_name>
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
- `<ca_directory>`: The directory where the CA files will be stored.
|
||||
- `<ca_name>`: The name of the CA.
|
||||
- `--days <validity_days>`: Optional. The number of days the CA certificate will be valid. Default is 3650 days (10 years).
|
||||
- `--issuing-ca <name>`: Optional. If specified, creates an intermediate CA with <ca_name> as the intermediate CA name and using <name> as certificate and key file prefix for the issuing CA (instead of root's `ca`).
|
||||
Python additionally writes:
|
||||
|
||||
It also maintains a `ca_bundle.pem` file in the CA directory containing the root CA and any issuing CA certificates concatenated together. Use this bundle with `openssl verify -CAfile <ca_directory>/ca_bundle.pem` instead of relying on hash symlinks — this works identically on Linux, macOS, and Windows without symlink privileges.
|
||||
|
||||
### `make_cert()`
|
||||
|
||||
This function generates a certificate and private key with TLS Web Server Authentication and Client Authentication EKUs. The certificate is signed by the specified CA (either root or intermediate).
|
||||
|
||||
Usage:
|
||||
|
||||
```bash
|
||||
make_cert --ca-dir <ca_directory> [--days <validity_days>] [--issuing-ca <name>] <cert_directory> <subject_name>
|
||||
```
|
||||
CA_DIR/
|
||||
simple-ca.json config + certificate history
|
||||
crl.pem root CA CRL (make-crl)
|
||||
{issuing_ca}/
|
||||
crl.pem issuing CA CRL (make-crl)
|
||||
```
|
||||
|
||||
- `<ca_directory>`: The directory where the CA files are stored (used to find the CA certificate and key for signing).
|
||||
- `<cert_directory>`: The directory where the generated certificate and key will be stored.
|
||||
- `<subject_name>`: The subject name (Common Name) for the certificate.
|
||||
- `--days <validity_days>`: Optional. The number of days the certificate will be valid. Default is 365 days.
|
||||
- `--issuing-ca <name>`: Optional. If specified, uses the CA with the key `<name>_key.pem` and certificate `<name>_cert.pem` for signing instead of the root CA.
|
||||
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.
|
||||
|
||||
### `make_pfx()`
|
||||
---
|
||||
|
||||
This function creates a PKCS#12 (PFX) file containing the certificate, private key, and CA certificate chain. This is useful for importing the certificate into applications that require a PFX file.
|
||||
## Shell — `simple-ca.sh`
|
||||
|
||||
Usage:
|
||||
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
|
||||
make_pfx --ca-dir <ca_directory> [--issuing-ca <file_prefix>] --path <pfx_file_path> [--password <pfx_password>]
|
||||
source simple-ca.sh
|
||||
```
|
||||
|
||||
- `--ca-dir <ca_directory>`: The directory where the CA files are stored (used to find the CA certificate for the chain).
|
||||
- `--issuing-ca <file_prefix>`: The file prefix of the issuing CA to include in the chain.
|
||||
- `--path <pfx_file_path>`: The path where the generated PFX file will be saved.
|
||||
- `--password <pfx_password>`: Optional. The custom password to protect the PFX, instead of the default `changeit`.
|
||||
### `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
|
||||
|
||||
`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).
|
||||
Generates Apple `.mobileconfig` profiles for distributing CA certificates and optionally client certificates and IKEv2 VPN configuration to Apple devices (macOS / iOS / iPadOS).
|
||||
|
||||
### Modes
|
||||
|
||||
@@ -66,7 +174,7 @@ make_pfx --ca-dir <ca_directory> [--issuing-ca <file_prefix>] --path <pfx_file_p
|
||||
|---|---|
|
||||
| `--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 |
|
||||
| All of the above + `--remote-address` + `--dns` + `--match-domains` | CA + client cert + IKEv2 VPN |
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -74,10 +182,9 @@ make_pfx --ca-dir <ca_directory> [--issuing-ca <file_prefix>] --path <pfx_file_p
|
||||
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] \
|
||||
[--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"] \
|
||||
[--openssl /usr/bin/openssl]
|
||||
[--client-name "My Cert"] [--vpn-name "My VPN Connection"]
|
||||
```
|
||||
|
||||
#### Required arguments
|
||||
@@ -94,6 +201,7 @@ generate-mobileconfig.py --ca-cert CA.pem --output profile.mobileconfig \
|
||||
#### 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)
|
||||
@@ -103,10 +211,6 @@ generate-mobileconfig.py --ca-cert CA.pem --output profile.mobileconfig \
|
||||
- `--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:**
|
||||
@@ -137,13 +241,16 @@ python3 generate-mobileconfig.py \
|
||||
--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 Ceritifcate
|
||||
---
|
||||
|
||||
The following command will create a *full-featured* self-signed certificate that can act as CA certificate and be used for client and server authentication:
|
||||
## 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 \
|
||||
|
||||
@@ -67,6 +67,7 @@ def main():
|
||||
|
||||
g_vpn = parser.add_argument_group("VPN (optional, requires client certificate)")
|
||||
g_vpn.add_argument("--remote-address", metavar="FQDN", help="VPN gateway FQDN")
|
||||
g_vpn.add_argument("--dns", metavar="IP", nargs="+", help="DNS server(s) for split DNS")
|
||||
g_vpn.add_argument("--match-domains", metavar="DOMAIN", nargs="+", help="Split DNS domains")
|
||||
|
||||
g_meta = parser.add_argument_group("Profile metadata")
|
||||
@@ -91,7 +92,7 @@ def main():
|
||||
if args.client_key and not args.client_cert:
|
||||
parser.error("--client-cert is required when --client-key is specified")
|
||||
|
||||
vpn_args = [args.remote_address, args.match_domains]
|
||||
vpn_args = [args.remote_address, args.dns, args.match_domains]
|
||||
if any(vpn_args) and not all(vpn_args):
|
||||
parser.error("--remote-address and --match-domains must be specified together")
|
||||
if args.remote_address and not args.client_cert:
|
||||
@@ -164,9 +165,13 @@ def main():
|
||||
"AuthenticationMethod": "None",
|
||||
"ExtendedAuthEnabled": 1,
|
||||
"PayloadCertificateUUID": uuid_cert,
|
||||
"SupplementalMatchDomains": args.match_domains,
|
||||
"OnDemandEnabled": 0,
|
||||
},
|
||||
"DNS": {
|
||||
"ServerAddresses": args.dns,
|
||||
"SupplementalMatchDomains": args.match_domains,
|
||||
"SupplementalMatchDomainsNoSearch": 1,
|
||||
},
|
||||
})
|
||||
|
||||
profile = {
|
||||
|
||||
+44
-8
@@ -41,6 +41,13 @@ reset_dirs() {
|
||||
SIMPLE_CA_DIR=""
|
||||
}
|
||||
|
||||
assert_file() {
|
||||
if [[ ! -f "$1" ]]; then
|
||||
echo "ERROR: expected file not found: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
verify_cert() {
|
||||
local CERT_PATH="$1"
|
||||
if ! openssl verify -CAfile "$CA_DIR/ca_bundle.pem" "$CERT_PATH" 2>/dev/null; then
|
||||
@@ -50,6 +57,18 @@ verify_cert() {
|
||||
echo "Verified: $CERT_PATH"
|
||||
}
|
||||
|
||||
assert_eku() {
|
||||
local CERT_PATH="$1"
|
||||
local EKU="$2"
|
||||
local TEXT
|
||||
TEXT="$(openssl x509 -in "$CERT_PATH" -noout -text 2>/dev/null)"
|
||||
if ! echo "$TEXT" | grep -q "$EKU"; then
|
||||
echo "ERROR: EKU '$EKU' not found in $CERT_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "EKU OK: $EKU"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Standalone CA — certs issued by root CA go into CA_DIR
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -58,12 +77,12 @@ echo
|
||||
echo "--- [shell] Standalone CA ---"
|
||||
reset_dirs
|
||||
make_ca --ca-dir "$CA_DIR" "Test CA" 2>/dev/null
|
||||
[[ -f "$CA_DIR/ca_cert.pem" ]] || { echo "ERROR: ca_cert.pem not created" >&2; exit 1; }
|
||||
[[ -f "$CA_DIR/ca_bundle.pem" ]] || { echo "ERROR: ca_bundle.pem not created" >&2; exit 1; }
|
||||
assert_file "$CA_DIR/ca_cert.pem"
|
||||
assert_file "$CA_DIR/ca_bundle.pem"
|
||||
verify_cert "$CA_DIR/ca_cert.pem"
|
||||
|
||||
make_cert "test" "test.example.com" "127.0.0.1" 2>/dev/null
|
||||
[[ -f "$CA_DIR/test_cert.pem" ]] || { echo "ERROR: test_cert.pem not created in CA_DIR" >&2; exit 1; }
|
||||
assert_file "$CA_DIR/test_cert.pem"
|
||||
verify_cert "$CA_DIR/test_cert.pem"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -77,18 +96,35 @@ make_ca --ca-dir "$CA_DIR" "Test Root CA" 2>/dev/null
|
||||
verify_cert "$CA_DIR/ca_cert.pem"
|
||||
|
||||
make_ca --issuing-ca "issuing_ca" "Issuing CA" 2>/dev/null
|
||||
[[ -f "$CA_DIR/issuing_ca/ca_cert.pem" ]] || { echo "ERROR: issuing_ca/ca_cert.pem not created" >&2; exit 1; }
|
||||
assert_file "$CA_DIR/issuing_ca/ca_cert.pem"
|
||||
verify_cert "$CA_DIR/issuing_ca/ca_cert.pem"
|
||||
|
||||
make_cert --issuing-ca "issuing_ca" "test" "test.example.com" "127.0.0.1" 2>/dev/null
|
||||
[[ -f "$CA_DIR/issuing_ca/test_cert.pem" ]] || { echo "ERROR: issuing_ca/test_cert.pem not created" >&2; exit 1; }
|
||||
assert_file "$CA_DIR/issuing_ca/test_cert.pem"
|
||||
verify_cert "$CA_DIR/issuing_ca/test_cert.pem"
|
||||
assert_eku "$CA_DIR/issuing_ca/test_cert.pem" "TLS Web Server Authentication"
|
||||
assert_eku "$CA_DIR/issuing_ca/test_cert.pem" "TLS Web Client Authentication"
|
||||
|
||||
make_pfx --issuing-ca "issuing_ca" --password "s3cr3t" "$CA_DIR/issuing_ca/test_cert.pem" 2>/dev/null
|
||||
[[ -f "$CA_DIR/issuing_ca/test.pfx" ]] || { echo "ERROR: issuing_ca/test.pfx not created" >&2; exit 1; }
|
||||
openssl pkcs12 -in "$CA_DIR/issuing_ca/test.pfx" -noout -info -password pass:"s3cr3t" 2>/dev/null \
|
||||
|| { echo "ERROR: PFX verification failed" >&2; exit 1; }
|
||||
assert_file "$CA_DIR/issuing_ca/test.pfx"
|
||||
if ! openssl pkcs12 -in "$CA_DIR/issuing_ca/test.pfx" -noout -info -password pass:"s3cr3t" 2>/dev/null; then
|
||||
echo "ERROR: PFX verification failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "PFX: OK"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# User certificate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo
|
||||
echo "--- [shell] User certificate ---"
|
||||
make_cert --issuing-ca "issuing_ca" --type user "Alice Example" "alice@example.com" 2>/dev/null
|
||||
assert_file "$CA_DIR/issuing_ca/Alice Example_cert.pem"
|
||||
verify_cert "$CA_DIR/issuing_ca/Alice Example_cert.pem"
|
||||
assert_eku "$CA_DIR/issuing_ca/Alice Example_cert.pem" "TLS Web Client Authentication"
|
||||
assert_eku "$CA_DIR/issuing_ca/Alice Example_cert.pem" "E-mail Protection"
|
||||
assert_eku "$CA_DIR/issuing_ca/Alice Example_cert.pem" "Code Signing"
|
||||
|
||||
echo
|
||||
echo "All shell tests passed."
|
||||
|
||||
+72
-31
@@ -39,7 +39,7 @@
|
||||
|
||||
SIMPLE_CA_DIR="${SIMPLE_CA_DIR:-}"
|
||||
|
||||
function _rebuild_ca_bundle() {
|
||||
_rebuild_ca_bundle() {
|
||||
local BUNDLE="$SIMPLE_CA_DIR/ca_bundle.pem"
|
||||
cat "$SIMPLE_CA_DIR/ca_cert.pem" > "$BUNDLE"
|
||||
for issuing_ca_dir in $SIMPLE_CA_DIR/*; do
|
||||
@@ -49,7 +49,7 @@ function _rebuild_ca_bundle() {
|
||||
done
|
||||
}
|
||||
|
||||
function _require_ca_dir() {
|
||||
_require_ca_dir() {
|
||||
SIMPLE_CA_DIR="${SIMPLE_CA_DIR:-$(pwd)}"
|
||||
if [[ ! -d "$SIMPLE_CA_DIR" ]]; then
|
||||
echo "ERROR: CA directory '$SIMPLE_CA_DIR' does not exist." >&2
|
||||
@@ -57,10 +57,11 @@ function _require_ca_dir() {
|
||||
fi
|
||||
}
|
||||
|
||||
function _is_ip() { [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; }
|
||||
function _is_dns() { [[ "$1" =~ ^[a-z0-9-]+(\.[a-z0-9-]+)*$ ]]; }
|
||||
_is_ip() { [[ "$1" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; }
|
||||
_is_dns() { [[ "$1" =~ ^[a-z0-9-]+(\.[a-z0-9-]+)*$ ]]; }
|
||||
_is_email() { [[ "$1" =~ ^[^@]+@[^@]+\.[^@]+$ ]]; }
|
||||
|
||||
function make_ca() {
|
||||
make_ca() {
|
||||
local CA_DAYS=3650
|
||||
local ISSUING_CA=""
|
||||
local AIA_BASE_URL=""
|
||||
@@ -183,10 +184,11 @@ function make_ca() {
|
||||
return 0
|
||||
}
|
||||
|
||||
function make_cert() {
|
||||
make_cert() {
|
||||
local ISSUING_CA=""
|
||||
local CERT_DAYS=365
|
||||
local CERT_DIR=""
|
||||
local CERT_TYPE="server"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
@@ -218,6 +220,16 @@ function make_cert() {
|
||||
return 1
|
||||
fi
|
||||
CERT_DAYS="$2"; shift 2 ;;
|
||||
--type)
|
||||
if [[ -z "$2" ]]; then
|
||||
echo "ERROR: --type requires a value." >&2
|
||||
return 1
|
||||
fi
|
||||
if [[ "$2" != "server" && "$2" != "user" ]]; then
|
||||
echo "ERROR: --type must be 'server' or 'user'." >&2
|
||||
return 1
|
||||
fi
|
||||
CERT_TYPE="$2"; shift 2 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
@@ -233,7 +245,7 @@ function make_cert() {
|
||||
echo "ERROR: Subject name is required." >&2
|
||||
return 1
|
||||
fi
|
||||
if ! _is_dns "$CERT_SUBJECT_NAME"; then
|
||||
if [[ "$CERT_TYPE" == "server" ]] && ! _is_dns "$CERT_SUBJECT_NAME"; then
|
||||
echo "ERROR: Invalid subject name '$CERT_SUBJECT_NAME'. Must be a valid DNS name." >&2
|
||||
return 1
|
||||
fi
|
||||
@@ -256,11 +268,17 @@ function make_cert() {
|
||||
fi
|
||||
|
||||
local CERT_NAME="${CERT_SUBJECT_NAME%%.*}"
|
||||
local SANS=("DNS:${CERT_SUBJECT_NAME}")
|
||||
local SANS=()
|
||||
|
||||
if [[ "$CERT_TYPE" == "server" ]]; then
|
||||
SANS=("DNS:${CERT_SUBJECT_NAME}")
|
||||
fi
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if _is_ip "$1"; then
|
||||
SANS+=("IP:$1")
|
||||
elif _is_email "$1"; then
|
||||
SANS+=("email:$1")
|
||||
elif _is_dns "$1"; then
|
||||
SANS+=("DNS:$1")
|
||||
else
|
||||
@@ -270,7 +288,11 @@ function make_cert() {
|
||||
shift
|
||||
done
|
||||
|
||||
echo "Generating server certificate for '$CERT_SUBJECT_NAME' with SANs:"
|
||||
if [[ "$CERT_TYPE" == "server" ]]; then
|
||||
echo "Generating server certificate for '$CERT_SUBJECT_NAME' with SANs:"
|
||||
else
|
||||
echo "Generating user certificate for '$CERT_SUBJECT_NAME':"
|
||||
fi
|
||||
for san in "${SANS[@]}"; do echo " - $san"; done
|
||||
|
||||
if [[ -f "$CERT_DIR/${CERT_NAME}_cert.pem" && -f "$CERT_DIR/${CERT_NAME}_key.pem" ]]; then
|
||||
@@ -278,33 +300,52 @@ function make_cert() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
local SANS_EXT="subjectAltName=$(IFS=,; echo "${SANS[*]}")"
|
||||
local REQ_ARGS=(
|
||||
-newkey rsa:4096
|
||||
-keyout "$CERT_DIR/${CERT_NAME}_key.pem"
|
||||
-noenc
|
||||
-subj "/CN=${CERT_SUBJECT_NAME}"
|
||||
-addext "basicConstraints=critical,CA:FALSE"
|
||||
)
|
||||
|
||||
echo "Generating server certificate and key..."
|
||||
if ! openssl req \
|
||||
-newkey rsa:4096 \
|
||||
-keyout "$CERT_DIR/${CERT_NAME}_key.pem" \
|
||||
-noenc \
|
||||
-subj "/CN=${CERT_SUBJECT_NAME}" \
|
||||
-addext "basicConstraints=critical,CA:FALSE" \
|
||||
-addext "keyUsage=critical,digitalSignature,keyEncipherment" \
|
||||
-addext "extendedKeyUsage=serverAuth,clientAuth" \
|
||||
-addext "$SANS_EXT" \
|
||||
${AIA_URL:+-addext "authorityInfoAccess=caIssuers;URI:${AIA_URL}"} \
|
||||
| openssl x509 \
|
||||
-req \
|
||||
-CA "$SIGNING_CERT" \
|
||||
-CAkey "$SIGNING_KEY" \
|
||||
-copy_extensions copyall \
|
||||
-days "$CERT_DAYS" \
|
||||
-text \
|
||||
-out "$CERT_DIR/${CERT_NAME}_cert.pem"; then
|
||||
echo "ERROR: Failed to generate server certificate and key." >&2
|
||||
local X509_ARGS=(
|
||||
-req
|
||||
-CA "$SIGNING_CERT"
|
||||
-CAkey "$SIGNING_KEY"
|
||||
-copy_extensions copyall
|
||||
-days "$CERT_DAYS"
|
||||
-text
|
||||
-out "$CERT_DIR/${CERT_NAME}_cert.pem"
|
||||
)
|
||||
|
||||
if [[ "$CERT_TYPE" == "server" ]]; then
|
||||
REQ_ARGS+=(
|
||||
-addext "keyUsage=critical,digitalSignature,keyEncipherment"
|
||||
-addext "extendedKeyUsage=serverAuth,clientAuth"
|
||||
-addext "subjectAltName=$(IFS=,; echo "${SANS[*]}")"
|
||||
)
|
||||
else
|
||||
REQ_ARGS+=(
|
||||
-addext "keyUsage=critical,digitalSignature,nonRepudiation"
|
||||
-addext "extendedKeyUsage=clientAuth,emailProtection,codeSigning"
|
||||
)
|
||||
if [[ ${#SANS[@]} -gt 0 ]]; then
|
||||
REQ_ARGS+=(-addext "subjectAltName=$(IFS=,; echo "${SANS[*]}")")
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n "$AIA_URL" ]]; then
|
||||
REQ_ARGS+=(-addext "authorityInfoAccess=caIssuers;URI:${AIA_URL}")
|
||||
fi
|
||||
|
||||
echo "Generating certificate and key..."
|
||||
if ! openssl req "${REQ_ARGS[@]}" | openssl x509 "${X509_ARGS[@]}"; then
|
||||
echo "ERROR: Failed to generate certificate and key." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function make_pfx() {
|
||||
make_pfx() {
|
||||
local ISSUING_CA=""
|
||||
local PFX_PASSWORD=""
|
||||
local APPLE_OPENSSL=0
|
||||
|
||||
+57
-20
@@ -286,21 +286,25 @@ func makeCA(caDir, caName string, days int, issuingCA, aiaBaseURL string) error
|
||||
// ---- makeCert ---------------------------------------------------------------
|
||||
|
||||
var (
|
||||
ipRE = regexp.MustCompile(`^[0-9]{1,3}(\.[0-9]{1,3}){3}$`)
|
||||
dnsRE = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)*$`)
|
||||
ipRE = regexp.MustCompile(`^[0-9]{1,3}(\.[0-9]{1,3}){3}$`)
|
||||
dnsRE = regexp.MustCompile(`^[a-z0-9-]+(\.[a-z0-9-]+)*$`)
|
||||
emailRE = regexp.MustCompile(`^[^@]+@[^@]+\.[^@]+$`)
|
||||
)
|
||||
|
||||
func makeCert(subjectName string, sans []string, caDir, certDir, issuingCA string, days int) error {
|
||||
func makeCert(subjectName string, sans []string, caDir, certDir, issuingCA, certType string, days int) error {
|
||||
if issuingCA == "ca" {
|
||||
return errors.New("--issuing-ca cannot be 'ca'")
|
||||
}
|
||||
if certType != "server" && certType != "user" {
|
||||
return fmt.Errorf("--type must be 'server' or 'user', got '%s'", certType)
|
||||
}
|
||||
if !dirExists(caDir) {
|
||||
return fmt.Errorf("CA directory %s does not exist", caDir)
|
||||
}
|
||||
if subjectName == "" {
|
||||
return errors.New("subject name is required")
|
||||
}
|
||||
if !dnsRE.MatchString(subjectName) {
|
||||
if certType == "server" && !dnsRE.MatchString(subjectName) {
|
||||
return fmt.Errorf("invalid subject name '%s'. Must be a valid DNS name", subjectName)
|
||||
}
|
||||
|
||||
@@ -337,12 +341,20 @@ func makeCert(subjectName string, sans []string, caDir, certDir, issuingCA strin
|
||||
certName = certName[:i]
|
||||
}
|
||||
|
||||
dnsNames := []string{subjectName}
|
||||
var dnsNames []string
|
||||
var emails []string
|
||||
var ips []net.IP
|
||||
|
||||
if certType == "server" {
|
||||
dnsNames = []string{subjectName}
|
||||
}
|
||||
|
||||
for _, entry := range sans {
|
||||
switch {
|
||||
case ipRE.MatchString(entry):
|
||||
ips = append(ips, net.ParseIP(entry))
|
||||
case emailRE.MatchString(entry):
|
||||
emails = append(emails, entry)
|
||||
case dnsRE.MatchString(entry):
|
||||
dnsNames = append(dnsNames, entry)
|
||||
default:
|
||||
@@ -350,10 +362,17 @@ func makeCert(subjectName string, sans []string, caDir, certDir, issuingCA strin
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Generating server certificate for '%s' with SANs:\n", subjectName)
|
||||
if certType == "server" {
|
||||
fmt.Printf("Generating server certificate for '%s' with SANs:\n", subjectName)
|
||||
} else {
|
||||
fmt.Printf("Generating user certificate for '%s':\n", subjectName)
|
||||
}
|
||||
for _, dns := range dnsNames {
|
||||
fmt.Printf(" - DNS:%s\n", dns)
|
||||
}
|
||||
for _, email := range emails {
|
||||
fmt.Printf(" - email:%s\n", email)
|
||||
}
|
||||
for _, ip := range ips {
|
||||
fmt.Printf(" - IP:%s\n", ip)
|
||||
}
|
||||
@@ -365,7 +384,7 @@ func makeCert(subjectName string, sans []string, caDir, certDir, issuingCA strin
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("Generating server certificate and key...")
|
||||
fmt.Println("Generating certificate and key...")
|
||||
caCert, err := loadCert(caCertPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -383,18 +402,35 @@ func makeCert(subjectName string, sans []string, caDir, certDir, issuingCA strin
|
||||
return err
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
tmpl := &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: subjectName},
|
||||
NotBefore: now,
|
||||
NotAfter: now.AddDate(0, 0, days),
|
||||
IsCA: false,
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
|
||||
var tmpl *x509.Certificate
|
||||
if certType == "server" {
|
||||
tmpl = &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: subjectName},
|
||||
NotBefore: now,
|
||||
NotAfter: now.AddDate(0, 0, days),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
}
|
||||
} else {
|
||||
tmpl = &x509.Certificate{
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{CommonName: subjectName},
|
||||
NotBefore: now,
|
||||
NotAfter: now.AddDate(0, 0, days),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageContentCommitment,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection, x509.ExtKeyUsageCodeSigning},
|
||||
EmailAddresses: emails,
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
}
|
||||
}
|
||||
|
||||
if aiaURL != "" {
|
||||
tmpl.IssuingCertificateURL = []string{aiaURL}
|
||||
}
|
||||
@@ -575,19 +611,20 @@ func newMakeCACmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func newMakeCertCmd() *cobra.Command {
|
||||
var certDir, caDir, issuingCA string
|
||||
var certDir, caDir, issuingCA, certType string
|
||||
var days int
|
||||
cmd := &cobra.Command{
|
||||
Use: "make-cert SUBJECT [SAN...]",
|
||||
Short: "Create a server/client certificate signed by the CA.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
return makeCert(args[0], args[1:], resolveCADir(caDir), certDir, issuingCA, days)
|
||||
return makeCert(args[0], args[1:], resolveCADir(caDir), certDir, issuingCA, certType, days)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVar(&certDir, "cert-dir", "", "output directory (default: signing CA directory)")
|
||||
cmd.Flags().StringVar(&caDir, "ca-dir", "", "CA root directory")
|
||||
cmd.Flags().StringVar(&issuingCA, "issuing-ca", "", "issuing CA directory name")
|
||||
cmd.Flags().StringVar(&certType, "type", "server", "certificate type: server or user")
|
||||
cmd.Flags().IntVar(&days, "days", 365, "validity period in days")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,17 @@ func verifyCert(t *testing.T, bundle, cert string) {
|
||||
}
|
||||
}
|
||||
|
||||
func assertEKU(t *testing.T, cert, eku string) {
|
||||
t.Helper()
|
||||
out, err := exec.Command("openssl", "x509", "-in", cert, "-noout", "-text").CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("openssl x509 failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(out), eku) {
|
||||
t.Fatalf("EKU %q not found in %s", eku, cert)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStandaloneCA(t *testing.T) {
|
||||
caDir := t.TempDir()
|
||||
|
||||
@@ -29,7 +41,7 @@ func TestStandaloneCA(t *testing.T) {
|
||||
}
|
||||
verifyCert(t, filepath.Join(caDir, "ca_bundle.pem"), filepath.Join(caDir, "ca_cert.pem"))
|
||||
|
||||
if err := makeCert("test", []string{"test.example.com", "127.0.0.1"}, caDir, "", "", 365); err != nil {
|
||||
if err := makeCert("test", []string{"test.example.com", "127.0.0.1"}, caDir, "", "", "server", 365); err != nil {
|
||||
t.Fatalf("makeCert: %v", err)
|
||||
}
|
||||
certPath := filepath.Join(caDir, "test_cert.pem")
|
||||
@@ -56,7 +68,7 @@ func TestTwoLevelCA(t *testing.T) {
|
||||
}
|
||||
verifyCert(t, filepath.Join(caDir, "ca_bundle.pem"), issuingCert)
|
||||
|
||||
if err := makeCert("test", []string{"test.example.com", "127.0.0.1"}, caDir, "", "issuing_ca", 365); err != nil {
|
||||
if err := makeCert("test", []string{"test.example.com", "127.0.0.1"}, caDir, "", "issuing_ca", "server", 365); err != nil {
|
||||
t.Fatalf("makeCert: %v", err)
|
||||
}
|
||||
certPath := filepath.Join(caDir, "issuing_ca", "test_cert.pem")
|
||||
@@ -64,6 +76,8 @@ func TestTwoLevelCA(t *testing.T) {
|
||||
t.Fatal("issuing_ca/test_cert.pem not created")
|
||||
}
|
||||
verifyCert(t, filepath.Join(caDir, "ca_bundle.pem"), certPath)
|
||||
assertEKU(t, certPath, "TLS Web Server Authentication")
|
||||
assertEKU(t, certPath, "TLS Web Client Authentication")
|
||||
|
||||
if err := makePFX(certPath, caDir, "issuing_ca", "s3cr3t", false); err != nil {
|
||||
t.Fatalf("makePFX: %v", err)
|
||||
@@ -79,6 +93,29 @@ func TestTwoLevelCA(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserCert(t *testing.T) {
|
||||
caDir := t.TempDir()
|
||||
|
||||
if err := makeCA(caDir, "Test CA", 3650, "", ""); err != nil {
|
||||
t.Fatalf("makeCA: %v", err)
|
||||
}
|
||||
if err := makeCA(caDir, "Issuing CA", 3650, "issuing_ca", ""); err != nil {
|
||||
t.Fatalf("makeCA issuing: %v", err)
|
||||
}
|
||||
|
||||
if err := makeCert("Alice Example", []string{"alice@example.com"}, caDir, "", "issuing_ca", "user", 365); err != nil {
|
||||
t.Fatalf("makeCert user: %v", err)
|
||||
}
|
||||
certPath := filepath.Join(caDir, "issuing_ca", "Alice Example_cert.pem")
|
||||
if !fileExists(certPath) {
|
||||
t.Fatal("Alice Example_cert.pem not created")
|
||||
}
|
||||
verifyCert(t, filepath.Join(caDir, "ca_bundle.pem"), certPath)
|
||||
assertEKU(t, certPath, "TLS Web Client Authentication")
|
||||
assertEKU(t, certPath, "E-mail Protection")
|
||||
assertEKU(t, certPath, "Code Signing")
|
||||
}
|
||||
|
||||
func TestCertDirOverride(t *testing.T) {
|
||||
caDir := t.TempDir()
|
||||
certDir := t.TempDir()
|
||||
@@ -86,7 +123,7 @@ func TestCertDirOverride(t *testing.T) {
|
||||
if err := makeCA(caDir, "Test CA", 3650, "", ""); err != nil {
|
||||
t.Fatalf("makeCA: %v", err)
|
||||
}
|
||||
if err := makeCert("test", []string{"test.example.com"}, caDir, certDir, "", 365); err != nil {
|
||||
if err := makeCert("test", []string{"test.example.com"}, caDir, certDir, "", "server", 365); err != nil {
|
||||
t.Fatalf("makeCert: %v", err)
|
||||
}
|
||||
certPath := filepath.Join(certDir, "test_cert.pem")
|
||||
@@ -109,7 +146,7 @@ func TestAppleOpenSSL(t *testing.T) {
|
||||
if err := makeCA(caDir, "Issuing CA", 3650, "issuing_ca", ""); err != nil {
|
||||
t.Fatalf("makeCA issuing: %v", err)
|
||||
}
|
||||
if err := makeCert("test", []string{"test.example.com"}, caDir, "", "issuing_ca", 365); err != nil {
|
||||
if err := makeCert("test", []string{"test.example.com"}, caDir, "", "issuing_ca", "server", 365); err != nil {
|
||||
t.Fatalf("makeCert: %v", err)
|
||||
}
|
||||
certPath := filepath.Join(caDir, "issuing_ca", "test_cert.pem")
|
||||
|
||||
Reference in New Issue
Block a user