feat(deploy): device client-CA + issuance/revocation + frp/nginx mTLS templates (V2/V7)
Committable VPS artifacts for the native reverse-tunnel (no live-ops, no secrets):
- gen-device-ca.sh: EC P-256 self-signed device CA (CA:TRUE pathlen:0, keyCertSign+cRLSign),
openssl ca db + empty CRL, idempotent, --kind frp-client scaffolds the control-channel CA.
- issue-device-cert.sh: P-256 leaf, EKU=clientAuth only, 825d, emits .p12 (-legacy) + .pem/.key/.fullchain,
copy_extensions=none prevents CSR-injected serverAuth/CA:TRUE; CN traversal guard.
- revoke-device.sh: openssl ca revoke -> regenerate crl.pem (revocation enforced via -crl_check).
- frp/{frps.toml.example,frpc.example.toml}: control-channel mTLS+token, disableCustomTLSFirstByte=true.
- nginx/frp-mtls.conf: :8470 TLS-term, ssl_verify_client on + device-CA + ssl_crl, WS-upgrade proxy to :7080.
- nginx/stream-sni-additions.md: the two additive SNI map lines (merge-only, coexistence-safe).
Verified: gen->issue->openssl verify->revoke->CRL chain green in a tmp dir (OpenSSL 3.0.18).
This commit is contained in:
163
deploy/scripts/gen-device-ca.sh
Executable file
163
deploy/scripts/gen-device-ca.sh
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# RELAY-PHASE1 · Native mTLS tunnel · V2 — Device client-CA (and frp-client control-CA).
|
||||
#
|
||||
# STAGING TEMPLATE. Mints a PRIVATE client-authentication CA whose leaves the native clients (iOS /
|
||||
# Android / desktop) present at nginx :8470 (`ssl_verify_client on`). This CA is the ONE AND ONLY
|
||||
# security gate for the tunnelled base app (the base app has no login of its own — PLAN §1), so it
|
||||
# MUST be a SEPARATE trust root from Let's Encrypt (the public :8470 SERVER cert) and from the
|
||||
# agent-enrollment CA (deploy/scripts/gen-agent-ca.sh). Mixing them would let a foreign leaf enroll.
|
||||
#
|
||||
# *** P-256 (prime256v1), NEVER Ed25519. *** nginx `ssl_verify_client` + iOS client-certificate
|
||||
# selection require ECDSA-P256/RSA; Ed25519 client certs are not universally accepted (PLAN V2).
|
||||
#
|
||||
# Two KINDs (same structure, different name/subject/default dir):
|
||||
# device → /etc/relay/device-ca (device-ca.cert.pem) leaves = client devices (V2/V4)
|
||||
# frp-client → /etc/relay/frp-client-ca (frp-client-ca.cert.pem) leaves = per-host frpc control mTLS (V1/V1b)
|
||||
#
|
||||
# Produces an OpenSSL `ca` database (index.txt / serial / crlnumber / newcerts / openssl.cnf) so that
|
||||
# deploy/scripts/issue-device-cert.sh can sign tracked leaves and deploy/scripts/revoke-device.sh can
|
||||
# revoke by serial and regenerate the CRL. An EMPTY crl.pem is initialised NOW so V4's `ssl_crl` has a
|
||||
# file to reference from day one.
|
||||
#
|
||||
# Outputs (0600 key, 0644 cert) under CA_DIR:
|
||||
# <CA_NAME>.key.pem <CA_NAME>.cert.pem crl.pem
|
||||
# index.txt serial crlnumber index.txt.attr newcerts/ openssl.cnf (the `openssl ca` db)
|
||||
#
|
||||
# Config via ENV or flags (no hardcoded secrets — this CA key is generated in place, never committed):
|
||||
# KIND device | frp-client (default device) — or --kind <k>
|
||||
# CA_DIR output dir (default per-KIND above) — or --dir <path>
|
||||
# CA_DAYS CA lifetime in days (default 3650)
|
||||
#
|
||||
# Idempotent: an existing CA (key+cert) is REUSED, never overwritten (delete the dir to rotate).
|
||||
#
|
||||
# Verify (syntax): bash -n deploy/scripts/gen-device-ca.sh
|
||||
set -euo pipefail
|
||||
|
||||
KIND="${KIND:-device}"
|
||||
CA_DIR="${CA_DIR:-}"
|
||||
CA_DAYS="${CA_DAYS:-3650}"
|
||||
|
||||
# ---- args (flags override env) --------------------------------------------------------------------
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--kind) KIND="${2:?--kind needs a value}"; shift 2 ;;
|
||||
--dir) CA_DIR="${2:?--dir needs a value}"; shift 2 ;;
|
||||
-h|--help)
|
||||
grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "FATAL: unknown argument '$1' (see --help)" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "${KIND}" in
|
||||
device) CA_NAME="device-ca"; CA_CN="web-terminal Device CA"; CA_OU="device-ca"
|
||||
CA_DIR="${CA_DIR:-/etc/relay/device-ca}" ;;
|
||||
frp-client) CA_NAME="frp-client-ca"; CA_CN="web-terminal frp-client CA"; CA_OU="frp-client-ca"
|
||||
CA_DIR="${CA_DIR:-/etc/relay/frp-client-ca}" ;;
|
||||
*) echo "FATAL: KIND must be 'device' or 'frp-client' (got '${KIND}')." >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
command -v openssl >/dev/null 2>&1 || { echo "FATAL: openssl not found on PATH." >&2; exit 3; }
|
||||
|
||||
umask 077
|
||||
mkdir -p "${CA_DIR}/newcerts"
|
||||
# Resolve to an absolute path so `openssl ca` (which we run cd'd into CA_DIR with dir=.) is stable.
|
||||
CA_DIR="$(cd "${CA_DIR}" && pwd)"
|
||||
|
||||
KEY="${CA_DIR}/${CA_NAME}.key.pem"
|
||||
CERT="${CA_DIR}/${CA_NAME}.cert.pem"
|
||||
|
||||
echo "== Device/frp-client CA (${KIND}, PRIVATE — not Let's Encrypt) into ${CA_DIR} =="
|
||||
|
||||
# ---- 1. CA keypair + self-signed cert (P-256, CA:TRUE, keyCertSign+cRLSign) -----------------------
|
||||
if [[ -f "${KEY}" && -f "${CERT}" ]]; then
|
||||
echo ">> CA already exists — reusing (delete ${CA_DIR} to rotate)."
|
||||
else
|
||||
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "${KEY}"
|
||||
openssl req -x509 -new -key "${KEY}" -days "${CA_DAYS}" \
|
||||
-subj "/O=web-terminal-relay/OU=${CA_OU}/CN=${CA_CN}" \
|
||||
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
|
||||
-addext "keyUsage=critical,keyCertSign,cRLSign" \
|
||||
-addext "subjectKeyIdentifier=hash" \
|
||||
-out "${CERT}"
|
||||
fi
|
||||
|
||||
# ---- 2. OpenSSL `ca` database (so leaves are tracked → revocable via the CRL) ----------------------
|
||||
[[ -f "${CA_DIR}/index.txt" ]] || : > "${CA_DIR}/index.txt"
|
||||
[[ -f "${CA_DIR}/serial" ]] || echo 1000 > "${CA_DIR}/serial"
|
||||
[[ -f "${CA_DIR}/crlnumber" ]] || echo 1000 > "${CA_DIR}/crlnumber"
|
||||
# unique_subject=no allows re-issuing a leaf for the same CN (device/host cert rotation).
|
||||
[[ -f "${CA_DIR}/index.txt.attr" ]] || echo "unique_subject = no" > "${CA_DIR}/index.txt.attr"
|
||||
|
||||
if [[ ! -f "${CA_DIR}/openssl.cnf" ]]; then
|
||||
# dir = . → the issue/revoke scripts `cd` into CA_DIR before invoking `openssl ca`, so the db is
|
||||
# relocatable (works in a mktemp test dir). $dir is expanded by openssl; ${CA_NAME} by bash here.
|
||||
cat > "${CA_DIR}/openssl.cnf" <<EOF
|
||||
[ ca ]
|
||||
default_ca = CA_default
|
||||
|
||||
[ CA_default ]
|
||||
dir = .
|
||||
database = \$dir/index.txt
|
||||
serial = \$dir/serial
|
||||
new_certs_dir = \$dir/newcerts
|
||||
certificate = \$dir/${CA_NAME}.cert.pem
|
||||
private_key = \$dir/${CA_NAME}.key.pem
|
||||
crlnumber = \$dir/crlnumber
|
||||
crl = \$dir/crl.pem
|
||||
default_md = sha256
|
||||
default_days = 825
|
||||
default_crl_days = 30
|
||||
policy = policy_anything
|
||||
copy_extensions = none
|
||||
x509_extensions = client_cert
|
||||
crl_extensions = crl_ext
|
||||
|
||||
[ policy_anything ]
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
localityName = optional
|
||||
organizationName = optional
|
||||
organizationalUnitName = optional
|
||||
commonName = supplied
|
||||
emailAddress = optional
|
||||
|
||||
# Leaf profile: client-authentication ONLY (never serverAuth, never CA).
|
||||
[ client_cert ]
|
||||
basicConstraints = critical, CA:FALSE
|
||||
keyUsage = critical, digitalSignature
|
||||
extendedKeyUsage = clientAuth
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
|
||||
[ crl_ext ]
|
||||
authorityKeyIdentifier = keyid:always
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ---- 3. Empty CRL now (V4 `ssl_crl` needs the file to exist even with zero revocations) ------------
|
||||
if [[ ! -f "${CA_DIR}/crl.pem" ]]; then
|
||||
( cd "${CA_DIR}" && openssl ca -config openssl.cnf -gencrl -out crl.pem >/dev/null 2>&1 )
|
||||
fi
|
||||
|
||||
# ---- 4. Permissions + summary ---------------------------------------------------------------------
|
||||
chmod 700 "${CA_DIR}"
|
||||
chmod 600 "${KEY}"
|
||||
chmod 644 "${CERT}" "${CA_DIR}/crl.pem"
|
||||
|
||||
cat <<SUMMARY
|
||||
|
||||
${KIND} CA ready in ${CA_DIR}:
|
||||
CA cert = ${CERT} (0644 — the trust anchor to deploy)
|
||||
CA key = ${KEY} (0600 — NEVER commit / NEVER leave a shared host)
|
||||
CRL = ${CA_DIR}/crl.pem (empty; regenerated by revoke-device.sh)
|
||||
ca db = index.txt / serial / crlnumber / newcerts / openssl.cnf
|
||||
|
||||
Wire it:
|
||||
device → nginx :8470 ssl_client_certificate ${CERT} ; ssl_crl ${CA_DIR}/crl.pem ;
|
||||
frp-client → frps.toml transport.tls.trustedCaFile = ${CERT}
|
||||
|
||||
Next:
|
||||
issue a leaf : CA_DIR=${CA_DIR} bash deploy/scripts/issue-device-cert.sh <CN> <out-dir>
|
||||
revoke a leaf : CA_DIR=${CA_DIR} bash deploy/scripts/revoke-device.sh <leaf.cert.pem>
|
||||
SUMMARY
|
||||
120
deploy/scripts/issue-device-cert.sh
Executable file
120
deploy/scripts/issue-device-cert.sh
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# RELAY-PHASE1 · Native mTLS tunnel · V2 — Issue a client-auth LEAF from the device CA.
|
||||
#
|
||||
# STAGING TEMPLATE. Signs a P-256 client-certificate (EKU=clientAuth, 825d) with the CA minted by
|
||||
# deploy/scripts/gen-device-ca.sh and emits BOTH delivery formats:
|
||||
# <CN>.p12 — PKCS#12, `-legacy` (3DES/RC2) for iOS / Android import compatibility (V2)
|
||||
# <CN>.cert.pem — leaf cert ┐
|
||||
# <CN>.key.pem — leaf private key (0600) ├─ desktop / curl --cert (M1 acceptance)
|
||||
# <CN>.fullchain.pem— leaf + CA cert ┘
|
||||
# The leaf is signed via `openssl ca`, so it is recorded in the CA's index.txt and can later be
|
||||
# revoked (CRL) by deploy/scripts/revoke-device.sh. CN + serial are appended to <CA_DIR>/issued.log.
|
||||
#
|
||||
# NOTE: the SAME script issues the per-host frp-client control certs (V1b) — just point CA_DIR at the
|
||||
# frp-client CA: CA_DIR=/etc/relay/frp-client-ca bash issue-device-cert.sh <hostname> <out>
|
||||
# (frpc uses the .cert.pem/.key.pem; ignore the .p12 for that use.)
|
||||
#
|
||||
# Usage: issue-device-cert.sh <CN> [OUT_DIR]
|
||||
# <CN> required — the device/host common name (also the filename stem); [A-Za-z0-9._-]+
|
||||
# [OUT_DIR] optional — where the leaf artifacts land (default <CA_DIR>/issued/<CN>)
|
||||
#
|
||||
# Config via ENV or flags (no hardcoded secrets):
|
||||
# CA_DIR which CA signs (default /etc/relay/device-ca) — or --ca-dir <path>
|
||||
# P12_PASSWORD .p12 export passphrase (REQUIRED via env/arg; auto-generated + printed ONCE if
|
||||
# unset) — or --pass <secret>
|
||||
# LEAF_DAYS leaf lifetime (default 825)
|
||||
#
|
||||
# Verify (syntax): bash -n deploy/scripts/issue-device-cert.sh
|
||||
set -euo pipefail
|
||||
|
||||
CA_DIR="${CA_DIR:-/etc/relay/device-ca}"
|
||||
LEAF_DAYS="${LEAF_DAYS:-825}"
|
||||
CN=""
|
||||
OUT_DIR=""
|
||||
|
||||
# ---- args ----------------------------------------------------------------------------------------
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--ca-dir) CA_DIR="${2:?--ca-dir needs a value}"; shift 2 ;;
|
||||
--pass) P12_PASSWORD="${2:?--pass needs a value}"; shift 2 ;;
|
||||
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
-*) echo "FATAL: unknown flag '$1' (see --help)" >&2; exit 2 ;;
|
||||
*) if [[ -z "${CN}" ]]; then CN="$1"; elif [[ -z "${OUT_DIR}" ]]; then OUT_DIR="$1";
|
||||
else echo "FATAL: too many positional args at '$1'" >&2; exit 2; fi; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "${CN}" ]] || { echo "FATAL: <CN> is required. usage: issue-device-cert.sh <CN> [OUT_DIR]" >&2; exit 2; }
|
||||
# Boundary validation: CN becomes a filename — reject anything that could traverse or surprise.
|
||||
[[ "${CN}" =~ ^[A-Za-z0-9._-]+$ ]] || { echo "FATAL: CN '${CN}' must match [A-Za-z0-9._-]+ (it is used as a filename)." >&2; exit 2; }
|
||||
|
||||
command -v openssl >/dev/null 2>&1 || { echo "FATAL: openssl not found on PATH." >&2; exit 3; }
|
||||
|
||||
CA_CERT="${CA_DIR}/$(basename "${CA_DIR}").cert.pem"
|
||||
# gen-device-ca names the CA cert after its dir (device-ca.cert.pem / frp-client-ca.cert.pem); if the
|
||||
# dir was renamed, fall back to the single *.cert.pem that is not a leaf. Keep it explicit + validated.
|
||||
if [[ ! -f "${CA_CERT}" ]]; then
|
||||
CA_CERT="$(ls "${CA_DIR}"/*-ca.cert.pem 2>/dev/null | head -1 || true)"
|
||||
fi
|
||||
[[ -f "${CA_DIR}/openssl.cnf" && -f "${CA_CERT}" ]] || {
|
||||
echo "FATAL: no CA db in ${CA_DIR}. Run gen-device-ca.sh first (expected openssl.cnf + *-ca.cert.pem)." >&2
|
||||
exit 3; }
|
||||
|
||||
OUT_DIR="${OUT_DIR:-${CA_DIR}/issued/${CN}}"
|
||||
umask 077
|
||||
mkdir -p "${OUT_DIR}"
|
||||
OUT_DIR="$(cd "${OUT_DIR}" && pwd)" # absolute — we cd into CA_DIR before `openssl ca`
|
||||
|
||||
# ---- passphrase (never hardcoded; auto-generate + print once if not supplied) ---------------------
|
||||
P12_GENERATED=0
|
||||
if [[ -z "${P12_PASSWORD:-}" ]]; then
|
||||
P12_PASSWORD="$(openssl rand -base64 18 | tr -dc 'A-Za-z0-9' | head -c 24)"
|
||||
P12_GENERATED=1
|
||||
fi
|
||||
|
||||
KEY="${OUT_DIR}/${CN}.key.pem"
|
||||
CERT="${OUT_DIR}/${CN}.cert.pem"
|
||||
CHAIN="${OUT_DIR}/${CN}.fullchain.pem"
|
||||
P12="${OUT_DIR}/${CN}.p12"
|
||||
CSR="${OUT_DIR}/${CN}.csr.pem"
|
||||
|
||||
echo "== Issue client-auth leaf CN=${CN} (${LEAF_DAYS}d) signed by ${CA_CERT} → ${OUT_DIR} =="
|
||||
|
||||
# ---- 1. leaf keypair + CSR (P-256) ----------------------------------------------------------------
|
||||
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "${KEY}"
|
||||
openssl req -new -key "${KEY}" -subj "/O=web-terminal-relay/OU=device/CN=${CN}" -out "${CSR}"
|
||||
|
||||
# ---- 2. sign via `openssl ca` (tracks the leaf in index.txt → revocable) ---------------------------
|
||||
( cd "${CA_DIR}" && openssl ca -config openssl.cnf -batch -notext \
|
||||
-extensions client_cert -days "${LEAF_DAYS}" \
|
||||
-in "${CSR}" -out "${CERT}" )
|
||||
|
||||
cat "${CERT}" "${CA_CERT}" > "${CHAIN}"
|
||||
|
||||
# ---- 3. PKCS#12 for iOS / Android (legacy 3DES/RC2 — imports reliably on mobile) -------------------
|
||||
openssl pkcs12 -export -legacy \
|
||||
-inkey "${KEY}" -in "${CERT}" -certfile "${CA_CERT}" \
|
||||
-name "${CN}" -passout "pass:${P12_PASSWORD}" -out "${P12}"
|
||||
|
||||
# ---- 4. bookkeeping -------------------------------------------------------------------------------
|
||||
SERIAL="$(openssl x509 -in "${CERT}" -noout -serial | cut -d= -f2)"
|
||||
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') CN=${CN} serial=${SERIAL} out=${OUT_DIR}" >> "${CA_DIR}/issued.log"
|
||||
|
||||
rm -f "${CSR}"
|
||||
chmod 600 "${KEY}" "${P12}"
|
||||
chmod 644 "${CERT}" "${CHAIN}"
|
||||
|
||||
cat <<SUMMARY
|
||||
|
||||
Leaf issued for CN=${CN} (serial ${SERIAL}):
|
||||
mobile (iOS/Android) : ${P12} (0600) import + passphrase in-app
|
||||
desktop / curl : ${CERT}
|
||||
${KEY} (0600)
|
||||
${CHAIN}
|
||||
logged : ${CA_DIR}/issued.log
|
||||
|
||||
Deliver the .p12 SECURELY (scp / AirDrop / Files — NEVER email).
|
||||
$( [[ "${P12_GENERATED}" == "1" ]] && printf ' P12 PASSPHRASE (shown ONCE — record it now): %s\n' "${P12_PASSWORD}" )
|
||||
Revoke later: CA_DIR=${CA_DIR} bash deploy/scripts/revoke-device.sh ${CERT}
|
||||
SUMMARY
|
||||
68
deploy/scripts/revoke-device.sh
Executable file
68
deploy/scripts/revoke-device.sh
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# RELAY-PHASE1 · Native mTLS tunnel · V2 — Revoke a device/frp-client leaf, regenerate the CRL.
|
||||
#
|
||||
# STAGING TEMPLATE. Marks a previously-issued leaf revoked in the CA database and regenerates
|
||||
# <CA_DIR>/crl.pem, which nginx :8470 serves via `ssl_crl` (V4) — so the revoked device is rejected at
|
||||
# the TLS handshake fleet-wide the moment nginx reloads. This script does NOT touch the remote VPS: it
|
||||
# only prints the reload hint; run the reload yourself on the box after copying the CRL.
|
||||
#
|
||||
# Requires the `openssl ca` database created by deploy/scripts/gen-device-ca.sh (index.txt/serial/…).
|
||||
#
|
||||
# Usage: revoke-device.sh <LEAF_CERT.pem> # revoke by the issued cert file
|
||||
# or revoke-device.sh --serial <HEX_SERIAL> # revoke by serial (uses <CA_DIR>/newcerts/<serial>.pem)
|
||||
#
|
||||
# Config via ENV or flags (no secrets):
|
||||
# CA_DIR which CA to revoke against (default /etc/relay/device-ca) — or --ca-dir <path>
|
||||
#
|
||||
# Verify (syntax): bash -n deploy/scripts/revoke-device.sh
|
||||
set -euo pipefail
|
||||
|
||||
CA_DIR="${CA_DIR:-/etc/relay/device-ca}"
|
||||
LEAF=""
|
||||
SERIAL=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--ca-dir) CA_DIR="${2:?--ca-dir needs a value}"; shift 2 ;;
|
||||
--serial) SERIAL="${2:?--serial needs a value}"; shift 2 ;;
|
||||
-h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
-*) echo "FATAL: unknown flag '$1' (see --help)" >&2; exit 2 ;;
|
||||
*) if [[ -z "${LEAF}" ]]; then LEAF="$1";
|
||||
else echo "FATAL: too many positional args at '$1'" >&2; exit 2; fi; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v openssl >/dev/null 2>&1 || { echo "FATAL: openssl not found on PATH." >&2; exit 3; }
|
||||
[[ -f "${CA_DIR}/openssl.cnf" ]] || { echo "FATAL: no CA db in ${CA_DIR} (run gen-device-ca.sh first)." >&2; exit 3; }
|
||||
|
||||
# ---- resolve the target leaf (by path or by serial) to an absolute path --------------------------
|
||||
if [[ -n "${SERIAL}" ]]; then
|
||||
# Boundary-validate the serial (used to build a path); openssl stores newcerts/<UPPERCASE-HEX>.pem.
|
||||
[[ "${SERIAL}" =~ ^[0-9A-Fa-f]+$ ]] || { echo "FATAL: --serial must be hex." >&2; exit 2; }
|
||||
LEAF="${CA_DIR}/newcerts/$(echo "${SERIAL}" | tr '[:lower:]' '[:upper:]').pem"
|
||||
fi
|
||||
[[ -n "${LEAF}" ]] || { echo "FATAL: pass a <LEAF_CERT.pem> or --serial <HEX>." >&2; exit 2; }
|
||||
[[ -f "${LEAF}" ]] || { echo "FATAL: leaf cert not found: ${LEAF}" >&2; exit 3; }
|
||||
LEAF="$(cd "$(dirname "${LEAF}")" && pwd)/$(basename "${LEAF}")"
|
||||
|
||||
REVOKED_SERIAL="$(openssl x509 -in "${LEAF}" -noout -serial | cut -d= -f2)"
|
||||
echo "== Revoking serial ${REVOKED_SERIAL} against CA ${CA_DIR} =="
|
||||
|
||||
# ---- revoke + regenerate the CRL (cd in so openssl.cnf's dir=. resolves to CA_DIR) ----------------
|
||||
(
|
||||
cd "${CA_DIR}"
|
||||
openssl ca -config openssl.cnf -revoke "${LEAF}"
|
||||
openssl ca -config openssl.cnf -gencrl -out crl.pem
|
||||
)
|
||||
chmod 644 "${CA_DIR}/crl.pem"
|
||||
|
||||
cat <<SUMMARY
|
||||
|
||||
Revoked serial ${REVOKED_SERIAL}. Updated CRL: ${CA_DIR}/crl.pem
|
||||
Confirm: openssl crl -in ${CA_DIR}/crl.pem -noout -text | grep -A1 'Serial Number'
|
||||
|
||||
RELOAD HINT (run ON THE VPS after copying the new crl.pem into place — NOT executed here):
|
||||
nginx -t && nginx -s reload
|
||||
Until nginx reloads the CRL, the revoked cert is still accepted.
|
||||
SUMMARY
|
||||
Reference in New Issue
Block a user