Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/v0.1.0-beta4.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ SYNC
- A damaged or forged object from one device no longer fails the whole sync. Reading that device's changes pauses there, and the other devices' changes still arrive.
- The same holds when a new device starts from a snapshot or a profile starts syncing: a damaged or forged object no longer fails the sync, and the profile is caught up once it can be read.
- A snapshot or profile name from another device that carries an out-of-range number no longer stops a synced profile from being read.
- Linked devices accept a synced object's data only in the one form every Flow app writes, so two devices can't read different data from the same object.
- A malformed roster entry from another device on the local network is refused, so the group stays readable on this computer.
- Joining a group over the local network now refuses a malformed welcome from the other device.
- Linked devices: Replace recovery words gives your group a new recovery key from any linked device. The old words stop working, and your devices move to a new encryption key.
Expand Down
16 changes: 13 additions & 3 deletions src-tauri/src/sync2/payload.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::io::Read;

use flate2::Compression;
use flate2::read::GzDecoder;
use flate2::bufread::GzDecoder;
use flate2::read::GzEncoder;

use super::{Error, Result};

const MIN_SIZE: usize = 256;
const MAX_INFLATED: u64 = 64 * 1024 * 1024;
/// `ID1`, `ID2`, `CM` 8 and `FLG` 0 (spec/crypto.md, Payload).
const GZIP_START: [u8; 4] = [0x1f, 0x8b, 0x08, 0x00];

/// PADMÉ with a 256-byte floor.
pub fn padded_size(n: usize) -> usize {
Expand Down Expand Up @@ -38,12 +40,20 @@ pub fn decode_payload(payload: &[u8]) -> Result<Vec<u8>> {
if length > rest.len() || rest[length..].iter().any(|&b| b != 0) {
return Err(Error::Payload);
}
let body = &rest[..length];
if !body.starts_with(&GZIP_START) {
return Err(Error::Payload);
}
// The buffered reader of `read::GzDecoder` would hide what is left after the member.
let mut decoder = GzDecoder::new(body);
let mut content = Vec::new();
GzDecoder::new(&rest[..length])
decoder
.by_ref()
.take(MAX_INFLATED + 1)
.read_to_end(&mut content)
.map_err(|_| Error::Payload)?;
if content.len() as u64 > MAX_INFLATED {
// A read that stopped at the limit left the trailer, so this refuses that too.
if !decoder.into_inner().is_empty() {
return Err(Error::Payload);
}
Ok(content)
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/tests/fixtures/flow-sync-2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ All byte strings are lowercase hex, except inside roster entries, which use the
| `derive.json` | `Derive` and `Hash` ([crypto.md](../crypto.md#labelled-derivation)) |
| `ed25519.json` | Strict verification: valid, tampered, wrong key, non-canonical `S`, small-order key; the eight small-order encodings; a non-canonical key encoding |
| `box.json` | Box seal and open with fixed salts; tampered ciphertext, tag, commitment and salt, wrong key, wrong AAD, truncation |
| `payload.json` | PADMÉ sizes; payload decoding; bad length, non-zero padding, corrupt gzip |
| `payload.json` | PADMÉ sizes; payload decoding, including another encoder's header bytes and a stored block; bad length, non-zero padding, corrupt gzip, more than one member, a bad or short trailer, any header flag, an empty body, bytes around the member |
| `keys.json` | Member ids, epoch commitment, purpose keys, HPKE wrap with a fixed ephemeral key, wrong-epoch and wrong-member unwraps, history chain, wrap and chain paths |
| `recovery.json` | Recovery secret, 24 words and derived keys; words with a bad checksum, 23 words, and a valid 12-word phrase |
| `envelope.json` | Log objects of each type, including a store login, opened in stream order, a live message, a snapshot blob; truncation, magic, suite, group, unknown sender, signature, tampering, replay, gap, a log read from the middle, wrong `prev`, swapped epoch, `seq` above 2<sup>53</sup> − 1, blob hash |
| `roster.json` | A chain through genesis, add, rename, remove, restore, recovery change, add, leave and rotate, with the state after each entry (including whether a rotate is due, and each member's epochs and `cut`) and the device names it carries; bad and missing signatures, unknown fields, wrong `seq`, truncated chain, removed signer, weak key, re-adding a removed device, wrong epoch sets and numbers, self-removal, renaming the recovery key, a non-member signer, a used recovery key; a leave by a non-member, by the recovery member and by the last device, a leave removing another device or carrying an epoch, a rotate without an epoch, with a removal, or wrapped to a device that left; six forks and their winners, including leave against remove and rotate, and concurrent rotates; a genesis whose group id isn't the one its keys commit to; a `cut` on the wrong op, for a device not removed, empty, or at `seq` 0; `replacements`, entries on top of the chain where the device that set the recovery key is removed or has left |
| `forks.json` | Named entries, and cases that settle a store's view of them: `applied` is what the device had applied, `view` is the store's answer, `pins` the chosen entries, `linear` a Flow server. Each case gives the outcome and, in any order of `view`, the same chain, standing entries that lost and start an epoch, contested set and sides |
| `senders.json` | Step 2 of [reading an envelope](../store.md#reading-an-envelope): a roster, then `senders` with who may write under which epoch and how far along its log, then `walks` of a log down from a `cut`, with the hashes found from the lowest `seq` up or the check that refuses it. An empty string in `log` is an object the store doesn't have |
| `pairing.json` | Pairing keys, SAS, mailbox slot, and a sealed `join` message |
| `pairing.json` | Commitment, proof, the scanner's first message, pairing keys, SAS, mailbox slot, and a sealed `join` message; `refused` holds a shower key that doesn't match the commitment and a proof made with another token |
| `lan.json` | Discovery tag, hello AAD, transcripts, session keys, frame AAD |

## Using them
Expand Down
Loading
Loading