fix Cryptgeon protocol and add CA passphrase / external config resolution

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>
This commit is contained in:
Vlad Doloman
2026-07-06 12:58:21 +03:00
parent 24cae3de6a
commit bd17cdd68d
6 changed files with 368 additions and 27 deletions

View File

@@ -200,6 +200,65 @@ def test_main_no_args_launches_tui(monkeypatch):
app.run.assert_called_once_with()
def test_main_resolves_ca_passphrase_before_dispatch(monkeypatch, tmp_path):
"""The configured CA_PASSPHRASE sentinel must be resolved to an actual
passphrase before any EasyRSA call, and that resolved value (not the
sentinel) must be what reaches them."""
monkeypatch.setattr(sys, "argv", ["prog", "--gen-crl"])
monkeypatch.setattr("openvpncertupdate.CA_PASSPHRASE", "!ask")
monkeypatch.setattr("openvpncertupdate.EASYRSA_DIR", "/er")
monkeypatch.setattr("openvpncertupdate.EASYRSA_PKI_DIR", str(tmp_path))
mock_gen_crl = MagicMock()
monkeypatch.setattr("openvpncertupdate.gen_crl", mock_gen_crl)
monkeypatch.setattr("openvpncertupdate.copy_crl", MagicMock())
mock_resolve = MagicMock(return_value="typed-pass")
monkeypatch.setattr("openvpncertupdate.resolve_ca_passphrase", mock_resolve)
main()
mock_resolve.assert_called_once_with("!ask", str(tmp_path))
def test_main_applies_config_overrides_before_dispatch(monkeypatch, tmp_path):
"""Overrides returned by load_settings_overrides() must land in the module
globals before EasyRSA calls, so a config file can redirect EASYRSA_PKI_DIR etc."""
monkeypatch.setattr(sys, "argv", ["prog", "--gen-crl"])
monkeypatch.setattr("openvpncertupdate.EASYRSA_DIR", "/er")
monkeypatch.setattr("openvpncertupdate.EASYRSA_PKI_DIR", "/original/pki")
monkeypatch.setattr("openvpncertupdate.CA_PASSPHRASE", "!empty")
mock_gen_crl = MagicMock()
monkeypatch.setattr("openvpncertupdate.gen_crl", mock_gen_crl)
monkeypatch.setattr("openvpncertupdate.copy_crl", MagicMock())
mock_load = MagicMock(return_value={"EASYRSA_PKI_DIR": "/overridden/pki"})
monkeypatch.setattr("openvpncertupdate.load_settings_overrides", mock_load)
main()
mock_gen_crl.assert_called_once_with("/er", "/overridden/pki", "")
def test_main_passes_config_flag_to_load_overrides(monkeypatch):
monkeypatch.setattr(sys, "argv", ["prog", "--gen-crl", "--config", "/explicit/path.conf"])
monkeypatch.setattr("openvpncertupdate.CA_PASSPHRASE", "!empty")
monkeypatch.setattr("openvpncertupdate.CliRunner", lambda: MagicMock())
mock_load = MagicMock(return_value={})
monkeypatch.setattr("openvpncertupdate.load_settings_overrides", mock_load)
main()
assert mock_load.call_args[0][0] == "/explicit/path.conf"
def test_main_exits_with_error_when_explicit_config_missing(monkeypatch, capsys):
monkeypatch.setattr(sys, "argv", ["prog", "--gen-crl", "--config", "/does/not/exist.conf"])
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code != 0
assert "/does/not/exist.conf" in capsys.readouterr().err
# ---------------------------------------------------------------------------
# --send-email / --no-send-email
# ---------------------------------------------------------------------------