# QUBES_OS.md ## 1. Goal Run `n_signer` inside a dedicated Qubes OS qube (for example `vault`-like behavior), and let caller qubes access signing via qrexec with explicit policy control. This doc outlines what must be implemented/packaged for a reliable Qubes deployment path. --- ## 2. Current status (where we are now) Implemented in current codebase: - `nsigner` supports `--listen qrexec` and `--listen stdio`. - Framing is transport-agnostic and shared via length-prefixed JSON (`4-byte big-endian length + payload`). - In qrexec/stdio mode, server handles one framed request-response exchange. - Caller identity extraction supports `QREXEC_REMOTE_DOMAIN`, surfaced as `qubes:` when available. Still missing for complete Qubes integration: - qrexec service file + wrapper script artifacts. - dom0 qrexec policy artifacts with sane defaults. - install/uninstall guidance and verification flow for real Qubes deployment. - packaging path (`packaging/qubes/`) and docs wired into README map. --- ## 3. Architecture in Qubes ### 3.1 Components - **Signer qube** (target): runs `nsigner` service entrypoint. - **Caller qube(s)**: apps/tools invoking qrexec service. - **dom0 policy**: controls which caller qubes may invoke signer service. ### 3.2 Request path 1. Caller qube invokes qrexec service (e.g. `qubes.NsignerRpc`). 2. qrexec starts service command inside signer qube. 3. Service command runs `nsigner --listen qrexec`. 4. Caller sends framed JSON-RPC request over qrexec stdio channel. 5. `nsigner` returns framed JSON-RPC response. ### 3.3 Trust and identity - Source qube identity comes from `QREXEC_REMOTE_DOMAIN`. - `n_signer` maps caller as `qubes:` where available. - qrexec policy in dom0 remains first enforcement boundary. - `n_signer` policy/approval remains second boundary. ### 3.4 Optional per-program identity inside one qube (`--auth optional`) qrexec now supports an optional auth-envelope layer: - `nsigner --listen qrexec --auth off` (default): identity remains `qubes:`. - `nsigner --listen qrexec --auth optional`: if a caller includes a valid auth envelope, identity becomes `qubes:+pubkey:`. - `nsigner --listen qrexec --auth required`: all requests must carry valid envelopes and identity is always `qubes:+pubkey:`. This enables per-program approvals inside a single caller qube while preserving existing behavior for legacy callers that do not emit auth envelopes. --- ## 4. Required implementation tasks ## 4.1 Service entrypoint artifacts ✅ Implemented Implemented repo artifacts: - `packaging/qubes/rpc/qubes.NsignerRpc` - `packaging/qubes/install-service.sh` `qubes.NsignerRpc` runs: - `exec /usr/local/bin/nsigner --listen qrexec` Install inside the signer qube: ```bash sudo sh packaging/qubes/install-service.sh ``` This installs the qrexec service to `/etc/qubes-rpc/qubes.NsignerRpc` with executable permissions. ## 4.2 dom0 policy artifacts ✅ Implemented Implemented repo artifacts: - `packaging/qubes/policy.d/40-nsigner.policy` - `packaging/qubes/install-policy.sh` Policy defaults now use explicit `ask` plus deny catch-all: - `qubes.NsignerRpc * @anyvm @tag:nsigner-signer ask default_target=nsigner-vault` - `qubes.NsignerRpc * @anyvm @anyvm deny` Install in dom0: ```bash sudo sh packaging/qubes/install-policy.sh ``` This installs `/etc/qubes/policy.d/40-nsigner.policy` and prints signer-tag guidance. ## 4.3 Policy model inside n_signer for qubes callers ✅ Implemented Current code reads caller as `qubes:` and qrexec default behavior is hardened. In qrexec mode, default prompt behavior is now: - `PROMPT_EVERY_REQUEST` This replaces the previous permissive `PROMPT_NEVER` temporary setting. If qrexec auth envelopes are enabled (`--auth optional` or `--auth required`), approved callers can also be recorded as composite identities: - `qubes:+pubkey:` This gives independent approval rows for distinct programs running in the same source qube. ## 4.4 Client helper examples ✅ Implemented Added: - `documents/qubes_client_examples.md` Includes: - shell helper example invoking `qrexec-client-vm` with framed request/response handling - Python helper example implementing frame encode/decode over qrexec stdio channel - reference to `documents/CLIENT_IMPLEMENTATION.md` for full protocol details --- ## 5. Operational runbook ## 5.1 Setup signer qube - install `nsigner` binary at `/usr/local/bin/nsigner` - run `sudo sh packaging/qubes/install-service.sh` - verify `/etc/qubes-rpc/qubes.NsignerRpc` exists and is executable ## 5.2 Setup dom0 policy - run `sudo sh packaging/qubes/install-policy.sh` - tag signer qube (example): `qvm-tags nsigner-vault add nsigner-signer` - reload qrexec policy per Qubes procedure/version ## 5.3 Verification - from caller qube, invoke test request (`get_public_key`) - confirm signer qube receives request - confirm activity log displays `qubes:` caller prefix - with `--auth optional`, confirm envelope-enabled clients show `qubes:+pubkey:` - validate deny behavior from unauthorized qube ## 5.4 Failure checks - malformed frame -> parse error response - missing policy -> deny path - missing `QREXEC_REMOTE_DOMAIN` -> fallback identity path --- ## 6. Security requirements - Never run signer service in disposable qube if mnemonic persistence is expected. - Prefer dedicated minimal template for signer qube. - Keep qrexec policy narrowly scoped (explicit source + target). - Require user approval for sensitive methods unless explicitly intended otherwise. - Log caller identity and method (without secret payload logging). --- ## 7. Documentation tasks Update these after packaging lands: - `README.md` - add Qubes deployment subsection under transport/usage - add `documents/QUBES_OS.md` and moved `documents/CLIENT_IMPLEMENTATION.md` in document map - `plans/nsigner.md` - mark T1 done with packaging status clearly separated --- ## 8. Definition of done (Qubes) Qubes integration is considered complete when: 1. qrexec service artifact exists and is installable. 2. dom0 policy artifact exists with secure default pattern. 3. End-to-end call from allowed caller qube succeeds. 4. Call from unauthorized qube is denied. 5. Caller displayed as `qubes:` in activity. 6. README + docs include full setup and troubleshooting.