6 from sys import argv, exit, stdin, stdout, stderr
7 from getopt import getopt, GetoptError
9 from fnmatch import fnmatch
11 if 'PWSAFE' in environ:
12 file = environ['PWSAFE']
14 file = '%s/.pwsafe' % environ['HOME']
16 class DecryptError (Exception):
19 class Crypto (object):
20 def __init__(me, c, h, m, ck, mk):
25 if me.c.__class__.blksz:
26 iv = C.rand.block(me.c.__class__.blksz)
30 y = iv + me.c.encrypt(pt)
31 t = me.m().hash(y).done()
34 t = ct[:me.m.__class__.tagsz]
35 y = ct[me.m.__class__.tagsz:]
36 if t != me.m().hash(y).done():
38 iv = y[:me.c.__class__.blksz]
39 if me.c.__class__.blksz: me.c.setiv(iv)
40 return me.c.decrypt(y[me.c.__class__.blksz:])
43 def __init__(me, pp, c, h, m, salt = None):
44 if not salt: salt = C.rand.block(h.hashsz)
45 tag = '%s\0%s' % (pp, salt)
46 Crypto.__init__(me, c, h, m,
47 h().hash('cipher:' + tag).done(),
48 h().hash('mac:' + tag).done())
51 class Buffer (object):
57 if n + i > len(me.str):
58 raise IndexError, 'buffer underflow'
60 return me.str[i:i + n]
64 return struct.unpack(fmt, me.get(struct.calcsize(fmt)))
66 return me.get(me.unpack('>H')[0])
68 if me.i != len(me.str):
69 raise ValueError, 'junk at end of buffer'
72 return struct.pack('>H', len(s)) + s
74 class PWIter (object):
77 me.k = me.pw.db.firstkey()
85 k = me.pw.db.nextkey(k)
86 me.k = me.pw.db.nextkey(k)
87 return me.pw.unpack(me.pw.db[k])[0]
89 def __init__(me, file, mode = 'r'):
90 me.db = gdbm.open(file, mode)
91 c = C.gcciphers[me.db['cipher']]
92 h = C.gchashes[me.db['hash']]
93 m = C.gcmacs[me.db['mac']]
95 ppk = PPK(C.ppread(tag), c, h, m, me.db['salt'])
97 buf = Buffer(ppk.decrypt(me.db['key']))
101 me.ck = buf.getstring()
102 me.mk = buf.getstring()
104 me.k = Crypto(c, h, m, me.ck, me.mk)
105 me.magic = me.k.decrypt(me.db['magic'])
106 def keyxform(me, key):
107 return '$' + me.k.h().hash(me.magic).hash(key).done()
111 ppk = PPK(C.ppread(tag, C.PMODE_VERIFY),
112 me.k.c.__class__, me.k.h, me.k.m.__class__)
113 me.db['key'] = ppk.encrypt(wrapstr(me.ck) + wrapstr(me.mk))
114 me.db['salt'] = ppk.salt
115 def pack(me, key, value):
116 w = wrapstr(key) + wrapstr(value)
117 pl = (len(w) + 255) & ~255
118 w += '\0' * (pl - len(w))
119 return me.k.encrypt(w)
121 buf = Buffer(me.k.decrypt(p))
122 key = buf.getstring()
123 value = buf.getstring()
125 def __getitem__(me, key):
126 return me.unpack(me.db[me.keyxform(key)])[1]
127 def __setitem__(me, key, value):
128 me.db[me.keyxform(key)] = me.pack(key, value)
129 def __delitem__(me, key):
130 del me.db[me.keyxform(key)]
135 cipher = 'blowfish-cbc'
139 opts, args = getopt(av, 'c:h:m:', ['cipher=', 'mac=', 'hash='])
143 if o in ('-c', '--cipher'):
145 elif o in ('-m', '--mac'):
147 elif o in ('-h', '--hash'):
157 db = gdbm.open(file, 'n', 0600)
158 pp = C.ppread(tag, C.PMODE_VERIFY)
159 if not mac: mac = hash + '-hmac'
160 c = C.gcciphers[cipher]
163 ppk = PPK(pp, c, h, m)
164 ck = C.rand.block(c.keysz.default)
165 mk = C.rand.block(m.keysz.default)
166 k = Crypto(c, h, m, ck, mk)
168 db['salt'] = ppk.salt
169 db['cipher'] = cipher
172 db['key'] = ppk.encrypt(wrapstr(ck) + wrapstr(mk))
173 db['magic'] = k.encrypt(C.rand.block(h.hashsz))
175 def cmd_changepp(av):
188 if len(av) < 1 or len(av) > 2:
192 pp = C.getpass("Enter passphrase `%s': " % tag)
193 vpp = C.getpass("Confirm passphrase `%s': " % tag)
195 raise ValueError, "passphrases don't match"
197 pp = stdin.readline()
204 if len(av) < 1 or len(av) > 2:
207 pw_out = PW(av[0], 'w')
213 if pat is None or fnmatch(k, pat):
225 if pat is None or fnmatch(k, pat):
229 if len(av) < 1 or len(av) > 2:
237 C.Pixie().set(pptag, pw[tag])
248 if ch < ' ' or ch > '~': return False
251 if asciip(s): return s
252 return C.ByteString(s)
254 db = gdbm.open(file, 'r')
258 print '%r: %r' % (present(k), present(db[k]))
261 commands = { 'create': [cmd_create,
262 '[-c CIPHER] [-h HASH] [-m MAC] [PP-TAG]'],
263 'find' : [cmd_find, 'LABEL'],
264 'store' : [cmd_store, 'LABEL [VALUE]'],
265 'list' : [cmd_list, '[GLOB-PATTERN]'],
266 'changepp' : [cmd_changepp, ''],
267 'copy' : [cmd_copy, 'DEST-FILE [GLOB-PATTERN]'],
268 'to-pixie' : [cmd_topixie, 'TAG [PIXIE-TAG]'],
269 'delete' : [cmd_del, 'TAG'],
270 'dump' : [cmd_dump, '']}
275 print >>fp, 'Usage: pwsafe COMMAND [ARGS...]'
281 Maintains passwords or other short secrets securely.
285 -h, --help Show this help text.
286 -v, --version Show program version number.
287 -u, --usage Show short usage message.
292 print '%s %s' % (c, commands[c][1])
295 opts, argv = getopt(argv[1:],
297 ['help', 'version', 'usage', 'file='])
302 if o in ('-h', '--help'):
305 elif o in ('-v', '--version'):
308 elif o in ('-u', '--usage'):
311 elif o in ('-f', '--file'):
319 if argv[0] in commands:
324 if commands[c][0](argv):
325 print >>stderr, 'Usage: pwsafe %s %s' % (c, commands[c][1])