summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--admin/benchmarks.py2
-rw-r--r--passlib/exc.py54
-rw-r--r--passlib/handlers/bcrypt.py2
-rw-r--r--passlib/handlers/cisco.py4
-rw-r--r--passlib/handlers/des_crypt.py16
-rw-r--r--passlib/handlers/django.py8
-rw-r--r--passlib/handlers/fshp.py6
-rw-r--r--passlib/handlers/ldap_digests.py6
-rw-r--r--passlib/handlers/md5_crypt.py2
-rw-r--r--passlib/handlers/misc.py6
-rw-r--r--passlib/handlers/mssql.py12
-rw-r--r--passlib/handlers/oracle.py4
-rw-r--r--passlib/handlers/pbkdf2.py24
-rw-r--r--passlib/handlers/scram.py14
-rw-r--r--passlib/handlers/sha1_crypt.py4
-rw-r--r--passlib/handlers/sha2_crypt.py12
-rw-r--r--passlib/handlers/sun_md5_crypt.py14
-rw-r--r--passlib/handlers/windows.py4
-rw-r--r--passlib/tests/test_utils_handlers.py2
-rw-r--r--passlib/utils/handlers.py105
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)