diff --git a/designs/lightweight-design.md b/designs/lightweight-design.md index a729e14161b354cc9778c589c66de540519b1bae..841b73dab6626ae7c5cb29bc3106e81d83f0bb12 100644 --- a/designs/lightweight-design.md +++ b/designs/lightweight-design.md @@ -39,8 +39,6 @@ We have an existing contractual obligation with DRL to deliver a "working Tahoe- Rootcap: a Tahoe-LAFS directory capability typically managed by Gridsync/PrivateStorageDeskop that contains links to any/all other capabilities that the user generally cares about: by traversing all of the links beneath a rootcap, one can traverse (i.e., read the contents of) all of that given user's magic-folders. -Devicecap: a Tahoe-LAFS directory capability created by Gridsync/PrivateStorageDesktap that contains links to any/all (magic-folder) capabilities that a separate target/mobile device is intended to access -- in other words, a rootcap created by one (trusted) device on behalf of another. In the present design, the (RW) desktop device creates a "devicecap" for the (RO) mobile device (since the RO mobile device lacks the ability to create its own caps), and shares it with that mobile device, thereby granting it the ability to access the magic-folders linked therein. - **User stories** @@ -137,66 +135,160 @@ In sum, a large part of the appeal of "cloud" computing for mobile users lies i _Focus on external and internal interfaces, how externally-triggered system events (e.g. sudden reboot; network congestion) will affect the system, scalability and performance._ -**Device linking protocol overview** +## Device linking protocol overview + +Below is described a protocol for inviting a "read-only" device to an existing magic-folder. +"Existing magic-folder" implies that some other device (i.e. Desktop Private Storage) has created such a folder, and is the administrator of it. + +"Read-only" means that there is no support for handling or spending ZKAPs, which means no support for writing to or creating any mutables. + +We currently assume both devices already have access to the same Grid. +How they obtained such access or configuration is outside the scope of this protocol. + + +The protocol has two parts: establishing a communication channel, and actually joining folders. +We separate this explicitly because we expect that future revisions will add more messages that can re-use the existing communication channel. + +## Part One: Establish Communications + +To establish a communication channel, we use Magic Wormhole. +The exact functioning of Magic Wormhole is beyond the scope of this document. +We do _not_ use the "Transit" portion of Magic Wormhole: all communications are via the mailbox only. + +1. The Desktop device allocates a Wormhole code + - uses AppID `private.storage/wormhole/invites` + - this means the Desktop is connected to the Wormhole mailbox server, has an allocated mailbox, and has a user-friendly code ready to present to the user + +2. Transcribe the code to the mobile device + - (this could simply be typed in by the user -- simple to implement: first cut?) + - the code is encoded in a QR code using a variation of a "wormhole URI" + - "Wormhole URIs" are currently specified for file-transfer: https://github.com/magic-wormhole/magic-wormhole-protocols/blob/main/uri-scheme.md + - our scheme doesn't use file-transfer (it is a custom protocol) so we need a different scheme (likely with some overlap to the above) + - something like `ps-pair:4-hurricane-equipment` with defaults for AppID (`private.storage/magic-folder/invites`), version (`0`), and rendezvous (`mailbox.mw.leastauthority.com`) + - that is, the QR code encodes the bytes: `ps-pair:4-hurricane-equipment` + - we _could_ include query-type arguments for the other parts (similar to file-transfer) if we need + - (our application could register as the handler for `ps-pair:` URIs). + - the read-only application decodes the QR code (or is handed it via the "Intent" API if the above point is followed) + +3. Use the code to establish communications + - whether it was typed or came from a QR code, we now have the code and can complete the Wormhole protocol + - the read-only application now has access to the same mailbox as the desktop with the same shared secret + - they know this because a magic-wormhole `versions` message has been exchanged (serving as key-verification). See [Magic Wormhole documentation](https://github.com/magic-wormhole/magic-wormhole/blob/master/docs/client-protocol.md) for more details. + +4. Ensure protocol compatibility + - We use the "app-versions" feature of Magic Wormhole, which is exchanged during the `versions` message mentioned above. + - Application can include arbitrary keys here. We use this to specifiy what versions of messages are supported. + - This will look like this:: + + "magic-folder": { + "supported-messages": [ + "invite-v1", + ] + } + - This is part of the magic-wormhole protocol. For example, in the Python API, this would be passed as `versions={"magic-folder": ...}` in the `wormhole.create()` call. + - As additional message kinds are added, they should be added here too. + - In individual messages, the `"protocol": ...` key shall correspond to a `"supported-messages"` value. + - A peer MUST only send messages for protocols the other side supports. + -In absence of full support for purchasing ZKAPs on mobile and/or the ability to transfer spendable ZKAPs between devices currently (in this case, so as to enable a mobile device to directly create Tahoe-LAFS directory capability or upload/link files into them), the application described herein naturally depends on the prior existence of some set of Tahoe-LAFS capabilities for reading. In other words, this design assumes that some _other_ device in the user's possession (such as a desktop device with some amount of spendable ZKAPs available) already has read-write ("RW") access to a storage grid and that the mobile device does not (and will only have read-only/RO access). Functionality that aims to provide or expand RW abilities to mobile devices (e.g., by enabling mobile-only users to purchase some amount of ZKAPs directly from their mobile device or by transferring spendable ZKAPs between devices) is considered out-of-scope for the present design/document. +## Part Two: Join a Folder -The following describes a protocol for transferring such capabalities from a) a device/application that posseses RW-permissions to b) one that does not (i.e., the local mobile device). This protocol is described in two phases: 1) producing the invite, 2) consuming the invite. +Once communications are established, we're ready to send and receive application messages via the mailbox. +All such messages are JSON encoded. +All such messages include a `kind` field. +All such messages include a `protocol` field indicating which protocol they correspond to (see "app_versions" in "Establish Communications"). -**1. Producing the invite** +Note that although we only specifiy a single sort of message here, there will likely be more messages in the future. -We hereby assume the _producer_ of the invite to be an instance of Gridsync/PrivateStorage running on another desktop device which a) belongs to the same user (i.e., and not a potentially malicious party) and b) already has the ability to create Tahoe-LAFS capabilities (for example, because it possess the storage fURLs and ZKAPs necessary to do so). +The inviting device sends an "invite" message to the read-only device:: -From the user's perspective, this functionality of producing an invite may be presented by a simple "Link device" button within Gridsync/PrivateStorage (or equivalent) which, when clicked, performs the following actions: + { + "kind": "join-folder", + "protocol": "invite-v1", + "folder-name": "arbitrary string", + "collective-dmd": "<read-only Directory URI>", + "participant-name": "arbitrary string", + "mode": "read-only", + } -1. Create a new directory capability ("devicecap")* intended to serve as the rootcap for the target/mobile device -2. For each currently-available magic-folder (or some selection of folders intended to be accessed from the mobile device?): - 1. Create a magic-folder invite for the device - 2. Link the resultant magic-folder caps into the devicecap -3. Link the devicecap into the rootcap (optional; as a backup) -4. Present a QR code * to the user containing/encoding the configuration values necessary to download objects from the grid: - 1. The devicecap - 2. A list of storage server values containing: - 1. The server "nickname" - 2. The name of the storage server's "economic plugin" - 3. the storage service NURL +The "`folder-name`" is an arbitrary string which is what the admin device calls this folder; +it may be re-used by the read-only device or ignored (e.g. the UI may allow the read-only application to call this folder whatever they want). -For the sake of consistency and future compatibility, the directory structure of the devicecap should probably follow that of a standard Gridsync rootcap. A devicecap with two magic-folders -- "Documents" and "Cat Pics" would thus have the following structure/contents (where "$dircap" corresponds to the directory capability): +The "`collective-dmd`" is a read-only Tahoe-LAFS directory URI and represents all participants in the magic-folder. +It could serve as a unique identifier for the magic-folder. -``` - $dircap/v1/.magic-folders/Documents (collective) - $dircap/v1/.magic-folders/Documents (personal) - $dircap/v1/.magic-folders/Cat Pics (collective) - $dircap/v1/.magic-folders/Cat Pics (personal) -``` +The administrating device decides all participant names, and tells the read-only device what its name will be in "`participant-name`" (i.e. a sub-directory by this name will ultimately appear in the Collective). +This name is arbitrary (i.e. the human using the administration device types it in). -* A single 40-L QR code can encode a maximum of 4,296 alphanumeric characters while a (RO) tahoe dircap is 91 characters. This leaves a total of 4,205 characters to embed grid-connection information -- which is not very much (the current PrivateStorage production configuration JSON blob is 4819 characters, for example, while the HRO Cloud config is 4950). One way around this limitation would be to embed common/expected grid configuration into the application bundle (as is done already with Gridsync for PrivateStorage, HRO Cloud, and others) and encode only the grid name (or some other equivalent identifier) into the QR code to inform the mobile application which grid configuration to use. Another approach would be to use the same QR code to establish an entirely separate communication channel (e.g., using magic-wormhole) through which the above-described information can be transferred without such limitations. Additionally, since the devicecap is secret/key material, it is desirable to prevent attackers -- and other nearby cameras -- from reading it via the QR code in the first place (in which case the "one-time-use" property of magic-wormhole becomes increasingly attractive... In past versions of the "device linking" flow, Gridsync would spin up a local HTTP server, encode the URL to that HTTP server in the QR code, and deliver the device cap to the first request made to the HTTP server -- closing/terminating the HTTP server immediately after one request in order to prevent subsequent reads of the QR code from accessing the devicecap. This also provided a mechanism for communicating to Gridsync when the devicecap was successfully communicated to the mobile device.) +For these kinds of invites, the only `"mode"` supported is `"read-only"`. +A read-only device receiving an invite with any other `"mode"` SHOULD reply with an error. +Upon receipt of such an invite, the read-only device MUST respond with a reply message. +This message can be affirmative or not -- for example, a UI may ask the read-only device user for confirmation. +An affirmative reply is essentially empty and looks like:: + { + "protocol": "invite-v1", + "kind": "join-folder-okay", + } -**2. Consuming the invite** +A rejection reply says why we are rejecting the invite and looks like:: -We hereby assume the consumer of the invite to be a mobile device with a) a working camera that is b) in physical proximity to the producer device and c) owned/operated by the same individual user (i.e., not a malicious party). + { + "protocol": "invite-v1", + "kind": "join-folder-reject", + "reject-reason": "<arbitrary string>", + } -From the user's perspective, the functionality of consuming the invite is achieved by scanning the QR code presented by the producer of the invite (i.e., the desktop instance). The option to scan a QR code is presented to the user during the initial setup flow. +If the inviting device receives a rejection message, it MUST NOT add the participant to the Collective. +When receiving an affirmative reply, the inviting device adds a read-only, immutable, empty directory to the Collective under the given name. +This entry indicates to all devices in the Collective that a given device has read-only access. -Upon scanning the QR code, the software should perform the following actions +The inviter side sends back a final acknowledgement to the invited device. +This indicates whether the participant was added successfully or not with the `"success"` field:: -1. Validate the decoded contents of the QR code, displaying an error message if validation fails -- e.g., because: - * The decoded content is in a different format or of a different type than expected - * Some necessary configuration value is missing - * Some necessary configuration value is in a different format or of a different type than expected - * Some additional/unknown configuration value was supplied (which may indicade an out-of-date version of the software) -2. Store the necessary configuration values decoded from the QR code locally (e.g., in the app-specific storage directory) -3. Supply the necessary configuration values to the local Tahoe-LAFS client as needed -4. Use the configured Tahoe-LAFS client to fetch the remote magic-folder contents (e.g., `GET /uri/$devicecap/v1/.magic-folders/` to list magic-folder names, `GET /uri/$devicecap/v1/.magic-folders/$name (personal)` to list magic-folder content) -5. Populate the file-browser/list-view using the JSON content returned in the body of the HTTP request(s) above + { + "protocol": "invite-v1", + "kind": "join-folder-ack", + "success": True, + } + +This concludes the protocol and both sides close their mailboxes. + +**Future enhancement**: a participant could be invited to multiple folders by doing multiple rounds of the above communication. This then needs a way to associate replies to requests (e.g. unique IDs, or enforced sequential answers, or some other technique) + +**Future enhancement**: either side could perform multiple rounds of folder-invites. (Similar to above this might require IDs or other techniques to tie replies to requests). + +**Future enhancement**: further kinds of messages could exchange other kinds of information between devices (e.g. sending ZKAPs or Grid configuration) + +**Future enhancement**: the communication channel could become longer-term by using the proposed "Seeds" feature of magic-wormhole (or a future message type over the wormhole could exchange capabilities to perform such communication over the Grid). + + +## Rejected Ideas + +- **Rejected idea**: have the read-only device reply with a "pre-canned" LIT capability. +This doesn't have any excess authority or security implications, but doesn't fit as well with the notion that "the administation device decides". +Currently, that device has authority over the list of participants and what they are called because it retains the sole write-capability to the Collective. +It thus makes sense for it to also decide which devices are read-only and which can write data. +(Note that future revisions may distribute such authority further) + +- **Rejected idea**: produce a "devicecap" or "rootcap" _for_ the read-only device. This leads to excess authority on the inviting device (it could retain the write-capabilities and impersonate the read-only device forever). **Data integrity** **Security** +- Excess authority: even though the read-only device cannot perform changes to the Grid, this design avoids leaving excess authority with the inviting device. + +- QR Code interception: if a magic-wormhole code is intercepted, an adversary may race the legitimate participant in establishing the wormhole connection. If successful, the adversary will learn the read-only Collective capability and may forever read all contents of the folder. The legitimate participant will learn that they lost, because their wormhole connection will fail with an error. + +It is possible to mitigate this by using the "verifier" approach for Magic Wormhole connections. +This would mean pausing after the wormhole is established and before sending any folder invites so that the two participants can confirm a verifier string and thus be assured that they have the same mailbox and shared secret. + +- Wormhole interception: we believe it is computationally infeasible to brute-force or otherwise gain access to the shared secret necessary to perform wormhole communications (as per Magic Wormhole security arguments). An advesary would have to gain access to one of the participant devices and extract the key from there. They could give themselves more time by DoS-ing the network of the other participant, however the time-window is still small (between when the wormhole is established and the end of communications). After both sides close their wormholes and erase their secrets it is impossible to compromise the communications (even if the adversary has access to the mailbox server) without brute-forcing the key. + + ## Threat Model **Threat**: visual collection by passive observation