chiark / gitweb /
67b249e747bd11ef1304287501010a915ac1e329
[catacomb-python] / catacomb / pwsafe.py
1 ### -*-python-*-
2 ###
3 ### Management of a secure password database
4 ###
5 ### (c) 2005 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 modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
16 ###
17 ### Catacomb/Python is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ### GNU General Public License for more details.
21 ###
22 ### You should have received a copy of the GNU General Public License along
23 ### with Catacomb/Python; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 ###--------------------------------------------------------------------------
27 ### Imported modules.
28
29 from __future__ import with_statement
30
31 import catacomb as _C
32 import gdbm as _G
33
34 ###--------------------------------------------------------------------------
35 ### Underlying cryptography.
36
37 class DecryptError (Exception):
38   """
39   I represent a failure to decrypt a message.
40
41   Usually this means that someone used the wrong key, though it can also
42   mean that a ciphertext has been modified.
43   """
44   pass
45
46 class Crypto (object):
47   """
48   I represent a symmetric crypto transform.
49
50   There's currently only one transform implemented, which is the obvious
51   generic-composition construction: given a message m, and keys K0 and K1, we
52   choose an IV v, and compute:
53
54     * y = v || E(K0, v; m)
55     * t = M(K1; y)
56
57   The final ciphertext is t || y.
58   """
59
60   def __init__(me, c, h, m, ck, mk):
61     """
62     Initialize the Crypto object with a given algorithm selection and keys.
63
64     We need a GCipher subclass C, a GHash subclass H, a GMAC subclass M, and
65     keys CK and MK for C and M respectively.
66     """
67     me.c = c(ck)
68     me.m = m(mk)
69     me.h = h
70
71   def encrypt(me, pt):
72     """
73     Encrypt the message PT and return the resulting ciphertext.
74     """
75     blksz = me.c.__class__.blksz
76     b = _C.WriteBuffer()
77     if blksz:
78       iv = _C.rand.block(blksz)
79       me.c.setiv(iv)
80       b.put(iv)
81     b.put(me.c.encrypt(pt))
82     t = me.m().hash(b).done()
83     return t + str(buffer(b))
84
85   def decrypt(me, ct):
86     """
87     Decrypt the ciphertext CT, returning the plaintext.
88
89     Raises DecryptError if anything goes wrong.
90     """
91     blksz = me.c.__class__.blksz
92     tagsz = me.m.__class__.tagsz
93     b = _C.ReadBuffer(ct)
94     t = b.get(tagsz)
95     h = me.m()
96     if blksz:
97       iv = b.get(blksz)
98       me.c.setiv(iv)
99       h.hash(iv)
100     x = b.get(b.left)
101     h.hash(x)
102     if t != h.done(): raise DecryptError
103     return me.c.decrypt(x)
104
105 class PPK (Crypto):
106   """
107   I represent a crypto transform whose keys are derived from a passphrase.
108
109   The password is salted and hashed; the salt is available as the `salt'
110   attribute.
111   """
112
113   def __init__(me, pp, c, h, m, salt = None):
114     """
115     Initialize the PPK object with a passphrase and algorithm selection.
116
117     We want a passphrase PP, a GCipher subclass C, a GHash subclass H, a GMAC
118     subclass M, and a SALT.  The SALT may be None, if we're generating new
119     keys, indicating that a salt should be chosen randomly.
120     """
121     if not salt: salt = _C.rand.block(h.hashsz)
122     tag = '%s\0%s' % (pp, salt)
123     Crypto.__init__(me, c, h, m,
124                     h().hash('cipher:' + tag).done(),
125                     h().hash('mac:' + tag).done())
126     me.salt = salt
127
128 ###--------------------------------------------------------------------------
129 ### Backend storage.
130
131 class StorageBackend (object):
132   """
133   I provide basic protocol for password storage backends.
134
135   I'm an abstract class: you want one of my subclasses if you actually want
136   to do something useful.
137
138   Backends are responsible for storing and retrieving stuff, but not for the
139   cryptographic details.  Backends need to store two kinds of information:
140
141     * metadata, consisting of a number of property names and their values;
142       and
143
144     * password mappings, consisting of a number of binary labels and
145       payloads.
146
147   Backends need to implement the following ordinary methods.  See the calling
148   methods for details of the subclass responsibilities.
149
150   BE._create(FILE)      Create a new database in FILE; used by `create'.
151
152   BE._open(FILE, WRITEP)
153                         Open the existing database FILE; used by `open'.
154
155   BE._close()           Close the database, freeing up any resources.
156
157   BE._get_meta(NAME, DEFAULT)
158                         Return the value of the metadata item with the given
159                         NAME, or DEFAULT if it doesn't exist; used by
160                         `get_meta'.
161
162   BE._put_meta(NAME, VALUE)
163                         Set the VALUE of the metadata item with the given
164                         NAME, creating one if necessary; used by `put_meta'.
165
166   BE._del_meta(NAME)    Forget the metadata item with the given NAME; raise
167                         `KeyError' if there is no such item; used by
168                         `del_meta'.
169
170   BE._iter_meta()       Return an iterator over the metadata (NAME, VALUE)
171                         pairs; used by `iter_meta'.
172
173   BE._get_passwd(LABEL)
174                         Return the password payload stored with the (binary)
175                         LABEL; used by `get_passwd'.
176
177   BE._put_passwd(LABEL, PAYLOAD)
178                         Associate the (binary) PAYLOAD with the LABEL,
179                         forgetting any previous payload for that LABEL; used
180                         by `put_passwd'.
181
182   BE._del_passwd(LABEL) Forget the password record with the given LABEL; used
183                         by `_del_passwd'.
184
185   BE._iter_passwds()    Return an iterator over the password (LABEL, PAYLOAD)
186                         pairs; used by `iter_passwds'.
187   """
188
189   FAIL = ['FAIL']
190
191   ## Life cycle methods.
192
193   @classmethod
194   def create(cls, file):
195     """
196     Create a new database in the named FILE, using this backend.
197
198     Subclasses must implement the `_create' instance method.
199     """
200     return cls(writep = True, _magic = lambda me: me._create(file))
201
202   def __init__(me, file = None, writep = False, _magic = None, *args, **kw):
203     """
204     Main constructor.
205
206     Subclasses are not, in general, expected to override this: there's a
207     somewhat hairy protocol between the constructor and some of the class
208     methods.  Instead, the main hook for customization is the subclass's
209     `_open' method, which is invoked in the usual case.
210     """
211     super(StorageBackend, me).__init__(*args, **kw)
212     if _magic is not None: _magic(me)
213     elif file is None: raise ValueError, 'missing file parameter'
214     else: me._open(file, writep)
215     me._writep = writep
216     me._livep = True
217
218   def close(me):
219     """
220     Close the database.
221
222     It is harmless to attempt to close a database which has been closed
223     already.  Calls the subclass's `_close' method.
224     """
225     if me._livep:
226       me._livep = False
227       me._close()
228
229   ## Utilities.
230
231   def _check_live(me):
232     """Raise an error if the receiver has been closed."""
233     if not me._livep: raise ValueError, 'database is closed'
234
235   def _check_write(me):
236     """Raise an error if the receiver is not open for writing."""
237     me._check_live()
238     if not me._writep: raise ValueError, 'database is read-only'
239
240   def _check_meta_name(me, name):
241     """
242     Raise an error unless NAME is a valid name for a metadata item.
243
244     Metadata names may not start with `$': such names are reserved for
245     password storage.
246     """
247     if name.startswith('$'):
248       raise ValueError, "invalid metadata key `%s'" % name
249
250   ## Context protocol.
251
252   def __enter__(me):
253     """Context protocol: make sure the database is closed on exit."""
254     return me
255   def __exit__(me, exctype, excvalue, exctb):
256     """Context protocol: see `__enter__'."""
257     me.close()
258
259   ## Metadata.
260
261   def get_meta(me, name, default = FAIL):
262     """
263     Fetch the value for the metadata item NAME.
264
265     If no such item exists, then return DEFAULT if that was set; otherwise
266     raise a `KeyError'.
267
268     This calls the subclass's `_get_meta' method, which should return the
269     requested item or return the given DEFAULT value.  It may assume that the
270     name is valid and the database is open.
271     """
272     me._check_meta_name(name)
273     me._check_live()
274     value = me._get_meta(name, default)
275     if value is StorageBackend.FAIL: raise KeyError, name
276     return value
277
278   def put_meta(me, name, value):
279     """
280     Store VALUE in the metadata item called NAME.
281
282     This calls the subclass's `_put_meta' method, which may assume that the
283     name is valid and the database is open for writing.
284     """
285     me._check_meta_name(name)
286     me._check_write()
287     me._put_meta(name, value)
288
289   def del_meta(me, name):
290     """
291     Forget about the metadata item with the given NAME.
292
293     This calls the subclass's `_del_meta' method, which may assume that the
294     name is valid and the database is open for writing.
295     """
296     me._check_meta_name(name)
297     me._check_write()
298     me._del_meta(name)
299
300   def iter_meta(me):
301     """
302     Return an iterator over the name/value metadata items.
303
304     This calls the subclass's `_iter_meta' method, which may assume that the
305     database is open.
306     """
307     me._check_live()
308     return me._iter_meta()
309
310   def get_passwd(me, label):
311     """
312     Fetch and return the payload stored with the (opaque, binary) LABEL.
313
314     If there is no such payload then raise `KeyError'.
315
316     This calls the subclass's `_get_passwd' method, which may assume that the
317     database is open.
318     """
319     me._check_live()
320     return me._get_passwd(label)
321
322   def put_passwd(me, label, payload):
323     """
324     Associate the (opaque, binary) PAYLOAD with the (opaque, binary) LABEL.
325
326     Any previous payload for LABEL is forgotten.
327
328     This calls the subclass's `_put_passwd' method, which may assume that the
329     database is open for writing.
330     """
331     me._check_write()
332     me._put_passwd(label, payload)
333
334   def del_passwd(me, label):
335     """
336     Forget any PAYLOAD associated with the (opaque, binary) LABEL.
337
338     If there is no such payload then raise `KeyError'.
339
340     This calls the subclass's `_del_passwd' method, which may assume that the
341     database is open for writing.
342     """
343     me._check_write()
344     me._del_passwd(label, payload)
345
346   def iter_passwds(me):
347     """
348     Return an iterator over the stored password label/payload pairs.
349
350     This calls the subclass's `_iter_passwds' method, which may assume that
351     the database is open.
352     """
353     me._check_live()
354     return me._iter_passwds()
355
356 class GDBMStorageBackend (StorageBackend):
357   """
358   My instances store password data in a GDBM database.
359
360   Metadata and password entries are mixed into the same database.  The key
361   for a metadata item is simply its name; the key for a password entry is
362   the entry's label prefixed by `$', since we're guaranteed that no
363   metadata item name begins with `$'.
364   """
365
366   def _open(me, file, writep):
367     try: me._db = _G.open(file, writep and 'w' or 'r')
368     except _G.error, e: raise StorageBackendRefusal, e
369
370   def _create(me, file):
371     me._db = _G.open(file, 'n', 0600)
372
373   def _close(me):
374     me._db.close()
375     me._db = None
376
377   def _get_meta(me, name, default):
378     try: return me._db[name]
379     except KeyError: return default
380
381   def _put_meta(me, name, value):
382     me._db[name] = value
383
384   def _del_meta(me, name):
385     del me._db[name]
386
387   def _iter_meta(me):
388     k = me._db.firstkey()
389     while k is not None:
390       if not k.startswith('$'): yield k, me._db[k]
391       k = me._db.nextkey(k)
392
393   def _get_passwd(me, label):
394     return me._db['$' + label]
395
396   def _put_passwd(me, label, payload):
397     me._db['$' + label] = payload
398
399   def _del_passwd(me, label):
400     del me._db['$' + label]
401
402   def _iter_passwds(me):
403     k = me._db.firstkey()
404     while k is not None:
405       if k.startswith('$'): yield k[1:], me._db[k]
406       k = me._db.nextkey(k)
407
408 ###--------------------------------------------------------------------------
409 ### Password storage.
410
411 class PW (object):
412   """
413   I represent a secure (ish) password store.
414
415   I can store short secrets, associated with textual names, in a way which
416   doesn't leak too much information about them.
417
418   I implement (some of) the Python mapping protocol.
419
420   I keep track of everything using a StorageBackend object.  This contains
421   password entries, identified by cryptographic labels, and a number of
422   metadata items.
423
424   cipher        Names the Catacomb cipher selected.
425
426   hash          Names the Catacomb hash function selected.
427
428   key           Cipher and MAC keys, each prefixed by a 16-bit big-endian
429                 length and concatenated, encrypted using the master
430                 passphrase.
431
432   mac           Names the Catacomb message authentication code selected.
433
434   magic         A magic string for obscuring password tag names.
435
436   salt          The salt for hashing the passphrase.
437
438   tag           The master passphrase's tag, for the Pixie's benefit.
439
440   Password entries are assigned labels of the form `$' || H(MAGIC || TAG);
441   the corresponding value consists of a pair (TAG, PASSWD), prefixed with
442   16-bit lengths, concatenated, padded to a multiple of 256 octets, and
443   encrypted using the stored keys.
444   """
445
446   def __init__(me, file, writep = False):
447     """
448     Initialize a PW object from the database in FILE.
449
450     If WRITEP is false (the default) then the database is opened read-only;
451     if true then it may be written.  Requests the database password from the
452     Pixie, which may cause interaction.
453     """
454
455     ## Open the database.
456     me.db = GDBMStorageBackend(file, writep)
457
458     ## Find out what crypto to use.
459     c = _C.gcciphers[me.db.get_meta('cipher')]
460     h = _C.gchashes[me.db.get_meta('hash')]
461     m = _C.gcmacs[me.db.get_meta('mac')]
462
463     ## Request the passphrase and extract the master keys.
464     tag = me.db.get_meta('tag')
465     ppk = PPK(_C.ppread(tag), c, h, m, me.db.get_meta('salt'))
466     try:
467       b = _C.ReadBuffer(ppk.decrypt(me.db.get_meta('key')))
468     except DecryptError:
469       _C.ppcancel(tag)
470       raise
471     me.ck = b.getblk16()
472     me.mk = b.getblk16()
473     if not b.endp: raise ValueError, 'trailing junk'
474
475     ## Set the key, and stash it and the tag-hashing secret.
476     me.k = Crypto(c, h, m, me.ck, me.mk)
477     me.magic = me.k.decrypt(me.db.get_meta('magic'))
478
479   @classmethod
480   def create(cls, file, tag, c, h, m):
481     """
482     Create and initialize a new database FILE.
483
484     We want a GCipher subclass C, a GHash subclass H, and a GMAC subclass M;
485     and a Pixie passphrase TAG.
486
487     This doesn't return a working object: it just creates the database file
488     and gets out of the way.
489     """
490
491     ## Set up the cryptography.
492     pp = _C.ppread(tag, _C.PMODE_VERIFY)
493     ppk = PPK(pp, c, h, m)
494     ck = _C.rand.block(c.keysz.default)
495     mk = _C.rand.block(c.keysz.default)
496     k = Crypto(c, h, m, ck, mk)
497
498     ## Set up and initialize the database.
499     kct = ppk.encrypt(_C.WriteBuffer().putblk16(ck).putblk16(mk))
500     with GDBM.StorageBackend.create(file) as db:
501       db.put_meta('tag', tag)
502       db.put_meta('salt', ppk.salt)
503       db.put_meta('cipher', c.name)
504       db.put_meta('hash', h.name)
505       db.put_meta('mac', m.name)
506       db.put_meta('key', kct)
507       db.put_meta('magic', k.encrypt(_C.rand.block(h.hashsz)))
508
509   def keyxform(me, key):
510     """Transform the KEY (actually a password tag) into a password label."""
511     return me.k.h().hash(me.magic).hash(key).done()
512
513   def changepp(me):
514     """
515     Change the database password.
516
517     Requests the new password from the Pixie, which will probably cause
518     interaction.
519     """
520     tag = me.db.get_meta('tag')
521     _C.ppcancel(tag)
522     ppk = PPK(_C.ppread(tag, _C.PMODE_VERIFY),
523               me.k.c.__class__, me.k.h, me.k.m.__class__)
524     kct = ppk.encrypt(_C.WriteBuffer().putblk16(me.ck).putblk16(me.mk))
525     me.db.put_meta('key', kct)
526     me.db.put_meta('salt', ppk.salt)
527
528   def pack(me, key, value):
529     """Pack the KEY and VALUE into a ciphertext, and return it."""
530     b = _C.WriteBuffer()
531     b.putblk16(key).putblk16(value)
532     b.zero(((b.size + 255) & ~255) - b.size)
533     return me.k.encrypt(b)
534
535   def unpack(me, ct):
536     """
537     Unpack a ciphertext CT and return a (KEY, VALUE) pair.
538
539     Might raise DecryptError, of course.
540     """
541     b = _C.ReadBuffer(me.k.decrypt(ct))
542     key = b.getblk16()
543     value = b.getblk16()
544     return key, value
545
546   ## Mapping protocol.
547
548   def __getitem__(me, key):
549     """Return the password for the given KEY."""
550     try: return me.unpack(me.db.get_passwd(me.keyxform(key)))[1]
551     except KeyError: raise KeyError, key
552
553   def __setitem__(me, key, value):
554     """Associate the password VALUE with the KEY."""
555     me.db.put_passwd(me.keyxform(key), me.pack(key, value))
556
557   def __delitem__(me, key):
558     """Forget all about the KEY."""
559     try: me.db.del_passwd(me.keyxform(key))
560     except KeyError: raise KeyError, key
561
562   def __iter__(me):
563     """Iterate over the known password tags."""
564     for _, pld in me.db.iter_passwds():
565       yield me.unpack(pld)[0]
566
567   ## Context protocol.
568
569   def __enter__(me):
570     return me
571   def __exit__(me, excty, excval, exctb):
572     me.db.close()
573
574 ###----- That's all, folks --------------------------------------------------