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
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ fun b64(
.getOrNull()
?.takeIf { it.size == size && SyncBytes.b64urlEncode(it) == value }

/** A `meta` value, which is opaque past this: trailing bits and length stay unchecked, so it isn't decoded here (spec/protocol.md, Roster). */
private val META_VALUE = Regex("[A-Za-z0-9_-]+")

/** The largest integer every implementation reads exactly. */
private const val MAX_SEQ = (1L shl 53) - 1

Expand Down Expand Up @@ -194,7 +197,11 @@ private fun wellFormed(entry: Entry): Boolean {
} ?: true
) &&
(entry.epoch?.let { it.n >= 0 && b64(it.kc, 32) != null && dids(it.to) } ?: true) &&
(entry.meta?.all { (did, sealed) -> isHex(did, 16) && sealed.isNotEmpty() } ?: true) &&
(
entry.meta?.let { meta ->
meta.isNotEmpty() && meta.all { (did, sealed) -> isHex(did, 16) && META_VALUE.matches(sealed) }
} ?: true
) &&
entry.sigs.all { (did, signature) -> isHex(did, 16) && b64(signature, 64) != null }
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/test/resources/sync2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ All byte strings are lowercase hex, except inside roster entries, which use the
| `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 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 |
| `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; `meta` that is empty, or holds a value outside `[A-Za-z0-9_-]`; `replacements`, entries on top of the chain: where the device that set the recovery key is removed or has left, and `meta` values that are valid though not strict base64 |
| `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 |
| `pairing.json` | Pairing keys, SAS, mailbox slot, and a sealed `join` message |
| `lan.json` | Discovery tag, hello AAD, transcripts, session keys, frame AAD |
Expand Down
295 changes: 295 additions & 0 deletions app/src/test/resources/sync2/roster.json
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,127 @@
},
"error": "roster-signer"
},
{
"name": "meta-not-base64url",
"base": 1,
"entry": {
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 1,
"prev": "c2d714b286d2cc8b4f11911a6024e65e916302ec7a0c1ecbef9db9bdcac76143",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": "not base64url!"
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "Oy_ES7SHj8WtQi7X9DBtHhlHwlcDXH4Zl3TeYHF7W8LEvyeM1LgM4hqWojmpPLRwDreup7E50LeuHelIc3mBAg"
}
},
"error": "roster-format"
},
{
"name": "meta-padded",
"base": 1,
"entry": {
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 1,
"prev": "c2d714b286d2cc8b4f11911a6024e65e916302ec7a0c1ecbef9db9bdcac76143",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": "AAAA="
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "A0yJXEP4W-s1DKyRE-w3twbCDU07lQrjX6hnWQQF0Ux0Vak_E1KoogScOuYrbANxlGcd4NaXwcELltT17M6zDg"
}
},
"error": "roster-format"
},
{
"name": "meta-non-ascii",
"base": 1,
"entry": {
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 1,
"prev": "c2d714b286d2cc8b4f11911a6024e65e916302ec7a0c1ecbef9db9bdcac76143",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": "€"
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "FgMXNPkt1kLdC1kzQtr9WRQ4HsDNikKp5hi89bd2lRAaSWw4FxyOjc66iPjrdLU7bf2bwFCjHNi3hwqnQD76Dw"
}
},
"error": "roster-format"
},
{
"name": "meta-trailing-newline",
"base": 1,
"entry": {
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 1,
"prev": "c2d714b286d2cc8b4f11911a6024e65e916302ec7a0c1ecbef9db9bdcac76143",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": "AAAA\n"
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "FjJSzA0T1ikYvmRHTzh8oVInlW6ARylrSvt5aKgKf0SC5uN4bpTQw6DDCP_JLBX2JduxKm6XITaReSx5WKV0Dw"
}
},
"error": "roster-format"
},
{
"name": "meta-empty-value",
"base": 1,
"entry": {
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 1,
"prev": "c2d714b286d2cc8b4f11911a6024e65e916302ec7a0c1ecbef9db9bdcac76143",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": ""
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "YEhIf6kYQQNiLfthHfmlrXoox0wBRm8WfBK62GuYcwYPOVH4zmQ9YpImzcLqhmIPrcykF1fVx465WCnRWgCkAg"
}
},
"error": "roster-format"
},
{
"name": "empty-meta",
"base": 1,
"entry": {
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 1,
"prev": "c2d714b286d2cc8b4f11911a6024e65e916302ec7a0c1ecbef9db9bdcac76143",
"op": "add",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"add": [
{
"did": "addf195306ed8f3c4d02642d8b6ebd6a",
"sig": "Pv9XrxPpjXsHr8QrMZKgC1ZO9Q2cOgBPCr7P22C3MWY",
"kem": "bmiQy52XcNEUqT_nj29df8adxbK84jlUCWdacit50FA",
"role": "device"
}
],
"meta": {},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "22GvNL1vzwVik9qHks7bM5-o6RTNCUrmI6MS3eBQ7vMiHsa4mwPSmbBSiNJnXb03bgrjWE4WZ0IKFEidW6tdAg",
"addf195306ed8f3c4d02642d8b6ebd6a": "TqDWI5AItI_yB_WFdVk_J01UThgsJb1iRXANB_HF4rlhcPgTazrXsrZC9Nqq5CyyBHftw2FOVI9gFVpwzbwsBw"
}
},
"error": "roster-format"
},
{
"name": "leave-by-non-member",
"base": 7,
Expand Down Expand Up @@ -1711,6 +1832,180 @@
}
],
"replacements": [
{
"name": "meta-with-trailing-bits",
"steps": [
{
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 9,
"prev": "18eb362b0fb928a0f35b684a8cbfae6f0073cfcd3ba0c5c6dbd7af74343f3224",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": "AB"
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "xGqhs0OS1uBZ2EhSpa5oBLl_RG_r6LmK6VyHg4TnFfknqGgHzaL9hhwQ2YBj_LkYIiuM99Zo4PK4eoAHHXFsAQ"
}
}
],
"state": {
"seq": 9,
"head": "4c0ef6ca2e835eafc55b3a446a7e49f61b9506eb2125557ad3c0a4456ea4e1c2",
"epoch": 4,
"rotate": false,
"recoveryDue": false,
"members": [
{
"did": "6e1946f855d4d7de61c0d3e3e4491cae",
"role": "recovery",
"used": false,
"installer": "addf195306ed8f3c4d02642d8b6ebd6a"
},
{
"did": "addf195306ed8f3c4d02642d8b6ebd6a",
"role": "device",
"used": false
},
{
"did": "d8576f2de42861ceb8030cc722b4c669",
"role": "device",
"used": false
}
],
"senders": [
{
"did": "1edce5f29fbda5bba7ada194b0a578c6",
"role": "device",
"since": 3,
"until": 3,
"cut": {
"seq": 1,
"hash": "301bf18e9cce13b93870928117aecd06ff793354402b03810bff6476e123865b"
}
},
{
"did": "6e1946f855d4d7de61c0d3e3e4491cae",
"role": "recovery",
"since": 3
},
{
"did": "894e311b2b2058e3bcc2ef3bde8ec864",
"role": "device",
"since": 0,
"until": 0,
"cut": {
"seq": 2,
"hash": "4d4573758bd47b198e4305475ff362af7b9806c157caff61d657837e94134ffe"
}
},
{
"did": "ab6095990ebaaf36d4c49e86fc3a65c3",
"role": "recovery",
"since": 0,
"until": 2
},
{
"did": "addf195306ed8f3c4d02642d8b6ebd6a",
"role": "device",
"since": 2
},
{
"did": "d8576f2de42861ceb8030cc722b4c669",
"role": "device",
"since": 0
}
]
}
},
{
"name": "meta-of-an-undecodable-length",
"steps": [
{
"v": 2,
"group": "669a44456c08e4a12e4ceb3538860317",
"seq": 9,
"prev": "18eb362b0fb928a0f35b684a8cbfae6f0073cfcd3ba0c5c6dbd7af74343f3224",
"op": "rename",
"signer": "d8576f2de42861ceb8030cc722b4c669",
"meta": {
"d8576f2de42861ceb8030cc722b4c669": "A"
},
"sigs": {
"d8576f2de42861ceb8030cc722b4c669": "NxJy_7T86gFZOHmdKkciYUdz-3lQ6GRdD1dJfaYFwGecpOjUW3WuOHDt0bAT9y-akYYvwvdfThEppJ5KFlNpAw"
}
}
],
"state": {
"seq": 9,
"head": "7993b447f137be0f54e3ec108d0c21beb5687584e9ec626993c2834906048e35",
"epoch": 4,
"rotate": false,
"recoveryDue": false,
"members": [
{
"did": "6e1946f855d4d7de61c0d3e3e4491cae",
"role": "recovery",
"used": false,
"installer": "addf195306ed8f3c4d02642d8b6ebd6a"
},
{
"did": "addf195306ed8f3c4d02642d8b6ebd6a",
"role": "device",
"used": false
},
{
"did": "d8576f2de42861ceb8030cc722b4c669",
"role": "device",
"used": false
}
],
"senders": [
{
"did": "1edce5f29fbda5bba7ada194b0a578c6",
"role": "device",
"since": 3,
"until": 3,
"cut": {
"seq": 1,
"hash": "301bf18e9cce13b93870928117aecd06ff793354402b03810bff6476e123865b"
}
},
{
"did": "6e1946f855d4d7de61c0d3e3e4491cae",
"role": "recovery",
"since": 3
},
{
"did": "894e311b2b2058e3bcc2ef3bde8ec864",
"role": "device",
"since": 0,
"until": 0,
"cut": {
"seq": 2,
"hash": "4d4573758bd47b198e4305475ff362af7b9806c157caff61d657837e94134ffe"
}
},
{
"did": "ab6095990ebaaf36d4c49e86fc3a65c3",
"role": "recovery",
"since": 0,
"until": 2
},
{
"did": "addf195306ed8f3c4d02642d8b6ebd6a",
"role": "device",
"since": 2
},
{
"did": "d8576f2de42861ceb8030cc722b4c669",
"role": "device",
"since": 0
}
]
}
},
{
"name": "removing-the-device-that-set-the-key-replaces-it",
"steps": [
Expand Down
Loading