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
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_app/testing.dart';
|
||||
|
|
@ -84,6 +86,35 @@ void main() {
|
|||
expect(await bob.safetyCodeWith('alice'), code);
|
||||
});
|
||||
|
||||
test('content sealed with a space key opens from any member seen in the '
|
||||
'space, the person included, and from no one else (D20)', () async {
|
||||
const context = [1, 2, 3];
|
||||
final plain = Uint8List.fromList([4, 5, 6]);
|
||||
aliceCipher.newKey('k1');
|
||||
await alice.share(space, 'k1');
|
||||
await bob.openMine(space);
|
||||
// Mallory, never a member, holds the key all the same.
|
||||
final mallory = await cipherOf(malloryAccount);
|
||||
expect(
|
||||
mallory.openSharedKey(
|
||||
'k1',
|
||||
aliceCipher.sealKeyTo('k1', keysOf(malloryAccount)),
|
||||
sealedBy: aliceAccount.signingPublicKey,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
|
||||
final bobs = bobCipher.seal(plain, keyId: 'k1', context: context);
|
||||
final alices = aliceCipher.seal(plain, keyId: 'k1', context: context);
|
||||
final mallorys = mallory.seal(plain, keyId: 'k1', context: context);
|
||||
|
||||
expect(aliceCipher.unseal(bobs, context: context), plain);
|
||||
expect(aliceCipher.unseal(alices, context: context), plain);
|
||||
expect(bobCipher.unseal(alices, context: context), plain);
|
||||
expect(aliceCipher.unseal(mallorys, context: context), isNull);
|
||||
expect(bobCipher.unseal(mallorys, context: context), isNull);
|
||||
});
|
||||
|
||||
test('keys swapped after they were pinned are neither sealed to nor '
|
||||
'trusted, and named until trusted anew', () async {
|
||||
aliceCipher.newKey('k1');
|
||||
|
|
|
|||
Loading…
Reference in a new issue