Files

46 lines
7.4 KiB
Markdown

# Local Yandex Key OTP Import Implementation Plan
**Intent:** Import the user's selected VPN OTP from an official local Yandex Key export and generate it on this Mac without Bitwarden.
**Current Behavior:** Keychain/PyOTP and OTP autofill already exist, but manual-keychain disables OTP and the auto mode uses Bitwarden; there is no backup importer.
**Expected Outcome:** Installer and CLI expose a local wizard: choose file and entry, verify the current phone code, save only the selected secret in Keychain and enable local OTP. Existing manual setup remains the default.
**Target-Perspective Output:** A synthetic export imported through the real wizard yields one verified Keychain write and the persisted local OTP flag; a fake client receives shown browser, OTP on, submit off. Cancellation/invalid input yields no writes or launch.
**Truth Owner:** `openconnect-lite` Keychain account `totp/<LDAP username>` owns the secret; `env` owns `LEMANA_VPN_LOCAL_OTP`; CLI owns status and launch flags. Python helper owns backup parsing and verified import only.
**Contract Boundary:** New CLI `--import-yandex-key`, installer option/question, optional status metadata. No secret in shell arguments, env, output, errors or logs. Only the helper accesses the selected secret in memory.
**Cutover:** Opt-in `LEMANA_VPN_LOCAL_OTP=1` changes manual-keychain OTP from off to on after verification and persistence. Browser stays visible, submit stays off. Existing bitwarden-auto and full-manual behavior remain.
**Displaced Path:** The unconditional manual-mode OTP-off branch becomes a validated optional setting; no new generator, cloud extractor, third auth mode or alternate Keychain store.
**Value Density:** Reuse existing keyring/PyOTP dependencies and existing runtime patches. One helper next to CLI, installed/uninstalled with it.
**Acceptance Evidence:** Deterministic tests of parsing, real PyOTP RFC fixture verification, fake Keychain writes/rollback, CLI dispatch and fake-client capture; status readiness and installer preservation; full automated regression ladder.
**Evidence Lane:** Temporary homes, synthetic public test secrets and stub Keychain only. No actual user export, Keychain, GUI, live VPN, install or uninstall.
**Kill Criteria:** No unsupported OTP silently accepted; no writes before verification; failed activation restores prior Keychain entry; no OTP enabled by mere presence of a saved seed; full-manual never fills OTP.
**Architecture Slice:** `install.sh` -> installed CLI -> sibling `lemana-vpn-import-yandex.py` via OC Python -> existing Keychain + atomic env update -> CLI status/launch -> Swift status. `uninstall.sh` removes the helper. DNS/reconnect/runtime SSO patches and unrelated dirty work are excluded.
**Plan Review Gate:** Requires PRE review before execution.
## Input and lifecycle contract
- Verified plaintext format: JSON array with `name`, `secret`, `techInfo` (OTP URI); primary implementations: [GNOME Authenticator](https://world.pages.gitlab.gnome.org/Authenticator/src/authenticator/backup/yandex.rs.html), [Yandex export help](https://yandex.ru/support/id/ru/authorization/twofa-copy).
- Accept only `otpauth://totp`, SHA1, 6 digits, 30 seconds, valid Base32. Reject duplicate critical parameters, secret mismatch, HOTP/YAOTP and unsupported parameters. Never fetch metadata URLs. Mixed backups may contain unsupported records; validate the selected entry.
- Bound file reading to 4 MiB and entries to 1000; sanitize/cap display names. Require a real terminal at production entry, use hidden input for current phone code, allow EOF/interrupt to cancel without repeated prompts.
- Require saved LDAP username/password first (`vpn --configure-login`). Show destination account and the visible/no-submit behavior. Verify current/adjacent 30-second code locally, then write selected secret and enable manual-keychain/local OTP. Prepare the mode-0600 env replacement before changing Keychain; roll back the old secret if activation fails. Preserve unrelated env settings, no backup copy retained or removed automatically.
- `LEMANA_VPN_LOCAL_OTP` is 0 by default, only 0/1 accepted; invocation environment overrides env file. Reset to 0 in env to return to manual codes while retaining the saved seed. Bitwarden ignores this flag; full-manual ignores it for launch/preflight.
- Status must distinguish local OTP readiness and missing-seed guidance without reading/displaying secrets. Existing older CLI/Swift decoding remains compatible.
## Ordered tasks
1. Root implements Python helper and focused tests (`bin/lemana-vpn-import-yandex.py`, `tests/yandex_import.py`). Read-only explorer/review agents validate format/security. Test synthetic success, wrong code/selection, malformed/unsupported input, EOF, secret-safe output, storage/config failures and rollback.
2. Root integrates CLI flag/config, local preflight, status and launch (`bin/vpn-lemanapro.sh`) plus manual readiness decoding (`VPNManager.swift`). Preserve old command/event shapes where unchanged. Add fake-launch/status/no-TTY tests to `tests/smoke.sh` and focused wizard tests.
3. Root integrates installer question/flag and helper packaging/removal (`install.sh`, `uninstall.sh`), documents workflow and limits (`README.md`). Existing unowned-env preservation retains local OTP across updates; add evidence to `tests/configuration.py`. No extra GUI controls.
4. Read-only correctness/maintainability/POST plan review, fix blockers, then final syntax, wizard/config/logging tests, smoke and Swift release build. Main agent owns edits and integration; independent read-only work can run in parallel.
## Risks
Wrong OTP type/parameters produces silent login failures; unsupported settings must fail before persistence. Wrong selected account is addressed by explicit selection and phone-code verification. File/Keychain errors must not silently switch modes or replace usable credentials. Real macOS Keychain prompts and live SSO remain unverified without a separate current request.
## Completed evidence
- PRE and POST plan/maintainability reviews passed. Security review findings resolved: saved/effective/runtime LDAP binding, strict parameter allowlist, case/whitespace-safe secret masking before label truncation, Keychain readback, and a consistent commit point with interruption handling.
- `tests/yandex_import.py` passes with the existing OC Python. Synthetic RFC TOTP data exercise valid import, mixed exports, unsupported types/parameters, malformed/oversized files, EOF/wrong code, Keychain/config failures, rollback/commit and silent-backend failure.
- Automated PTY drives the actual CLI and sibling helper against a temporary fake Keychain: verified one-account write, unchanged LDAP password, no echoed phone code/secret, persisted setting and ready CLI JSON. Runtime account mismatches and explicit local-OTP override are checked.
- Shell syntax, `tests/smoke.sh`, `tests/configuration.py` (10 checks), `tests/logging.py`, `git diff --check`: PASS. Smoke captures local OTP on, full-manual off, missing seed blocking launch, unchanged Bitwarden flow, installer action/packaging and uninstall removal.
- `swift build -c release --package-path app`: PASS (3.29 s). Restricted run hit compiler cache and nested sandbox restrictions; approved build escalation completed without launching the app.
- No real export, real Keychain writes, real installation, GUI or live VPN acceptance was performed. Abrupt process death/power loss across separate Keychain/filesystem stores remains a documented limitation.