MoxiVueDownload

Security

How MoxiVue devices join, sign and talk to each other

The protocol, written down so that it can be checked: what each exchange holds, what is signed and with which key, what is sealed to whom, and what the Dispatcher and the witnesses can see. Nothing here needs to be secret for the protection to hold.

Describes the current version. This description is free to read, quote and implement. It covers the protocol; the apps themselves are not open source. Found a flaw? Tell us.

Keys and the log

Every device makes its own keys, a separate key for each purpose and a fresh set for every account it joins. Every signing key below has two halves, and a signature counts only when both verify. A public key that has ever been in an account's log is refused if it appears again.

KeyWhat it doesWhere it lives
Log signing keySigns log entries, proposals, confirmations, cancels and root requests, each under its own labelEvery device; made there, the private key never leaves it
Channel keyProves the device in TLS (as a raw public key) and signs the connection's binding and the login to the DispatcherEvery device
Receipt keyReceives keys sealed to it: generation keys for consoles, settings document keys for machinesEvery device
Record keySigns what a machine records: readings, status, its audit records, recordingsEvery machine
Executor keySigns the root executor's results and audit recordsThe machine's root executor only; the agent never holds it
Generation key256 random bits per place (the whole account, a site, a group) and generation; derives the readers keys and the settings keyOnly on the consoles that reach the place, sealed to each in the log; never on a machine, never in clear on disk
Readers keysEight key pairs per generation, one for each 24-hour period: machines seal to the public halfPublic halves in the log; private halves derived on use by the place's consoles
Recovery keyCan only ask for a recoveryOn paper, with one person. Nothing is ever sealed to it
Install-token keySigns one scripted machine joinDerived from the token on the machine; public half in the log; used once
Witness keysCosign checkpoints and sign answersThe two witnesses; their public keys are built into the apps

The account log. One log per account holds every join, removal, role, reach, place, person, generation, settings version, rotation, proposal and confirmation, install token and recovery request. Every structure that is signed or hashed is written in one canonical encoding (a strict subset of deterministic CBOR, integers only), and every signature carries a label of the form moxivue/<purpose>/v1, one per purpose, so a signature made for one purpose can never be used for another. Each entry names the cosigned checkpoint it was made on. The entries form an RFC 6962 Merkle tree, the one whose root the witnesses cosign. Every device holds the whole log and verifies it from the first entry, with the same rules for every device and for the Dispatcher's early refusal of rule breaks, which no device relies on.

The account fingerprint is the SHA-256 of the first entry, 64 hexadecimal digits. A scripted machine join pins it in full on the command line.

Algorithm tags. Every key, signature, sealed item and checkpoint starts with a tag naming its algorithm, and every entry a format version; a device refuses a tag it does not expect. A change to new algorithms is a signed, confirmed switch-over in the log, with no way back to the old ones. Every algorithm is a classical/post-quantum hybrid — X25519MLKEM768 and X-Wing (X25519 with ML-KEM-768) for key agreement, Ed25519 with ML-DSA-65 for signatures, X-Wing (HPKE) for sealing, AES-256-GCM for contents — so a future quantum computer breaks none of it. Post-quantum, in detail.

1. A device connects to the Dispatcher

Every device keeps one connection to the Dispatcher: TLS 1.3 and nothing older. It proves which device it is with its own channel key, bound to that one connection. No password and no account key is involved.

Your consoleDispatcherTLS 1.3 handshakethe Dispatcher's ordinary web certificate (the outer layer only)a fresh noncechosen for this connection, accepted onceloginaccount, device number, and a signature with its channel key undermoxivue/dispatcher-login/v1 over {account, device, nonce,the Dispatcher's name, the TLS exporter value of this connection}checks the signature against the key its copy of theaccount log names for that device; refuses a device thatis waiting or removed

Because the login names the Dispatcher and carries the exporter value of this one connection, a party in the middle cannot pass it on to another connection, another Dispatcher or another account. The login only lets the Dispatcher relay and store for the device: every device still checks everything against the log itself, so a Dispatcher that skipped this check would gain nothing over the network it already is.

2. The witnesses: how a device knows its view is current

A signed log proves who made each entry, but not that you are shown the newest log. Two witnesses answer that. We run both, on servers of their own; they hold no key to your data.

Your consoleDispatcherWitnesses 1 and 2Your machinea new entry, signedappends it in order; makes a checkpoint:the size and the RFC 6962 tree root of the logthe checkpoint and the new entriesto witness 1 first, then witness 1's cosignature to witness 2each cosigns (C2SP checkpoint format, with its time)only if the new log extends the last one it cosignedfor that account; keeps its state on disk firsta nonce, to each witness directlyat least once an hour, and before serving or running anythingsigned answerthe newest checkpoint, the witness's time, pending recoveryrequests and cancel receipts for the accountfresh only if both witnesses' answers are under an hour oldon a clock that counts sleep, and their checkpoint equals itsown log; a disagreement raises the tamper alarm

A device seals data, serves a session, runs a root action or completes a recovery only at a checkpoint that both witnesses cosigned within the last hour and that equals its own log. A device's wall clock is used only to refuse times more than 5 minutes ahead: the Dispatcher is the network and could set it. An entry counts as done only once it is in a cosigned checkpoint. One witness that is down or hostile can stall an account; it cannot make a device accept a false view on its own.

3. A console joins with a code and three words

A new console proves it is talking to the inviting console, not to the Dispatcher, with a password-authenticated key exchange (CPace) over an 8-digit code. Three words from that exchange are compared by people.

Inviting consoleDispatcherNew consoleConfirming consoleshows an 8-digit code, valid 15 minutes, one run;posts the offer: account, its own key, role, reach,person; commits to its first messagethe 8 digits: you pass them on yourselfsigned in to the Dispatcher; finds its invitation byits sign-in, never by the codeCPace messages (relayed)the Dispatcher sees them; without the code they give it nothingCPace messages (relayed)both sides confirm the key; the new console sends itspublic keys bound to the exchange's transcript andshows three words derived from itthe person types the three words here; a wrong wordends the run (one try); a match: the console signs aproposal: the new console's keys, role, reach, personproposal, signedwaiting for confirmationrecomputes the three words from the transcript in theproposal; a person compares them with the newconsole's screen, in person or by voice; confirmsconfirmation, signedthe join is done once both witnesses cosign it

The Dispatcher never sees the code or the words. A wrong code or wrong words end the run with nothing joined; after three failed runs in an hour the inviting console waits before it makes a new code, doubling up to an hour. The very first console to join a new account needs no second confirmation (there is no one else yet); every later console join does. CPace itself is classical: the three words, compared by people, are what a stronger attacker would also have to beat.

4. A machine joins with a number and three words

A machine joins with root's consent on the machine, a number typed into the console and three words typed on the machine, so both ends check each other.

Your consoleDispatcherYour machinean owner or admin console makes a rendezvous for aplace (moxivue machine add)root runs sudo moxivue machine join <rendezvous>:root's consent, kept in a root-owned file; the rootexecutor makes its own key, which never leaves ita commitment to its nonce and keysits nonce, the account fingerprint and name, its key and the placethe reveal: its nonce and public keysshows an 8-digit number from the transcriptthe person types that number; a match: the consoleshows three run words and signs the machine's joinmachine join, signedone owner or admin approval; a notice to every owner and admin consolethe person types the three words on the machine; itchecks them before it starts

Because the machine commits before the console reveals, a Dispatcher playing either side cannot pick values that make the number match. For scripted installs an owner or admin issues install tokens instead: 256 random bits each, for machines only, used once, valid 1 to 24 hours, refused while the account is in set-up. The machine joins with sudo moxivue machine join --token --fingerprint <account fingerprint>, reads the token from the terminal and never sends it: the token derives a one-time signing key whose public half is in the log's token entry, and the full account fingerprint on the command line pins the account. A wrong fingerprint ends the join with exit status 4.

5. A connection between two of your devices

A terminal, a forward, an SSH session or a request to a machine runs in a connection between the two devices: directly where the network allows, otherwise carried through the Dispatcher. Either way it passes bytes it cannot read.

Your consoleDispatcherYour machineTLS 1.3 handshake, direct or carried by the Dispatcherno certificate authority: each side presents its channel keyas a raw public key; each checks the other's against the logif one side's log is older it catches up to the newercosigned head first; a fork or a withheld head ends itchannel-bind signaturewith its channel key under moxivue/channel-bind/v1 over the TLSexporter for (account, both device numbers, the newer head,the place's generation)channel-bind signaturethe same from the machine's sidenothing else is sent by either side before it hasverified the other's signaturea request with a fresh action ide.g. open a terminalre-checks the peer's role, reach and state at its ownfresh head before each request, and every 30 secondsduring a session

No 0-RTT data and no resumption across a change of the log. A peer whose key is not in the log, was removed, frozen or rotated away, or that presents another device's key, is refused; so is a TLS 1.2 hello or a connection that sends data before the binding. State-changing actions carry a durable action id, so a replay is refused also after a restart. A session ends at the latest an hour after the peer's removal, demotion or move is cosigned.

Direct links. A direct link is QUIC between the two devices, found through the Dispatcher's address reflector, with the same TLS 1.3 handshake, raw keys and binding; the Dispatcher only tells each side its public address. A link that hears nothing for 15 seconds ends, and its sessions go on through the Dispatcher.

What the machine allows. A terminal opens only where the account switched terminals on for the machine's place (off as installed; switching on is an owner's or admin's sensitive change, confirmed by a second console), for a console on the machine's list of allowed consoles, and, where the machine asks each time, after the person at it says yes within a minute. A session ends after 15 minutes without typing or 8 hours in all, and the machine shows a banner with End while it is open. Forwards follow the same rules under their own switch: to a port of the machine itself, or to a device on a network the machine is directly connected to by cable or Wi-Fi, never through another router or into a VPN or container network; the machine resolves a name once and connects only to that address. SSH to a device behind a machine is a forward to its port 22.

6. Readings, status and settings: sealed per place

What a machine records leaves it sealed to the consoles that may read its place. The Dispatcher stores and forwards it by a header it can read, around a body it cannot.

Your machineDispatcherYour consolean owner or admin console made the place's generation:a random key G, sealed to each recipient console's receiptkey, with a commitment to G in the log entrytakes the current readers public key of its place fromthe log (a new one every 24 hours, 8 per generation)recordheader in clear (account, machine, place, generation, period,sequence); body sealed with HPKE to the readers key;signed with the machine's record keythe stored recordto consoles of that placechecks the signature first; derives the readers privatekey from G; opens it; takes only a higher sequence andlists the ones missing

Status is sealed and signed the same way, every 2 minutes, and names the machine's newest record sequence, so records the Dispatcher holds back show as missing, not as false. A removal, demotion, move or freeze starts new generations whose recipients are exactly the remaining consoles; machines stop sealing to the old one within an hour of the change being cosigned. A console reaching one site is never a recipient of another site's generations. Machines hold no generation key at all: they only seal. After the account's history period (400 days, or less if an owner shortens it) the Dispatcher deletes the sealed records of that time and every device erases the generation and readers keys they were sealed under, so nobody can open them afterwards. A settings document is encrypted under a document key, which is encrypted for the place's consoles under a key derived from G and sealed to each machine's receipt key; its version and hash are fixed by a signed settings entry, so a changed, older or swapped document is refused. If the witnesses cannot be reached for more than an hour, machines stop sealing and keep records in a buffer of up to 128 MiB per account.

7. A recorded session

A machine records every terminal session it hosts, and every SSH session with a key from the account, unless an owner switched recording off for its place. Only consoles that may play the recording can open it.

Your machineDispatcherYour consolemakes a random key for this one session; seals it to thereceipt key of every active owner, admin and auditorconsole covering the machine at its fresh head;signs that key set with its record keykey set, then chunkseach chunk: ChaCha20-Poly1305 under the session key, with aheader (account, machine, session, sequence, last) as aad,signed with the machine's record keythe key set and the chunksto a console that may play itchecks each signature, takes chunks in order from 0,nothing after the last: no splicing or reordering

The recording holds what the terminal showed, with its timing (asciicast v2); what was typed is never recorded. The Dispatcher keeps recordings 90 days, or 30 to 400 days as an owner sets, up to 2 GB per account; while it cannot take them, they wait sealed on the machine for up to 30 days or 1 GB. A reader added later gets an older recording only through an owner, who re-seals that session's key to it and signs the copy under moxivue/recording-reseal/v1. The session starts with a line saying it is recorded, and a session whose recording cannot be started does not start.

8. Root actions and sudo: the root executor

The program that talks to the network never runs anything as root. A separate small root program, the root executor, acts only on a signed request it checks itself.

Your consoleYour machineRoot executorsigns a root request with its log signing key undermoxivue/root-request/v1: account, machine, kind, a hash ofthe arguments, a random action id, a cosigned basecheckpoint, an expiry at most an hour after itthe signed request, over the connectionthe request, as untrusted transportover a local socket; the agent cannot sign one itselfchecks against its OWN copy of the log, verified from thefirst entry, at a head both witnesses cosigned within thehour: the requester's role and reach, the kind switched onby root on this machine, the action id never seen beforeruns it; signs the result with its own key, whichnothing else on the machine holdssigned result and audit record

The kinds are fixed, and a request can name nothing else: starting a session's shell (operators and up), one sudo command in a session, and a packet capture (owners and admins covering the machine); an update of MoxiVue on the machine and a restart are defined the same way (owners and admins; for an update the machine upgrades its own MoxiVue package from the package source already on it, which checks the signature). Root on the machine decides which kinds are on (sudo moxivue machine kinds): after a join, updates, sessions and their sudo are on, restart and capture off. In a session's shell, every sudo asks the executor, which builds the request from what it knows itself (the session, a fresh nonce, the hash of the command) and waits 30 seconds for the console that opened the session to sign it: owners and admins only, the console unlocked with its computer's password. sudo outside a MoxiVue session is refused. Without a fresh witnessed head the executor runs nothing.

9. Recovery with the paper

The recovery paper is a signing key of its own, kept on paper by one person. It holds no key to data: all it can do is ask for a recovery, which every owner and admin sees for a week.

Your consoleDispatcherWitnesses 1 and 2Your machinea new console, signed in: the paper typed in;names devices of the holder's own person onlyrecovery request, signed by the paper's key and the new console's keyinto the logthe same request, lodged with each witnesscount 7 days on their own clocks; list cancelsevery owner and admin console shows the notice withCancel, from the log and from the witnesses' answersafter 7 days: completion, with both witnesses' cancel lists

A cancel from a whole-account device that the request does not name ends it; a cancel from a named device, or one with a narrower reach, extends it by 14 days; two named devices of the same person end it. An ended request suspends the paper until it is replaced. On completion the new console becomes an owner, the named devices are removed, every place gets a new generation, the new console receives only keys made after it, and a new paper is shown once on a separate new console. The Dispatcher can withhold a cancel from the log; the witnesses' cancel lists still count.

What the Dispatcher sees

The account log is signed. Its structure is in clear, so that the Dispatcher can order it and the witnesses can cosign it; the names in it are sealed. Everything else a device stores there is sealed.

Readable to the DispatcherNot readable to it
Sign-in email addresses and linked sign-in ids; the email address of a person invited to an accountReadings, history, processes, networks, events
The account log's structure: device, place and person numbers, roles and reach, device kinds (console or machine), public keys, which settings are switched on (terminals, forwards, recording, SSH, sudo, per-person accounts), SSH key grants, recovery requestsThe names of the account, its sites and groups, and people (sealed in the log); the content of settings documents
Record, status and recording headers: which machine, which place, when, how large, which consoles may open a recordingWhat a record, a status or a recording holds; machines' host names (they travel only sealed)
Which device is connected, from which IP address, who talks to whom, when and how muchWhat is asked and answered; what is typed, shown or carried in a terminal, forward or SSH session
The messages of a code or machine join and the rendezvous textThe code, the three words and the number

Devices have no names in the log at all; the names of the account, its sites and groups, and people are sealed there for the account's consoles, so the Dispatcher and the witnesses see only their numbers.

Who can do what

Someone who controlsCanCannot
The Dispatcher, or a copy of its databaseSee everything in the left column above; refuse service; drop, delay, reorder or replay messages; hold back log entries or records, which then show as stale or missingRead sealed data; forge or change an entry; add a device; make a machine act; make a stale checkpoint fresh; hide a removal for more than an hour past its cosignature
One witnessStall the accounts it servesMake a device accept a false view: freshness needs both witnesses
The network between a device and the DispatcherSee that MoxiVue is in use, and how muchAnything more: the connection is TLS 1.3, and the content is protected again inside it
Your sign-inStart a join as you, or create accounts in your nameJoin a console without the code and the three words, confirmed by a second owner or admin console
One console of your accountWhat its role and reach allow; its sensitive changes wait for another owner or admin consoleAct beyond its role; confirm its own change; stay after removal: it receives no key of a later generation
The machine agent (the program on the network)Carry requests to the root executorMake the root executor act: it checks every request itself and signs its own results
Your recovery paperAsk for a recovery of its holder's own devicesDo so unseen or at once: every owner and admin console shows the notice for 7 days with Cancel

Known limits

  • Malware on a console running with the user's rights can read that console's keys and act as it, within its role and reach, until it is removed. The console lock (the computer's password for sensitive actions and sessions, 15 minutes at a time, locked with the screen) and the rule that an owner's or admin's computer hosts no sessions for others narrow this; they do not remove it.
  • Metadata. The Dispatcher sees the account log and who talks to whom, when and how much, as listed above. Every member console sees the whole account's log too, because it verifies it.
  • One hour. A machine may still serve a removed console, or seal to a replaced generation, for at most an hour after the removal is cosigned.
  • Stalling. A Dispatcher or one witness can stop an account working; past an hour without fresh checkpoints, machines end sessions and buffer what they record.
  • Recovery is slow on purpose: at least 7 days, 21 when a named device objects.
  • A person can be talked into it. The words prove the joining console's key, not that the join is wanted. Compare them in person, or by voice with someone you know.
  • CPace is classical. The code exchange rests on the discrete-logarithm problem; the three words compared by people are the backstop.
  • Removal needs a confirmer. Out of set-up an account always has three whole-account owner or admin consoles, two of them owners, so any one can be removed by the others. When two of them fail at once, or before the third has joined, a stolen console may only be removable by its own person's recovery, or not at all, until the machines are moved to a new account.
  • A removed machine's record key could, with the Dispatcher's help, add records that claim periods from before its removal; consoles mark such a record where they can see that it first appeared afterwards.
  • The apps are not open source, so that the program you run does what this page says rests on our tests and on what you can observe from outside (below).

Checking it yourself

An exported audit trail can be checked with verify-audit5.py, a script in plain Python that is not ours to trust: it applies the same log rules as the apps, from the first entry. moxivue log verify does the same check with the app, and moxivue witness status shows both witnesses' last answers and whether your view is fresh. The audit export page describes the files.