diff options
| author | Eli Collins <elic@assurancetechnologies.com> | 2012-03-10 17:48:58 -0500 |
|---|---|---|
| committer | Eli Collins <elic@assurancetechnologies.com> | 2012-03-10 17:48:58 -0500 |
| commit | d3c7d16915f7ef3919245f211b8dab8ae35ade70 (patch) | |
| tree | 47e128e356e5d5363c7efdde5de944a52fd6191a | |
| parent | 50965db6ee2b6ff2c9227ea6c740e9513963c4f6 (diff) | |
| download | passlib-d3c7d16915f7ef3919245f211b8dab8ae35ade70.tar.gz | |
did rewrite of unix_fallback as unix_disabled; unix_fallback is now deprecated
| -rw-r--r-- | CHANGES | 5 | ||||
| -rw-r--r-- | docs/lib/passlib.hash.rst | 2 | ||||
| -rw-r--r-- | docs/lib/passlib.hash.unix_disabled.rst | 47 | ||||
| -rw-r--r-- | docs/lib/passlib.hash.unix_fallback.rst | 52 | ||||
| -rw-r--r-- | docs/lib/passlib.hosts.rst | 6 | ||||
| -rw-r--r-- | passlib/handlers/misc.py | 77 | ||||
| -rw-r--r-- | passlib/hosts.py | 15 | ||||
| -rw-r--r-- | passlib/registry.py | 3 | ||||
| -rw-r--r-- | passlib/tests/test_handlers.py | 22 | ||||
| -rw-r--r-- | passlib/tests/test_hosts.py | 16 |
10 files changed, 173 insertions, 72 deletions
@@ -8,6 +8,11 @@ Release History Hashes + * The *unix_fallback* handler has been deprecated, and will be removed + in Passlib 1.8. Please use the improved replacement, + :doc:`unix_disabled <lib/passlib.hash.unix_disabled>`, + instead. + * Added support for Window's Domain Cached Credentials (aka "dcc", "mscache", "mscash"), versions 1 and 2: :doc:`msdcc <lib/passlib.hash.msdcc>` and diff --git a/docs/lib/passlib.hash.rst b/docs/lib/passlib.hash.rst index d06fed1..1dda3b9 100644 --- a/docs/lib/passlib.hash.rst +++ b/docs/lib/passlib.hash.rst @@ -104,7 +104,7 @@ behavior found in many Linux & BSD password files: .. toctree:: :maxdepth: 1 - passlib.hash.unix_fallback + passlib.hash.unix_disabled .. _ldap-hashes: diff --git a/docs/lib/passlib.hash.unix_disabled.rst b/docs/lib/passlib.hash.unix_disabled.rst new file mode 100644 index 0000000..bfd725d --- /dev/null +++ b/docs/lib/passlib.hash.unix_disabled.rst @@ -0,0 +1,47 @@ +================================================================== +:class:`passlib.hash.unix_disabled` - Unix Disabled Account Helper +================================================================== + +.. currentmodule:: passlib.hash + +This class does not provide an encryption scheme, +but instead provides a helper for handling disabled +password fields as found in unix ``/etc/shadow`` files. + +Usage +===== +This class is mainly useful only for plugging into a +:class:`~passlib.context.CryptContext` instance. +It can be used directly as follows:: + + >>> from passlib.hash import unix_disabled as ud + + >>> # 'encrypting' a password always results in "!" or "*" + >>> ud.encrypt("password") + '!' + + >>> # verifying will fail for all passwords and hashes + >>> ud.verify("password", "!") + False + >>> ud.verify("letmein", "*NOPASSWORD*") + False + + >>> # all strings are recognized - if used in conjunction with other hashes, + >>> # this should be the last one checked. + >>> ud.identify('!') + True + >>> ud.identify('*') + True + >>> ud.identify('') + True + + +Interface +========= +.. autoclass:: unix_disabled + +Deviations +========== +According to the Linux ``shadow`` man page, an empty string is treated +as a wildcard by Linux, allowing all passwords. For security purposes, +this behavior is NOT supported; empty strings are treated the same as ``!``. diff --git a/docs/lib/passlib.hash.unix_fallback.rst b/docs/lib/passlib.hash.unix_fallback.rst deleted file mode 100644 index 4870d82..0000000 --- a/docs/lib/passlib.hash.unix_fallback.rst +++ /dev/null @@ -1,52 +0,0 @@ -================================================================== -:class:`passlib.hash.unix_fallback` - Unix Fallback Helper -================================================================== - -.. currentmodule:: passlib.hash - -This class does not provide an encryption scheme, -but instead provides a helper for handling disabled / wildcard -password fields as found in unix ``/etc/shadow`` files. - -Usage -===== -This class is mainly useful only for plugging into a :class:`~passlib.context.CryptContext`. -When used, it should always be the last scheme in the list, -as it is designed to provide a fallback behavior. -It can be used directly as follows:: - - >>> from passlib.hash import unix_fallback as uf - - >>> #'encrypting' a password always results in "!", the default reject hash. - >>> uf.encrypt("password") - '!' - - >>> #check if hash is recognized (all strings are recognized) - >>> uf.identify('!') - True - >>> uf.identify('*') - True - >>> uf.identify('') - True - - >>> #verify against non-empty string - no passwords allowed - >>> uf.verify("password", "!") - False - - >>> #verify against empty string: - >>> # * by default, no passwords allowed - >>> # * all passwords allowed IF enable_wildcard=True - >>> uf.verify("password", "") - False - >>> uf.verify("password", "", enable_wildcard=True) - True - -Interface -========= -.. autoclass:: unix_fallback - -Deviations -========== -According to the Linux ``shadow`` man page, an empty string is treated -as a wildcard by Linux, allowing all passwords. For security purposes, -this behavior is not enabled unless specifically requested by the application. diff --git a/docs/lib/passlib.hosts.rst b/docs/lib/passlib.hosts.rst index c03bbed..5ca13db 100644 --- a/docs/lib/passlib.hosts.rst +++ b/docs/lib/passlib.hosts.rst @@ -49,12 +49,10 @@ for the following Unix variants: .. note:: - All of the above contexts include the :class:`~passlib.hash.unix_fallback` handler + All of the above contexts include the :class:`~passlib.hash.unix_disabled` handler as a final fallback. This special handler treats all strings as invalid passwords, particularly the common strings ``!`` and ``*`` which are used to indicate - that an account has been disabled [#shadow]_. It can also be configured - to treat empty strings as a wildcard allowing in all passwords, - though this behavior is disabled by default for security reasons. + that an account has been disabled [#shadow]_. A quick usage example, using the :data:`!linux_context` instance:: diff --git a/passlib/handlers/misc.py b/passlib/handlers/misc.py index 4244e14..38b8c25 100644 --- a/passlib/handlers/misc.py +++ b/passlib/handlers/misc.py @@ -4,6 +4,7 @@ #imports #========================================================= #core +import sys import logging; log = logging.getLogger(__name__) from warnings import warn #site @@ -14,6 +15,7 @@ import passlib.utils.handlers as uh #pkg #local __all__ = [ + "unix_disabled", "unix_fallback", "plaintext", ] @@ -24,6 +26,12 @@ __all__ = [ class unix_fallback(uh.StaticHandler): """This class provides the fallback behavior for unix shadow files, and follows the :ref:`password-hash-api`. + .. note:: + + This class has been deprecated as of Passlib 1.6, + and will be removed in Passlib 1.8. + Use 'unix_disabled' instead. + This class does not implement a hash, but instead provides fallback behavior as found in /etc/shadow on most unix variants. If used, should be the last scheme in the context. @@ -43,6 +51,10 @@ class unix_fallback(uh.StaticHandler): return hash is not None def __init__(self, enable_wildcard=False, **kwds): + warn("'unix_fallback' is deprecated, " + "and will be removed in Passlib 1.8; " + "please use 'unix_disabled' instead.", + DeprecationWarning) super(unix_fallback, self).__init__(**kwds) self.enable_wildcard = enable_wildcard @@ -67,6 +79,71 @@ class unix_fallback(uh.StaticHandler): else: return enable_wildcard +class unix_disabled(object): + """This class provides disabled password behavior for unix shadow files, + and follows the :ref:`password-hash-api`. This class does not implement a + hash, but instead provides disabled account behavior as found in + ``/etc/shadow`` on most unix variants. + + * this class will positively identify all hash strings. + because of this it should be checked last. + * "encrypting" a password will simply return the disabled account marker. + * it will reject all passwords, no matter the hash. + + The :meth:`encrypt` method supports one optional keyword: + + :param marker: + Optional marker string which overrides the platform default + used to indicate a disabled account. + + If not specified, this will default to ``*`` on BSD systems, + and use the Linux default ``!`` for all other platforms. + (:attr:`!unix_disabled.marker` will contain the default value) + """ + name = "unix_disabled" + setting_kwds = ("marker",) + context_kwds = () + + if 'bsd' in sys.platform: + marker = u("*") + else: + # use the linux default for other systems + # (glibc also supports adding old hash after the marker + # so it can be restored later). + marker = u("!") + + @classmethod + def identify(cls, hash): + return hash is not None + + @classmethod + def encrypt(cls, secret, marker=None): + return cls.genhash(secret, None, marker) + + @classmethod + def verify(cls, secret, hash): + if secret is None: + raise TypeError("no secret provided") + if hash is None: + raise TypeError("no hash provided") + return False + + @classmethod + def genconfig(cls): + return None + + @classmethod + def genhash(cls, secret, config, marker=None): + if secret is None: + raise TypeError("secret must be string") + if config is not None: + # NOTE: config/hash will generally be "!" or "*", + # but we want to preserve it in case it has some other content, + # such as ``"!" + original hash``, which glibc uses. + return to_native_str(config, errname="config") + else: + return to_native_str(marker or cls.marker, errname="marker") + class plaintext(object): """This class stores passwords in plaintext, and follows the :ref:`password-hash-api`. diff --git a/passlib/hosts.py b/passlib/hosts.py index 6fc5570..5d3abc6 100644 --- a/passlib/hosts.py +++ b/passlib/hosts.py @@ -27,7 +27,7 @@ __all__ = [ linux_context = linux2_context = LazyCryptContext( schemes = [ "sha512_crypt", "sha256_crypt", "md5_crypt", - "des_crypt", "unix_fallback" ], + "des_crypt", "unix_disabled" ], deprecated = [ "des_crypt" ], ) @@ -50,9 +50,14 @@ linux_context = linux2_context = LazyCryptContext( # netbsd - des, ext, md5, bcrypt, sha1 # openbsd - des, ext, md5, bcrypt -freebsd_context = LazyCryptContext([ "bcrypt", "md5_crypt", "bsd_nthash", "des_crypt", "unix_fallback" ]) -openbsd_context = LazyCryptContext([ "bcrypt", "md5_crypt", "bsdi_crypt", "des_crypt", "unix_fallback" ]) -netbsd_context = LazyCryptContext([ "bcrypt", "sha1_crypt", "md5_crypt", "bsdi_crypt", "des_crypt", "unix_fallback" ]) +freebsd_context = LazyCryptContext(["bcrypt", "md5_crypt", "bsd_nthash", + "des_crypt", "unix_disabled"]) + +openbsd_context = LazyCryptContext(["bcrypt", "md5_crypt", "bsdi_crypt", + "des_crypt", "unix_disabled"]) + +netbsd_context = LazyCryptContext(["bcrypt", "sha1_crypt", "md5_crypt", + "bsdi_crypt", "des_crypt", "unix_disabled"]) #========================================================= #current host @@ -73,7 +78,7 @@ if has_crypt: if found: #only offer fallback if there's another scheme in front, #as this can't actually hash any passwords - yield "unix_fallback" + yield "unix_disabled" else: #no idea what OS this could happen on, but just in case... warn("crypt.crypt() function is present, but doesn't support any " diff --git a/passlib/registry.py b/passlib/registry.py index bf252fd..5806816 100644 --- a/passlib/registry.py +++ b/passlib/registry.py @@ -147,7 +147,8 @@ _handler_locations = { "sha256_crypt": ("passlib.handlers.sha2_crypt", "sha256_crypt"), "sha512_crypt": ("passlib.handlers.sha2_crypt", "sha512_crypt"), "sun_md5_crypt": ("passlib.handlers.sun_md5_crypt","sun_md5_crypt"), - "unix_fallback": ("passlib.handlers.misc", "unix_fallback"), + "unix_disabled": ("passlib.handlers.misc", "unix_disabled"), + "unix_fallback": ("passlib.handlers.misc", "unix_fallback"), } #: master regexp for detecting valid handler names diff --git a/passlib/tests/test_handlers.py b/passlib/tests/test_handlers.py index 260d011..2ab9837 100644 --- a/passlib/tests/test_handlers.py +++ b/passlib/tests/test_handlers.py @@ -2164,8 +2164,23 @@ class sun_md5_crypt_test(HandlerCase): return self.handler.verify(secret, hash) #========================================================= -#unix fallback +#unix disabled / fallback #========================================================= +class unix_disabled_test(HandlerCase): + handler = hash.unix_disabled + accepts_all_hashes = True + is_disabled_handler = True + + known_correct_hashes = [ + # everything should hash to "!" (or "*" on BSD), + # and nothing should verify against either string + ("password", "!"), + (UPASS_TABLE, "*"), + ] + + # TODO: test custom marker support + # TODO: test default marker selection + class unix_fallback_test(HandlerCase): handler = hash.unix_fallback accepts_all_hashes = True @@ -2177,6 +2192,11 @@ class unix_fallback_test(HandlerCase): (UPASS_TABLE, "!"), ] + # silence annoying deprecation warning + def setUp(self): + super(unix_fallback_test, self).setUp() + warnings.filterwarnings("ignore", "'unix_fallback' is deprecated") + def test_90_wildcard(self): "test enable_wildcard flag" h = self.handler diff --git a/passlib/tests/test_hosts.py b/passlib/tests/test_hosts.py index 06cc3c5..de744a8 100644 --- a/passlib/tests/test_hosts.py +++ b/passlib/tests/test_hosts.py @@ -24,14 +24,14 @@ class HostsTest(TestCase): # they mainly try to ensure no typos # or dynamic behavior foul-ups. - def check_unix_fallback(self, ctx): + def check_unix_disabled(self, ctx): for hash in [ "", "!", "*", "!$1$TXl/FX/U$BZge.lr.ux6ekjEjxmzwz0", ]: - self.assertEqual(ctx.identify(hash), 'unix_fallback') + self.assertEqual(ctx.identify(hash), 'unix_disabled') self.assertFalse(ctx.verify('test', hash)) def test_linux_context(self): @@ -45,7 +45,7 @@ class HostsTest(TestCase): 'kAJJz.Rwp0A/I', ]: self.assertTrue(ctx.verify("test", hash)) - self.check_unix_fallback(ctx) + self.check_unix_disabled(ctx) def test_bsd_contexts(self): for ctx in [ @@ -63,7 +63,7 @@ class HostsTest(TestCase): self.assertTrue(ctx.verify("test", h1)) else: self.assertEqual(ctx.identify(h1), "bcrypt") - self.check_unix_fallback(ctx) + self.check_unix_disabled(ctx) def test_host_context(self): ctx = getattr(hosts, "host_context", None) @@ -71,16 +71,16 @@ class HostsTest(TestCase): return self.skipTest("host_context not available on this platform") # validate schemes is non-empty, - # and contains unix_fallback + at least one real scheme + # and contains unix_disabled + at least one real scheme schemes = ctx.policy.schemes() self.assertTrue(schemes, "appears to be unix system, but no known schemes supported by crypt") - self.assertTrue('unix_fallback' in schemes) - schemes.remove("unix_fallback") + self.assertTrue('unix_disabled' in schemes) + schemes.remove("unix_disabled") self.assertTrue(schemes, "should have schemes beside fallback scheme") self.assertTrue(set(unix_crypt_schemes).issuperset(schemes)) # check for hash support - self.check_unix_fallback(ctx) + self.check_unix_disabled(ctx) for scheme, hash in [ ("sha512_crypt", ('$6$rounds=41128$VoQLvDjkaZ6L6BIE$4pt.1Ll1XdDYduEwEYPCMOBiR6W6' 'znsyUEoNlcVXpv2gKKIbQolgmTGe6uEEVJ7azUxuc8Tf7zV9SD2z7Ij751')), |
