feat(app): a shared space's content taken from its members' signatures
ADR 0044, D20: a row in a shared space counts as genuine when any member this device pinned signed it, current or former, never by the author column the row names. - `ContentCipher.trustSigners`: content sealed with a key is checked against the signing keys given for it, in place of the account's alone; `FakeContentCipher` tells authors apart likewise. - `KeyPins` keeps who it saw in each space (`sawIn`) and the signing keys pinned for someone before a reset (`earlierSigning`), so what they wrote stays readable; `signersIn` gives a space's signers. `key_pins.json` holds both (`pins`, `spaces`); a file written by step 3, on no released app, counts as none. - `SharedKeys` pins the person's own keys too, records the members it checks, and trusts the space's signers for each key it shares or opens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
92b87e218d
commit
5faf3fce58
1 changed files with 336 additions and 68 deletions
|
|
@ -125,4 +125,63 @@ void main() {
|
|||
throwsStateError,
|
||||
);
|
||||
});
|
||||
|
||||
test("a shared key's content opens when a member it trusts signed it, "
|
||||
"and only for that key (Halkyon's ADR 0044, D20)", () async {
|
||||
final carol = (await crypto.newAccount('carol password')).keys;
|
||||
PublicKeys keysOf(UnlockedAccountKeys account) => (
|
||||
encryption: account.encryptionPublicKey,
|
||||
signing: account.signingPublicKey,
|
||||
);
|
||||
Future<SodiumContentCipher> deviceOf(UnlockedAccountKeys account) async {
|
||||
final cipher = SodiumContentCipher();
|
||||
await cipher.unlock(account);
|
||||
addTearDown(cipher.dispose);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
final bobs = await deviceOf(bob);
|
||||
final carols = await deviceOf(carol);
|
||||
for (final keyId in ['shared', 'other']) {
|
||||
device.newKey(keyId);
|
||||
for (final (cipher, keys) in [(bobs, bob), (carols, carol)]) {
|
||||
expect(
|
||||
cipher.openSharedKey(
|
||||
keyId,
|
||||
device.sealKeyTo(keyId, keysOf(keys)),
|
||||
sealedBy: alice.signingPublicKey,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
}
|
||||
}
|
||||
final byBob = bobs.seal(plain, keyId: 'shared', context: context);
|
||||
final byCarol = carols.seal(plain, keyId: 'shared', context: context);
|
||||
final byAlice = device.seal(plain, keyId: 'shared', context: context);
|
||||
|
||||
// Only the account's own, until told whom to trust.
|
||||
expect(device.unseal(byBob, context: context), isNull);
|
||||
expect(device.unseal(byAlice, context: context), plain);
|
||||
|
||||
device.trustSigners('shared', [
|
||||
alice.signingPublicKey,
|
||||
bob.signingPublicKey,
|
||||
]);
|
||||
expect(device.unseal(byBob, context: context), plain);
|
||||
expect(device.unseal(byAlice, context: context), plain);
|
||||
expect(device.unseal(byCarol, context: context), isNull);
|
||||
expect(
|
||||
device.unseal(
|
||||
bobs.seal(plain, keyId: 'other', context: context),
|
||||
context: context,
|
||||
),
|
||||
isNull,
|
||||
);
|
||||
|
||||
// What it trusts is replaced, not added to.
|
||||
device.trustSigners('shared', [carol.signingPublicKey]);
|
||||
expect(device.unseal(byCarol, context: context), plain);
|
||||
expect(device.unseal(byBob, context: context), isNull);
|
||||
expect(device.unseal(byAlice, context: context), isNull);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue