key.c: Make the `KeyFile' key-lookup methods behave consistently.
[catacomb-python] / t / t-key.py
CommitLineData
ffad1322
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
30import catacomb as C
31import sys as SYS
32import unittest as U
33import testutils as T
34import time as TM
35
36###--------------------------------------------------------------------------
37class 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###--------------------------------------------------------------------------
59class 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"])
f205cc7a
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")
ffad1322 170 me.assertRaises(TypeError, kf.bytype, 12345)
f205cc7a 171 me.assertEqual(kf.bytype("notexist", fail = False), None)
ffad1322 172 me.assertRaises(C.KeyError, kf.byid, 0x12345678)
f205cc7a 173 me.assertEqual(kf.byid(0x12345678, fail = False), None)
ffad1322 174
98a1d50b
MW
175 me.assertRaises(C.KeyError, kf.mergeline, "nowhere", 2, "")
176
ffad1322
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")
98a1d50b
MW
181 me.assertRaises(C.KeyError, kf.mergeline, "notexist", 1,
182 "22222222:test integer,public:32519164 forever forever -")
ffad1322
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
1fae937d 194 me.assertEqual(k, kf[0x11111111])
ffad1322
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
98a1d50b
MW
214 kf.mergeline("notexist", 1,
215 "22222222:test integer,public:32519164 forever forever -")
216
ffad1322 217###--------------------------------------------------------------------------
ffad1322
MW
218class 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):
1fae937d
MW
254 me.assertEqual(C.KeyData.decode(kd.encode()), kd)
255 me.assertEqual(C.KeyData.read(kd.write()), (kd, ""))
ffad1322
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"),
c25a8ca2
MW
294 "b": C.KeyDataString("b") },
295 c = C.KeyDataString("c"),
296 d = C.KeyDataString("d"))
ffad1322
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" })
dedc5483
MW
301 me.assertRaises(ValueError, C.KeyDataStructured,
302 { "a": C.KeyDataString("a") },
303 a = C.KeyDataString("b"))
ffad1322
MW
304
305###--------------------------------------------------------------------------
306### Mappings.
307
308class 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
88ec9a2d
MW
320 me.assertEqual(kf[1], kf[1])
321
ffad1322
MW
322 me.check_immutable_mapping(kf, model)
323
1fae937d
MW
324class 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
ffad1322
MW
331class 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
345if __name__ == "__main__": U.main()