Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

441 řádky
16 KiB

  1. """
  2. passlib.handlers.cisco -- Cisco password hashes
  3. """
  4. #=============================================================================
  5. # imports
  6. #=============================================================================
  7. # core
  8. from binascii import hexlify, unhexlify
  9. from hashlib import md5
  10. import logging; log = logging.getLogger(__name__)
  11. from warnings import warn
  12. # site
  13. # pkg
  14. from passlib.utils import right_pad_string, to_unicode, repeat_string, to_bytes
  15. from passlib.utils.binary import h64
  16. from passlib.utils.compat import unicode, u, join_byte_values, \
  17. join_byte_elems, iter_byte_values, uascii_to_str
  18. import passlib.utils.handlers as uh
  19. # local
  20. __all__ = [
  21. "cisco_pix",
  22. "cisco_asa",
  23. "cisco_type7",
  24. ]
  25. #=============================================================================
  26. # utils
  27. #=============================================================================
  28. #: dummy bytes used by spoil_digest var in cisco_pix._calc_checksum()
  29. _DUMMY_BYTES = b'\xFF' * 32
  30. #=============================================================================
  31. # cisco pix firewall hash
  32. #=============================================================================
  33. class cisco_pix(uh.HasUserContext, uh.StaticHandler):
  34. """
  35. This class implements the password hash used by older Cisco PIX firewalls,
  36. and follows the :ref:`password-hash-api`.
  37. It does a single round of hashing, and relies on the username
  38. as the salt.
  39. This class only allows passwords <= 16 bytes, anything larger
  40. will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_pix.hash`,
  41. and be silently rejected if passed to :meth:`~cisco_pix.verify`.
  42. The :meth:`~passlib.ifc.PasswordHash.hash`,
  43. :meth:`~passlib.ifc.PasswordHash.genhash`, and
  44. :meth:`~passlib.ifc.PasswordHash.verify` methods
  45. all support the following extra keyword:
  46. :param str user:
  47. String containing name of user account this password is associated with.
  48. This is *required* in order to correctly hash passwords associated
  49. with a user account on the Cisco device, as it is used to salt
  50. the hash.
  51. Conversely, this *must* be omitted or set to ``""`` in order to correctly
  52. hash passwords which don't have an associated user account
  53. (such as the "enable" password).
  54. .. versionadded:: 1.6
  55. .. versionchanged:: 1.7.1
  56. Passwords > 16 bytes are now rejected / throw error instead of being silently truncated,
  57. to match Cisco behavior. A number of :ref:`bugs <passlib-asa96-bug>` were fixed
  58. which caused prior releases to generate unverifiable hashes in certain cases.
  59. """
  60. #===================================================================
  61. # class attrs
  62. #===================================================================
  63. #--------------------
  64. # PasswordHash
  65. #--------------------
  66. name = "cisco_pix"
  67. truncate_size = 16
  68. # NOTE: these are the default policy for PasswordHash,
  69. # but want to set them explicitly for now.
  70. truncate_error = True
  71. truncate_verify_reject = True
  72. #--------------------
  73. # GenericHandler
  74. #--------------------
  75. checksum_size = 16
  76. checksum_chars = uh.HASH64_CHARS
  77. #--------------------
  78. # custom
  79. #--------------------
  80. #: control flag signalling "cisco_asa" mode, set by cisco_asa class
  81. _is_asa = False
  82. #===================================================================
  83. # methods
  84. #===================================================================
  85. def _calc_checksum(self, secret):
  86. """
  87. This function implements the "encrypted" hash format used by Cisco
  88. PIX & ASA. It's behavior has been confirmed for ASA 9.6,
  89. but is presumed correct for PIX & other ASA releases,
  90. as it fits with known test vectors, and existing literature.
  91. While nearly the same, the PIX & ASA hashes have slight differences,
  92. so this function performs differently based on the _is_asa class flag.
  93. Noteable changes from PIX to ASA include password size limit
  94. increased from 16 -> 32, and other internal changes.
  95. """
  96. # select PIX vs or ASA mode
  97. asa = self._is_asa
  98. #
  99. # encode secret
  100. #
  101. # per ASA 8.4 documentation,
  102. # http://www.cisco.com/c/en/us/td/docs/security/asa/asa84/configuration/guide/asa_84_cli_config/ref_cli.html#Supported_Character_Sets,
  103. # it supposedly uses UTF-8 -- though some double-encoding issues have
  104. # been observed when trying to actually *set* a non-ascii password
  105. # via ASDM, and access via SSH seems to strip 8-bit chars.
  106. #
  107. if isinstance(secret, unicode):
  108. secret = secret.encode("utf-8")
  109. #
  110. # check if password too large
  111. #
  112. # Per ASA 9.6 changes listed in
  113. # http://www.cisco.com/c/en/us/td/docs/security/asa/roadmap/asa_new_features.html,
  114. # prior releases had a maximum limit of 32 characters.
  115. # Testing with an ASA 9.6 system bears this out --
  116. # setting 32-char password for a user account,
  117. # and logins will fail if any chars are appended.
  118. # (ASA 9.6 added new PBKDF2-based hash algorithm,
  119. # which supports larger passwords).
  120. #
  121. # Per PIX documentation
  122. # http://www.cisco.com/en/US/docs/security/pix/pix50/configuration/guide/commands.html,
  123. # it would not allow passwords > 16 chars.
  124. #
  125. # Thus, we unconditionally throw a password size error here,
  126. # as nothing valid can come from a larger password.
  127. # NOTE: assuming PIX has same behavior, but at 16 char limit.
  128. #
  129. spoil_digest = None
  130. if len(secret) > self.truncate_size:
  131. if self.use_defaults:
  132. # called from hash()
  133. msg = "Password too long (%s allows at most %d bytes)" % \
  134. (self.name, self.truncate_size)
  135. raise uh.exc.PasswordSizeError(self.truncate_size, msg=msg)
  136. else:
  137. # called from verify() --
  138. # We don't want to throw error, or return early,
  139. # as that would let attacker know too much. Instead, we set a
  140. # flag to add some dummy data into the md5 digest, so that
  141. # output won't match truncated version of secret, or anything
  142. # else that's fixed and predictable.
  143. spoil_digest = secret + _DUMMY_BYTES
  144. #
  145. # append user to secret
  146. #
  147. # Policy appears to be:
  148. #
  149. # * Nothing appended for enable password (user = "")
  150. #
  151. # * ASA: If user present, but secret is >= 28 chars, nothing appended.
  152. #
  153. # * 1-2 byte users not allowed.
  154. # DEVIATION: we're letting them through, and repeating their
  155. # chars ala 3-char user, to simplify testing.
  156. # Could issue warning in the future though.
  157. #
  158. # * 3 byte user has first char repeated, to pad to 4.
  159. # (observed under ASA 9.6, assuming true elsewhere)
  160. #
  161. # * 4 byte users are used directly.
  162. #
  163. # * 5+ byte users are truncated to 4 bytes.
  164. #
  165. user = self.user
  166. if user:
  167. if isinstance(user, unicode):
  168. user = user.encode("utf-8")
  169. if not asa or len(secret) < 28:
  170. secret += repeat_string(user, 4)
  171. #
  172. # pad / truncate result to limit
  173. #
  174. # While PIX always pads to 16 bytes, ASA increases to 32 bytes IFF
  175. # secret+user > 16 bytes. This makes PIX & ASA have different results
  176. # where secret size in range(13,16), and user is present --
  177. # PIX will truncate to 16, ASA will truncate to 32.
  178. #
  179. if asa and len(secret) > 16:
  180. pad_size = 32
  181. else:
  182. pad_size = 16
  183. secret = right_pad_string(secret, pad_size)
  184. #
  185. # md5 digest
  186. #
  187. if spoil_digest:
  188. # make sure digest won't match truncated version of secret
  189. secret += spoil_digest
  190. digest = md5(secret).digest()
  191. #
  192. # drop every 4th byte
  193. # NOTE: guessing this was done because it makes output exactly
  194. # 16 bytes, which may have been a general 'char password[]'
  195. # size limit under PIX
  196. #
  197. digest = join_byte_elems(c for i, c in enumerate(digest) if (i + 1) & 3)
  198. #
  199. # encode using Hash64
  200. #
  201. return h64.encode_bytes(digest).decode("ascii")
  202. # NOTE: works, but needs UTs.
  203. # @classmethod
  204. # def same_as_pix(cls, secret, user=""):
  205. # """
  206. # test whether (secret + user) combination should
  207. # have the same hash under PIX and ASA.
  208. #
  209. # mainly present to help unittests.
  210. # """
  211. # # see _calc_checksum() above for details of this logic.
  212. # size = len(to_bytes(secret, "utf-8"))
  213. # if user and size < 28:
  214. # size += 4
  215. # return size < 17
  216. #===================================================================
  217. # eoc
  218. #===================================================================
  219. class cisco_asa(cisco_pix):
  220. """
  221. This class implements the password hash used by Cisco ASA/PIX 7.0 and newer (2005).
  222. Aside from a different internal algorithm, it's use and format is identical
  223. to the older :class:`cisco_pix` class.
  224. For passwords less than 13 characters, this should be identical to :class:`!cisco_pix`,
  225. but will generate a different hash for most larger inputs
  226. (See the `Format & Algorithm`_ section for the details).
  227. This class only allows passwords <= 32 bytes, anything larger
  228. will result in a :exc:`~passlib.exc.PasswordSizeError` if passed to :meth:`~cisco_asa.hash`,
  229. and be silently rejected if passed to :meth:`~cisco_asa.verify`.
  230. .. versionadded:: 1.7
  231. .. versionchanged:: 1.7.1
  232. Passwords > 32 bytes are now rejected / throw error instead of being silently truncated,
  233. to match Cisco behavior. A number of :ref:`bugs <passlib-asa96-bug>` were fixed
  234. which caused prior releases to generate unverifiable hashes in certain cases.
  235. """
  236. #===================================================================
  237. # class attrs
  238. #===================================================================
  239. #--------------------
  240. # PasswordHash
  241. #--------------------
  242. name = "cisco_asa"
  243. #--------------------
  244. # TruncateMixin
  245. #--------------------
  246. truncate_size = 32
  247. #--------------------
  248. # cisco_pix
  249. #--------------------
  250. _is_asa = True
  251. #===================================================================
  252. # eoc
  253. #===================================================================
  254. #=============================================================================
  255. # type 7
  256. #=============================================================================
  257. class cisco_type7(uh.GenericHandler):
  258. """
  259. This class implements the "Type 7" password encoding used by Cisco IOS,
  260. and follows the :ref:`password-hash-api`.
  261. It has a simple 4-5 bit salt, but is nonetheless a reversible encoding
  262. instead of a real hash.
  263. The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
  264. :type salt: int
  265. :param salt:
  266. This may be an optional salt integer drawn from ``range(0,16)``.
  267. If omitted, one will be chosen at random.
  268. :type relaxed: bool
  269. :param relaxed:
  270. By default, providing an invalid value for one of the other
  271. keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
  272. and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
  273. will be issued instead. Correctable errors include
  274. ``salt`` values that are out of range.
  275. Note that while this class outputs digests in upper-case hexadecimal,
  276. it will accept lower-case as well.
  277. This class also provides the following additional method:
  278. .. automethod:: decode
  279. """
  280. #===================================================================
  281. # class attrs
  282. #===================================================================
  283. #--------------------
  284. # PasswordHash
  285. #--------------------
  286. name = "cisco_type7"
  287. setting_kwds = ("salt",)
  288. #--------------------
  289. # GenericHandler
  290. #--------------------
  291. checksum_chars = uh.UPPER_HEX_CHARS
  292. #--------------------
  293. # HasSalt
  294. #--------------------
  295. # NOTE: encoding could handle max_salt_value=99, but since key is only 52
  296. # chars in size, not sure what appropriate behavior is for that edge case.
  297. min_salt_value = 0
  298. max_salt_value = 52
  299. #===================================================================
  300. # methods
  301. #===================================================================
  302. @classmethod
  303. def using(cls, salt=None, **kwds):
  304. subcls = super(cisco_type7, cls).using(**kwds)
  305. if salt is not None:
  306. salt = subcls._norm_salt(salt, relaxed=kwds.get("relaxed"))
  307. subcls._generate_salt = staticmethod(lambda: salt)
  308. return subcls
  309. @classmethod
  310. def from_string(cls, hash):
  311. hash = to_unicode(hash, "ascii", "hash")
  312. if len(hash) < 2:
  313. raise uh.exc.InvalidHashError(cls)
  314. salt = int(hash[:2]) # may throw ValueError
  315. return cls(salt=salt, checksum=hash[2:].upper())
  316. def __init__(self, salt=None, **kwds):
  317. super(cisco_type7, self).__init__(**kwds)
  318. if salt is not None:
  319. salt = self._norm_salt(salt)
  320. elif self.use_defaults:
  321. salt = self._generate_salt()
  322. assert self._norm_salt(salt) == salt, "generated invalid salt: %r" % (salt,)
  323. else:
  324. raise TypeError("no salt specified")
  325. self.salt = salt
  326. @classmethod
  327. def _norm_salt(cls, salt, relaxed=False):
  328. """
  329. validate & normalize salt value.
  330. .. note::
  331. the salt for this algorithm is an integer 0-52, not a string
  332. """
  333. if not isinstance(salt, int):
  334. raise uh.exc.ExpectedTypeError(salt, "integer", "salt")
  335. if 0 <= salt <= cls.max_salt_value:
  336. return salt
  337. msg = "salt/offset must be in 0..52 range"
  338. if relaxed:
  339. warn(msg, uh.PasslibHashWarning)
  340. return 0 if salt < 0 else cls.max_salt_value
  341. else:
  342. raise ValueError(msg)
  343. @staticmethod
  344. def _generate_salt():
  345. return uh.rng.randint(0, 15)
  346. def to_string(self):
  347. return "%02d%s" % (self.salt, uascii_to_str(self.checksum))
  348. def _calc_checksum(self, secret):
  349. # XXX: no idea what unicode policy is, but all examples are
  350. # 7-bit ascii compatible, so using UTF-8
  351. if isinstance(secret, unicode):
  352. secret = secret.encode("utf-8")
  353. return hexlify(self._cipher(secret, self.salt)).decode("ascii").upper()
  354. @classmethod
  355. def decode(cls, hash, encoding="utf-8"):
  356. """decode hash, returning original password.
  357. :arg hash: encoded password
  358. :param encoding: optional encoding to use (defaults to ``UTF-8``).
  359. :returns: password as unicode
  360. """
  361. self = cls.from_string(hash)
  362. tmp = unhexlify(self.checksum.encode("ascii"))
  363. raw = self._cipher(tmp, self.salt)
  364. return raw.decode(encoding) if encoding else raw
  365. # type7 uses a xor-based vingere variant, using the following secret key:
  366. _key = u("dsfd;kfoA,.iyewrkldJKDHSUBsgvca69834ncxv9873254k;fg87")
  367. @classmethod
  368. def _cipher(cls, data, salt):
  369. """xor static key against data - encrypts & decrypts"""
  370. key = cls._key
  371. key_size = len(key)
  372. return join_byte_values(
  373. value ^ ord(key[(salt + idx) % key_size])
  374. for idx, value in enumerate(iter_byte_values(data))
  375. )
  376. #=============================================================================
  377. # eof
  378. #=============================================================================