1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
"""
Tier 2 for chat (docs/MESHBAY_DESIGN.md §3.3): whose device signed a message,
checked against the group's roster and the keys this machine accepted before —
through `main()`, against the in-memory node, with real countersignatures.
"""
import json
import pytest
from fake_hub import PASSPHRASE, USERNAME, FakeHub
from fake_node import FakeNode
from meshbay_cli import __main__ as cli
from meshbay_cli.client.keys import Identity
from meshbay_cli.client.roster import RosterCheck, judge, verify_roster
from meshbay_cli.commands import chat, context
def _new() -> Identity:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
return Identity(Ed25519PrivateKey.generate(), X25519PrivateKey.generate())
@pytest.fixture
def node(monkeypatch, capsys):
hub, fake = FakeHub(), FakeNode()
monkeypatch.setattr(context, "Hub", lambda url: hub)
async def open_group(account, group):
return fake.session()
monkeypatch.setattr(chat, "open_group", open_group)
assert cli.main(["--hub", hub.url, "-u", USERNAME, "--password", PASSPHRASE,
"login"]) == 0
capsys.readouterr()
return fake
def _last(capsys, n=1) -> list[dict]:
assert cli.main(["--json", "chat", f"-{n}", "Family"]) == 0
return json.loads(capsys.readouterr().out)
def test_each_verdict_on_a_message_signing_device(node, capsys):
phone = node.say("from bob's phone")
assert _last(capsys)[0]["trust"] == "first" # first sight: pinned
assert _last(capsys)[0]["trust"] == "pinned"
# A second device, countersigned by the first: no notice.
laptop = _new()
node.list_device("bob-test", laptop, countersigned_by=phone)
node.say("from bob's laptop", device=laptop)
assert _last(capsys)[0]["trust"] == "linked"
# Named as countersigned and carrying no proof, as a lying node would list it.
planted = _new()
node.list_device("bob-test", planted, countersigned_by=phone, proof=False)
node.say("hello, it's bob", device=planted)
assert _last(capsys)[0]["trust"] == "changed"
# Not in the roster at all.
node.say("hello again", device=_new())
assert _last(capsys)[0]["trust"] == "changed"
# Said in plain text under the message, and nowhere else.
assert cli.main(["chat", "-5", "Family"]) == 0
out = capsys.readouterr().out.splitlines()
assert sum("key you have not seen before" in line for line in out) == 2
assert out[-1].lstrip().startswith("! This account")
assert out[-3].lstrip().startswith("! This account")
# Another account, first sight.
node.say("hi from carol", sender="carol-test")
assert _last(capsys)[0]["trust"] == "first"
async def test_a_device_listed_after_the_roster_was_read_is_not_a_substitution(node):
"""The roster is read once per connection; a member's new device added since
is read again before "changed" is said."""
from meshbay_cli.client.account import Account
from meshbay_cli.client.store import Store
store = Store()
account = Account.open(context.Hub("x"), None, lambda: PASSPHRASE, store)
session = node.session()
phone = node.say("one")
check = RosterCheck(account, session)
assert await check.status("u-bob-test", phone.pk_ed) == "first"
tablet = _new()
node.list_device("bob-test", tablet, countersigned_by=phone) # after the read
assert await check.status("u-bob-test", tablet.pk_ed) == "linked"
def test_a_root_is_a_device_naming_no_countersigner():
a, b = _new(), _new()
rows = [{"user_id": "u", "pk_ed25519": a.pk_ed, "pk_x25519": a.pk_x,
"added_by_pk": ""},
{"user_id": "u", "pk_ed25519": b.pk_ed, "pk_x25519": b.pk_x,
"added_by_pk": a.pk_ed, "add_sig": "", "add_nonce": "", "add_ts": 0}]
got = verify_roster({"devices": rows, "node_pk": "pk"}, "pk")["u"]
assert got.verified == [a.pk_ed] and got.unevidenced == [b.pk_ed]
assert judge([a.pk_ed], got, b.pk_ed) == ("changed", None)
assert judge(None, got, b.pk_ed)[0] == "first"
|