Files
lemana-vpn/docs/goals/yandex-key-import/PLAN.md
T

7.4 KiB

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, Yandex export help.
  • 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.