Renewal failed with an empty error box for any CN listed in index.txt
without a corresponding pki/issued/<CN>.crt — the state you get when an
index.txt is carried over from an older EasyRSA install but the issued/
files are not.
Two defects:
1. EasyRSA writes its diagnostics to stdout, not stderr: print() is
`printf '%s\n'`, and both die() and user_error() route through it.
stderr only carries output from the tools EasyRSA shells out to, and
even that is silenced under -S/--silent-ssl. _run_easyrsa built its
message from stderr alone, so every EasyRSA failure reported blank.
_easyrsa_diagnostics() now merges both streams (stderr first, so the
specific openssl message is not what the dialog clips) and drops the
version banner and blank padding.
2. EasyRSA reads the serial out of the .crt itself, so revoke-issued
cannot revoke a CN whose cert file is gone — and there is nothing to
add to the CRL either. has_issued_cert() now gates the revoke and CRL
steps in both CliRunner._issue() and CursesApp._process_cert(); the
workflow warns and goes straight to build-client-full. An explicit
--revoke / TUI `r` still fails loudly rather than silently no-op.
The post-build failure message now keys off whether a revoke actually
happened, not off is_renewal, so it no longer claims "has been revoked"
when nothing was.
Adds tests/test_app_reissue.py: the TUI re-issue path had no coverage at
all, and it is the path this bug was reported from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Remove Python 3.7+ constructs so the tool runs on 3.6:
- Drop `from __future__ import annotations` (3.7+) and convert all
builtin-generic annotations (list[]/dict[]/tuple[]/set[]) to their
typing.List/Dict/Tuple/Set equivalents, since without the future
import these annotations are evaluated eagerly and builtin generic
subscription only exists on 3.9+.
- Replace subprocess.run(capture_output=, text=) — both 3.7+ — with
stdout/stderr=PIPE and universal_newlines=True; update the matching
test assertion.
- requirements.txt: gate cryptography>=41 behind python_version>=3.7 and
pin cryptography<41 (last 3.6-compatible line, 40.0.2) plus the
dataclasses backport for 3.6.
- Suppress cryptography 40.x's per-import "Python 3.6 is no longer
supported" deprecation warning so it doesn't pollute CLI/cron stderr;
the <41 pin keeps us on a supported release regardless.
The only cryptography API used is AESGCM (present since 2.0), so no
x509/API-surface changes are needed for the older pin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reissue internally revokes the old cert (revoke_issued), which makes
the published CRL immediately stale, but neither the CLI --reissue
path nor the TUI renew flow ever regenerated/copied it — only
standalone revoke and "Regenerate CRL" did. A cert revoked as part of
a reissue could keep authenticating against OpenVPN until someone
remembered to regenerate the CRL by hand.
Both reissue paths now call gen_crl()+copy_crl() right after a
successful revoke_issued(), same as standalone revoke. This isn't
gated on the subsequent build_client_full() succeeding, since the CRL
is already stale the moment the old cert is revoked. A CRL-regen
failure here is reported as a warning and the reissue continues to
build the replacement cert — a new cert is more urgent than a fresh
CRL, and "Regenerate CRL"/--gen-crl remain available to retry.
Also add a RESTORECON_BINARY setting (default "restorecon", "" to
disable): copy_crl() now runs it on the destination file after every
copy, everywhere copy_crl() is called. Best-effort like
is_ca_key_encrypted() — a missing/misconfigured binary is swallowed
rather than breaking CRL deployment on non-SELinux systems.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cryptgeon's real protocol (verified against upstream occulto/frontend/backend
source) never matched what create_note() sent: it used a SHA-256-derived key
instead of the raw one, a single-blob ciphertext instead of the delimited
AES-GCM--nonce--ciphertext format, an empty meta field instead of a JSON
string, and a hash-bang URL instead of the real /note/<id>#<key> route -
notes uploaded fine but were undecryptable in the browser.
Also adds CA_PASSPHRASE auto-detection/prompting ("" auto-detects an
encrypted CA key and prompts, "!empty"/"!ask" opt out of/force the prompt)
and an external .conf file (--config / CONFIG_PATH / <script>.conf) that can
override the SETTINGS block without editing the script.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>