diff options
| -rw-r--r-- | admin/benchmarks.py | 2 | ||||
| -rw-r--r-- | passlib/exc.py | 54 | ||||
| -rw-r--r-- | passlib/handlers/bcrypt.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/cisco.py | 4 | ||||
| -rw-r--r-- | passlib/handlers/des_crypt.py | 16 | ||||
| -rw-r--r-- | passlib/handlers/django.py | 8 | ||||
| -rw-r--r-- | passlib/handlers/fshp.py | 6 | ||||
| -rw-r--r-- | passlib/handlers/ldap_digests.py | 6 | ||||
| -rw-r--r-- | passlib/handlers/md5_crypt.py | 2 | ||||
| -rw-r--r-- | passlib/handlers/misc.py | 6 | ||||
| -rw-r--r-- | passlib/handlers/mssql.py | 12 | ||||
| -rw-r--r-- | passlib/handlers/oracle.py | 4 | ||||
| -rw-r--r-- | passlib/handlers/pbkdf2.py | 24 | ||||
| -rw-r--r-- | passlib/handlers/scram.py | 14 | ||||
| -rw-r--r-- | passlib/handlers/sha1_crypt.py | 4 | ||||
| -rw-r--r-- | passlib/handlers/sha2_crypt.py | 12 | ||||
| -rw-r--r-- | passlib/handlers/sun_md5_crypt.py | 14 | ||||
| -rw-r--r-- | passlib/handlers/windows.py | 4 | ||||
| -rw-r--r-- | passlib/tests/test_utils_handlers.py | 2 | ||||
| -rw-r--r-- | passlib/utils/handlers.py | 105 |
20 files changed, 185 insertions, 116 deletions
diff --git a/admin/benchmarks.py b/admin/benchmarks.py index 45756d5..bdf2fdb 100644 --- a/admin/benchmarks.py +++ b/admin/benchmarks.py @@ -49,7 +49,7 @@ class BlankHandler(uh.HasRounds, uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): - r,s,c = uh.parse_mc3(hash, cls.ident, cls.name) + r,s,c = uh.parse_mc3(hash, cls.ident, handler=cls) return cls(rounds=int(r), salt=s, checksum=c) def to_string(self): diff --git a/passlib/exc.py b/passlib/exc.py index 44fadd4..70347ee 100644 --- a/passlib/exc.py +++ b/passlib/exc.py @@ -87,5 +87,59 @@ class PasslibSecurityWarning(PasslibWarning): """ #========================================================================== +# error constructors +# +# note: these functions are used by the hashes in Passlib to raise common +# error messages. They are currently just functions which return ValueError, +# rather than subclasses of ValueError, since the specificity isn't needed +# yet; and who wants to import a bunch of error classes when catching +# ValueError will do? +#========================================================================== + +def _get_name(handler): + return handler.name if handler else "<unnamed>" + +#---------------------------------------------------------------- +# encrypt/verify parameter errors +#---------------------------------------------------------------- +def MissingHashError(handler=None): + "error raised if no hash provided to handler" + return ValueError("no hash specified") + +def MissingDigestError(handler=None): + "raised when verify() method gets passed config string instead of hash" + name = _get_name(handler) + return ValueError("expected %s hash, got %s config string instead" % + (name, name)) + +#---------------------------------------------------------------- +# errors when parsing hashes +#---------------------------------------------------------------- +def InvalidHashError(handler=None): + "error raised if unrecognized hash provided to handler" + raise ValueError("not a valid %s hash" % _get_name(handler)) + +def MalformedHashError(handler=None, reason=None): + "error raised if recognized-but-malformed hash provided to handler" + text = "malformed %s hash" % _get_name(handler) + if reason: + text = "%s (%s)" % (text, reason) + raise ValueError(text) + +def ZeroPaddedRoundsError(handler=None): + "error raised if hash was recognized but contained zero-padded rounds field" + return MalformedHashError(handler, "zero-padded rounds") + +#---------------------------------------------------------------- +# settings / hash component errors +#---------------------------------------------------------------- +def ChecksumSizeError(handler, raw=False): + "error raised if hash was recognized, but checksum was wrong size" + checksum_size = handler.checksum_size + unit = "bytes" if raw else "chars" + return ValueError("checksum wrong size (%s checksum must be " + "exactly %d %s" % (handler.name, checksum_size, unit)) + +#========================================================================== # eof #========================================================================== diff --git a/passlib/handlers/bcrypt.py b/passlib/handlers/bcrypt.py index a1270e2..3c1cb2e 100644 --- a/passlib/handlers/bcrypt.py +++ b/passlib/handlers/bcrypt.py @@ -130,7 +130,7 @@ class bcrypt(uh.HasManyIdents, uh.HasRounds, uh.HasSalt, uh.HasManyBackends, uh. rounds, data = tail.split(u("$")) rval = int(rounds) if rounds != u('%02d') % (rval,): - raise ValueError("invalid bcrypt hash (rounds not zero-padded)") + raise uh.exc.ZeroPaddedRoundsError(cls) salt, chk = data[:22], data[22:] return cls( rounds=rval, diff --git a/passlib/handlers/cisco.py b/passlib/handlers/cisco.py index 37d7233..8b241e6 100644 --- a/passlib/handlers/cisco.py +++ b/passlib/handlers/cisco.py @@ -124,9 +124,9 @@ class cisco_type7(uh.GenericHandler): if not hash: if hash is None: return cls(use_defaults=True) - raise ValueError("no hash provided") + raise uh.exc.MissingHashError(cls) if len(hash) < 2: - raise ValueError("invalid cisco_type7 hash") + raise uh.exc.InvalidHashError(cls) if isinstance(hash, bytes): hash = hash.decode("latin-1") salt = int(hash[:2]) # may throw ValueError diff --git a/passlib/handlers/des_crypt.py b/passlib/handlers/des_crypt.py index a65d147..d5b3d14 100644 --- a/passlib/handlers/des_crypt.py +++ b/passlib/handlers/des_crypt.py @@ -183,7 +183,7 @@ class des_crypt(uh.HasManyBackends, uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") salt, chk = hash[:2], hash[2:] @@ -296,12 +296,12 @@ class bsdi_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("invalid ext-des-crypt hash") + raise uh.exc.InvalidHashError(cls) rounds, salt, chk = m.group("rounds", "salt", "chk") return cls( rounds=h64.decode_int24(rounds.encode("ascii")), @@ -381,12 +381,12 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("invalid bigcrypt hash") + raise uh.exc.InvalidHashError(cls) salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk) @@ -397,7 +397,7 @@ class bigcrypt(uh.HasSalt, uh.GenericHandler): def _norm_checksum(self, value): value = super(bigcrypt, self)._norm_checksum(value) if value and len(value) % 11: - raise ValueError("invalid bigcrypt hash") + raise uh.exc.InvalidHashError(cls) return value #========================================================= @@ -461,12 +461,12 @@ class crypt16(uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("invalid crypt16 hash") + raise uh.exc.InvalidHashError(cls) salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk) diff --git a/passlib/handlers/django.py b/passlib/handlers/django.py index b91c265..9a22a38 100644 --- a/passlib/handlers/django.py +++ b/passlib/handlers/django.py @@ -50,13 +50,13 @@ class DjangoSaltedHash(uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") ident = cls.ident assert ident.endswith(u("$")) if not hash.startswith(ident): - raise ValueError("invalid %s hash" % (cls.name,)) + raise uh.exc.InvalidHashError(cls) _, salt, chk = hash.split(u("$")) return cls(salt=salt, checksum=chk or None) @@ -160,7 +160,7 @@ class django_des_crypt(DjangoSaltedHash): chk = self.checksum if salt and chk: if salt[:2] != chk[:2]: - raise ValueError("invalid django_des_crypt hash: " + raise uh.exc.MalformedHashError(cls, "first two digits of salt and checksum must match") # repeat stub checksum detection since salt isn't set # when _norm_checksum() is called. @@ -218,7 +218,7 @@ class django_disabled(uh.StaticHandler): if secret is None: raise TypeError("no secret provided") if not cls.identify(hash): - raise ValueError("invalid django-disabled hash") + raise uh.exc.InvalidHashError(cls) return False #========================================================= diff --git a/passlib/handlers/fshp.py b/passlib/handlers/fshp.py index f24805d..eb6fcfd 100644 --- a/passlib/handlers/fshp.py +++ b/passlib/handlers/fshp.py @@ -142,12 +142,12 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("not a valid FSHP hash") + raise uh.exc.InvalidHashError(cls) variant, salt_size, rounds, data = m.group(1,2,3,4) variant = int(variant) salt_size = int(salt_size) @@ -155,7 +155,7 @@ class fshp(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): try: data = b64decode(data.encode("ascii")) except ValueError: - raise ValueError("malformed FSHP hash") + raise uh.exc.MalformedHashError(cls) salt = data[:salt_size] chk = data[salt_size:] return cls(salt=salt, checksum=chk, rounds=rounds, variant=variant) diff --git a/passlib/handlers/ldap_digests.py b/passlib/handlers/ldap_digests.py index 5c8d9cf..4acbd4c 100644 --- a/passlib/handlers/ldap_digests.py +++ b/passlib/handlers/ldap_digests.py @@ -79,16 +79,16 @@ class _SaltedBase64DigestHelper(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHand @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode('ascii') m = cls._hash_regex.match(hash) if not m: - raise ValueError("not a %s hash" % (cls.name,)) + raise uh.exc.InvalidHashError(cls) try: data = b64decode(m.group("tmp").encode("ascii")) except TypeError: - raise ValueError("malformed %s hash" % (cls.name,)) + raise uh.exc.MalformedHashError(cls) cs = cls.checksum_size assert cs return cls(checksum=data[:cs], salt=data[cs:]) diff --git a/passlib/handlers/md5_crypt.py b/passlib/handlers/md5_crypt.py index 7dc2ed8..3237b42 100644 --- a/passlib/handlers/md5_crypt.py +++ b/passlib/handlers/md5_crypt.py @@ -172,7 +172,7 @@ class _Md5Common(uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): - salt, chk = uh.parse_mc2(hash, cls.ident, cls.name) + salt, chk = uh.parse_mc2(hash, cls.ident, handler=cls) return cls(salt=salt, checksum=chk) def to_string(self): diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py index 2fb3f13..e63ea1b 100644 --- a/passlib/handlers/misc.py +++ b/passlib/handlers/misc.py @@ -73,7 +73,7 @@ class unix_fallback(uh.StaticHandler): if secret is None: raise TypeError("secret must be string") elif hash is None: - raise ValueError("no hash provided") + raise uh.exc.MissingHashError(cls) elif hash: return False else: @@ -179,7 +179,7 @@ class plaintext(object): if hash is None: raise TypeError("no hash specified") elif not cls.identify(hash): - raise ValueError("not a %s hash" % (cls.name,)) + raise uh.exc.InvalidHashError(cls) hash = to_native_str(hash, cls._hash_encoding, "hash") return consteq(cls.encrypt(secret), hash) @@ -190,7 +190,7 @@ class plaintext(object): @classmethod def genhash(cls, secret, hash): if hash is not None and not cls.identify(hash): - raise ValueError("not a %s hash" % (cls.name,)) + raise uh.exc.InvalidHashError(cls) return cls.encrypt(secret) #========================================================= diff --git a/passlib/handlers/mssql.py b/passlib/handlers/mssql.py index 4bc7e24..eafd44a 100644 --- a/passlib/handlers/mssql.py +++ b/passlib/handlers/mssql.py @@ -79,10 +79,10 @@ def _ident_mssql(hash, csize, bsize): ## return True return False -def _parse_mssql(hash, csize, bsize, name): +def _parse_mssql(hash, csize, bsize, handler): "common parser for mssql 2000/2005; returns 4 byte salt + checksum" if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(handler) if isinstance(hash, unicode): if len(hash) == csize and hash.startswith(UIDENT): try: @@ -99,7 +99,7 @@ def _parse_mssql(hash, csize, bsize, name): pass ##elif len(hash) == bsize and hash.startswith(BIDENT2): # raw bytes ## return hash[2:] - raise ValueError("invalid %s hash" % name) + raise uh.exc.InvalidHashError(handler) class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): """This class implements the password hash used by MS-SQL 2000, and follows the :ref:`password-hash-api`. @@ -139,7 +139,7 @@ class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): @classmethod def from_string(cls, hash): - data = _parse_mssql(hash, 94, 46, cls.name) + data = _parse_mssql(hash, 94, 46, cls) return cls(salt=data[:4], checksum=data[4:]) def to_string(self): @@ -159,7 +159,7 @@ class mssql2000(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): self = cls.from_string(hash) chk = self.checksum if chk is None: - raise uh.MissingDigestError(cls) + raise uh.exc.MissingDigestError(cls) if secret and len(secret) > uh.MAX_PASSWORD_SIZE: raise uh.exc.PasswordSizeError() secret = to_unicode(secret, 'utf-8', errname='secret') @@ -207,7 +207,7 @@ class mssql2005(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): @classmethod def from_string(cls, hash): - data = _parse_mssql(hash, 54, 26, cls.name) + data = _parse_mssql(hash, 54, 26, cls) return cls(salt=data[:4], checksum=data[4:]) def to_string(self): diff --git a/passlib/handlers/oracle.py b/passlib/handlers/oracle.py index 1322ea9..9a0af1b 100644 --- a/passlib/handlers/oracle.py +++ b/passlib/handlers/oracle.py @@ -139,12 +139,12 @@ class oracle11(uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash provided") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("invalid oracle-11g hash") + raise uh.exc.InvalidHashError(cls) salt, chk = m.group("salt", "chk") return cls(salt=salt, checksum=chk.upper()) diff --git a/passlib/handlers/pbkdf2.py b/passlib/handlers/pbkdf2.py index 477e09d..5c4b38a 100644 --- a/passlib/handlers/pbkdf2.py +++ b/passlib/handlers/pbkdf2.py @@ -67,10 +67,10 @@ class Pbkdf2DigestHandler(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gen def from_string(cls, hash): if not hash: raise ValueError("no hash specified") - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls) int_rounds = int(rounds) if rounds != unicode(int_rounds): #forbid zero padding, etc. - raise ValueError("invalid %s hash" % (cls.name,)) + raise uh.exc.ZeroPaddedRoundsError(cls) raw_salt = ab64_decode(salt.encode("ascii")) raw_chk = ab64_decode(chk.encode("ascii")) if chk else None return cls( @@ -202,12 +202,12 @@ class cta_pbkdf2_sha1(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Generic @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) if rounds.startswith("0"): #passlib deviation: forbidding #left-padded with zeroes - raise ValueError("invalid cta_pbkdf2_sha1 hash") + raise uh.exc.ZeroPaddedRoundsError(cls) rounds = int(rounds, 16) salt = b64decode(salt.encode("ascii"), CTA_ALTCHARS) if chk: @@ -299,10 +299,10 @@ class dlitz_pbkdf2_sha1(uh.HasRounds, uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) + raise uh.exc.MissingHashError(cls) + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls) if rounds.startswith("0"): #zero not allowed, nor left-padded with zeroes - raise ValueError("invalid dlitz_pbkdf2_sha1 hash") + raise uh.exc.ZeroPaddedRoundsError(cls) rounds = int(rounds, 16) if rounds else 400 return cls( rounds=rounds, @@ -362,12 +362,12 @@ class atlassian_pbkdf2_sha1(uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler) @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") ident = cls.ident if not hash.startswith(ident): - raise ValueError("invalid %s hash" % (cls.name,)) + raise uh.exc.InvalidHashError(cls) data = b64decode(hash[len(ident):].encode("ascii")) salt, chk = data[:16], data[16:] return cls(salt=salt, checksum=chk) @@ -428,11 +428,11 @@ class grub_pbkdf2_sha512(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.Gene @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name, sep=u(".")) + raise uh.exc.MissingHashError(cls) + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, sep=u("."), handler=cls) int_rounds = int(rounds) if rounds != str(int_rounds): #forbid zero padding, etc. - raise ValueError("invalid %s hash" % (cls.name,)) + raise uh.exc.ZeroPaddedRoundsError(cls) raw_salt = unhexlify(salt.encode("ascii")) raw_chk = unhexlify(chk.encode("ascii")) if chk else None return cls( diff --git a/passlib/handlers/scram.py b/passlib/handlers/scram.py index ab610c8..25ff036 100644 --- a/passlib/handlers/scram.py +++ b/passlib/handlers/scram.py @@ -211,30 +211,30 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): def from_string(cls, hash): # parse hash if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) hash = to_native_str(hash, "ascii", errname="hash") if not hash.startswith("$scram$"): - raise ValueError("invalid scram hash") + raise uh.exc.InvalidHashError(cls) parts = hash[7:].split("$") if len(parts) != 3: - raise ValueError("invalid scram hash") + raise uh.exc.MalformedHashError(cls) rounds_str, salt_str, chk_str = parts # decode rounds rounds = int(rounds_str) if rounds_str != str(rounds): #forbid zero padding, etc. - raise ValueError("invalid scram hash") + raise uh.exc.MalformedHashError(cls) # decode salt try: salt = ab64_decode(salt_str.encode("ascii")) except TypeError: - raise ValueError("malformed scram hash") + raise uh.exc.MalformedHashError(cls) # decode algs/digest list if not chk_str: # scram hashes MUST have something here. - raise ValueError("invalid scram hash") + raise uh.exc.MalformedHashError(cls) elif "=" in chk_str: # comma-separated list of 'alg=digest' pairs algs = None @@ -244,7 +244,7 @@ class scram(uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.GenericHandler): try: chkmap[alg] = ab64_decode(digest.encode("ascii")) except TypeError: - raise ValueError("malformed scram hash") + raise uh.exc.MalformedHashError(cls) else: # comma-separated list of alg names, no digests algs = chk_str diff --git a/passlib/handlers/sha1_crypt.py b/passlib/handlers/sha1_crypt.py index db61d12..fd2f502 100644 --- a/passlib/handlers/sha1_crypt.py +++ b/passlib/handlers/sha1_crypt.py @@ -80,9 +80,9 @@ class sha1_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandler @classmethod def from_string(cls, hash): - rounds, salt, chk = uh.parse_mc3(hash, cls.ident, cls.name) + rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls) if rounds.startswith("0"): - raise ValueError("invalid sha1-crypt hash (zero-padded rounds)") + raise uh.exc.ZeroPaddedRoundsError(cls) return cls( rounds=int(rounds), salt=salt, diff --git a/passlib/handlers/sha2_crypt.py b/passlib/handlers/sha2_crypt.py index 94472dc..9adb28c 100644 --- a/passlib/handlers/sha2_crypt.py +++ b/passlib/handlers/sha2_crypt.py @@ -299,15 +299,15 @@ class sha256_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandl @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("invalid sha256-crypt hash") + raise uh.exc.InvalidHashError(cls) rounds, salt1, salt2, chk = m.group("rounds", "salt1", "salt2", "chk") if rounds and rounds.startswith(u("0")): - raise ValueError("invalid sha256-crypt hash (zero-padded rounds)") + raise uh.exc.ZeroPaddedRoundsError(cls) return cls( implicit_rounds=not rounds, rounds=int(rounds) if rounds else 5000, @@ -455,15 +455,15 @@ class sha512_crypt(uh.HasManyBackends, uh.HasRounds, uh.HasSalt, uh.GenericHandl @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError() if isinstance(hash, bytes): hash = hash.decode("ascii") m = cls._hash_regex.match(hash) if not m: - raise ValueError("invalid sha512-crypt hash") + raise uh.exc.InvalidHashError(cls) rounds, salt1, salt2, chk = m.group("rounds", "salt1", "salt2", "chk") if rounds and rounds.startswith("0"): - raise ValueError("invalid sha512-crypt hash (zero-padded rounds)") + raise uh.exc.ZeroPaddedRoundsError(cls) return cls( implicit_rounds = not rounds, rounds=int(rounds) if rounds else 5000, diff --git a/passlib/handlers/sun_md5_crypt.py b/passlib/handlers/sun_md5_crypt.py index 184ad1a..e1d187b 100644 --- a/passlib/handlers/sun_md5_crypt.py +++ b/passlib/handlers/sun_md5_crypt.py @@ -247,7 +247,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not hash: - raise ValueError("no hash specified") + raise uh.exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") @@ -262,22 +262,22 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): elif hash.startswith(u("$md5,rounds=")): idx = hash.find(u("$"), 12) if idx == -1: - raise ValueError("invalid sun-md5-crypt hash (unexpected end of rounds)") + raise uh.exc.MalformedHashError(cls, "unexpected end of rounds") rstr = hash[12:idx] try: rounds = int(rstr) except ValueError: - raise ValueError("invalid sun-md5-crypt hash (bad rounds)") + raise uh.exc.MalformedHashError(cls, "bad rounds") if rstr != unicode(rounds): - raise ValueError("invalid sun-md5-crypt hash (zero-padded rounds)") + raise uh.exc.ZeroPaddedRoundsError(cls) if rounds == 0: #NOTE: not sure if this is forbidden by spec or not; # but allowing it would complicate things, # and it should never occur anyways. - raise ValueError("invalid sun-md5-crypt hash (explicit zero rounds)") + raise uh.exc.MalformedHashError(cls, "explicit zero rounds") salt_idx = idx+1 else: - raise ValueError("invalid sun-md5-crypt hash (unknown prefix)") + raise uh.exc.InvalidHashError(cls) # #salt/checksum separation is kinda weird, @@ -292,7 +292,7 @@ class sun_md5_crypt(uh.HasRounds, uh.HasSalt, uh.GenericHandler): bare_salt = True elif chk_idx == len(hash)-1: if chk_idx > salt_idx and hash[-2] == u("$"): - raise ValueError("invalid sun-md5-crypt hash (too many $)") + raise uh.exc.MalformedHashError(cls, "too many '$' separators") # $-config for $$-hash salt = hash[salt_idx:-1] chk = None diff --git a/passlib/handlers/windows.py b/passlib/handlers/windows.py index a60e911..1870b6e 100644 --- a/passlib/handlers/windows.py +++ b/passlib/handlers/windows.py @@ -201,7 +201,7 @@ bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$", ## @classmethod ## def genhash(cls, secret, config): ## if config is not None and not cls.identify(config): -## raise ValueError("not a valid %s hash" % (cls.name,)) +## raise uh.exc.InvalidHashError(cls) ## return cls.encrypt(secret) ## ## @classmethod @@ -216,7 +216,7 @@ bsd_nthash = uh.PrefixWrapper("bsd_nthash", nthash, prefix="$3$$", ident="$3$$", ## hash = hash.decode("latin-1") ## m = cls._hash_regex.match(hash) ## if not m: -## raise ValueError("not a valid %s hash" % (cls.name,)) +## raise uh.exc.InvalidHashError(cls) ## lm, nt = m.group("lm", "nt") ## # NOTE: verify against both in case encoding issue ## # causes one not to match. diff --git a/passlib/tests/test_utils_handlers.py b/passlib/tests/test_utils_handlers.py index cffac95..30194a6 100644 --- a/passlib/tests/test_utils_handlers.py +++ b/passlib/tests/test_utils_handlers.py @@ -548,7 +548,7 @@ class SaltedHash(uh.HasSalt, uh.GenericHandler): @classmethod def from_string(cls, hash): if not cls.identify(hash): - raise ValueError("not a salted-example hash") + raise uh.exc.InvalidHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") return cls(salt=hash[5:-40], checksum=hash[-40:]) diff --git a/passlib/utils/handlers.py b/passlib/utils/handlers.py index 7c8b747..bb7eadb 100644 --- a/passlib/utils/handlers.py +++ b/passlib/utils/handlers.py @@ -69,19 +69,36 @@ UC_HEX_CHARS = UPPER_HEX_CHARS LC_HEX_CHARS = LOWER_HEX_CHARS #========================================================= -#parsing helpers +# parsing helpers #========================================================= -def parse_mc2(hash, prefix, name="<unnamed>", sep=u("$")): - "parse hash using 2-part modular crypt format" - assert isinstance(prefix, unicode) - assert isinstance(sep, unicode) - #eg: MD5-Crypt: $1$salt[$checksum] +_UDOLLAR = u("$") +_UZERO = u("0") + +def parse_mc2(hash, prefix, sep=_UDOLLAR, handler=None): + """parse hash using 2-part modular crypt format. + + this expects a hash of the format :samp:`{prefix}{salt}[${checksum}]`, + such as md5_crypt, and parses it into salt / checksum portions. + + :arg hash: the hash to parse (bytes or unicode) + :arg prefix: the identifying prefix (unicode) + :param sep: field separator (unicode, defaults to ``$``). + :param handler: handler class to pass to error constructors. + + :returns: + a ``(salt, chk | None)`` tuple. + """ + # detect prefix if not hash: - raise ValueError("no hash specified") + raise exc.MissingHashError(handler) if isinstance(hash, bytes): hash = hash.decode('ascii') + assert isinstance(prefix, unicode) if not hash.startswith(prefix): - raise ValueError("not a valid %s hash (wrong prefix)" % (name,)) + raise exc.InvalidHashError(handler) + + # parse 2-part hash or 1-part config string + assert isinstance(sep, unicode) parts = hash[len(prefix):].split(sep) if len(parts) == 2: salt, chk = parts @@ -89,19 +106,33 @@ def parse_mc2(hash, prefix, name="<unnamed>", sep=u("$")): elif len(parts) == 1: return parts[0], None else: - raise ValueError("not a valid %s hash (malformed)" % (name,)) + raise exc.MalformedHashError(handler) -def parse_mc3(hash, prefix, name="<unnamed>", sep=u("$")): - "parse hash using 3-part modular crypt format" - assert isinstance(prefix, unicode) - assert isinstance(sep, unicode) - #eg: SHA1-Crypt: $sha1$rounds$salt[$checksum] +def parse_mc3(hash, prefix, sep=_UDOLLAR, handler=None): + """parse hash using 3-part modular crypt format. + + this expects a hash of the format :samp:`{prefix}[{rounds}$]{salt}[${checksum}]`, + such as sha1_crypt, and parses it into rounds / salt / checksum portions. + + :arg hash: the hash to parse (bytes or unicode) + :arg prefix: the identifying prefix (unicode) + :param sep: field separator (unicode, defaults to ``$``). + :param handler: handler class to pass to error constructors. + + :returns: + a ``(rounds : str, salt, chk | None)`` tuple. + """ + # detect prefix if not hash: - raise ValueError("no hash specified") + raise exc.MissingHashError(handler) if isinstance(hash, bytes): hash = hash.decode('ascii') + assert isinstance(prefix, unicode) if not hash.startswith(prefix): - raise ValueError("not a valid %s hash" % (name,)) + raise exc.InvalidHashError(handler) + + # parse 3-part hash or 2-part config string + assert isinstance(sep, unicode) parts = hash[len(prefix):].split(sep) if len(parts) == 3: rounds, salt, chk = parts @@ -110,7 +141,7 @@ def parse_mc3(hash, prefix, name="<unnamed>", sep=u("$")): rounds, salt = parts return rounds, salt, None else: - raise ValueError("not a valid %s hash" % (name,)) + raise exc.MalformedHashError(handler) #===================================================== #formatting helpers @@ -131,22 +162,6 @@ def render_mc3(ident, rounds, salt, checksum, sep=u("$")): hash = u("%s%s%s%s") % (ident, rounds, sep, salt) return uascii_to_str(hash) -#========================================================================== -# not proper exceptions, just predefined error message constructors -# used by various handlers. -#========================================================================== -def ChecksumSizeError(handler, size, raw=False): - name = handler.name - unit = "bytes" if raw else "chars" - return ValueError("checksum wrong size (%s checksum must be " - "exactly %d %s" % (name, size, unit)) - -def MissingDigestError(handler): - "raised when verify() method gets passed config string instead of hash" - name = handler.name - return ValueError("expected %s hash, got %s config string instead" % - (name, name)) - #===================================================== #GenericHandler #===================================================== @@ -343,7 +358,7 @@ class GenericHandler(object): # check size cc = self.checksum_size if cc and len(checksum) != cc: - raise ChecksumSizeError(self, cc, raw=raw) + raise exc.ChecksumSizeError(self, raw=raw) # check charset if not raw: @@ -478,7 +493,7 @@ class GenericHandler(object): self = cls.from_string(hash, **context) chk = self.checksum if chk is None: - raise MissingDigestError(cls) + raise exc.MissingDigestError(cls) return consteq(self._calc_checksum(secret), chk) #========================================================= @@ -549,7 +564,7 @@ class StaticHandler(GenericHandler): if hash.startswith(prefix): hash = hash[len(prefix):] else: - raise ValueError("not a valid %s hash" % (cls.name,)) + raise exc.InvalidHashError(cls) return cls(checksum=hash, **context) @classmethod @@ -570,7 +585,7 @@ class StaticHandler(GenericHandler): def genhash(cls, secret, config, **context): # since it has no settings, just verify config, and call encrypt() if config is not None and not cls.identify(config): - raise ValueError("not a %s hash" % (cls.name,)) + raise exc.InvalidHashError(cls) return cls.encrypt(secret, **context) __cc_compat_hack = False @@ -736,13 +751,13 @@ class HasManyIdents(GenericHandler): def _parse_ident(cls, hash): """extract ident prefix from hash, helper for subclasses' from_string()""" if not hash: - raise ValueError("no hash specified") + raise exc.MissingHashError(cls) if isinstance(hash, bytes): hash = hash.decode("ascii") for ident in cls.ident_values: if hash.startswith(ident): return ident, hash[len(ident):] - raise ValueError("invalid %s hash" % (cls.name,)) + raise exc.InvalidHashError(cls) #========================================================= #eoc @@ -1253,9 +1268,9 @@ class HasManyBackends(GenericHandler): if cls.has_backend(name): break else: - raise MissingBackendError(cls._no_backends_msg()) + raise exc.MissingBackendError(cls._no_backends_msg()) elif not cls.has_backend(name): - raise MissingBackendError("%s backend not available: %r" % + raise exc.MissingBackendError("%s backend not available: %r" % (cls.name, name)) cls._calc_checksum = getattr(cls, "_calc_checksum_" + name) cls._backend = name @@ -1318,7 +1333,7 @@ class PrefixWrapper(object): if isinstance(ident, bytes): ident = ident.decode("ascii") if ident[:len(prefix)] != prefix[:len(ident)]: - raise ValueError("ident agree with prefix") + raise ValueError("ident must agree with prefix") self._ident = ident _wrapped_name = None @@ -1419,7 +1434,7 @@ class PrefixWrapper(object): hash = hash.decode('ascii') prefix = self.prefix if not hash.startswith(prefix): - raise ValueError("not a valid %s hash" % (self.name,)) + raise exc.InvalidHashError(self) #NOTE: always passing to handler as unicode, to save reconversion return self.orig_prefix + hash[len(prefix):] @@ -1431,7 +1446,7 @@ class PrefixWrapper(object): hash = hash.decode('ascii') orig_prefix = self.orig_prefix if not hash.startswith(orig_prefix): - raise ValueError("not a valid %s hash" % (self.wrapped.name,)) + raise exc.InvalidHashError(self.wrapped) wrapped = self.prefix + hash[len(orig_prefix):] return uascii_to_str(wrapped) @@ -1462,7 +1477,7 @@ class PrefixWrapper(object): def verify(self, secret, hash, **kwds): if not hash: - raise ValueError("no %s hash specified" % (self.name,)) + raise exc.MissingHashError(self) hash = self._unwrap_hash(hash) return self.wrapped.verify(secret, hash, **kwds) |
