1 | """ |
---|
2 | Testtools-style matchers useful to the Tahoe-LAFS test suite. |
---|
3 | |
---|
4 | Ported to Python 3. |
---|
5 | """ |
---|
6 | |
---|
7 | import attr |
---|
8 | from hyperlink import DecodedURL |
---|
9 | |
---|
10 | from testtools.matchers import ( |
---|
11 | Mismatch, |
---|
12 | AfterPreprocessing, |
---|
13 | MatchesStructure, |
---|
14 | MatchesDict, |
---|
15 | MatchesListwise, |
---|
16 | Always, |
---|
17 | Equals, |
---|
18 | ) |
---|
19 | |
---|
20 | from foolscap.furl import ( |
---|
21 | decode_furl, |
---|
22 | ) |
---|
23 | |
---|
24 | from allmydata.util import ( |
---|
25 | base32, |
---|
26 | ) |
---|
27 | from allmydata.node import ( |
---|
28 | read_config, |
---|
29 | ) |
---|
30 | from allmydata.crypto import ( |
---|
31 | ed25519, |
---|
32 | error, |
---|
33 | ) |
---|
34 | |
---|
35 | @attr.s |
---|
36 | class MatchesNodePublicKey(object): |
---|
37 | """ |
---|
38 | Match an object representing the node's private key. |
---|
39 | |
---|
40 | To verify, the private key is loaded from the node's private config |
---|
41 | directory at the time the match is checked. |
---|
42 | """ |
---|
43 | basedir = attr.ib() |
---|
44 | |
---|
45 | def match(self, other): |
---|
46 | """ |
---|
47 | Match a private key which is the same as the private key in the node at |
---|
48 | ``self.basedir``. |
---|
49 | |
---|
50 | :param other: A signing key (aka "private key") from |
---|
51 | ``allmydata.crypto.ed25519``. This is the key to check against |
---|
52 | the node's key. |
---|
53 | |
---|
54 | :return Mismatch: If the keys don't match. |
---|
55 | """ |
---|
56 | config = read_config(self.basedir, u"tub.port") |
---|
57 | privkey_bytes = config.get_private_config("node.privkey").encode("utf-8") |
---|
58 | private_key = ed25519.signing_keypair_from_string(privkey_bytes)[0] |
---|
59 | signature = ed25519.sign_data(private_key, b"") |
---|
60 | other_public_key = ed25519.verifying_key_from_signing_key(other) |
---|
61 | try: |
---|
62 | ed25519.verify_signature(other_public_key, signature, b"") |
---|
63 | except error.BadSignature: |
---|
64 | return Mismatch("The signature did not verify.") |
---|
65 | |
---|
66 | |
---|
67 | def matches_storage_announcement(basedir, anonymous=True, options=None): |
---|
68 | """ |
---|
69 | Match a storage announcement. |
---|
70 | |
---|
71 | :param bytes basedir: The path to the node base directory which is |
---|
72 | expected to emit the announcement. This is used to determine the key |
---|
73 | which is meant to sign the announcement. |
---|
74 | |
---|
75 | :param bool anonymous: If True, matches a storage announcement containing |
---|
76 | an anonymous access fURL. Otherwise, fails to match such an |
---|
77 | announcement. |
---|
78 | |
---|
79 | :param list[matcher]|NoneType options: If a list, matches a storage |
---|
80 | announcement containing a list of storage plugin options matching the |
---|
81 | elements of the list. If None, fails to match an announcement with |
---|
82 | storage plugin options. |
---|
83 | |
---|
84 | :return: A matcher with the requested behavior. |
---|
85 | """ |
---|
86 | announcement = { |
---|
87 | u"permutation-seed-base32": matches_base32(), |
---|
88 | } |
---|
89 | if anonymous: |
---|
90 | announcement[u"anonymous-storage-FURL"] = matches_furl() |
---|
91 | announcement[u"anonymous-storage-NURLs"] = matches_nurls() |
---|
92 | if options: |
---|
93 | announcement[u"storage-options"] = MatchesListwise(options) |
---|
94 | return MatchesStructure( |
---|
95 | # Has each of these keys with associated values that match |
---|
96 | service_name=Equals(u"storage"), |
---|
97 | ann=MatchesDict(announcement), |
---|
98 | signing_key=MatchesNodePublicKey(basedir), |
---|
99 | ) |
---|
100 | |
---|
101 | |
---|
102 | def matches_furl(): |
---|
103 | """ |
---|
104 | Match any Foolscap fURL byte string. |
---|
105 | """ |
---|
106 | return AfterPreprocessing(decode_furl, Always()) |
---|
107 | |
---|
108 | |
---|
109 | def matches_nurls(): |
---|
110 | """ |
---|
111 | Matches a sequence of NURLs. |
---|
112 | """ |
---|
113 | return AfterPreprocessing( |
---|
114 | lambda nurls: [DecodedURL.from_text(u) for u in nurls], |
---|
115 | Always() |
---|
116 | ) |
---|
117 | |
---|
118 | |
---|
119 | def matches_base32(): |
---|
120 | """ |
---|
121 | Match any base32 encoded byte string. |
---|
122 | """ |
---|
123 | return AfterPreprocessing(base32.a2b, Always()) |
---|
124 | |
---|
125 | |
---|
126 | |
---|
127 | class MatchesSameElements(object): |
---|
128 | """ |
---|
129 | Match if the two-tuple value given contains two elements that are equal to |
---|
130 | each other. |
---|
131 | """ |
---|
132 | def match(self, value): |
---|
133 | left, right = value |
---|
134 | return Equals(left).match(right) |
---|