3 ### Management of a secure password database
5 ### (c) 2005 Straylight/Edgeware
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of the Python interface to Catacomb.
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.
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.
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.
26 ###--------------------------------------------------------------------------
29 from __future__
import with_statement
36 ###--------------------------------------------------------------------------
37 ### Underlying cryptography.
39 class DecryptError (Exception):
41 I represent a failure to decrypt a message.
43 Usually this means that someone used the wrong key, though it can also
44 mean that a ciphertext has been modified.
48 class Crypto (object):
50 I represent a symmetric crypto transform.
52 There's currently only one transform implemented, which is the obvious
53 generic-composition construction: given a message m, and keys K0 and K1, we
54 choose an IV v, and compute:
56 * y = v || E(K0, v; m)
59 The final ciphertext is t || y.
62 def __init__(me
, c
, h
, m
, ck
, mk
):
64 Initialize the Crypto object with a given algorithm selection and keys.
66 We need a GCipher subclass C, a GHash subclass H, a GMAC subclass M, and
67 keys CK and MK for C and M respectively.
75 Encrypt the message PT and return the resulting ciphertext.
77 blksz
= me
.c
.__class__
.blksz
80 iv
= _C
.rand
.block(blksz
)
83 b
.put(me
.c
.encrypt(pt
))
84 t
= me
.m().hash(b
).done()
85 return t
+ str(buffer(b
))
89 Decrypt the ciphertext CT, returning the plaintext.
91 Raises DecryptError if anything goes wrong.
93 blksz
= me
.c
.__class__
.blksz
94 tagsz
= me
.m
.__class__
.tagsz
104 if t
!= h
.done(): raise DecryptError
105 return me
.c
.decrypt(x
)
109 I represent a crypto transform whose keys are derived from a passphrase.
111 The password is salted and hashed; the salt is available as the `salt'
115 def __init__(me
, pp
, c
, h
, m
, salt
= None):
117 Initialize the PPK object with a passphrase and algorithm selection.
119 We want a passphrase PP, a GCipher subclass C, a GHash subclass H, a GMAC
120 subclass M, and a SALT. The SALT may be None, if we're generating new
121 keys, indicating that a salt should be chosen randomly.
123 if not salt
: salt
= _C
.rand
.block(h
.hashsz
)
124 tag
= '%s\0%s' %
(pp
, salt
)
125 Crypto
.__init__(me
, c
, h
, m
,
126 h().hash('cipher:' + tag
).done(),
127 h().hash('mac:' + tag
).done())
130 ###--------------------------------------------------------------------------
133 class StorageBackendRefusal (Exception):
135 I signify that a StorageBackend subclass has refused to open a file.
137 This is used by the StorageBackend.open class method.
141 class StorageBackendClass (type):
143 I am a metaclass for StorageBackend classes.
145 My main feature is that I register my concrete instances (with a `NAME'
146 which is not `None') with the StorageBackend class.
148 def __init__(me
, name
, supers
, dict):
150 Register a new concrete StorageBackend subclass.
152 super(StorageBackendClass
, me
).__init__(name
, supers
, dict)
153 if me
.NAME
is not None: StorageBackend
.register_concrete_subclass(me
)
155 class StorageBackend (object):
157 I provide basic protocol for password storage backends.
159 I'm an abstract class: you want one of my subclasses if you actually want
160 to do something useful. But I maintain a list of my subclasses and can
161 choose an appropriate one to open a database file you've found lying about.
163 Backends are responsible for storing and retrieving stuff, but not for the
164 cryptographic details. Backends need to store two kinds of information:
166 * metadata, consisting of a number of property names and their values;
169 * password mappings, consisting of a number of binary labels and
172 Backends need to implement the following ordinary methods. See the calling
173 methods for details of the subclass responsibilities.
175 BE._create(FILE) Create a new database in FILE; used by `create'.
177 BE._open(FILE, WRITEP)
178 Open the existing database FILE; used by `open'.
180 BE._close(ABRUPTP) Close the database, freeing up any resources. If
181 ABRUPTP then don't try to commit changes.
183 BE._get_meta(NAME, DEFAULT)
184 Return the value of the metadata item with the given
185 NAME, or DEFAULT if it doesn't exist; used by
188 BE._put_meta(NAME, VALUE)
189 Set the VALUE of the metadata item with the given
190 NAME, creating one if necessary; used by `put_meta'.
192 BE._del_meta(NAME) Forget the metadata item with the given NAME; raise
193 `KeyError' if there is no such item; used by
196 BE._iter_meta() Return an iterator over the metadata (NAME, VALUE)
197 pairs; used by `iter_meta'.
199 BE._get_passwd(LABEL)
200 Return the password payload stored with the (binary)
201 LABEL; used by `get_passwd'.
203 BE._put_passwd(LABEL, PAYLOAD)
204 Associate the (binary) PAYLOAD with the LABEL,
205 forgetting any previous payload for that LABEL; used
208 BE._del_passwd(LABEL) Forget the password record with the given LABEL; used
211 BE._iter_passwds() Return an iterator over the password (LABEL, PAYLOAD)
212 pairs; used by `iter_passwds'.
214 Also, concrete subclasses should define the following class attributes.
216 NAME The name of the backend, so that the user can select
217 it when creating a new database.
219 PRIO An integer priority: backends are tried in decreasing
220 priority order when opening an existing database.
223 __metaclass__
= StorageBackendClass
227 ## The registry of subclasses.
233 def register_concrete_subclass(sub
):
234 """Register a concrete subclass, so that `open' can try it."""
235 StorageBackend
.CLASSES
[sub
.NAME
] = sub
240 Return the concrete subclass with the given NAME.
242 Raise `KeyError' if the name isn't found.
244 return StorageBackend
.CLASSES
[name
]
248 """Return an iterator over the concrete subclasses."""
249 return StorageBackend
.CLASSES
.itervalues()
252 def open(file, writep
= False):
253 """Open a database FILE, using some appropriate backend."""
255 for cls
in sorted(StorageBackend
.CLASSES
.values(), reverse
= True,
256 key
= lambda cls
: cls
.PRIO
):
257 try: return cls(file, writep
)
258 except StorageBackendRefusal
: pass
259 raise StorageBackendRefusal
262 def create(cls
, file):
264 Create a new database in the named FILE, using this backend.
266 Subclasses must implement the `_create' instance method.
268 return cls(writep
= True, _magic
= lambda me
: me
._create(file))
270 def __init__(me
, file = None, writep
= False, _magic
= None, *args
, **kw
):
274 Subclasses are not, in general, expected to override this: there's a
275 somewhat hairy protocol between the constructor and some of the class
276 methods. Instead, the main hook for customization is the subclass's
277 `_open' method, which is invoked in the usual case.
279 super(StorageBackend
, me
).__init__(*args
, **kw
)
280 if me
.NAME
is None: raise ValueError, 'abstract class'
281 if _magic
is not None: _magic(me
)
282 elif file is None: raise ValueError, 'missing file parameter'
283 else: me
._open(file, writep
)
287 def close(me
, abruptp
= False):
291 It is harmless to attempt to close a database which has been closed
292 already. Calls the subclass's `_close' method.
301 """Raise an error if the receiver has been closed."""
302 if not me
._livep
: raise ValueError, 'database is closed'
304 def _check_write(me
):
305 """Raise an error if the receiver is not open for writing."""
307 if not me
._writep
: raise ValueError, 'database is read-only'
309 def _check_meta_name(me
, name
):
311 Raise an error unless NAME is a valid name for a metadata item.
313 Metadata names may not start with `$': such names are reserved for
316 if name
.startswith('$'):
317 raise ValueError, "invalid metadata key `%s'" % name
322 """Context protocol: make sure the database is closed on exit."""
324 def __exit__(me
, exctype
, excvalue
, exctb
):
325 """Context protocol: see `__enter__'."""
326 me
.close(excvalue
is not None)
330 def get_meta(me
, name
, default
= FAIL
):
332 Fetch the value for the metadata item NAME.
334 If no such item exists, then return DEFAULT if that was set; otherwise
337 This calls the subclass's `_get_meta' method, which should return the
338 requested item or return the given DEFAULT value. It may assume that the
339 name is valid and the database is open.
341 me
._check_meta_name(name
)
343 value
= me
._get_meta(name
, default
)
344 if value
is StorageBackend
.FAIL
: raise KeyError, name
347 def put_meta(me
, name
, value
):
349 Store VALUE in the metadata item called NAME.
351 This calls the subclass's `_put_meta' method, which may assume that the
352 name is valid and the database is open for writing.
354 me
._check_meta_name(name
)
356 me
._put_meta(name
, value
)
358 def del_meta(me
, name
):
360 Forget about the metadata item with the given NAME.
362 This calls the subclass's `_del_meta' method, which may assume that the
363 name is valid and the database is open for writing.
365 me
._check_meta_name(name
)
371 Return an iterator over the name/value metadata items.
373 This calls the subclass's `_iter_meta' method, which may assume that the
377 return me
._iter_meta()
379 def get_passwd(me
, label
):
381 Fetch and return the payload stored with the (opaque, binary) LABEL.
383 If there is no such payload then raise `KeyError'.
385 This calls the subclass's `_get_passwd' method, which may assume that the
389 return me
._get_passwd(label
)
391 def put_passwd(me
, label
, payload
):
393 Associate the (opaque, binary) PAYLOAD with the (opaque, binary) LABEL.
395 Any previous payload for LABEL is forgotten.
397 This calls the subclass's `_put_passwd' method, which may assume that the
398 database is open for writing.
401 me
._put_passwd(label
, payload
)
403 def del_passwd(me
, label
):
405 Forget any PAYLOAD associated with the (opaque, binary) LABEL.
407 If there is no such payload then raise `KeyError'.
409 This calls the subclass's `_del_passwd' method, which may assume that the
410 database is open for writing.
413 me
._del_passwd(label
, payload
)
415 def iter_passwds(me
):
417 Return an iterator over the stored password label/payload pairs.
419 This calls the subclass's `_iter_passwds' method, which may assume that
420 the database is open.
423 return me
._iter_passwds()
425 class GDBMStorageBackend (StorageBackend
):
427 My instances store password data in a GDBM database.
429 Metadata and password entries are mixed into the same database. The key
430 for a metadata item is simply its name; the key for a password entry is
431 the entry's label prefixed by `$', since we're guaranteed that no
432 metadata item name begins with `$'.
437 def _open(me
, file, writep
):
438 try: me
._db
= _G
.open(file, writep
and 'w' or 'r')
439 except _G
.error
, e
: raise StorageBackendRefusal
, e
441 def _create(me
, file):
442 me
._db
= _G
.open(file, 'n', 0600)
444 def _close(me
, abruptp
):
448 def _get_meta(me
, name
, default
):
449 try: return me
._db
[name
]
450 except KeyError: return default
452 def _put_meta(me
, name
, value
):
455 def _del_meta(me
, name
):
459 k
= me
._db
.firstkey()
461 if not k
.startswith('$'): yield k
, me
._db
[k
]
462 k
= me
._db
.nextkey(k
)
464 def _get_passwd(me
, label
):
465 return me
._db
['$' + label
]
467 def _put_passwd(me
, label
, payload
):
468 me
._db
['$' + label
] = payload
470 def _del_passwd(me
, label
):
471 del me
._db
['$' + label
]
473 def _iter_passwds(me
):
474 k
= me
._db
.firstkey()
476 if k
.startswith('$'): yield k
[1:], me
._db
[k
]
477 k
= me
._db
.nextkey(k
)
479 ###--------------------------------------------------------------------------
480 ### Password storage.
484 I represent a secure (ish) password store.
486 I can store short secrets, associated with textual names, in a way which
487 doesn't leak too much information about them.
489 I implement (some of) the Python mapping protocol.
491 I keep track of everything using a StorageBackend object. This contains
492 password entries, identified by cryptographic labels, and a number of
495 cipher Names the Catacomb cipher selected.
497 hash Names the Catacomb hash function selected.
499 key Cipher and MAC keys, each prefixed by a 16-bit big-endian
500 length and concatenated, encrypted using the master
503 mac Names the Catacomb message authentication code selected.
505 magic A magic string for obscuring password tag names.
507 salt The salt for hashing the passphrase.
509 tag The master passphrase's tag, for the Pixie's benefit.
511 Password entries are assigned labels of the form `$' || H(MAGIC || TAG);
512 the corresponding value consists of a pair (TAG, PASSWD), prefixed with
513 16-bit lengths, concatenated, padded to a multiple of 256 octets, and
514 encrypted using the stored keys.
517 def __init__(me
, file, writep
= False):
519 Initialize a PW object from the database in FILE.
521 If WRITEP is false (the default) then the database is opened read-only;
522 if true then it may be written. Requests the database password from the
523 Pixie, which may cause interaction.
526 ## Open the database.
527 me
.db
= StorageBackend
.open(file, writep
)
529 ## Find out what crypto to use.
530 c
= _C
.gcciphers
[me
.db
.get_meta('cipher')]
531 h
= _C
.gchashes
[me
.db
.get_meta('hash')]
532 m
= _C
.gcmacs
[me
.db
.get_meta('mac')]
534 ## Request the passphrase and extract the master keys.
535 tag
= me
.db
.get_meta('tag')
536 ppk
= PPK(_C
.ppread(tag
), c
, h
, m
, me
.db
.get_meta('salt'))
538 b
= _C
.ReadBuffer(ppk
.decrypt(me
.db
.get_meta('key')))
544 if not b
.endp
: raise ValueError, 'trailing junk'
546 ## Set the key, and stash it and the tag-hashing secret.
547 me
.k
= Crypto(c
, h
, m
, me
.ck
, me
.mk
)
548 me
.magic
= me
.k
.decrypt(me
.db
.get_meta('magic'))
551 def create(cls
, dbcls
, file, tag
, c
, h
, m
):
553 Create and initialize a new database FILE using StorageBackend DBCLS.
555 We want a GCipher subclass C, a GHash subclass H, and a GMAC subclass M;
556 and a Pixie passphrase TAG.
558 This doesn't return a working object: it just creates the database file
559 and gets out of the way.
562 ## Set up the cryptography.
563 pp
= _C
.ppread(tag
, _C
.PMODE_VERIFY
)
564 ppk
= PPK(pp
, c
, h
, m
)
565 ck
= _C
.rand
.block(c
.keysz
.default
)
566 mk
= _C
.rand
.block(c
.keysz
.default
)
567 k
= Crypto(c
, h
, m
, ck
, mk
)
569 ## Set up and initialize the database.
570 kct
= ppk
.encrypt(_C
.WriteBuffer().putblk16(ck
).putblk16(mk
))
571 with dbcls
.create(file) as db
:
572 db
.put_meta('tag', tag
)
573 db
.put_meta('salt', ppk
.salt
)
574 db
.put_meta('cipher', c
.name
)
575 db
.put_meta('hash', h
.name
)
576 db
.put_meta('mac', m
.name
)
577 db
.put_meta('key', kct
)
578 db
.put_meta('magic', k
.encrypt(_C
.rand
.block(h
.hashsz
)))
580 def keyxform(me
, key
):
581 """Transform the KEY (actually a password tag) into a password label."""
582 return me
.k
.h().hash(me
.magic
).hash(key
).done()
586 Change the database password.
588 Requests the new password from the Pixie, which will probably cause
591 tag
= me
.db
.get_meta('tag')
593 ppk
= PPK(_C
.ppread(tag
, _C
.PMODE_VERIFY
),
594 me
.k
.c
.__class__
, me
.k
.h
, me
.k
.m
.__class__
)
595 kct
= ppk
.encrypt(_C
.WriteBuffer().putblk16(me
.ck
).putblk16(me
.mk
))
596 me
.db
.put_meta('key', kct
)
597 me
.db
.put_meta('salt', ppk
.salt
)
599 def pack(me
, key
, value
):
600 """Pack the KEY and VALUE into a ciphertext, and return it."""
602 b
.putblk16(key
).putblk16(value
)
603 b
.zero(((b
.size
+ 255) & ~
255) - b
.size
)
604 return me
.k
.encrypt(b
)
608 Unpack a ciphertext CT and return a (KEY, VALUE) pair.
610 Might raise DecryptError, of course.
612 b
= _C
.ReadBuffer(me
.k
.decrypt(ct
))
619 def __getitem__(me
, key
):
620 """Return the password for the given KEY."""
621 try: return me
.unpack(me
.db
.get_passwd(me
.keyxform(key
)))[1]
622 except KeyError: raise KeyError, key
624 def __setitem__(me
, key
, value
):
625 """Associate the password VALUE with the KEY."""
626 me
.db
.put_passwd(me
.keyxform(key
), me
.pack(key
, value
))
628 def __delitem__(me
, key
):
629 """Forget all about the KEY."""
630 try: me
.db
.del_passwd(me
.keyxform(key
))
631 except KeyError: raise KeyError, key
634 """Iterate over the known password tags."""
635 for _
, pld
in me
.db
.iter_passwds():
636 yield me
.unpack(pld
)[0]
642 def __exit__(me
, excty
, excval
, exctb
):
643 me
.db
.close(excval
is not None)
645 ###----- That's all, folks --------------------------------------------------