You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					441 lines
				
				16 KiB
			
		
		
			
		
	
	
					441 lines
				
				16 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""
							 | 
						||
| 
								 | 
							
								passlib.handlers.cisco -- Cisco password hashes
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								from binascii import hexlify, unhexlify
							 | 
						||
| 
								 | 
							
								from hashlib import md5
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								from warnings import warn
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib.utils import right_pad_string, to_unicode, repeat_string, to_bytes
							 | 
						||
| 
								 | 
							
								from passlib.utils.binary import h64
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import unicode, u, join_byte_values, \
							 | 
						||
| 
								 | 
							
								             join_byte_elems, iter_byte_values, uascii_to_str
							 | 
						||
| 
								 | 
							
								import passlib.utils.handlers as uh
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    "cisco_pix",
							 | 
						||
| 
								 | 
							
								    "cisco_asa",
							 | 
						||
| 
								 | 
							
								    "cisco_type7",
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# utils
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#: dummy bytes used by spoil_digest var in cisco_pix._calc_checksum()
							 | 
						||
| 
								 | 
							
								_DUMMY_BYTES = b'\xFF' * 32
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# cisco pix firewall hash
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class cisco_pix(uh.HasUserContext, uh.StaticHandler):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    This class implements the password hash used by older Cisco PIX firewalls,
							 | 
						||
| 
								 | 
							
								    and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								    It does a single round of hashing, and relies on the username
							 | 
						||
| 
								 | 
							
								    as the salt.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This class only allows passwords <= 16 bytes, anything larger
							 | 
						||
| 
								 | 
							
								    will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_pix.hash`,
							 | 
						||
| 
								 | 
							
								    and be silently rejected if passed to :meth:`~cisco_pix.verify`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.hash`,
							 | 
						||
| 
								 | 
							
								    :meth:`~passlib.ifc.PasswordHash.genhash`, and
							 | 
						||
| 
								 | 
							
								    :meth:`~passlib.ifc.PasswordHash.verify` methods
							 | 
						||
| 
								 | 
							
								    all support the following extra keyword:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param str user:
							 | 
						||
| 
								 | 
							
								        String containing name of user account this password is associated with.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This is *required* in order to correctly hash passwords associated
							 | 
						||
| 
								 | 
							
								        with a user account on the Cisco device, as it is used to salt
							 | 
						||
| 
								 | 
							
								        the hash.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Conversely, this *must* be omitted or set to ``""`` in order to correctly
							 | 
						||
| 
								 | 
							
								        hash passwords which don't have an associated user account
							 | 
						||
| 
								 | 
							
								        (such as the "enable" password).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged:: 1.7.1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Passwords > 16 bytes are now rejected / throw error instead of being silently truncated,
							 | 
						||
| 
								 | 
							
								        to match Cisco behavior.  A number of :ref:`bugs <passlib-asa96-bug>` were fixed
							 | 
						||
| 
								 | 
							
								        which caused prior releases to generate unverifiable hashes in certain cases.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # PasswordHash
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    name = "cisco_pix"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    truncate_size = 16
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: these are the default policy for PasswordHash,
							 | 
						||
| 
								 | 
							
								    #       but want to set them explicitly for now.
							 | 
						||
| 
								 | 
							
								    truncate_error = True
							 | 
						||
| 
								 | 
							
								    truncate_verify_reject = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # GenericHandler
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    checksum_size = 16
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.HASH64_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # custom
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #: control flag signalling "cisco_asa" mode, set by cisco_asa class
							 | 
						||
| 
								 | 
							
								    _is_asa = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # methods
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        This function implements the "encrypted" hash format used by Cisco
							 | 
						||
| 
								 | 
							
								        PIX & ASA. It's behavior has been confirmed for ASA 9.6,
							 | 
						||
| 
								 | 
							
								        but is presumed correct for PIX & other ASA releases,
							 | 
						||
| 
								 | 
							
								        as it fits with known test vectors, and existing literature.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        While nearly the same, the PIX & ASA hashes have slight differences,
							 | 
						||
| 
								 | 
							
								        so this function performs differently based on the _is_asa class flag.
							 | 
						||
| 
								 | 
							
								        Noteable changes from PIX to ASA include password size limit
							 | 
						||
| 
								 | 
							
								        increased from 16 -> 32, and other internal changes.
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        # select PIX vs or ASA mode
							 | 
						||
| 
								 | 
							
								        asa = self._is_asa
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # encode secret
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # per ASA 8.4 documentation,
							 | 
						||
| 
								 | 
							
								        # http://www.cisco.com/c/en/us/td/docs/security/asa/asa84/configuration/guide/asa_84_cli_config/ref_cli.html#Supported_Character_Sets,
							 | 
						||
| 
								 | 
							
								        # it supposedly uses UTF-8 -- though some double-encoding issues have
							 | 
						||
| 
								 | 
							
								        # been observed when trying to actually *set* a non-ascii password
							 | 
						||
| 
								 | 
							
								        # via ASDM, and access via SSH seems to strip 8-bit chars.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # check if password too large
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Per ASA 9.6 changes listed in
							 | 
						||
| 
								 | 
							
								        # http://www.cisco.com/c/en/us/td/docs/security/asa/roadmap/asa_new_features.html,
							 | 
						||
| 
								 | 
							
								        # prior releases had a maximum limit of 32 characters.
							 | 
						||
| 
								 | 
							
								        # Testing with an ASA 9.6 system bears this out --
							 | 
						||
| 
								 | 
							
								        # setting 32-char password for a user account,
							 | 
						||
| 
								 | 
							
								        # and logins will fail if any chars are appended.
							 | 
						||
| 
								 | 
							
								        # (ASA 9.6 added new PBKDF2-based hash algorithm,
							 | 
						||
| 
								 | 
							
								        #  which supports larger passwords).
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Per PIX documentation
							 | 
						||
| 
								 | 
							
								        # http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html,
							 | 
						||
| 
								 | 
							
								        # it would not allow passwords > 16 chars.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Thus, we unconditionally throw a password size error here,
							 | 
						||
| 
								 | 
							
								        # as nothing valid can come from a larger password.
							 | 
						||
| 
								 | 
							
								        # NOTE: assuming PIX has same behavior, but at 16 char limit.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        spoil_digest = None
							 | 
						||
| 
								 | 
							
								        if len(secret) > self.truncate_size:
							 | 
						||
| 
								 | 
							
								            if self.use_defaults:
							 | 
						||
| 
								 | 
							
								                # called from hash()
							 | 
						||
| 
								 | 
							
								                msg = "Password too long (%s allows at most %d bytes)" % \
							 | 
						||
| 
								 | 
							
								                      (self.name, self.truncate_size)
							 | 
						||
| 
								 | 
							
								                raise uh.exc.PasswordSizeError(self.truncate_size, msg=msg)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # called from verify() --
							 | 
						||
| 
								 | 
							
								                # We don't want to throw error, or return early,
							 | 
						||
| 
								 | 
							
								                # as that would let attacker know too much.  Instead, we set a
							 | 
						||
| 
								 | 
							
								                # flag to add some dummy data into the md5 digest, so that
							 | 
						||
| 
								 | 
							
								                # output won't match truncated version of secret, or anything
							 | 
						||
| 
								 | 
							
								                # else that's fixed and predictable.
							 | 
						||
| 
								 | 
							
								                spoil_digest = secret + _DUMMY_BYTES
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # append user to secret
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Policy appears to be:
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # * Nothing appended for enable password (user = "")
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # * ASA: If user present, but secret is >= 28 chars, nothing appended.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # * 1-2 byte users not allowed.
							 | 
						||
| 
								 | 
							
								        #   DEVIATION: we're letting them through, and repeating their
							 | 
						||
| 
								 | 
							
								        #   chars ala 3-char user, to simplify testing.
							 | 
						||
| 
								 | 
							
								        #   Could issue warning in the future though.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # * 3 byte user has first char repeated, to pad to 4.
							 | 
						||
| 
								 | 
							
								        #   (observed under ASA 9.6, assuming true elsewhere)
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # * 4 byte users are used directly.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # * 5+ byte users are truncated to 4 bytes.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        user = self.user
							 | 
						||
| 
								 | 
							
								        if user:
							 | 
						||
| 
								 | 
							
								            if isinstance(user, unicode):
							 | 
						||
| 
								 | 
							
								                user = user.encode("utf-8")
							 | 
						||
| 
								 | 
							
								            if not asa or len(secret) < 28:
							 | 
						||
| 
								 | 
							
								                secret += repeat_string(user, 4)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # pad / truncate result to limit
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # While PIX always pads to 16 bytes, ASA increases to 32 bytes IFF
							 | 
						||
| 
								 | 
							
								        # secret+user > 16 bytes.  This makes PIX & ASA have different results
							 | 
						||
| 
								 | 
							
								        # where secret size in range(13,16), and user is present --
							 | 
						||
| 
								 | 
							
								        # PIX will truncate to 16, ASA will truncate to 32.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if asa and len(secret) > 16:
							 | 
						||
| 
								 | 
							
								            pad_size = 32
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            pad_size = 16
							 | 
						||
| 
								 | 
							
								        secret = right_pad_string(secret, pad_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # md5 digest
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if spoil_digest:
							 | 
						||
| 
								 | 
							
								            # make sure digest won't match truncated version of secret
							 | 
						||
| 
								 | 
							
								            secret += spoil_digest
							 | 
						||
| 
								 | 
							
								        digest = md5(secret).digest()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # drop every 4th byte
							 | 
						||
| 
								 | 
							
								        # NOTE: guessing this was done because it makes output exactly
							 | 
						||
| 
								 | 
							
								        #       16 bytes, which may have been a general 'char password[]'
							 | 
						||
| 
								 | 
							
								        #       size limit under PIX
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        digest = join_byte_elems(c for i, c in enumerate(digest) if (i + 1) & 3)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # encode using Hash64
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        return h64.encode_bytes(digest).decode("ascii")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: works, but needs UTs.
							 | 
						||
| 
								 | 
							
								    # @classmethod
							 | 
						||
| 
								 | 
							
								    # def same_as_pix(cls, secret, user=""):
							 | 
						||
| 
								 | 
							
								    #     """
							 | 
						||
| 
								 | 
							
								    #     test whether (secret + user) combination should
							 | 
						||
| 
								 | 
							
								    #     have the same hash under PIX and ASA.
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    #     mainly present to help unittests.
							 | 
						||
| 
								 | 
							
								    #     """
							 | 
						||
| 
								 | 
							
								    #     # see _calc_checksum() above for details of this logic.
							 | 
						||
| 
								 | 
							
								    #     size = len(to_bytes(secret, "utf-8"))
							 | 
						||
| 
								 | 
							
								    #     if user and size < 28:
							 | 
						||
| 
								 | 
							
								    #         size += 4
							 | 
						||
| 
								 | 
							
								    #     return size < 17
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class cisco_asa(cisco_pix):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    This class implements the password hash used by Cisco ASA/PIX 7.0 and newer (2005).
							 | 
						||
| 
								 | 
							
								    Aside from a different internal algorithm, it's use and format is identical
							 | 
						||
| 
								 | 
							
								    to the older :class:`cisco_pix` class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    For passwords less than 13 characters, this should be identical to :class:`!cisco_pix`,
							 | 
						||
| 
								 | 
							
								    but will generate a different hash for most larger inputs
							 | 
						||
| 
								 | 
							
								    (See the `Format & Algorithm`_ section for the details).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This class only allows passwords <= 32 bytes, anything larger
							 | 
						||
| 
								 | 
							
								    will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_asa.hash`,
							 | 
						||
| 
								 | 
							
								    and be silently rejected if passed to :meth:`~cisco_asa.verify`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.7
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionchanged:: 1.7.1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Passwords > 32 bytes are now rejected / throw error instead of being silently truncated,
							 | 
						||
| 
								 | 
							
								        to match Cisco behavior.  A number of :ref:`bugs <passlib-asa96-bug>` were fixed
							 | 
						||
| 
								 | 
							
								        which caused prior releases to generate unverifiable hashes in certain cases.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # PasswordHash
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    name = "cisco_asa"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # TruncateMixin
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    truncate_size = 32
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # cisco_pix
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    _is_asa = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # eoc
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# type 7
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								class cisco_type7(uh.GenericHandler):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    This class implements the "Type 7" password encoding used by Cisco IOS,
							 | 
						||
| 
								 | 
							
								    and follows the :ref:`password-hash-api`.
							 | 
						||
| 
								 | 
							
								    It has a simple 4-5 bit salt, but is nonetheless a reversible encoding
							 | 
						||
| 
								 | 
							
								    instead of a real hash.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type salt: int
							 | 
						||
| 
								 | 
							
								    :param salt:
							 | 
						||
| 
								 | 
							
								        This may be an optional salt integer drawn from ``range(0,16)``.
							 | 
						||
| 
								 | 
							
								        If omitted, one will be chosen at random.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :type relaxed: bool
							 | 
						||
| 
								 | 
							
								    :param relaxed:
							 | 
						||
| 
								 | 
							
								        By default, providing an invalid value for one of the other
							 | 
						||
| 
								 | 
							
								        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
							 | 
						||
| 
								 | 
							
								        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
							 | 
						||
| 
								 | 
							
								        will be issued instead. Correctable errors include
							 | 
						||
| 
								 | 
							
								        ``salt`` values that are out of range.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Note that while this class outputs digests in upper-case hexadecimal,
							 | 
						||
| 
								 | 
							
								    it will accept lower-case as well.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This class also provides the following additional method:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. automethod:: decode
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # class attrs
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # PasswordHash
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    name = "cisco_type7"
							 | 
						||
| 
								 | 
							
								    setting_kwds = ("salt",)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # GenericHandler
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    checksum_chars = uh.UPPER_HEX_CHARS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								    # HasSalt
							 | 
						||
| 
								 | 
							
								    #--------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # NOTE: encoding could handle max_salt_value=99, but since key is only 52
							 | 
						||
| 
								 | 
							
								    #       chars in size, not sure what appropriate behavior is for that edge case.
							 | 
						||
| 
								 | 
							
								    min_salt_value = 0
							 | 
						||
| 
								 | 
							
								    max_salt_value = 52
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    # methods
							 | 
						||
| 
								 | 
							
								    #===================================================================
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def using(cls, salt=None, **kwds):
							 | 
						||
| 
								 | 
							
								        subcls = super(cisco_type7, cls).using(**kwds)
							 | 
						||
| 
								 | 
							
								        if salt is not None:
							 | 
						||
| 
								 | 
							
								            salt = subcls._norm_salt(salt, relaxed=kwds.get("relaxed"))
							 | 
						||
| 
								 | 
							
								            subcls._generate_salt = staticmethod(lambda: salt)
							 | 
						||
| 
								 | 
							
								        return subcls
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def from_string(cls, hash):
							 | 
						||
| 
								 | 
							
								        hash = to_unicode(hash, "ascii", "hash")
							 | 
						||
| 
								 | 
							
								        if len(hash) < 2:
							 | 
						||
| 
								 | 
							
								            raise uh.exc.InvalidHashError(cls)
							 | 
						||
| 
								 | 
							
								        salt = int(hash[:2]) # may throw ValueError
							 | 
						||
| 
								 | 
							
								        return cls(salt=salt, checksum=hash[2:].upper())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, salt=None, **kwds):
							 | 
						||
| 
								 | 
							
								        super(cisco_type7, self).__init__(**kwds)
							 | 
						||
| 
								 | 
							
								        if salt is not None:
							 | 
						||
| 
								 | 
							
								            salt = self._norm_salt(salt)
							 | 
						||
| 
								 | 
							
								        elif self.use_defaults:
							 | 
						||
| 
								 | 
							
								            salt = self._generate_salt()
							 | 
						||
| 
								 | 
							
								            assert self._norm_salt(salt) == salt, "generated invalid salt: %r" % (salt,)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise TypeError("no salt specified")
							 | 
						||
| 
								 | 
							
								        self.salt = salt
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def _norm_salt(cls, salt, relaxed=False):
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        validate & normalize salt value.
							 | 
						||
| 
								 | 
							
								        .. note::
							 | 
						||
| 
								 | 
							
								            the salt for this algorithm is an integer 0-52, not a string
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        if not isinstance(salt, int):
							 | 
						||
| 
								 | 
							
								            raise uh.exc.ExpectedTypeError(salt, "integer", "salt")
							 | 
						||
| 
								 | 
							
								        if 0 <= salt <= cls.max_salt_value:
							 | 
						||
| 
								 | 
							
								            return salt
							 | 
						||
| 
								 | 
							
								        msg = "salt/offset must be in 0..52 range"
							 | 
						||
| 
								 | 
							
								        if relaxed:
							 | 
						||
| 
								 | 
							
								            warn(msg, uh.PasslibHashWarning)
							 | 
						||
| 
								 | 
							
								            return 0 if salt < 0 else cls.max_salt_value
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise ValueError(msg)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @staticmethod
							 | 
						||
| 
								 | 
							
								    def _generate_salt():
							 | 
						||
| 
								 | 
							
								        return uh.rng.randint(0, 15)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_string(self):
							 | 
						||
| 
								 | 
							
								        return "%02d%s" % (self.salt, uascii_to_str(self.checksum))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _calc_checksum(self, secret):
							 | 
						||
| 
								 | 
							
								        # XXX: no idea what unicode policy is, but all examples are
							 | 
						||
| 
								 | 
							
								        # 7-bit ascii compatible, so using UTF-8
							 | 
						||
| 
								 | 
							
								        if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								            secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								        return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def decode(cls, hash, encoding="utf-8"):
							 | 
						||
| 
								 | 
							
								        """decode hash, returning original password.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        :arg hash: encoded password
							 | 
						||
| 
								 | 
							
								        :param encoding: optional encoding to use (defaults to ``UTF-8``).
							 | 
						||
| 
								 | 
							
								        :returns: password as unicode
							 | 
						||
| 
								 | 
							
								        """
							 | 
						||
| 
								 | 
							
								        self = cls.from_string(hash)
							 | 
						||
| 
								 | 
							
								        tmp = unhexlify(self.checksum.encode("ascii"))
							 | 
						||
| 
								 | 
							
								        raw = self._cipher(tmp, self.salt)
							 | 
						||
| 
								 | 
							
								        return raw.decode(encoding) if encoding else raw
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # type7 uses a xor-based vingere variant, using the following secret key:
							 | 
						||
| 
								 | 
							
								    _key = u("dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def _cipher(cls, data, salt):
							 | 
						||
| 
								 | 
							
								        """xor static key against data - encrypts & decrypts"""
							 | 
						||
| 
								 | 
							
								        key = cls._key
							 | 
						||
| 
								 | 
							
								        key_size = len(key)
							 | 
						||
| 
								 | 
							
								        return join_byte_values(
							 | 
						||
| 
								 | 
							
								            value ^ ord(key[(salt + idx) % key_size])
							 | 
						||
| 
								 | 
							
								            for idx, value in enumerate(iter_byte_values(data))
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |