1 | """ |
---|
2 | Ported to Python 3. |
---|
3 | """ |
---|
4 | |
---|
5 | import re |
---|
6 | |
---|
7 | from foolscap.furl import decode_furl |
---|
8 | from allmydata.crypto.util import remove_prefix |
---|
9 | from allmydata.crypto import ed25519 |
---|
10 | from allmydata.util import base32, jsonbytes as json |
---|
11 | |
---|
12 | |
---|
13 | def get_tubid_string_from_ann(ann): |
---|
14 | furl = ann.get("anonymous-storage-FURL") or ann.get("FURL") |
---|
15 | return get_tubid_string(furl) |
---|
16 | |
---|
17 | def get_tubid_string(furl): |
---|
18 | m = re.match(r'pb://(\w+)@', furl) |
---|
19 | assert m |
---|
20 | return m.group(1).lower().encode("ascii") |
---|
21 | |
---|
22 | |
---|
23 | def sign_to_foolscap(announcement, signing_key): |
---|
24 | """ |
---|
25 | :param signing_key: a (private) signing key, as returned from |
---|
26 | e.g. :func:`allmydata.crypto.ed25519.signing_keypair_from_string` |
---|
27 | |
---|
28 | :returns: 3-tuple of (msg, sig, vk) where msg is a UTF8 JSON |
---|
29 | serialization of the `announcement` (bytes), sig is bytes (a |
---|
30 | signature of msg) and vk is the verifying key bytes |
---|
31 | """ |
---|
32 | # return (bytes, sig-str, pubkey-str). A future HTTP-based serialization |
---|
33 | # will use JSON({msg:b64(JSON(msg).utf8), sig:v0-b64(sig), |
---|
34 | # pubkey:v0-b64(pubkey)}) . |
---|
35 | msg = json.dumps(announcement).encode("utf-8") |
---|
36 | sig = b"v0-" + base32.b2a( |
---|
37 | ed25519.sign_data(signing_key, msg) |
---|
38 | ) |
---|
39 | verifying_key_string = ed25519.string_from_verifying_key( |
---|
40 | ed25519.verifying_key_from_signing_key(signing_key) |
---|
41 | ) |
---|
42 | ann_t = (msg, sig, remove_prefix(verifying_key_string, b"pub-")) |
---|
43 | return ann_t |
---|
44 | |
---|
45 | |
---|
46 | class UnknownKeyError(Exception): |
---|
47 | pass |
---|
48 | |
---|
49 | |
---|
50 | def unsign_from_foolscap(ann_t): |
---|
51 | (msg, sig_vs, claimed_key_vs) = ann_t |
---|
52 | if not sig_vs or not claimed_key_vs: |
---|
53 | raise UnknownKeyError("only signed announcements recognized") |
---|
54 | if not sig_vs.startswith(b"v0-"): |
---|
55 | raise UnknownKeyError("only v0- signatures recognized") |
---|
56 | if not claimed_key_vs.startswith(b"v0-"): |
---|
57 | raise UnknownKeyError("only v0- keys recognized") |
---|
58 | |
---|
59 | claimed_key = ed25519.verifying_key_from_string(b"pub-" + claimed_key_vs) |
---|
60 | sig_bytes = base32.a2b(remove_prefix(sig_vs, b"v0-")) |
---|
61 | ed25519.verify_signature(claimed_key, sig_bytes, msg) |
---|
62 | key_vs = claimed_key_vs |
---|
63 | ann = json.loads(msg.decode("utf-8")) |
---|
64 | return (ann, key_vs) |
---|
65 | |
---|
66 | |
---|
67 | class SubscriberDescriptor(object): |
---|
68 | """This describes a subscriber, for status display purposes. It contains |
---|
69 | the following attributes: |
---|
70 | |
---|
71 | .service_name: what they subscribed to (string) |
---|
72 | .when: time when they subscribed (seconds since epoch) |
---|
73 | .nickname: their self-provided nickname, or "?" (unicode) |
---|
74 | .version: their self-provided version (string) |
---|
75 | .app_versions: versions of each library they use (dict str->str) |
---|
76 | .remote_address: the external address from which they connected (string) |
---|
77 | .tubid: for subscribers connecting with Foolscap, their tubid (string) |
---|
78 | """ |
---|
79 | |
---|
80 | def __init__(self, service_name, when, |
---|
81 | nickname, version, app_versions, |
---|
82 | remote_address, tubid): |
---|
83 | self.service_name = service_name |
---|
84 | self.when = when |
---|
85 | self.nickname = nickname |
---|
86 | self.version = version |
---|
87 | self.app_versions = app_versions |
---|
88 | self.remote_address = remote_address |
---|
89 | self.tubid = tubid |
---|
90 | |
---|
91 | class AnnouncementDescriptor(object): |
---|
92 | """This describes an announcement, for status display purposes. It |
---|
93 | contains the following attributes, which will be empty ("" for |
---|
94 | strings) if the client did not provide them: |
---|
95 | |
---|
96 | .when: time the announcement was first received (seconds since epoch) |
---|
97 | .index: the announcements 'index', a tuple of (string-or-None). |
---|
98 | The server remembers one announcement per index. |
---|
99 | .canary: a Referenceable on the announcer, so the server can learn |
---|
100 | when they disconnect (for the status display) |
---|
101 | .announcement: raw dictionary of announcement data |
---|
102 | .service_name: which service they are announcing (string) |
---|
103 | .version: 'my-version' portion of announcement (string) |
---|
104 | .nickname: their self-provided nickname, or "" (unicode) |
---|
105 | .serverid: the server identifier. This is a pubkey (for V2 clients), |
---|
106 | or a tubid (for V1 clients). |
---|
107 | .connection_hints: where they listen (list of strings) if the |
---|
108 | announcement included a key for |
---|
109 | 'anonymous-storage-FURL', else an empty list. |
---|
110 | """ |
---|
111 | |
---|
112 | def __init__(self, when, index, canary, ann_d): |
---|
113 | self.when = when |
---|
114 | self.index = index |
---|
115 | self.canary = canary |
---|
116 | self.announcement = ann_d |
---|
117 | self.service_name = ann_d["service-name"] |
---|
118 | self.version = ann_d.get("my-version", "") |
---|
119 | self.nickname = ann_d.get("nickname", u"") |
---|
120 | (_, key_s) = index |
---|
121 | self.serverid = key_s |
---|
122 | furl = ann_d.get("anonymous-storage-FURL") |
---|
123 | if furl: |
---|
124 | _, self.connection_hints, _ = decode_furl(furl) |
---|
125 | else: |
---|
126 | self.connection_hints = [] |
---|