chiark / gitweb /
Overhaul formatting.
[catacomb-python] / catacomb / pwsafe.py
1 # -*-python-*-
2
3 import catacomb as _C
4 import gdbm as _G
5 import struct as _S
6
7 class DecryptError (Exception):
8   pass
9
10 class 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
33 class 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
42 class 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
62 def _wrapstr(s):
63   return _S.pack('>H', len(s)) + s
64
65 class 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]
79 class 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):
117     try:
118       return me.unpack(me.db[me.keyxform(key)])[1]
119     except KeyError:
120       raise KeyError, key
121   def __setitem__(me, key, value):
122     me.db[me.keyxform(key)] = me.pack(key, value)
123   def __delitem__(me, key):
124     try:
125       del me.db[me.keyxform(key)]
126     except KeyError:
127       raise KeyError, key
128   def __iter__(me):
129     return PWIter(me)
130