Commit | Line | Data |
---|---|---|
553d59fe MW |
1 | ### -*-python-*- |
2 | ### | |
3 | ### Testing key-management functionality | |
4 | ### | |
5 | ### (c) 2019 Straylight/Edgeware | |
6 | ### | |
7 | ||
8 | ###----- Licensing notice --------------------------------------------------- | |
9 | ### | |
10 | ### This file is part of the Python interface to Catacomb. | |
11 | ### | |
12 | ### Catacomb/Python is free software: you can redistribute it and/or | |
13 | ### modify it under the terms of the GNU General Public License as | |
14 | ### published by the Free Software Foundation; either version 2 of the | |
15 | ### License, or (at your option) any later version. | |
16 | ### | |
17 | ### Catacomb/Python is distributed in the hope that it will be useful, but | |
18 | ### WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
20 | ### General Public License for more details. | |
21 | ### | |
22 | ### You should have received a copy of the GNU General Public License | |
23 | ### along with Catacomb/Python. If not, write to the Free Software | |
24 | ### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |
25 | ### USA. | |
26 | ||
27 | ###-------------------------------------------------------------------------- | |
28 | ### Imported modules. | |
29 | ||
30 | import catacomb as C | |
31 | import sys as SYS | |
32 | import unittest as U | |
33 | import testutils as T | |
34 | import time as TM | |
35 | ||
36 | ###-------------------------------------------------------------------------- | |
37 | class TestKeyError (U.TestCase): | |
38 | ||
39 | def test_keyerror(me): | |
40 | ||
41 | try: C.KeyFile("notexist", C.KOPEN_NOFILE).newkey(1, "foo") | |
42 | except C.KeyError: e = SYS.exc_info()[1] | |
43 | else: me.fail("expected `catacomb.KeyError'") | |
44 | me.assertEqual(e.err, C.KERR_READONLY) | |
45 | me.assertEqual(e.errstring, "Key file is read-only") | |
46 | me.assertEqual(e.args, (C.KERR_READONLY,)) | |
47 | me.assertEqual(str(e), | |
48 | "KERR_READONLY (%d): Key file is read-only" % | |
49 | C.KERR_READONLY) | |
50 | ||
51 | me.assertRaises(TypeError, C.KeyError) | |
52 | token = ["TOKEN"] | |
53 | e = C.KeyError(C.KERR_DUPID, token) | |
54 | me.assertEqual(e.err, C.KERR_DUPID) | |
55 | me.assertEqual(e.errstring, "Key id already exists") | |
56 | me.assertEqual(e.args, (C.KERR_DUPID, token)) | |
57 | ||
58 | ###-------------------------------------------------------------------------- | |
59 | class TestKeyFile (U.TestCase): | |
60 | ||
61 | def test_keyring(me): | |
62 | ||
63 | kf = C.KeyFile("t/keyring") | |
64 | ||
65 | ## Check basic attributes. | |
66 | me.assertEqual(kf.name, "t/keyring") | |
67 | me.assertEqual(kf.modifiedp, False) | |
68 | me.assertEqual(kf.writep, False) | |
69 | me.assertEqual(kf.filep, False) | |
70 | ||
71 | ## Check enumeration. | |
72 | me.assertEqual(set(k.type for k in T.itervalues(kf)), | |
73 | set(["rsa", "ec", "ec-param", "twofish"])) | |
74 | me.assertEqual(len(kf), 4) | |
75 | ||
76 | ## Start with `rsa'. | |
77 | k = kf.bytag("ron") | |
78 | me.assertEqual(k.type, "rsa") | |
79 | me.assertEqual(k.id, 0x8599dbab) | |
80 | me.assertEqual(type(k.data), C.KeyDataStructured) | |
81 | me.assertEqual(set(k.data), set(["e", "n", "private"])) | |
82 | priv = k.data["private"] | |
83 | me.assertEqual(type(priv), C.KeyDataEncrypted) | |
84 | me.assertRaises(C.KeyError, priv.unlock, T.bin("wrong secret")) | |
85 | priv = priv.unlock(T.bin("very secret")) | |
86 | me.assertEqual(type(priv), C.KeyDataStructured) | |
87 | me.assertEqual(set(priv), | |
88 | set(["p", "q", "d", "d-mod-p", "d-mod-q", "q-inv"])) | |
89 | me.assertEqual(k.data["n"].mp, priv["p"].mp*priv["q"].mp) | |
90 | ||
91 | ## This key has an attribute. Poke about at them. | |
92 | a = k.attr | |
93 | me.assertEqual(len(a), 1) | |
94 | me.assertEqual(set(a), set(["attr"])) | |
95 | me.assertEqual(a["attr"], "value") | |
96 | me.assertRaises(KeyError, lambda: a["notexist"]) | |
97 | me.assertEqual(a.get("attr"), "value") | |
98 | me.assertEqual(a.get("notexist"), None) | |
99 | ||
100 | ## Check fingerprinting while we're here. | |
101 | for filter in ["-secret", "none"]: | |
102 | h = C.sha256(); me.assertTrue(k.fingerprint(h, filter)); fp0 = h.done() | |
103 | h = C.sha256() | |
104 | h.hash(T.bin("catacomb-key-fingerprint:")) \ | |
105 | .hashu32(k.id) \ | |
106 | .hashbuf8(T.bin(k.type)) | |
107 | h.hash(k.data.encode(filter)) | |
108 | for a in sorted(T.iterkeys(k.attr)): | |
109 | h.hashbuf8(T.bin(a)).hashbuf16(T.bin(k.attr[a])) | |
110 | fp1 = h.done() | |
111 | me.assertEqual(fp0, fp1) | |
112 | ||
113 | ## Try `ec-param'. This should be fairly easy. | |
114 | k = kf["ec-param"] | |
115 | me.assertEqual(k.tag, None) | |
116 | me.assertEqual(k.id, 0x4a4e1ee7) | |
117 | me.assertEqual(type(k.data), C.KeyDataStructured) | |
118 | me.assertEqual(set(k.data), set(["curve"])) | |
119 | curve = k.data["curve"] | |
120 | me.assertEqual(type(curve), C.KeyDataString) | |
121 | me.assertEqual(curve.str, "nist-p256") | |
122 | ||
123 | ## Check qualified-tag lookups. | |
124 | me.assertRaises(C.KeyError, kf.qtag, "notexist.curve") | |
125 | me.assertRaises(C.KeyError, kf.qtag, "ec-param.notexist") | |
126 | t, k, kd = kf.qtag("ec-param.curve") | |
127 | me.assertEqual(t, "4a4e1ee7:ec-param.curve") | |
128 | me.assertEqual(k.type, "ec-param") | |
129 | me.assertEqual(type(kd), C.KeyDataString) | |
130 | me.assertEqual(kd.str, "nist-p256") | |
131 | ||
132 | ## Try `ec'. A little trickier. | |
133 | k = kf.bytype("ec") | |
134 | me.assertEqual(k.tag, None) | |
135 | me.assertEqual(k.id, 0xbd761d35) | |
136 | me.assertEqual(type(k.data), C.KeyDataStructured) | |
137 | me.assertEqual(set(k.data), set(["curve", "p", "private"])) | |
138 | curve = k.data["curve"] | |
139 | me.assertEqual(type(curve), C.KeyDataString) | |
140 | me.assertEqual(curve.str, "nist-p256") | |
141 | einfo = C.eccurves[curve.str] | |
142 | me.assertEqual(type(k.data["p"]), C.KeyDataECPt) | |
143 | X = k.data["p"].ecpt | |
144 | priv = k.data["private"] | |
145 | me.assertEqual(type(priv), C.KeyDataEncrypted) | |
146 | me.assertRaises(C.KeyError, priv.unlock, T.bin("wrong secret")) | |
147 | priv = priv.unlock(T.bin("super secret")) | |
148 | me.assertEqual(type(priv), C.KeyDataStructured) | |
149 | me.assertEqual(set(priv), set(["x"])) | |
150 | x = priv["x"].mp | |
151 | me.assertEqual(x*einfo.G, X) | |
152 | ||
153 | ## Finish with `twofish'. | |
154 | k = kf.byid(0x60090be2) | |
155 | me.assertEqual(k.tag, None) | |
156 | me.assertEqual(k.type, "twofish") | |
157 | me.assertEqual(type(k.data), C.KeyDataEncrypted) | |
158 | me.assertRaises(C.KeyError, k.data.unlock, T.bin("wrong secret")) | |
159 | kd = k.data.unlock(T.bin("not secret")) | |
160 | me.assertEqual(type(kd), C.KeyDataBinary) | |
161 | me.assertEqual(kd.bin, C.bytes("d337b98eea24425826df202a6a3d1ef8" | |
162 | "377b71923fe1179451564776da29bb84")) | |
163 | ||
164 | ## Check unsuccessful searches. | |
165 | me.assertRaises(KeyError, lambda: kf["notexist"]) | |
ebe7d65d MW |
166 | me.assertRaises(C.KeyError, kf.bytag, "notexist") |
167 | me.assertRaises(C.KeyError, kf.bytag, 12345) | |
168 | me.assertEqual(kf.bytag("notexist", fail = False), None) | |
169 | me.assertRaises(C.KeyError, kf.bytype, "notexist") | |
553d59fe | 170 | me.assertRaises(TypeError, kf.bytype, 12345) |
ebe7d65d | 171 | me.assertEqual(kf.bytype("notexist", fail = False), None) |
553d59fe | 172 | me.assertRaises(C.KeyError, kf.byid, 0x12345678) |
ebe7d65d | 173 | me.assertEqual(kf.byid(0x12345678, fail = False), None) |
553d59fe | 174 | |
618cbc92 MW |
175 | me.assertRaises(C.KeyError, kf.mergeline, "nowhere", 2, "") |
176 | ||
553d59fe MW |
177 | ## The keyring should be readonly. |
178 | me.assertRaises(C.KeyError, kf.newkey, 0x12345678, "fail") | |
179 | me.assertRaises(C.KeyError, setattr, k, "tag", "foo") | |
180 | me.assertRaises(C.KeyError, delattr, k, "tag") | |
618cbc92 MW |
181 | me.assertRaises(C.KeyError, kf.mergeline, "notexist", 1, |
182 | "22222222:test integer,public:32519164 forever forever -") | |
553d59fe MW |
183 | me.assertRaises(C.KeyError, setattr, k, "data", C.KeyDataString("foo")) |
184 | ||
185 | def test_keywrite(me): | |
186 | kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE) | |
187 | me.assertEqual(kf.modifiedp, False) | |
188 | now = int(TM.time()) | |
189 | exp = now + 86400 | |
190 | ||
191 | k = kf.newkey(0x11111111, "first", exp) | |
192 | me.assertEqual(kf.modifiedp, True) | |
193 | ||
c567f805 | 194 | me.assertEqual(k, kf[0x11111111]) |
553d59fe MW |
195 | me.assertEqual(k.exptime, exp) |
196 | me.assertEqual(k.deltime, exp) | |
197 | me.assertRaises(ValueError, setattr, k, "deltime", C.KEXP_FOREVER) | |
198 | k.exptime = exp + 5 | |
199 | me.assertEqual(k.data.str, "<unset>") | |
200 | n = 9876543210 | |
201 | k.data = C.KeyDataMP(n) | |
202 | me.assertEqual(k.data.mp, n) | |
203 | me.assertEqual(k.comment, None) | |
204 | c = ";; just a test" | |
205 | k.comment = c | |
206 | me.assertEqual(k.comment, c) | |
207 | k.comment = None | |
208 | me.assertEqual(k.comment, None) | |
209 | k.comment = c | |
210 | me.assertEqual(k.comment, c) | |
211 | del k.comment | |
212 | me.assertEqual(k.comment, None) | |
213 | ||
618cbc92 MW |
214 | kf.mergeline("notexist", 1, |
215 | "22222222:test integer,public:32519164 forever forever -") | |
216 | ||
553d59fe | 217 | ###-------------------------------------------------------------------------- |
553d59fe MW |
218 | class TestKeyData (U.TestCase): |
219 | ||
220 | def test_flags(me): | |
221 | me.assertEqual(C.KeyData.readflags("none"), (0, 0, "")) | |
222 | me.assertEqual(C.KeyData.readflags("ec,public:..."), | |
223 | (C.KENC_EC | C.KCAT_PUB, | |
224 | C.KF_ENCMASK | C.KF_CATMASK, | |
225 | ":...")) | |
226 | me.assertEqual(C.KeyData.readflags("int,burn"), | |
227 | (C.KENC_MP | C.KF_BURN, C.KF_ENCMASK | C.KF_BURN, "")) | |
228 | me.assertRaises(C.KeyError, C.KeyData.readflags, "int,burn?") | |
229 | me.assertRaises(C.KeyError, C.KeyData.readflags, "int,ec") | |
230 | me.assertRaises(C.KeyError, C.KeyData.readflags, "snork") | |
231 | me.assertEqual(C.KeyData.writeflags(0), "binary,symmetric") | |
232 | me.assertEqual(C.KeyData.writeflags(C.KENC_EC | C.KCAT_PUB), "ec,public") | |
233 | ||
234 | def test_misc(me): | |
235 | kd = C.KeyDataStructured({ "a": C.KeyDataString("foo", "public"), | |
236 | "b": C.KeyDataMP(12345, "private"), | |
237 | "c": C.KeyDataString("bar", "public") }) | |
238 | ||
239 | kd2 = kd.copy() | |
240 | me.assertEqual(type(kd2), C.KeyDataStructured) | |
241 | me.assertEqual(set(T.iterkeys(kd2)), set(["a", "b", "c"])) | |
242 | ||
243 | kd2 = C.KeyDataMP(12345, C.KCAT_PRIV).copy("private") | |
244 | ||
245 | kd2 = kd.copy("-secret") | |
246 | me.assertEqual(type(kd2), C.KeyDataStructured) | |
247 | me.assertEqual(set(T.iterkeys(kd2)), set(["a", "c"])) | |
248 | ||
249 | kd2 = kd.copy((0, C.KF_NONSECRET)) | |
250 | me.assertEqual(type(kd2), C.KeyDataStructured) | |
251 | me.assertEqual(set(T.iterkeys(kd2)), set(["b"])) | |
252 | ||
253 | def check_encode(me, kd): | |
c567f805 MW |
254 | me.assertEqual(C.KeyData.decode(kd.encode()), kd) |
255 | me.assertEqual(C.KeyData.read(kd.write()), (kd, "")) | |
553d59fe MW |
256 | |
257 | def test_bin(me): | |
258 | rng = T.detrand("kd-bin") | |
259 | by = rng.block(16) | |
260 | kd = C.KeyDataBinary(by, "symm,burn") | |
261 | me.assertEqual(kd.bin, by) | |
262 | me.check_encode(kd) | |
263 | ||
264 | def test_mp(me): | |
265 | rng = T.detrand("kd-mp") | |
266 | x = rng.mp(128) | |
267 | kd = C.KeyDataMP(x, "symm,burn") | |
268 | me.assertEqual(kd.mp, x) | |
269 | me.check_encode(kd) | |
270 | ||
271 | def test_string(me): | |
272 | s = "some random string" | |
273 | kd = C.KeyDataString(s, "symm,burn") | |
274 | me.assertEqual(kd.str, s) | |
275 | me.check_encode(kd) | |
276 | ||
277 | def test_enc(me): | |
278 | rng = T.detrand("kd-enc") | |
279 | ct = rng.block(16) | |
280 | kd = C.KeyDataEncrypted(ct, "symm") | |
281 | me.assertEqual(kd.ct, ct) | |
282 | me.check_encode(kd) | |
283 | ||
284 | def test_ecpt(me): | |
285 | rng = T.detrand("kd-ec") | |
286 | Q = C.ECPt(rng.mp(128), rng.mp(128)) | |
287 | kd = C.KeyDataECPt(Q, "symm,burn") | |
288 | me.assertEqual(kd.ecpt, Q) | |
289 | me.check_encode(kd) | |
290 | ||
291 | def test_struct(me): | |
292 | rng = T.detrand("kd-struct") | |
293 | kd = C.KeyDataStructured({ "a": C.KeyDataString("a"), | |
ad70a954 MW |
294 | "b": C.KeyDataString("b") }, |
295 | c = C.KeyDataString("c"), | |
296 | d = C.KeyDataString("d")) | |
553d59fe MW |
297 | for i in ["a", "b", "c", "d"]: me.assertEqual(kd[i].str, i) |
298 | me.assertEqual(len(kd), 4) | |
299 | me.check_encode(kd) | |
300 | me.assertRaises(TypeError, C.KeyDataStructured, { "a": "a" }) | |
f5a13360 MW |
301 | me.assertRaises(ValueError, C.KeyDataStructured, |
302 | { "a": C.KeyDataString("a") }, | |
303 | a = C.KeyDataString("b")) | |
553d59fe MW |
304 | |
305 | ###-------------------------------------------------------------------------- | |
306 | ### Mappings. | |
307 | ||
308 | class TestKeyFileMapping (T.ImmutableMappingTextMixin): | |
309 | def _mkkey(me, i): return i | |
310 | def _getkey(me, k): return k | |
311 | def _getvalue(me, v): return v.data.mp | |
312 | ||
313 | def test_keyfile(me): | |
314 | kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE) | |
315 | model = {} | |
316 | for i in [1, 2, 3]: | |
317 | model[i] = 100 + i | |
318 | kf.newkey(i, "k#%d" % i).data = C.KeyDataMP(100 + i) | |
319 | ||
c8c4c034 MW |
320 | me.assertEqual(kf[1], kf[1]) |
321 | ||
553d59fe MW |
322 | me.check_immutable_mapping(kf, model) |
323 | ||
c567f805 MW |
324 | class TestKeyStructMapping (T.MutableMappingTestMixin): |
325 | def _mkvalue(me, i): return C.KeyDataMP(i) | |
326 | def _getvalue(me, v): return v.mp | |
327 | ||
328 | def test_keystructmap(me): | |
329 | me.check_mapping(C.KeyDataStructured) | |
330 | ||
553d59fe MW |
331 | class TestKeyAttrMapping (T.MutableMappingTestMixin): |
332 | ||
333 | def test_attrmap(me): | |
334 | def mkmap(): | |
335 | kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE) | |
336 | k = kf.newkey(0x12345678, "test-key") | |
337 | return k.attr | |
338 | me.check_mapping(mkmap) | |
339 | ||
340 | a = mkmap() | |
341 | me.assertRaises(TypeError, a.update, { 3: 3, 4: 5 }) | |
342 | ||
343 | ###----- That's all, folks -------------------------------------------------- | |
344 | ||
345 | if __name__ == "__main__": U.main() |