chiark / gitweb /
Various other little bits.
[catacomb-python] / catacomb / pwsafe.py
CommitLineData
43c09851 1# -*-python-*-
2
3import catacomb as _C
4import gdbm as _G
5import struct as _S
6
7class DecryptError (Exception):
8 pass
9
10class Crypto (object):
11 def __init__(me, c, h, m, ck, mk):
12 me.c = c(ck)
13 me.m = m(mk)
14 me.h = h
15 def encrypt(me, pt):
16 if me.c.__class__.blksz:
17 iv = _C.rand.block(me.c.__class__.blksz)
18 me.c.setiv(iv)
19 else:
20 iv = ''
21 y = iv + me.c.encrypt(pt)
22 t = me.m().hash(y).done()
23 return t + y
24 def decrypt(me, ct):
25 t = ct[:me.m.__class__.tagsz]
26 y = ct[me.m.__class__.tagsz:]
27 if t != me.m().hash(y).done():
28 raise DecryptError
29 iv = y[:me.c.__class__.blksz]
30 if me.c.__class__.blksz: me.c.setiv(iv)
31 return me.c.decrypt(y[me.c.__class__.blksz:])
32
33class PPK (Crypto):
34 def __init__(me, pp, c, h, m, salt = None):
35 if not salt: salt = _C.rand.block(h.hashsz)
36 tag = '%s\0%s' % (pp, salt)
37 Crypto.__init__(me, c, h, m,
38 h().hash('cipher:' + tag).done(),
39 h().hash('mac:' + tag).done())
40 me.salt = salt
41
42class Buffer (object):
43 def __init__(me, s):
44 me.str = s
45 me.i = 0
46 def get(me, n):
47 i = me.i
48 if n + i > len(me.str):
49 raise IndexError, 'buffer underflow'
50 me.i += n
51 return me.str[i:i + n]
52 def getbyte(me):
53 return ord(me.get(1))
54 def unpack(me, fmt):
55 return _S.unpack(fmt, me.get(_S.calcsize(fmt)))
56 def getstring(me):
57 return me.get(me.unpack('>H')[0])
58 def checkend(me):
59 if me.i != len(me.str):
60 raise ValueError, 'junk at end of buffer'
61
62def _wrapstr(s):
63 return _S.pack('>H', len(s)) + s
64
65class PWIter (object):
66 def __init__(me, pw):
67 me.pw = pw
68 me.k = me.pw.db.firstkey()
69 def next(me):
70 k = me.k
71 while True:
72 if k is None:
73 raise StopIteration
74 if k[0] == '$':
75 break
76 k = me.pw.db.nextkey(k)
77 me.k = me.pw.db.nextkey(k)
78 return me.pw.unpack(me.pw.db[k])[0]
79class PW (object):
80 def __init__(me, file, mode = 'r'):
81 me.db = _G.open(file, mode)
82 c = _C.gcciphers[me.db['cipher']]
83 h = _C.gchashes[me.db['hash']]
84 m = _C.gcmacs[me.db['mac']]
85 tag = me.db['tag']
86 ppk = PPK(_C.ppread(tag), c, h, m, me.db['salt'])
87 try:
88 buf = Buffer(ppk.decrypt(me.db['key']))
89 except DecryptError:
90 _C.ppcancel(tag)
91 raise
92 me.ck = buf.getstring()
93 me.mk = buf.getstring()
94 buf.checkend()
95 me.k = Crypto(c, h, m, me.ck, me.mk)
96 me.magic = me.k.decrypt(me.db['magic'])
97 def keyxform(me, key):
98 return '$' + me.k.h().hash(me.magic).hash(key).done()
99 def changepp(me):
100 tag = me.db['tag']
101 _C.ppcancel(tag)
102 ppk = PPK(_C.ppread(tag, _C.PMODE_VERIFY),
103 me.k.c.__class__, me.k.h, me.k.m.__class__)
104 me.db['key'] = ppk.encrypt(_wrapstr(me.ck) + _wrapstr(me.mk))
105 me.db['salt'] = ppk.salt
106 def pack(me, key, value):
107 w = _wrapstr(key) + _wrapstr(value)
108 pl = (len(w) + 255) & ~255
109 w += '\0' * (pl - len(w))
110 return me.k.encrypt(w)
111 def unpack(me, p):
112 buf = Buffer(me.k.decrypt(p))
113 key = buf.getstring()
114 value = buf.getstring()
115 return key, value
116 def __getitem__(me, key):
2e6a3fda 117 try:
118 return me.unpack(me.db[me.keyxform(key)])[1]
119 except KeyError:
120 raise KeyError, key
43c09851 121 def __setitem__(me, key, value):
122 me.db[me.keyxform(key)] = me.pack(key, value)
123 def __delitem__(me, key):
2e6a3fda 124 try:
125 del me.db[me.keyxform(key)]
126 except KeyError:
127 raise KeyError, key
43c09851 128 def __iter__(me):
129 return PWIter(me)
130