/sshproxy/cipher.py
Python | 317 lines | 278 code | 17 blank | 22 comment | 9 complexity | ff7902e83cd3606f2b6ed1dd2191f7f4 MD5 | raw file
Possible License(s): GPL-2.0
- #!/usr/bin/env python
- # -*- coding: ISO-8859-15 -*-
- #
- # Copyright (C) 2005-2007 David Guerizec <david@guerizec.net>
- #
- # Last modified: 2007 Dec 09, 01:33:10 by david
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- from Crypto.Cipher import Blowfish
- import base64
- from config import ConfigSection, get_config
- _engine_registry = {}
- def register_engine(cls):
- _engine_registry[cls.cipher_id] = cls
- def list_engines():
- return _engine_registry.keys()
- def get_engine(cipher_type):
- engine = _engine_registry.get(cipher_type, None)
- if engine is None:
- if cipher_type != 'plain':
- raise ValueError('Unknown cipher_type %s' % cipher_type)
- engine = PlainCipher
- return engine
- def cipher(text, **kw):
- if not text:
- text = ''
- engine = _default_engine[0]
- if kw.has_key('type'):
- engine = get_engine(kw['type'])
- return '%s%s' % (engine.prefix(), engine.encrypt(text, **kw))
- def decipher(text, **kw):
- if not text:
- return text
- # guess the encryption method
- tokens = text.split('$', 2)
- # default decoder is Plain
- engine = PlainCipher
- if len(tokens) == 3:
- try:
- engine = get_engine(tokens[1])
- text = tokens[2]
- except ValueError:
- # maybe the text contains the magic prefix...
- # let's try to decode it as plain text
- pass
- return engine.decrypt(text, **kw)
- class BaseCipher(object):
- # cipher_id = ''
- @classmethod
- def set_default(cls):
- _default_engine[0] = cls
- @classmethod
- def prefix(cls):
- return '$%s$' % cls.cipher_id
- @classmethod
- def encrypt(cls, text, **kw):
- raise NotImplementedError
- @classmethod
- def decrypt(cls, text, **kw):
- raise NotImplementedError
- class PlainCipher(BaseCipher):
- cipher_id = "plain"
- @classmethod
- def prefix(cls):
- return ''
- @classmethod
- def encrypt(cls, text, **kw):
- return text
- @classmethod
- def decrypt(cls, text, **kw):
- return text
- register_engine(PlainCipher)
- class Base64Cipher(BaseCipher):
- cipher_id = "base64"
- @classmethod
- def encrypt(cls, text, **kw):
- return base64.b64encode(text)
- @classmethod
- def decrypt(cls, text, **kw):
- return base64.b64decode(text)
- register_engine(Base64Cipher)
- class BlowfishCipher(BaseCipher):
- cipher_id = "blowfish"
- @classmethod
- def set_default(cls):
- cls.engine = cls.get_engine(get_config('blowfish')['secret'])
- _default_engine[0] = cls
- @classmethod
- def get_engine(cls, secret=None):
- if secret is None:
- secret = getattr(cls, 'secret', get_config('blowfish')['secret'])
- return Blowfish.new(secret, Blowfish.MODE_ECB)
- @classmethod
- def encrypt(cls, text, **kw):
- engine = kw.get('engine', getattr(cls, 'engine', None))
- if not engine:
- engine = cls.get_engine(kw.get('secret', None))
- ftext = '%s:%s%s' % (len(text), text, '*'*8)
- ftext = ftext[:len(ftext) - (len(ftext)%8) ]
- return base64.b64encode(engine.encrypt(ftext))
- @classmethod
- def decrypt(cls, text, **kw):
- engine = kw.get('engine', getattr(cls, 'engine', None))
- if not engine:
- engine = cls.get_engine(kw.get('secret', None))
- crypted = base64.b64decode(text)
- # an exception here means we've lost the key...
- # how to handle that situation ?
- try:
- size, ftext = engine.decrypt(crypted).split(':', 1)
- except ValueError:
- import log
- log.error('Password probably encrypted with another passphrase')
- return ''
- return ftext[:int(size)]
- register_engine(BlowfishCipher)
- _default_engine = [ PlainCipher ]
- class BlowfishConfigSection(ConfigSection):
- section_id = 'blowfish'
- section_defaults = {
- 'secret': ('Enoch Root has an old cigar box on his lap.'
- ' Golden light is shining out of the crack around its lid.'),
- }
- def __setitem__(self, option, value):
- ConfigSection.__setitem__(self, option, value)
- if option == 'secret':
- reload()
- BlowfishConfigSection.register()
- def _init_cipher():
- cipher_type = get_config('sshproxy')['cipher_type']
- get_engine(cipher_type).set_default()
- _init_cipher()
- reload = _init_cipher
- #######################################################################
- ### Recipher database
- #######################################################################
- def recipher(cipher_type, password_fd, dry_run=False):
- import sys, getpass
- from sshproxy.backend import get_backend
- conf = get_config()
- newsecret = ''
- if cipher_type == 'blowfish':
- if conf['sshproxy']['cipher_type'] == 'blowfish':
- print("Recipher from blowfish to blowfish does not work "
- "at the moment.\nPlease recipher to base64 first, "
- "then recipher to blowfish.")
- print "Sorry for the inconvenience."
- sys.exit(0)
- try:
- if password_fd == True:
- newsecret = conf['blowfish']['secret']
- elif not password_fd:
- sec1 = 1
- sec2 = 2
- while not sec1 or sec1 != sec2:
- sec1 = getpass.getpass("Enter secret (1/2) ")
- sec2 = getpass.getpass("Enter secret (2/2) ")
- newsecret = sec1
- else:
- try:
- newsecret = password_fd.readlines()[0].strip()
- except IndexError:
- newsecret = ''
- except (KeyboardInterrupt, EOFError):
- print 'Aborted...'
- sys.exit(0)
- if len(newsecret) < 10:
- print 'Secret must be at least 10 characters long.'
- sys.exit(0)
- elif cipher_type not in ('plain', 'base64'):
- print "unknown cipher_type", cipher_type
- sys.exit(1)
-
-
- if cipher_type == 'blowfish':
- conf['blowfish']['newsecret'] = newsecret
- dry_run or conf.write() # just in case it goes wrong in the middle
-
- pwdb = get_backend()
-
- nb_passwords = 0
- nb_pkeys = 0
- total = 0
- sites = pwdb.list_site_users()
- for site in sites:
- if not site.login:
- continue
- name = site.name
- uid = site.login
- password = site.get_token('password')
- if password:
- # decipher with old secret
- oldpass = decipher(password)
- # cipher with new secret
- if oldpass is not None:
- newpass = cipher(oldpass, type=cipher_type, secret=newsecret)
- # check if we can decipher the new password
- if (oldpass is not None and
- oldpass != decipher(newpass, secret=newsecret) ):
- raise KeyError('Problem with %s cipher on %s@%s password!' %
- (cipher_type, uid, name))
- # be verbose when in dry-run mode
- if dry_run and password:
- print '-- %s@%s [ %s / %s / %s ]' % (uid, name,
- password, oldpass, newpass)
- # if the password changed, update it if not in dry-run mode
- if password != newpass:
- if not dry_run:
- site.set_tokens(password=newpass)
- site.save()
- nb_passwords += 1
- pkey = site.get_token('pkey')
- if pkey:
- # decipher with old secret
- oldpkey = decipher(pkey)
- # cipher with new secret
- if oldpkey is not None:
- newpkey = cipher(oldpkey, type=cipher_type, secret=newsecret)
- # check if we can decipher the new password
- if (oldpkey is not None and
- oldpkey != decipher(newpkey, secret=newsecret) ):
- raise KeyError('Problem with %s cipher on %s@%s pkey!' %
- (cipher_type, uid, name))
- # be verbose when in dry-run mode
- if dry_run and pkey:
- print ' %s' % (pkey or '').replace('\n', '\n ')
- print ' %s' % (oldpkey or '').replace('\n', '\n ')
- print ' %s' % (newpkey or '').replace('\n', '\n ')
- print
- # if the pkey changed, update it if not in dry-run mode
- if pkey != newpkey:
- if not dry_run:
- site.set_tokens(pkey=newpkey)
- site.save()
- nb_pkeys += 1
- total += 1
-
-
- if cipher_type == 'blowfish':
- del conf['blowfish']['newsecret']
- # update secret
- conf['blowfish']['secret'] = newsecret
- conf['sshproxy']['cipher_type'] = cipher_type
- dry_run or conf.write()
- print 'Reciphered %d passwords and %d pkeys on %d entries' % (nb_passwords,
- nb_pkeys,
- total)