key.c: Complain about duplicate subkeys passed to `KeyDataStructured'.
[catacomb-python] / t / t-key.py
CommitLineData
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
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"])
166 me.assertEqual(kf.bytag("notexist"), None)
167 me.assertEqual(kf.bytag(12345), None)
168 me.assertEqual(kf.bytype("notexist"), None)
169 me.assertRaises(TypeError, kf.bytype, 12345)
170 me.assertRaises(C.KeyError, kf.byid, 0x12345678)
171
618cbc92
MW
172 me.assertRaises(C.KeyError, kf.mergeline, "nowhere", 2, "")
173
553d59fe
MW
174 ## The keyring should be readonly.
175 me.assertRaises(C.KeyError, kf.newkey, 0x12345678, "fail")
176 me.assertRaises(C.KeyError, setattr, k, "tag", "foo")
177 me.assertRaises(C.KeyError, delattr, k, "tag")
618cbc92
MW
178 me.assertRaises(C.KeyError, kf.mergeline, "notexist", 1,
179 "22222222:test integer,public:32519164 forever forever -")
553d59fe
MW
180 me.assertRaises(C.KeyError, setattr, k, "data", C.KeyDataString("foo"))
181
182 def test_keywrite(me):
183 kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE)
184 me.assertEqual(kf.modifiedp, False)
185 now = int(TM.time())
186 exp = now + 86400
187
188 k = kf.newkey(0x11111111, "first", exp)
189 me.assertEqual(kf.modifiedp, True)
190
c567f805 191 me.assertEqual(k, kf[0x11111111])
553d59fe
MW
192 me.assertEqual(k.exptime, exp)
193 me.assertEqual(k.deltime, exp)
194 me.assertRaises(ValueError, setattr, k, "deltime", C.KEXP_FOREVER)
195 k.exptime = exp + 5
196 me.assertEqual(k.data.str, "<unset>")
197 n = 9876543210
198 k.data = C.KeyDataMP(n)
199 me.assertEqual(k.data.mp, n)
200 me.assertEqual(k.comment, None)
201 c = ";; just a test"
202 k.comment = c
203 me.assertEqual(k.comment, c)
204 k.comment = None
205 me.assertEqual(k.comment, None)
206 k.comment = c
207 me.assertEqual(k.comment, c)
208 del k.comment
209 me.assertEqual(k.comment, None)
210
618cbc92
MW
211 kf.mergeline("notexist", 1,
212 "22222222:test integer,public:32519164 forever forever -")
213
553d59fe 214###--------------------------------------------------------------------------
553d59fe
MW
215class TestKeyData (U.TestCase):
216
217 def test_flags(me):
218 me.assertEqual(C.KeyData.readflags("none"), (0, 0, ""))
219 me.assertEqual(C.KeyData.readflags("ec,public:..."),
220 (C.KENC_EC | C.KCAT_PUB,
221 C.KF_ENCMASK | C.KF_CATMASK,
222 ":..."))
223 me.assertEqual(C.KeyData.readflags("int,burn"),
224 (C.KENC_MP | C.KF_BURN, C.KF_ENCMASK | C.KF_BURN, ""))
225 me.assertRaises(C.KeyError, C.KeyData.readflags, "int,burn?")
226 me.assertRaises(C.KeyError, C.KeyData.readflags, "int,ec")
227 me.assertRaises(C.KeyError, C.KeyData.readflags, "snork")
228 me.assertEqual(C.KeyData.writeflags(0), "binary,symmetric")
229 me.assertEqual(C.KeyData.writeflags(C.KENC_EC | C.KCAT_PUB), "ec,public")
230
231 def test_misc(me):
232 kd = C.KeyDataStructured({ "a": C.KeyDataString("foo", "public"),
233 "b": C.KeyDataMP(12345, "private"),
234 "c": C.KeyDataString("bar", "public") })
235
236 kd2 = kd.copy()
237 me.assertEqual(type(kd2), C.KeyDataStructured)
238 me.assertEqual(set(T.iterkeys(kd2)), set(["a", "b", "c"]))
239
240 kd2 = C.KeyDataMP(12345, C.KCAT_PRIV).copy("private")
241
242 kd2 = kd.copy("-secret")
243 me.assertEqual(type(kd2), C.KeyDataStructured)
244 me.assertEqual(set(T.iterkeys(kd2)), set(["a", "c"]))
245
246 kd2 = kd.copy((0, C.KF_NONSECRET))
247 me.assertEqual(type(kd2), C.KeyDataStructured)
248 me.assertEqual(set(T.iterkeys(kd2)), set(["b"]))
249
250 def check_encode(me, kd):
c567f805
MW
251 me.assertEqual(C.KeyData.decode(kd.encode()), kd)
252 me.assertEqual(C.KeyData.read(kd.write()), (kd, ""))
553d59fe
MW
253
254 def test_bin(me):
255 rng = T.detrand("kd-bin")
256 by = rng.block(16)
257 kd = C.KeyDataBinary(by, "symm,burn")
258 me.assertEqual(kd.bin, by)
259 me.check_encode(kd)
260
261 def test_mp(me):
262 rng = T.detrand("kd-mp")
263 x = rng.mp(128)
264 kd = C.KeyDataMP(x, "symm,burn")
265 me.assertEqual(kd.mp, x)
266 me.check_encode(kd)
267
268 def test_string(me):
269 s = "some random string"
270 kd = C.KeyDataString(s, "symm,burn")
271 me.assertEqual(kd.str, s)
272 me.check_encode(kd)
273
274 def test_enc(me):
275 rng = T.detrand("kd-enc")
276 ct = rng.block(16)
277 kd = C.KeyDataEncrypted(ct, "symm")
278 me.assertEqual(kd.ct, ct)
279 me.check_encode(kd)
280
281 def test_ecpt(me):
282 rng = T.detrand("kd-ec")
283 Q = C.ECPt(rng.mp(128), rng.mp(128))
284 kd = C.KeyDataECPt(Q, "symm,burn")
285 me.assertEqual(kd.ecpt, Q)
286 me.check_encode(kd)
287
288 def test_struct(me):
289 rng = T.detrand("kd-struct")
290 kd = C.KeyDataStructured({ "a": C.KeyDataString("a"),
ad70a954
MW
291 "b": C.KeyDataString("b") },
292 c = C.KeyDataString("c"),
293 d = C.KeyDataString("d"))
553d59fe
MW
294 for i in ["a", "b", "c", "d"]: me.assertEqual(kd[i].str, i)
295 me.assertEqual(len(kd), 4)
296 me.check_encode(kd)
297 me.assertRaises(TypeError, C.KeyDataStructured, { "a": "a" })
f5a13360
MW
298 me.assertRaises(ValueError, C.KeyDataStructured,
299 { "a": C.KeyDataString("a") },
300 a = C.KeyDataString("b"))
553d59fe
MW
301
302###--------------------------------------------------------------------------
303### Mappings.
304
305class TestKeyFileMapping (T.ImmutableMappingTextMixin):
306 def _mkkey(me, i): return i
307 def _getkey(me, k): return k
308 def _getvalue(me, v): return v.data.mp
309
310 def test_keyfile(me):
311 kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE)
312 model = {}
313 for i in [1, 2, 3]:
314 model[i] = 100 + i
315 kf.newkey(i, "k#%d" % i).data = C.KeyDataMP(100 + i)
316
c8c4c034
MW
317 me.assertEqual(kf[1], kf[1])
318
553d59fe
MW
319 me.check_immutable_mapping(kf, model)
320
c567f805
MW
321class TestKeyStructMapping (T.MutableMappingTestMixin):
322 def _mkvalue(me, i): return C.KeyDataMP(i)
323 def _getvalue(me, v): return v.mp
324
325 def test_keystructmap(me):
326 me.check_mapping(C.KeyDataStructured)
327
553d59fe
MW
328class TestKeyAttrMapping (T.MutableMappingTestMixin):
329
330 def test_attrmap(me):
331 def mkmap():
332 kf = C.KeyFile("test", C.KOPEN_WRITE | C.KOPEN_NOFILE)
333 k = kf.newkey(0x12345678, "test-key")
334 return k.attr
335 me.check_mapping(mkmap)
336
337 a = mkmap()
338 me.assertRaises(TypeError, a.update, { 3: 3, 4: 5 })
339
340###----- That's all, folks --------------------------------------------------
341
342if __name__ == "__main__": U.main()