aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/src/meshbay_cli/commands/trust.py
blob: f48a95558fd886ffb2b1c663785b899678f1451d (plain) (blame)
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
"""
`trust`: the node keys this machine pinned at first contact.

A node presenting another key than the one pinned is refused outright (exit 6,
docs/MESHBAY_DESIGN.md §5.2). `trust reset NODE` is the deliberate way out, for a
rotation the node's operator has announced — the counterpart of the reset in
the application's Settings. Nothing resets a pin by itself.
"""

import json
import sys

from ..client.account import Account, no_passphrase
from ..errors import NOT_FOUND, USAGE, CliError
from ..output import safe, sentence
from .context import Context

HELP = "the node keys pinned at first contact: list them, or forget one"


def add_parser(sub, parent) -> None:
    p = sub.add_parser("trust", parents=[parent], help=HELP, description=sentence(HELP)
                       + ". A node whose key changed is refused until its pin is reset.")
    p.add_argument("verb", choices=("list", "reset"))
    p.add_argument("node", nargs="?", help="the node's id, as the refusal names it")


async def run(ctx: Context) -> int:
    a = ctx.args
    if not ctx.store.peek():
        pins = {}
        account = None
    else:
        account = Account.open(ctx.hub, None, no_passphrase, ctx.store)
        pins = account.state.get("pins", {}) if account.remembered else {}
    if a.verb == "list":
        if a.json:
            json.dump(pins, sys.stdout, indent=1)
            print()
        else:
            for node_id, pk in sorted(pins.items()):
                print(f"{safe(node_id)}  {safe(pk)}")
        return 0
    if not a.node:
        raise CliError(USAGE, "Name the node: meshbay-cli trust reset NODE")
    if a.node not in pins:
        raise CliError(NOT_FOUND, f"No key is pinned for the node {safe(a.node)}.")

    def drop(state: dict, secrets: dict) -> None:
        state.get("pins", {}).pop(a.node, None)
    await account.update(drop)
    print(f"Forgot the key pinned for {safe(a.node)}; the next connection pins the key "
          "it presents.")
    return 0