VirtualMailManager/password.py
branchv0.6.x
changeset 291 7ef3f117a230
parent 290 e2785e04f92e
child 292 619dadc0fd25
equal deleted inserted replaced
290:e2785e04f92e 291:7ef3f117a230
    37 
    37 
    38 _ = lambda msg: msg
    38 _ = lambda msg: msg
    39 cfg_dget = lambda option: None
    39 cfg_dget = lambda option: None
    40 _sys_rand = SystemRandom()
    40 _sys_rand = SystemRandom()
    41 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len))
    41 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len))
    42 
       
    43 
       
    44 def _test_crypt_algorithms():
       
    45     """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt()."""
       
    46     blowfish_ = sha256_ = sha512_ = False
       
    47     _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru'
       
    48     _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\
       
    49 9qrb1jehe7hn4'
       
    50     _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\
       
    51 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1'
       
    52 
       
    53     if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish:
       
    54         blowfish_ = True
       
    55     if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256:
       
    56         sha256_ = True
       
    57     if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512:
       
    58         sha512_ = True
       
    59     return blowfish_, sha256_, sha512_
       
    60 
       
    61 CRYPT_BLOWFISH, CRYPT_SHA256, CRYPT_SHA512 = _test_crypt_algorithms()
       
    62 
    42 
    63 
    43 
    64 def _dovecotpw(password, scheme, encoding):
    44 def _dovecotpw(password, scheme, encoding):
    65     """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
    45     """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
    66     the hashed password: {scheme[.encoding]}hash
    46     the hashed password: {scheme[.encoding]}hash
   141     elif rounds > 31:
   121     elif rounds > 31:
   142         rounds = 31
   122         rounds = 31
   143     return '$2a$%02d$%s' % (rounds, _get_salt(22))
   123     return '$2a$%02d$%s' % (rounds, _get_salt(22))
   144 
   124 
   145 
   125 
   146 def _get_crypt_shaxxx_salt(crypt_id):
   126 def _get_crypt_sha2_salt(crypt_id):
   147     """Generates a salt for crypt using the SHA-256 or SHA-512 encryption
   127     """Generates a salt for crypt using the SHA-256 or SHA-512 encryption
   148     method.
   128     method.
   149     *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512).
   129     *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512).
   150     """
   130     """
   151     assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id
   131     assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id
   155         rounds = cfg_dget('misc.crypt_sha256_rounds')
   135         rounds = cfg_dget('misc.crypt_sha256_rounds')
   156     if rounds < 1000:
   136     if rounds < 1000:
   157         rounds = 1000
   137         rounds = 1000
   158     elif rounds > 999999999:
   138     elif rounds > 999999999:
   159         rounds = 999999999
   139         rounds = 999999999
       
   140     if rounds == 5000:
       
   141         return '$%d$%s' % (crypt_id, _get_salt(16))
   160     return '$%d$rounds=%d$%s' % (crypt_id, rounds, _get_salt(16))
   142     return '$%d$rounds=%d$%s' % (crypt_id, rounds, _get_salt(16))
   161 
   143 
   162 
   144 
   163 def _crypt_hash(password, scheme, encoding):
   145 def _crypt_hash(password, scheme, encoding):
   164     """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes."""
   146     """Generates (encoded) CRYPT/MD5/{BLF,MD5,SHA{256,512}}-CRYPT hashes."""
   165     if scheme == 'CRYPT':
   147     if scheme == 'CRYPT':
   166         if CRYPT_BLOWFISH and cfg_dget('misc.crypt_blowfish_rounds'):
   148         salt = _get_salt(2)
   167             salt = _get_crypt_blowfish_salt()
   149     elif scheme == 'BLF-CRYPT':
   168         elif CRYPT_SHA512 and cfg_dget('misc.crypt_sha512_rounds'):
   150         salt = _get_crypt_blowfish_salt()
   169             salt = _get_crypt_shaxxx_salt(6)
   151     elif scheme in ('MD5-CRYPT', 'MD5'):
   170         elif CRYPT_SHA256 and cfg_dget('misc.crypt_sha256_rounds'):
       
   171             salt = _get_crypt_shaxxx_salt(5)
       
   172         else:
       
   173             salt = _get_salt(2)
       
   174     else:
       
   175         salt = '$1$%s' % _get_salt(8)
   152         salt = '$1$%s' % _get_salt(8)
       
   153     elif scheme == 'SHA256-CRYPT':
       
   154         salt = _get_crypt_sha2_salt(5)
       
   155     else:
       
   156         salt = _get_crypt_sha2_salt(6)
   176     encrypted = crypt(password, salt)
   157     encrypted = crypt(password, salt)
   177     if encoding:
   158     if encoding:
   178         if encoding == 'HEX':
   159         if encoding == 'HEX':
   179             encrypted = encrypted.encode('hex')
   160             encrypted = encrypted.encode('hex')
   180         else:
   161         else:
   181             encrypted = encrypted.encode('base64').replace('\n', '')
   162             encrypted = encrypted.encode('base64').replace('\n', '')
       
   163     if scheme in ('BLF-CRYPT', 'SHA256-CRYPT', 'SHA512-CRYPT') and \
       
   164        cfg_dget('misc.dovecot_version') < 0x20000b06:
       
   165         scheme = 'CRYPT'
   182     return _format_digest(encrypted, scheme, encoding)
   166     return _format_digest(encrypted, scheme, encoding)
   183 
   167 
   184 
   168 
   185 def _md4_hash(password, scheme, encoding):
   169 def _md4_hash(password, scheme, encoding):
   186     """Generates encoded PLAIN-MD4 hashes."""
   170     """Generates encoded PLAIN-MD4 hashes."""
   361       * has a unknown encoding suffix
   345       * has a unknown encoding suffix
   362     """
   346     """
   363     assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme
   347     assert isinstance(scheme, basestring), 'Not a str/unicode: %r' % scheme
   364     scheme_encoding = scheme.upper().split('.')
   348     scheme_encoding = scheme.upper().split('.')
   365     scheme = scheme_encoding[0]
   349     scheme = scheme_encoding[0]
   366     if not scheme in _scheme_info:
   350     if scheme not in _scheme_info:
   367         raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme,
   351         raise VMMError(_(u"Unsupported password scheme: '%s'") % scheme,
   368                        VMM_ERROR)
   352                        VMM_ERROR)
   369     if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]:
   353     if cfg_dget('misc.dovecot_version') < _scheme_info[scheme][1]:
   370         raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot "
   354         raise VMMError(_(u"The password scheme '%(scheme)s' requires Dovecot "
   371                          u">= v%(version)s") % {'scheme': scheme,
   355                          u">= v%(version)s") % {'scheme': scheme,
   416     pw_len = cfg_dget('account.password_length')
   400     pw_len = cfg_dget('account.password_length')
   417     if pw_len < 8:
   401     if pw_len < 8:
   418         pw_len = 8
   402         pw_len = 8
   419     return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len))
   403     return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len))
   420 
   404 
       
   405 
       
   406 def _test_crypt_algorithms():
       
   407     """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt()."""
       
   408     _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru'
       
   409     _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\
       
   410 9qrb1jehe7hn4'
       
   411     _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\
       
   412 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1'
       
   413 
       
   414     if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish:
       
   415         _scheme_info['BLF-CRYPT'] = (_crypt_hash, 0x10000f00)
       
   416     if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256:
       
   417         _scheme_info['SHA256-CRYPT'] = (_crypt_hash, 0x10000f00)
       
   418     if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512:
       
   419         _scheme_info['SHA512-CRYPT'] = (_crypt_hash, 0x10000f00)
       
   420 
       
   421 _test_crypt_algorithms()
   421 del _, cfg_dget, _test_crypt_algorithms
   422 del _, cfg_dget, _test_crypt_algorithms