6874ae8d1e730fd45c41abc8c36221211b038527
[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 errno as _E
32 import os as _OS
33 from cStringIO import StringIO as _StringIO
34
35 import catacomb as _C
36
37 ###--------------------------------------------------------------------------
38 ### Python version portability.
39
40 def _iterkeys(dict): return dict.iterkeys()
41 def _itervalues(dict): return dict.itervalues()
42 def _iteritems(dict): return dict.iteritems()
43
44 def _bin(text): return text
45 def _text(bin): return bin
46
47 _NUL = _bin('\0')
48 _CIPHER = _bin('cipher:')
49 _MAC = _bin('mac:')
50
51 def _excval(): return _SYS.exc_info()[1]
52
53 ###--------------------------------------------------------------------------
54 ### Text encoding utilities.
55
56 def _literalp(s):
57 """
58 Answer whether S can be represented literally.
59
60 If True, then S can be stored literally, as a metadata item name or
61 value; if False, then S requires some kind of encoding.
62 """
63 return all(ch.isalnum() or ch in '-_:' for ch in s)
64
65 def _enc_metaname(name):
66 """Encode NAME as a metadata item name, returning the result."""
67 if _literalp(name):
68 return name
69 else:
70 sio = _StringIO()
71 sio.write('!')
72 for ch in name:
73 if _literalp(ch): sio.write(ch)
74 elif ch == ' ': sio.write('+')
75 else: sio.write('%%%02x' % ord(ch))
76 return sio.getvalue()
77
78 def _dec_metaname(name):
79 """Decode NAME as a metadata item name, returning the result."""
80 if not name.startswith('!'):
81 return name
82 else:
83 sio = _StringIO()
84 i, n = 1, len(name)
85 while i < n:
86 ch = name[i]
87 i += 1
88 if ch == '+':
89 sio.write(' ')
90 elif ch == '%':
91 sio.write(chr(int(name[i:i + 2], 16)))
92 i += 2
93 else:
94 sio.write(ch)
95 return sio.getvalue()
96
97 def _b64(s):
98 """Encode S as base64, without newlines, and trimming `=' padding."""
99 return s.encode('base64').replace('\n', '').rstrip('=')
100 def _unb64(s):
101 """Decode S as base64 with trimmed `=' padding."""
102 return (s + '='*((4 - len(s))%4)).decode('base64')
103
104 def _enc_metaval(val):
105 """Encode VAL as a metadata item value, returning the result."""
106 if _literalp(val): return val
107 else: return '?' + _b64(val)
108
109 def _dec_metaval(val):
110 """Decode VAL as a metadata item value, returning the result."""
111 if not val.startswith('?'): return val
112 else: return _unb64(val[1:])
113
114 ###--------------------------------------------------------------------------
115 ### Underlying cryptography.
116
117 class DecryptError (Exception):
118 """
119 I represent a failure to decrypt a message.
120
121 Usually this means that someone used the wrong key, though it can also
122 mean that a ciphertext has been modified.
123 """
124 pass
125
126 class Crypto (object):
127 """
128 I represent a symmetric crypto transform.
129
130 There's currently only one transform implemented, which is the obvious
131 generic-composition construction: given a message m, and keys K0 and K1, we
132 choose an IV v, and compute:
133
134 * y = v || E(K0, v; m)
135 * t = M(K1; y)
136
137 The final ciphertext is t || y.
138 """
139
140 def __init__(me, c, h, m, ck, mk):
141 """
142 Initialize the Crypto object with a given algorithm selection and keys.
143
144 We need a GCipher subclass C, a GHash subclass H, a GMAC subclass M, and
145 keys CK and MK for C and M respectively.
146 """
147 me.c = c(ck)
148 me.m = m(mk)
149 me.h = h
150
151 def encrypt(me, pt):
152 """
153 Encrypt the message PT and return the resulting ciphertext.
154 """
155 blksz = me.c.__class__.blksz
156 b = _C.WriteBuffer()
157 if blksz:
158 iv = _C.rand.block(blksz)
159 me.c.setiv(iv)
160 b.put(iv)
161 b.put(me.c.encrypt(pt))
162 t = me.m().hash(b).done()
163 return t + str(buffer(b))
164
165 def decrypt(me, ct):
166 """
167 Decrypt the ciphertext CT, returning the plaintext.
168
169 Raises DecryptError if anything goes wrong.
170 """
171 blksz = me.c.__class__.blksz
172 tagsz = me.m.__class__.tagsz
173 b = _C.ReadBuffer(ct)
174 t = b.get(tagsz)
175 h = me.m()
176 if blksz:
177 iv = b.get(blksz)
178 me.c.setiv(iv)
179 h.hash(iv)
180 x = b.get(b.left)
181 h.hash(x)
182 if t != h.done(): raise DecryptError
183 return me.c.decrypt(x)
184
185 class PPK (Crypto):
186 """
187 I represent a crypto transform whose keys are derived from a passphrase.
188
189 The password is salted and hashed; the salt is available as the `salt'
190 attribute.
191 """
192
193 def __init__(me, pp, c, h, m, salt = None):
194 """
195 Initialize the PPK object with a passphrase and algorithm selection.
196
197 We want a passphrase PP, a GCipher subclass C, a GHash subclass H, a GMAC
198 subclass M, and a SALT. The SALT may be None, if we're generating new
199 keys, indicating that a salt should be chosen randomly.
200 """
201 if not salt: salt = _C.rand.block(h.hashsz)
202 tag = pp + _NUL + salt
203 Crypto.__init__(me, c, h, m,
204 h().hash(_CIPHER).hash(tag).done(),
205 h().hash(_MAC).hash(tag).done())
206 me.salt = salt
207
208 ###--------------------------------------------------------------------------
209 ### Backend storage.
210
211 class StorageBackendRefusal (Exception):
212 """
213 I signify that a StorageBackend subclass has refused to open a file.
214
215 This is used by the StorageBackend.open class method.
216 """
217 pass
218
219 class StorageBackendClass (type):
220 """
221 I am a metaclass for StorageBackend classes.
222
223 My main feature is that I register my concrete instances (with a `NAME'
224 which is not `None') with the StorageBackend class.
225 """
226 def __init__(me, name, supers, dict):
227 """
228 Register a new concrete StorageBackend subclass.
229 """
230 super(StorageBackendClass, me).__init__(name, supers, dict)
231 if me.NAME is not None: StorageBackend.register_concrete_subclass(me)
232
233 class StorageBackend (object):
234 """
235 I provide basic protocol for password storage backends.
236
237 I'm an abstract class: you want one of my subclasses if you actually want
238 to do something useful. But I maintain a list of my subclasses and can
239 choose an appropriate one to open a database file you've found lying about.
240
241 Backends are responsible for storing and retrieving stuff, but not for the
242 cryptographic details. Backends need to store two kinds of information:
243
244 * metadata, consisting of a number of property names and their values;
245 and
246
247 * password mappings, consisting of a number of binary labels and
248 payloads.
249
250 Backends need to implement the following ordinary methods. See the calling
251 methods for details of the subclass responsibilities.
252
253 BE._create(FILE) Create a new database in FILE; used by `create'.
254
255 BE._open(FILE, WRITEP)
256 Open the existing database FILE; used by `open'.
257
258 BE._close(ABRUPTP) Close the database, freeing up any resources. If
259 ABRUPTP then don't try to commit changes.
260
261 BE._get_meta(NAME, DEFAULT)
262 Return the value of the metadata item with the given
263 NAME, or DEFAULT if it doesn't exist; used by
264 `get_meta'.
265
266 BE._put_meta(NAME, VALUE)
267 Set the VALUE of the metadata item with the given
268 NAME, creating one if necessary; used by `put_meta'.
269
270 BE._del_meta(NAME) Forget the metadata item with the given NAME; raise
271 `KeyError' if there is no such item; used by
272 `del_meta'.
273
274 BE._iter_meta() Return an iterator over the metadata (NAME, VALUE)
275 pairs; used by `iter_meta'.
276
277 BE._get_passwd(LABEL)
278 Return the password payload stored with the (binary)
279 LABEL; used by `get_passwd'.
280
281 BE._put_passwd(LABEL, PAYLOAD)
282 Associate the (binary) PAYLOAD with the LABEL,
283 forgetting any previous payload for that LABEL; used
284 by `put_passwd'.
285
286 BE._del_passwd(LABEL) Forget the password record with the given LABEL; used
287 by `_del_passwd'.
288
289 BE._iter_passwds() Return an iterator over the password (LABEL, PAYLOAD)
290 pairs; used by `iter_passwds'.
291
292 Also, concrete subclasses should define the following class attributes.
293
294 NAME The name of the backend, so that the user can select
295 it when creating a new database.
296
297 PRIO An integer priority: backends are tried in decreasing
298 priority order when opening an existing database.
299 """
300
301 __metaclass__ = StorageBackendClass
302 NAME = None
303 PRIO = 10
304
305 ## The registry of subclasses.
306 CLASSES = {}
307
308 FAIL = ['FAIL']
309
310 @staticmethod
311 def register_concrete_subclass(sub):
312 """Register a concrete subclass, so that `open' can try it."""
313 StorageBackend.CLASSES[sub.NAME] = sub
314
315 @staticmethod
316 def byname(name):
317 """
318 Return the concrete subclass with the given NAME.
319
320 Raise `KeyError' if the name isn't found.
321 """
322 return StorageBackend.CLASSES[name]
323
324 @staticmethod
325 def classes():
326 """Return an iterator over the concrete subclasses."""
327 return _itervalues(StorageBackend.CLASSES)
328
329 @staticmethod
330 def open(file, writep = False):
331 """Open a database FILE, using some appropriate backend."""
332 _OS.stat(file)
333 for cls in sorted(StorageBackend.CLASSES.values(), reverse = True,
334 key = lambda cls: cls.PRIO):
335 try: return cls(file, writep)
336 except StorageBackendRefusal: pass
337 raise StorageBackendRefusal
338
339 @classmethod
340 def create(cls, file):
341 """
342 Create a new database in the named FILE, using this backend.
343
344 Subclasses must implement the `_create' instance method.
345 """
346 return cls(writep = True, _magic = lambda me: me._create(file))
347
348 def __init__(me, file = None, writep = False, _magic = None, *args, **kw):
349 """
350 Main constructor.
351
352 Subclasses are not, in general, expected to override this: there's a
353 somewhat hairy protocol between the constructor and some of the class
354 methods. Instead, the main hook for customization is the subclass's
355 `_open' method, which is invoked in the usual case.
356 """
357 super(StorageBackend, me).__init__(*args, **kw)
358 if me.NAME is None: raise ValueError('abstract class')
359 if _magic is not None: _magic(me)
360 elif file is None: raise ValueError('missing file parameter')
361 else: me._open(file, writep)
362 me._writep = writep
363 me._livep = True
364
365 def close(me, abruptp = False):
366 """
367 Close the database.
368
369 It is harmless to attempt to close a database which has been closed
370 already. Calls the subclass's `_close' method.
371 """
372 if me._livep:
373 me._livep = False
374 me._close(abruptp)
375
376 ## Utilities.
377
378 def _check_live(me):
379 """Raise an error if the receiver has been closed."""
380 if not me._livep: raise ValueError('database is closed')
381
382 def _check_write(me):
383 """Raise an error if the receiver is not open for writing."""
384 me._check_live()
385 if not me._writep: raise ValueError('database is read-only')
386
387 def _check_meta_name(me, name):
388 """
389 Raise an error unless NAME is a valid name for a metadata item.
390
391 Metadata names may not start with `$': such names are reserved for
392 password storage.
393 """
394 if name.startswith('$'):
395 raise ValueError("invalid metadata key `%s'" % name)
396
397 ## Context protocol.
398
399 def __enter__(me):
400 """Context protocol: make sure the database is closed on exit."""
401 return me
402 def __exit__(me, exctype, excvalue, exctb):
403 """Context protocol: see `__enter__'."""
404 me.close(excvalue is not None)
405
406 ## Metadata.
407
408 def get_meta(me, name, default = FAIL):
409 """
410 Fetch the value for the metadata item NAME.
411
412 If no such item exists, then return DEFAULT if that was set; otherwise
413 raise a `KeyError'.
414
415 This calls the subclass's `_get_meta' method, which should return the
416 requested item or return the given DEFAULT value. It may assume that the
417 name is valid and the database is open.
418 """
419 me._check_meta_name(name)
420 me._check_live()
421 value = me._get_meta(name, default)
422 if value is StorageBackend.FAIL: raise KeyError(name)
423 return value
424
425 def put_meta(me, name, value):
426 """
427 Store VALUE in the metadata item called NAME.
428
429 This calls the subclass's `_put_meta' method, which may assume that the
430 name is valid and the database is open for writing.
431 """
432 me._check_meta_name(name)
433 me._check_write()
434 me._put_meta(name, value)
435
436 def del_meta(me, name):
437 """
438 Forget about the metadata item with the given NAME.
439
440 This calls the subclass's `_del_meta' method, which may assume that the
441 name is valid and the database is open for writing.
442 """
443 me._check_meta_name(name)
444 me._check_write()
445 me._del_meta(name)
446
447 def iter_meta(me):
448 """
449 Return an iterator over the name/value metadata items.
450
451 This calls the subclass's `_iter_meta' method, which may assume that the
452 database is open.
453 """
454 me._check_live()
455 return me._iter_meta()
456
457 def get_passwd(me, label):
458 """
459 Fetch and return the payload stored with the (opaque, binary) LABEL.
460
461 If there is no such payload then raise `KeyError'.
462
463 This calls the subclass's `_get_passwd' method, which may assume that the
464 database is open.
465 """
466 me._check_live()
467 return me._get_passwd(label)
468
469 def put_passwd(me, label, payload):
470 """
471 Associate the (opaque, binary) PAYLOAD with the (opaque, binary) LABEL.
472
473 Any previous payload for LABEL is forgotten.
474
475 This calls the subclass's `_put_passwd' method, which may assume that the
476 database is open for writing.
477 """
478 me._check_write()
479 me._put_passwd(label, payload)
480
481 def del_passwd(me, label):
482 """
483 Forget any PAYLOAD associated with the (opaque, binary) LABEL.
484
485 If there is no such payload then raise `KeyError'.
486
487 This calls the subclass's `_del_passwd' method, which may assume that the
488 database is open for writing.
489 """
490 me._check_write()
491 me._del_passwd(label)
492
493 def iter_passwds(me):
494 """
495 Return an iterator over the stored password label/payload pairs.
496
497 This calls the subclass's `_iter_passwds' method, which may assume that
498 the database is open.
499 """
500 me._check_live()
501 return me._iter_passwds()
502
503 try: import gdbm as _G
504 except ImportError: pass
505 else:
506 class GDBMStorageBackend (StorageBackend):
507 """
508 My instances store password data in a GDBM database.
509
510 Metadata and password entries are mixed into the same database. The key
511 for a metadata item is simply its name; the key for a password entry is
512 the entry's label prefixed by `$', since we're guaranteed that no
513 metadata item name begins with `$'.
514 """
515
516 NAME = 'gdbm'
517
518 def _open(me, file, writep):
519 try: me._db = _G.open(file, writep and 'w' or 'r')
520 except _G.error: raise StorageBackendRefusal(_excval())
521
522 def _create(me, file):
523 me._db = _G.open(file, 'n', 0600)
524
525 def _close(me, abruptp):
526 me._db.close()
527 me._db = None
528
529 def _get_meta(me, name, default):
530 try: return me._db[name]
531 except KeyError: return default
532
533 def _put_meta(me, name, value):
534 me._db[name] = value
535
536 def _del_meta(me, name):
537 del me._db[name]
538
539 def _iter_meta(me):
540 k = me._db.firstkey()
541 while k is not None:
542 if not k.startswith('$'): yield k, me._db[k]
543 k = me._db.nextkey(k)
544
545 def _get_passwd(me, label):
546 return me._db['$' + label]
547
548 def _put_passwd(me, label, payload):
549 me._db['$' + label] = payload
550
551 def _del_passwd(me, label):
552 del me._db['$' + label]
553
554 def _iter_passwds(me):
555 k = me._db.firstkey()
556 while k is not None:
557 if k.startswith('$'): yield k[1:], me._db[k]
558 k = me._db.nextkey(k)
559
560 try: import sqlite3 as _Q
561 except ImportError: pass
562 else:
563 class SQLiteStorageBackend (StorageBackend):
564 """
565 I represent a password database stored in SQLite.
566
567 Metadata and password items are stored in separate tables, so there's no
568 conflict. Some additional metadata is stored in the `meta' table, with
569 names beginning with `$' so as not to conflict with clients:
570
571 $version The schema version of the table.
572 """
573
574 NAME = 'sqlite'
575 VERSION = 0
576
577 def _open(me, file, writep):
578 try:
579 me._db = _Q.connect(file)
580 ver = me._query_scalar(
581 "SELECT value FROM meta WHERE name = '$version'",
582 "version check")
583 except (_Q.DatabaseError, _Q.OperationalError):
584 raise StorageBackendRefusal(_excval())
585 if ver is None: raise ValueError('database broken (missing $version)')
586 elif ver < me.VERSION: me._upgrade(ver)
587 elif ver > me.VERSION: raise ValueError \
588 ('unknown database schema version (%d > %d)' % (ver, me.VERSION))
589
590 def _create(me, file):
591 fd = _OS.open(file, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_EXCL, 0600)
592 _OS.close(fd)
593 try:
594 me._db = _Q.connect(file)
595 c = me._db.cursor()
596 c.execute("""
597 CREATE TABLE meta (
598 name TEXT PRIMARY KEY NOT NULL,
599 value BLOB NOT NULL);
600 """)
601 c.execute("""
602 CREATE TABLE passwd (
603 label BLOB PRIMARY KEY NOT NULL,
604 payload BLOB NOT NULL);
605 """)
606 c.execute("""
607 INSERT INTO meta (name, value) VALUES ('$version', ?);
608 """, [me.VERSION])
609 except:
610 try: _OS.unlink(file)
611 except OSError: pass
612 raise
613
614 def _upgrade(me, ver):
615 """Upgrade the database from schema version VER."""
616 assert False, 'how embarrassing'
617
618 def _close(me, abruptp):
619 if not abruptp: me._db.commit()
620 me._db.close()
621 me._db = None
622
623 def _fetch_scalar(me, c, what, default = None):
624 try: row = next(c)
625 except StopIteration: val = default
626 else: val, = row
627 try: row = next(c)
628 except StopIteration: pass
629 else: raise ValueError('multiple matching records for %s' % what)
630 return val
631
632 def _query_scalar(me, query, what, default = None, args = []):
633 c = me._db.cursor()
634 c.execute(query, args)
635 return me._fetch_scalar(c, what, default)
636
637 def _get_meta(me, name, default):
638 v = me._query_scalar("SELECT value FROM meta WHERE name = ?",
639 "metadata item `%s'" % name,
640 default = default, args = [name])
641 if v is default: return v
642 else: return str(v)
643
644 def _put_meta(me, name, value):
645 c = me._db.cursor()
646 c.execute("INSERT OR REPLACE INTO meta (name, value) VALUES (?, ?)",
647 [name, buffer(value)])
648
649 def _del_meta(me, name):
650 c = me._db.cursor()
651 c.execute("DELETE FROM meta WHERE name = ?", [name])
652 if not c.rowcount: raise KeyError(name)
653
654 def _iter_meta(me):
655 c = me._db.cursor()
656 c.execute("SELECT name, value FROM meta WHERE name NOT LIKE '$%'")
657 for k, v in c: yield k, str(v)
658
659 def _get_passwd(me, label):
660 pld = me._query_scalar("SELECT payload FROM passwd WHERE label = ?",
661 "password", default = None,
662 args = [buffer(label)])
663 if pld is None: raise KeyError(label)
664 return str(pld)
665
666 def _put_passwd(me, label, payload):
667 c = me._db.cursor()
668 c.execute("INSERT OR REPLACE INTO passwd (label, payload) "
669 "VALUES (?, ?)",
670 [buffer(label), buffer(payload)])
671
672 def _del_passwd(me, label):
673 c = me._db.cursor()
674 c.execute("DELETE FROM passwd WHERE label = ?", [label])
675 if not c.rowcount: raise KeyError(label)
676
677 def _iter_passwds(me):
678 c = me._db.cursor()
679 c.execute("SELECT label, payload FROM passwd")
680 for k, v in c: yield str(k), str(v)
681
682 class PlainTextBackend (StorageBackend):
683 """
684 I'm a utility base class for storage backends which use plain text files.
685
686 I provide subclasses with the following capabilities.
687
688 * Creating files, with given modes, optionally ensuring that the file
689 doesn't exist already.
690
691 * Parsing flat text files, checking leading magic, skipping comments, and
692 providing standard encodings of troublesome characters and binary
693 strings in metadata and password records. See below.
694
695 * Maintenance of metadata and password records in in-memory dictionaries,
696 with ready implementations of the necessary StorageBackend subclass
697 responsibility methods. (Subclasses can override these if they want to
698 make different arrangements.)
699
700 Metadata records are written with an optional prefix string chosen by the
701 caller, followed by a `NAME=VALUE' pair. The NAME is form-urlencoded and
702 prefixed with `!' if it contains strange characters; the VALUE is base64-
703 encoded (without the pointless trailing `=' padding) and prefixed with `?'
704 if necessary.
705
706 Password records are written with an optional prefix string chosen by the
707 caller, followed by a LABEL=PAYLOAD pair, both of which are base64-encoded
708 (without padding).
709
710 The following attributes are available for subclasses:
711
712 _meta Dictionary mapping metadata item names to their values.
713 Populated by `_parse_meta' and managed by `_get_meta' and
714 friends.
715
716 _pw Dictionary mapping password labels to encrypted payloads.
717 Populated by `_parse_passwd' and managed by `_get_passwd' and
718 friends.
719
720 _dirtyp Boolean: set if either of the dictionaries has been modified.
721 """
722
723 def __init__(me, *args, **kw):
724 """
725 Hook for initialization.
726
727 Sets up the published instance attributes.
728 """
729 me._meta = {}
730 me._pw = {}
731 me._dirtyp = False
732 super(PlainTextBackend, me).__init__(*args, **kw)
733
734 def _create_file(me, file, mode = 0600, freshp = False):
735 """
736 Make sure FILE exists, creating it with the given MODE if necessary.
737
738 If FRESHP is true, then make sure the file did not exist previously.
739 Return a file object for the newly created file.
740 """
741 flags = _OS.O_CREAT | _OS.O_WRONLY
742 if freshp: flags |= _OS.O_EXCL
743 else: flags |= _OS.O_TRUNC
744 fd = _OS.open(file, flags, mode)
745 return _OS.fdopen(fd, 'w')
746
747 def _mark_dirty(me):
748 """
749 Set the `_dirtyp' flag.
750
751 Subclasses might find it useful to intercept this method.
752 """
753 me._dirtyp = True
754
755 def _eqsplit(me, line):
756 """
757 Extract the KEY, VALUE pair from a LINE of the form `KEY=VALUE'.
758
759 Raise `ValueError' if there is no `=' in the LINE.
760 """
761 eq = line.index('=')
762 return line[:eq], line[eq + 1:]
763
764 def _parse_file(me, file, magic = None):
765 """
766 Parse a FILE.
767
768 Specifically:
769
770 * Raise `StorageBackendRefusal' if that the first line doesn't match
771 MAGIC (if provided). MAGIC should not contain the terminating
772 newline.
773
774 * Ignore comments (beginning `#') and blank lines.
775
776 * Call `_parse_line' (provided by the subclass) for other lines.
777 """
778 with open(file, 'r') as f:
779 if magic is not None:
780 if f.readline().rstrip('\n') != magic: raise StorageBackendRefusal
781 for line in f:
782 line = line.rstrip('\n')
783 if not line or line.startswith('#'): continue
784 me._parse_line(line)
785
786 def _write_file(me, file, writebody, mode = 0600, magic = None):
787 """
788 Update FILE atomically.
789
790 The newly created file will have the given MODE. If MAGIC is given, then
791 write that as the first line. Calls WRITEBODY(F) to write the main body
792 of the file where F is a file object for the new file.
793 """
794 new = file + '.new'
795 with me._create_file(new, mode) as f:
796 if magic is not None: f.write(magic + '\n')
797 writebody(f)
798 _OS.rename(new, file)
799
800 def _parse_meta(me, line):
801 """Parse LINE as a metadata NAME=VALUE pair, and updates `_meta'."""
802 k, v = me._eqsplit(line)
803 me._meta[_dec_metaname(k)] = _dec_metaval(v)
804
805 def _write_meta(me, f, prefix = ''):
806 """Write the metadata records to F, each with the given PREFIX."""
807 f.write('\n## Metadata.\n')
808 for k, v in _iteritems(me._meta):
809 f.write('%s%s=%s\n' % (prefix, _enc_metaname(k), _enc_metaval(v)))
810
811 def _get_meta(me, name, default):
812 return me._meta.get(name, default)
813 def _put_meta(me, name, value):
814 me._mark_dirty()
815 me._meta[name] = value
816 def _del_meta(me, name):
817 me._mark_dirty()
818 del me._meta[name]
819 def _iter_meta(me):
820 return _iteritems(me._meta)
821
822 def _parse_passwd(me, line):
823 """Parse LINE as a password LABEL=PAYLOAD pair, and updates `_pw'."""
824 k, v = me._eqsplit(line)
825 me._pw[_unb64(k)] = _unb64(v)
826
827 def _write_passwd(me, f, prefix = ''):
828 """Write the password records to F, each with the given PREFIX."""
829 f.write('\n## Password data.\n')
830 for k, v in _iteritems(me._pw):
831 f.write('%s%s=%s\n' % (prefix, _b64(k), _b64(v)))
832
833 def _get_passwd(me, label):
834 return me._pw[str(label)]
835 def _put_passwd(me, label, payload):
836 me._mark_dirty()
837 me._pw[str(label)] = payload
838 def _del_passwd(me, label):
839 me._mark_dirty()
840 del me._pw[str(label)]
841 def _iter_passwds(me):
842 return _iteritems(me._pw)
843
844 class FlatFileStorageBackend (PlainTextBackend):
845 """
846 I maintain a password database in a plain text file.
847
848 The text file consists of lines, as follows.
849
850 * Empty lines, and lines beginning with `#' (in the leftmost column only)
851 are ignored.
852
853 * Lines of the form `$LABEL=PAYLOAD' store password data. Both LABEL and
854 PAYLOAD are base64-encoded, without `=' padding.
855
856 * Lines of the form `NAME=VALUE' store metadata. If the NAME contains
857 characters other than alphanumerics, hyphens, underscores, and colons,
858 then it is form-urlencoded, and prefixed wth `!'. If the VALUE
859 contains such characters, then it is base64-encoded, without `='
860 padding, and prefixed with `?'.
861
862 * Other lines are erroneous.
863
864 The file is rewritten from scratch when it's changed: any existing
865 commentary is lost, and items may be reordered. There is no file locking,
866 but the file is updated atomically, by renaming.
867
868 It is expected that the FlatFileStorageBackend is used mostly for
869 diagnostics and transfer, rather than for a live system.
870 """
871
872 NAME = 'flat'
873 PRIO = 0
874 MAGIC = '### pwsafe password database'
875
876 def _open(me, file, writep):
877 if not _OS.path.isfile(file): raise StorageBackendRefusal
878 me._parse_file(file, magic = me.MAGIC)
879 def _parse_line(me, line):
880 if line.startswith('$'): me._parse_passwd(line[1:])
881 else: me._parse_meta(line)
882
883 def _create(me, file):
884 with me._create_file(file, freshp = True) as f: pass
885 me._file = file
886 me._mark_dirty()
887
888 def _close(me, abruptp):
889 if not abruptp and me._dirtyp:
890 me._write_file(me._file, me._write_body, magic = me.MAGIC)
891
892 def _write_body(me, f):
893 me._write_meta(f)
894 me._write_passwd(f, '$')
895
896 class DirectoryStorageBackend (PlainTextBackend):
897 """
898 I maintain a password database in a directory, with one file per password.
899
900 This makes password databases easy to maintain in a revision-control system
901 such as Git.
902
903 The directory is structured as follows.
904
905 dir/meta Contains metadata, similar to the `FlatFileBackend'.
906
907 dir/pw/LABEL Contains the (raw binary) payload for the given password
908 LABEL (base64-encoded, without the useless `=' padding, and
909 with `/' replaced by `.').
910
911 dir/tmp/ Contains temporary files used by the implementation.
912 """
913
914 NAME = 'dir'
915 METAMAGIC = '### pwsafe password directory metadata'
916
917 def _open(me, file, writep):
918 if not _OS.path.isdir(file) or \
919 not _OS.path.isdir(_OS.path.join(file, 'pw')) or \
920 not _OS.path.isdir(_OS.path.join(file, 'tmp')) or \
921 not _OS.path.isfile(_OS.path.join(file, 'meta')):
922 raise StorageBackendRefusal
923 me._dir = file
924 me._parse_file(_OS.path.join(file, 'meta'), magic = me.METAMAGIC)
925 def _parse_line(me, line):
926 me._parse_meta(line)
927
928 def _create(me, file):
929 _OS.mkdir(file, 0700)
930 _OS.mkdir(_OS.path.join(file, 'pw'), 0700)
931 _OS.mkdir(_OS.path.join(file, 'tmp'), 0700)
932 me._mark_dirty()
933 me._dir = file
934
935 def _close(me, abruptp):
936 if not abruptp and me._dirtyp:
937 me._write_file(_OS.path.join(me._dir, 'meta'),
938 me._write_meta, magic = me.METAMAGIC)
939
940 def _pwfile(me, label, dir = 'pw'):
941 return _OS.path.join(me._dir, dir, _b64(label).replace('/', '.'))
942 def _get_passwd(me, label):
943 try:
944 f = open(me._pwfile(label), 'rb')
945 except (OSError, IOError):
946 if _excval().errno == _E.ENOENT: raise KeyError(label)
947 else: raise
948 with f: return f.read()
949 def _put_passwd(me, label, payload):
950 new = me._pwfile(label, 'tmp')
951 fd = _OS.open(new, _OS.O_WRONLY | _OS.O_CREAT | _OS.O_TRUNC, 0600)
952 _OS.close(fd)
953 with open(new, 'wb') as f: f.write(payload)
954 _OS.rename(new, me._pwfile(label))
955 def _del_passwd(me, label):
956 try:
957 _OS.remove(me._pwfile(label))
958 except (OSError, IOError):
959 if _excval().errno == _E.ENOENT: raise KeyError(label)
960 else: raise
961 def _iter_passwds(me):
962 pw = _OS.path.join(me._dir, 'pw')
963 for i in _OS.listdir(pw):
964 with open(_OS.path.join(pw, i), 'rb') as f: pld = f.read()
965 yield _unb64(i.replace('.', '/')), pld
966
967 ###--------------------------------------------------------------------------
968 ### Password storage.
969
970 class PW (object):
971 """
972 I represent a secure (ish) password store.
973
974 I can store short secrets, associated with textual names, in a way which
975 doesn't leak too much information about them.
976
977 I implement (some of) the Python mapping protocol.
978
979 I keep track of everything using a StorageBackend object. This contains
980 password entries, identified by cryptographic labels, and a number of
981 metadata items.
982
983 cipher Names the Catacomb cipher selected.
984
985 hash Names the Catacomb hash function selected.
986
987 key Cipher and MAC keys, each prefixed by a 16-bit big-endian
988 length and concatenated, encrypted using the master
989 passphrase.
990
991 mac Names the Catacomb message authentication code selected.
992
993 magic A magic string for obscuring password tag names.
994
995 salt The salt for hashing the passphrase.
996
997 tag The master passphrase's tag, for the Pixie's benefit.
998
999 Password entries are assigned labels of the form `$' || H(MAGIC || TAG);
1000 the corresponding value consists of a pair (TAG, PASSWD), prefixed with
1001 16-bit lengths, concatenated, padded to a multiple of 256 octets, and
1002 encrypted using the stored keys.
1003 """
1004
1005 def __init__(me, file, writep = False):
1006 """
1007 Initialize a PW object from the database in FILE.
1008
1009 If WRITEP is false (the default) then the database is opened read-only;
1010 if true then it may be written. Requests the database password from the
1011 Pixie, which may cause interaction.
1012 """
1013
1014 ## Open the database.
1015 me.db = StorageBackend.open(file, writep)
1016
1017 ## Find out what crypto to use.
1018 c = _C.gcciphers[me.db.get_meta('cipher')]
1019 h = _C.gchashes[me.db.get_meta('hash')]
1020 m = _C.gcmacs[me.db.get_meta('mac')]
1021
1022 ## Request the passphrase and extract the master keys.
1023 tag = me.db.get_meta('tag')
1024 ppk = PPK(_C.ppread(tag), c, h, m, me.db.get_meta('salt'))
1025 try:
1026 b = _C.ReadBuffer(ppk.decrypt(me.db.get_meta('key')))
1027 except DecryptError:
1028 _C.ppcancel(tag)
1029 raise
1030 me.ck = b.getblk16()
1031 me.mk = b.getblk16()
1032 if not b.endp: raise ValueError('trailing junk')
1033
1034 ## Set the key, and stash it and the tag-hashing secret.
1035 me.k = Crypto(c, h, m, me.ck, me.mk)
1036 me.magic = me.k.decrypt(me.db.get_meta('magic'))
1037
1038 @classmethod
1039 def create(cls, dbcls, file, tag, c, h, m):
1040 """
1041 Create and initialize a new database FILE using StorageBackend DBCLS.
1042
1043 We want a GCipher subclass C, a GHash subclass H, and a GMAC subclass M;
1044 and a Pixie passphrase TAG.
1045
1046 This doesn't return a working object: it just creates the database file
1047 and gets out of the way.
1048 """
1049
1050 ## Set up the cryptography.
1051 pp = _C.ppread(tag, _C.PMODE_VERIFY)
1052 ppk = PPK(pp, c, h, m)
1053 ck = _C.rand.block(c.keysz.default)
1054 mk = _C.rand.block(c.keysz.default)
1055 k = Crypto(c, h, m, ck, mk)
1056
1057 ## Set up and initialize the database.
1058 kct = ppk.encrypt(_C.WriteBuffer().putblk16(ck).putblk16(mk))
1059 with dbcls.create(file) as db:
1060 db.put_meta('tag', tag)
1061 db.put_meta('salt', ppk.salt)
1062 db.put_meta('cipher', c.name)
1063 db.put_meta('hash', h.name)
1064 db.put_meta('mac', m.name)
1065 db.put_meta('key', kct)
1066 db.put_meta('magic', k.encrypt(_C.rand.block(h.hashsz)))
1067
1068 def keyxform(me, key):
1069 """Transform the KEY (actually a password tag) into a password label."""
1070 return me.k.h().hash(me.magic).hash(key).done()
1071
1072 def changepp(me):
1073 """
1074 Change the database password.
1075
1076 Requests the new password from the Pixie, which will probably cause
1077 interaction.
1078 """
1079 tag = me.db.get_meta('tag')
1080 _C.ppcancel(tag)
1081 ppk = PPK(_C.ppread(tag, _C.PMODE_VERIFY),
1082 me.k.c.__class__, me.k.h, me.k.m.__class__)
1083 kct = ppk.encrypt(_C.WriteBuffer().putblk16(me.ck).putblk16(me.mk))
1084 me.db.put_meta('key', kct)
1085 me.db.put_meta('salt', ppk.salt)
1086
1087 def pack(me, key, value):
1088 """Pack the KEY and VALUE into a ciphertext, and return it."""
1089 b = _C.WriteBuffer()
1090 b.putblk16(key).putblk16(value)
1091 b.zero(((b.size + 255) & ~255) - b.size)
1092 return me.k.encrypt(b)
1093
1094 def unpack(me, ct):
1095 """
1096 Unpack a ciphertext CT and return a (KEY, VALUE) pair.
1097
1098 Might raise DecryptError, of course.
1099 """
1100 b = _C.ReadBuffer(me.k.decrypt(ct))
1101 key = b.getblk16()
1102 value = b.getblk16()
1103 return key, value
1104
1105 ## Mapping protocol.
1106
1107 def __getitem__(me, key):
1108 """Return the password for the given KEY."""
1109 try: return me.unpack(me.db.get_passwd(me.keyxform(key)))[1]
1110 except KeyError: raise KeyError(key)
1111
1112 def __setitem__(me, key, value):
1113 """Associate the password VALUE with the KEY."""
1114 me.db.put_passwd(me.keyxform(key), me.pack(key, value))
1115
1116 def __delitem__(me, key):
1117 """Forget all about the KEY."""
1118 try: me.db.del_passwd(me.keyxform(key))
1119 except KeyError: raise KeyError(key)
1120
1121 def __iter__(me):
1122 """Iterate over the known password tags."""
1123 for _, pld in me.db.iter_passwds():
1124 yield me.unpack(pld)[0]
1125
1126 ## Context protocol.
1127
1128 def __enter__(me):
1129 return me
1130 def __exit__(me, excty, excval, exctb):
1131 me.db.close(excval is not None)
1132
1133 ###----- That's all, folks --------------------------------------------------