Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
cisco.py441 linesDownload Raw Back to handlers
1"""2passlib.handlers.cisco -- Cisco password hashes3"""4#=============================================================================5# imports6#=============================================================================7# core8from binascii import hexlify, unhexlify9from hashlib import md510import logging; log = logging.getLogger(__name__)11from warnings import warn12# site13# pkg14from passlib.utils import right_pad_string, to_unicode, repeat_string, to_bytes15from passlib.utils.binary import h6416from passlib.utils.compat import unicode, u, join_byte_values, \17             join_byte_elems, iter_byte_values, uascii_to_str18import passlib.utils.handlers as uh19# local20__all__ = [21    "cisco_pix",22    "cisco_asa",23    "cisco_type7",24]25 26#=============================================================================27# utils28#=============================================================================29 30#: dummy bytes used by spoil_digest var in cisco_pix._calc_checksum()31_DUMMY_BYTES = b'\xFF' * 3232 33#=============================================================================34# cisco pix firewall hash35#=============================================================================36class cisco_pix(uh.HasUserContext, uh.StaticHandler):37    """38    This class implements the password hash used by older Cisco PIX firewalls,39    and follows the :ref:`password-hash-api`.40    It does a single round of hashing, and relies on the username41    as the salt.42 43    This class only allows passwords <= 16 bytes, anything larger44    will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_pix.hash`,45    and be silently rejected if passed to :meth:`~cisco_pix.verify`.46 47    The :meth:`~passlib.ifc.PasswordHash.hash`,48    :meth:`~passlib.ifc.PasswordHash.genhash`, and49    :meth:`~passlib.ifc.PasswordHash.verify` methods50    all support the following extra keyword:51 52    :param str user:53        String containing name of user account this password is associated with.54 55        This is *required* in order to correctly hash passwords associated56        with a user account on the Cisco device, as it is used to salt57        the hash.58 59        Conversely, this *must* be omitted or set to ``""`` in order to correctly60        hash passwords which don't have an associated user account61        (such as the "enable" password).62 63    .. versionadded:: 1.664 65    .. versionchanged:: 1.7.166 67        Passwords > 16 bytes are now rejected / throw error instead of being silently truncated,68        to match Cisco behavior.  A number of :ref:`bugs <passlib-asa96-bug>` were fixed69        which caused prior releases to generate unverifiable hashes in certain cases.70    """71    #===================================================================72    # class attrs73    #===================================================================74 75    #--------------------76    # PasswordHash77    #--------------------78    name = "cisco_pix"79 80    truncate_size = 1681 82    # NOTE: these are the default policy for PasswordHash,83    #       but want to set them explicitly for now.84    truncate_error = True85    truncate_verify_reject = True86 87    #--------------------88    # GenericHandler89    #--------------------90    checksum_size = 1691    checksum_chars = uh.HASH64_CHARS92 93    #--------------------94    # custom95    #--------------------96 97    #: control flag signalling "cisco_asa" mode, set by cisco_asa class98    _is_asa = False99 100    #===================================================================101    # methods102    #===================================================================103    def _calc_checksum(self, secret):104        """105        This function implements the "encrypted" hash format used by Cisco106        PIX & ASA. It's behavior has been confirmed for ASA 9.6,107        but is presumed correct for PIX & other ASA releases,108        as it fits with known test vectors, and existing literature.109 110        While nearly the same, the PIX & ASA hashes have slight differences,111        so this function performs differently based on the _is_asa class flag.112        Noteable changes from PIX to ASA include password size limit113        increased from 16 -> 32, and other internal changes.114        """115        # select PIX vs or ASA mode116        asa = self._is_asa117 118        #119        # encode secret120        #121        # per ASA 8.4 documentation,122        # http://www.cisco.com/c/en/us/td/docs/security/asa/asa84/configuration/guide/asa_84_cli_config/ref_cli.html#Supported_Character_Sets,123        # it supposedly uses UTF-8 -- though some double-encoding issues have124        # been observed when trying to actually *set* a non-ascii password125        # via ASDM, and access via SSH seems to strip 8-bit chars.126        #127        if isinstance(secret, unicode):128            secret = secret.encode("utf-8")129 130        #131        # check if password too large132        #133        # Per ASA 9.6 changes listed in134        # http://www.cisco.com/c/en/us/td/docs/security/asa/roadmap/asa_new_features.html,135        # prior releases had a maximum limit of 32 characters.136        # Testing with an ASA 9.6 system bears this out --137        # setting 32-char password for a user account,138        # and logins will fail if any chars are appended.139        # (ASA 9.6 added new PBKDF2-based hash algorithm,140        #  which supports larger passwords).141        #142        # Per PIX documentation143        # http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html,144        # it would not allow passwords > 16 chars.145        #146        # Thus, we unconditionally throw a password size error here,147        # as nothing valid can come from a larger password.148        # NOTE: assuming PIX has same behavior, but at 16 char limit.149        #150        spoil_digest = None151        if len(secret) > self.truncate_size:152            if self.use_defaults:153                # called from hash()154                msg = "Password too long (%s allows at most %d bytes)" % \155                      (self.name, self.truncate_size)156                raise uh.exc.PasswordSizeError(self.truncate_size, msg=msg)157            else:158                # called from verify() --159                # We don't want to throw error, or return early,160                # as that would let attacker know too much.  Instead, we set a161                # flag to add some dummy data into the md5 digest, so that162                # output won't match truncated version of secret, or anything163                # else that's fixed and predictable.164                spoil_digest = secret + _DUMMY_BYTES165 166        #167        # append user to secret168        #169        # Policy appears to be:170        #171        # * Nothing appended for enable password (user = "")172        #173        # * ASA: If user present, but secret is >= 28 chars, nothing appended.174        #175        # * 1-2 byte users not allowed.176        #   DEVIATION: we're letting them through, and repeating their177        #   chars ala 3-char user, to simplify testing.178        #   Could issue warning in the future though.179        #180        # * 3 byte user has first char repeated, to pad to 4.181        #   (observed under ASA 9.6, assuming true elsewhere)182        #183        # * 4 byte users are used directly.184        #185        # * 5+ byte users are truncated to 4 bytes.186        #187        user = self.user188        if user:189            if isinstance(user, unicode):190                user = user.encode("utf-8")191            if not asa or len(secret) < 28:192                secret += repeat_string(user, 4)193 194        #195        # pad / truncate result to limit196        #197        # While PIX always pads to 16 bytes, ASA increases to 32 bytes IFF198        # secret+user > 16 bytes.  This makes PIX & ASA have different results199        # where secret size in range(13,16), and user is present --200        # PIX will truncate to 16, ASA will truncate to 32.201        #202        if asa and len(secret) > 16:203            pad_size = 32204        else:205            pad_size = 16206        secret = right_pad_string(secret, pad_size)207 208        #209        # md5 digest210        #211        if spoil_digest:212            # make sure digest won't match truncated version of secret213            secret += spoil_digest214        digest = md5(secret).digest()215 216        #217        # drop every 4th byte218        # NOTE: guessing this was done because it makes output exactly219        #       16 bytes, which may have been a general 'char password[]'220        #       size limit under PIX221        #222        digest = join_byte_elems(c for i, c in enumerate(digest) if (i + 1) & 3)223 224        #225        # encode using Hash64226        #227        return h64.encode_bytes(digest).decode("ascii")228 229    # NOTE: works, but needs UTs.230    # @classmethod231    # def same_as_pix(cls, secret, user=""):232    #     """233    #     test whether (secret + user) combination should234    #     have the same hash under PIX and ASA.235    #236    #     mainly present to help unittests.237    #     """238    #     # see _calc_checksum() above for details of this logic.239    #     size = len(to_bytes(secret, "utf-8"))240    #     if user and size < 28:241    #         size += 4242    #     return size < 17243 244    #===================================================================245    # eoc246    #===================================================================247 248 249class cisco_asa(cisco_pix):250    """251    This class implements the password hash used by Cisco ASA/PIX 7.0 and newer (2005).252    Aside from a different internal algorithm, it's use and format is identical253    to the older :class:`cisco_pix` class.254 255    For passwords less than 13 characters, this should be identical to :class:`!cisco_pix`,256    but will generate a different hash for most larger inputs257    (See the `Format & Algorithm`_ section for the details).258 259    This class only allows passwords <= 32 bytes, anything larger260    will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_asa.hash`,261    and be silently rejected if passed to :meth:`~cisco_asa.verify`.262 263    .. versionadded:: 1.7264 265    .. versionchanged:: 1.7.1266 267        Passwords > 32 bytes are now rejected / throw error instead of being silently truncated,268        to match Cisco behavior.  A number of :ref:`bugs <passlib-asa96-bug>` were fixed269        which caused prior releases to generate unverifiable hashes in certain cases.270    """271    #===================================================================272    # class attrs273    #===================================================================274 275    #--------------------276    # PasswordHash277    #--------------------278    name = "cisco_asa"279 280    #--------------------281    # TruncateMixin282    #--------------------283    truncate_size = 32284 285    #--------------------286    # cisco_pix287    #--------------------288    _is_asa = True289 290    #===================================================================291    # eoc292    #===================================================================293 294#=============================================================================295# type 7296#=============================================================================297class cisco_type7(uh.GenericHandler):298    """299    This class implements the "Type 7" password encoding used by Cisco IOS,300    and follows the :ref:`password-hash-api`.301    It has a simple 4-5 bit salt, but is nonetheless a reversible encoding302    instead of a real hash.303 304    The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:305 306    :type salt: int307    :param salt:308        This may be an optional salt integer drawn from ``range(0,16)``.309        If omitted, one will be chosen at random.310 311    :type relaxed: bool312    :param relaxed:313        By default, providing an invalid value for one of the other314        keywords will result in a :exc:`ValueError`. If ``relaxed=True``,315        and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`316        will be issued instead. Correctable errors include317        ``salt`` values that are out of range.318 319    Note that while this class outputs digests in upper-case hexadecimal,320    it will accept lower-case as well.321 322    This class also provides the following additional method:323 324    .. automethod:: decode325    """326    #===================================================================327    # class attrs328    #===================================================================329 330    #--------------------331    # PasswordHash332    #--------------------333    name = "cisco_type7"334    setting_kwds = ("salt",)335 336    #--------------------337    # GenericHandler338    #--------------------339    checksum_chars = uh.UPPER_HEX_CHARS340 341    #--------------------342    # HasSalt343    #--------------------344 345    # NOTE: encoding could handle max_salt_value=99, but since key is only 52346    #       chars in size, not sure what appropriate behavior is for that edge case.347    min_salt_value = 0348    max_salt_value = 52349 350    #===================================================================351    # methods352    #===================================================================353    @classmethod354    def using(cls, salt=None, **kwds):355        subcls = super(cisco_type7, cls).using(**kwds)356        if salt is not None:357            salt = subcls._norm_salt(salt, relaxed=kwds.get("relaxed"))358            subcls._generate_salt = staticmethod(lambda: salt)359        return subcls360 361    @classmethod362    def from_string(cls, hash):363        hash = to_unicode(hash, "ascii", "hash")364        if len(hash) < 2:365            raise uh.exc.InvalidHashError(cls)366        salt = int(hash[:2]) # may throw ValueError367        return cls(salt=salt, checksum=hash[2:].upper())368 369    def __init__(self, salt=None, **kwds):370        super(cisco_type7, self).__init__(**kwds)371        if salt is not None:372            salt = self._norm_salt(salt)373        elif self.use_defaults:374            salt = self._generate_salt()375            assert self._norm_salt(salt) == salt, "generated invalid salt: %r" % (salt,)376        else:377            raise TypeError("no salt specified")378        self.salt = salt379 380    @classmethod381    def _norm_salt(cls, salt, relaxed=False):382        """383        validate & normalize salt value.384        .. note::385            the salt for this algorithm is an integer 0-52, not a string386        """387        if not isinstance(salt, int):388            raise uh.exc.ExpectedTypeError(salt, "integer", "salt")389        if 0 <= salt <= cls.max_salt_value:390            return salt391        msg = "salt/offset must be in 0..52 range"392        if relaxed:393            warn(msg, uh.PasslibHashWarning)394            return 0 if salt < 0 else cls.max_salt_value395        else:396            raise ValueError(msg)397 398    @staticmethod399    def _generate_salt():400        return uh.rng.randint(0, 15)401 402    def to_string(self):403        return "%02d%s" % (self.salt, uascii_to_str(self.checksum))404 405    def _calc_checksum(self, secret):406        # XXX: no idea what unicode policy is, but all examples are407        # 7-bit ascii compatible, so using UTF-8408        if isinstance(secret, unicode):409            secret = secret.encode("utf-8")410        return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper()411 412    @classmethod413    def decode(cls, hash, encoding="utf-8"):414        """decode hash, returning original password.415 416        :arg hash: encoded password417        :param encoding: optional encoding to use (defaults to ``UTF-8``).418        :returns: password as unicode419        """420        self = cls.from_string(hash)421        tmp = unhexlify(self.checksum.encode("ascii"))422        raw = self._cipher(tmp, self.salt)423        return raw.decode(encoding) if encoding else raw424 425    # type7 uses a xor-based vingere variant, using the following secret key:426    _key = u("dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87")427 428    @classmethod429    def _cipher(cls, data, salt):430        """xor static key against data - encrypts & decrypts"""431        key = cls._key432        key_size = len(key)433        return join_byte_values(434            value ^ ord(key[(salt + idx) % key_size])435            for idx, value in enumerate(iter_byte_values(data))436        )437 438#=============================================================================439# eof440#=============================================================================441 
codekingpro/portable-devtools · Team Ai