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.
		
		
		
		
		
			
		
			
				
					1221 lines
				
				42 KiB
			
		
		
			
		
	
	
					1221 lines
				
				42 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"""passlib.utils -- helpers for writing password hashes"""
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# imports
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import JYTHON
							 | 
						||
| 
								 | 
							
								# core
							 | 
						||
| 
								 | 
							
								from binascii import b2a_base64, a2b_base64, Error as _BinAsciiError
							 | 
						||
| 
								 | 
							
								from base64 import b64encode, b64decode
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    from collections.abc import Sequence
							 | 
						||
| 
								 | 
							
								    from collections.abc import Iterable
							 | 
						||
| 
								 | 
							
								except ImportError:
							 | 
						||
| 
								 | 
							
								    # py2 compat
							 | 
						||
| 
								 | 
							
								    from collections import Sequence
							 | 
						||
| 
								 | 
							
								    from collections import Iterable
							 | 
						||
| 
								 | 
							
								from codecs import lookup as _lookup_codec
							 | 
						||
| 
								 | 
							
								from functools import update_wrapper
							 | 
						||
| 
								 | 
							
								import itertools
							 | 
						||
| 
								 | 
							
								import inspect
							 | 
						||
| 
								 | 
							
								import logging; log = logging.getLogger(__name__)
							 | 
						||
| 
								 | 
							
								import math
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import random
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								if JYTHON: # pragma: no cover -- runtime detection
							 | 
						||
| 
								 | 
							
								    # Jython 2.5.2 lacks stringprep module -
							 | 
						||
| 
								 | 
							
								    # see http://bugs.jython.org/issue1758320
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        import stringprep
							 | 
						||
| 
								 | 
							
								    except ImportError:
							 | 
						||
| 
								 | 
							
								        stringprep = None
							 | 
						||
| 
								 | 
							
								        _stringprep_missing_reason = "not present under Jython"
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    import stringprep
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								if stringprep:
							 | 
						||
| 
								 | 
							
								    import unicodedata
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    import threading
							 | 
						||
| 
								 | 
							
								except ImportError:
							 | 
						||
| 
								 | 
							
								    # module optional before py37
							 | 
						||
| 
								 | 
							
								    threading = None
							 | 
						||
| 
								 | 
							
								import timeit
							 | 
						||
| 
								 | 
							
								import types
							 | 
						||
| 
								 | 
							
								from warnings import warn
							 | 
						||
| 
								 | 
							
								# site
							 | 
						||
| 
								 | 
							
								# pkg
							 | 
						||
| 
								 | 
							
								from passlib.utils.binary import (
							 | 
						||
| 
								 | 
							
								    # [remove these aliases in 2.0]
							 | 
						||
| 
								 | 
							
								    BASE64_CHARS, AB64_CHARS, HASH64_CHARS, BCRYPT_CHARS,
							 | 
						||
| 
								 | 
							
								    Base64Engine, LazyBase64Engine, h64, h64big, bcrypt64,
							 | 
						||
| 
								 | 
							
								    ab64_encode, ab64_decode, b64s_encode, b64s_decode
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from passlib.utils.decor import (
							 | 
						||
| 
								 | 
							
								    # [remove these aliases in 2.0]
							 | 
						||
| 
								 | 
							
								    deprecated_function,
							 | 
						||
| 
								 | 
							
								    deprecated_method,
							 | 
						||
| 
								 | 
							
								    memoized_property,
							 | 
						||
| 
								 | 
							
								    classproperty,
							 | 
						||
| 
								 | 
							
								    hybrid_method,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from passlib.exc import ExpectedStringError, ExpectedTypeError
							 | 
						||
| 
								 | 
							
								from passlib.utils.compat import (add_doc, join_bytes, join_byte_values,
							 | 
						||
| 
								 | 
							
								                                  join_byte_elems, irange, imap, PY3, u,
							 | 
						||
| 
								 | 
							
								                                  join_unicode, unicode, byte_elem_value, nextgetter,
							 | 
						||
| 
								 | 
							
								                                  unicode_or_str, unicode_or_bytes_types,
							 | 
						||
| 
								 | 
							
								                                  get_method_function, suppress_cause, PYPY)
							 | 
						||
| 
								 | 
							
								# local
							 | 
						||
| 
								 | 
							
								__all__ = [
							 | 
						||
| 
								 | 
							
								    # constants
							 | 
						||
| 
								 | 
							
								    'JYTHON',
							 | 
						||
| 
								 | 
							
								    'sys_bits',
							 | 
						||
| 
								 | 
							
								    'unix_crypt_schemes',
							 | 
						||
| 
								 | 
							
								    'rounds_cost_values',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # unicode helpers
							 | 
						||
| 
								 | 
							
								    'consteq',
							 | 
						||
| 
								 | 
							
								    'saslprep',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # bytes helpers
							 | 
						||
| 
								 | 
							
								    "xor_bytes",
							 | 
						||
| 
								 | 
							
								    "render_bytes",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # encoding helpers
							 | 
						||
| 
								 | 
							
								    'is_same_codec',
							 | 
						||
| 
								 | 
							
								    'is_ascii_safe',
							 | 
						||
| 
								 | 
							
								    'to_bytes',
							 | 
						||
| 
								 | 
							
								    'to_unicode',
							 | 
						||
| 
								 | 
							
								    'to_native_str',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # host OS
							 | 
						||
| 
								 | 
							
								    'has_crypt',
							 | 
						||
| 
								 | 
							
								    'test_crypt',
							 | 
						||
| 
								 | 
							
								    'safe_crypt',
							 | 
						||
| 
								 | 
							
								    'tick',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # randomness
							 | 
						||
| 
								 | 
							
								    'rng',
							 | 
						||
| 
								 | 
							
								    'getrandbytes',
							 | 
						||
| 
								 | 
							
								    'getrandstr',
							 | 
						||
| 
								 | 
							
								    'generate_password',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # object type / interface tests
							 | 
						||
| 
								 | 
							
								    'is_crypt_handler',
							 | 
						||
| 
								 | 
							
								    'is_crypt_context',
							 | 
						||
| 
								 | 
							
								    'has_rounds_info',
							 | 
						||
| 
								 | 
							
								    'has_salt_info',
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# constants
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# bitsize of system architecture (32 or 64)
							 | 
						||
| 
								 | 
							
								sys_bits = int(math.log(sys.maxsize if PY3 else sys.maxint, 2) + 1.5)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# list of hashes algs supported by crypt() on at least one OS.
							 | 
						||
| 
								 | 
							
								# XXX: move to .registry for passlib 2.0?
							 | 
						||
| 
								 | 
							
								unix_crypt_schemes = [
							 | 
						||
| 
								 | 
							
								    "sha512_crypt", "sha256_crypt",
							 | 
						||
| 
								 | 
							
								    "sha1_crypt", "bcrypt",
							 | 
						||
| 
								 | 
							
								    "md5_crypt",
							 | 
						||
| 
								 | 
							
								    # "bsd_nthash",
							 | 
						||
| 
								 | 
							
								    "bsdi_crypt", "des_crypt",
							 | 
						||
| 
								 | 
							
								    ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# list of rounds_cost constants
							 | 
						||
| 
								 | 
							
								rounds_cost_values = [ "linear", "log2" ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# legacy import, will be removed in 1.8
							 | 
						||
| 
								 | 
							
								from passlib.exc import MissingBackendError
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# internal helpers
							 | 
						||
| 
								 | 
							
								_BEMPTY = b''
							 | 
						||
| 
								 | 
							
								_UEMPTY = u("")
							 | 
						||
| 
								 | 
							
								_USPACE = u(" ")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# maximum password size which passlib will allow; see exc.PasswordSizeError
							 | 
						||
| 
								 | 
							
								MAX_PASSWORD_SIZE = int(os.environ.get("PASSLIB_MAX_PASSWORD_SIZE") or 4096)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# type helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SequenceMixin(object):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    helper which lets result object act like a fixed-length sequence.
							 | 
						||
| 
								 | 
							
								    subclass just needs to provide :meth:`_as_tuple()`.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    def _as_tuple(self):
							 | 
						||
| 
								 | 
							
								        raise NotImplementedError("implement in subclass")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        return repr(self._as_tuple())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, idx):
							 | 
						||
| 
								 | 
							
								        return self._as_tuple()[idx]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        return iter(self._as_tuple())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __len__(self):
							 | 
						||
| 
								 | 
							
								        return len(self._as_tuple())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __eq__(self, other):
							 | 
						||
| 
								 | 
							
								        return self._as_tuple() == other
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __ne__(self, other):
							 | 
						||
| 
								 | 
							
								        return not self.__eq__(other)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if PY3:
							 | 
						||
| 
								 | 
							
								    # getargspec() is deprecated, use this under py3.
							 | 
						||
| 
								 | 
							
								    # even though it's a lot more awkward to get basic info :|
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _VAR_KEYWORD = inspect.Parameter.VAR_KEYWORD
							 | 
						||
| 
								 | 
							
								    _VAR_ANY_SET = set([_VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def accepts_keyword(func, key):
							 | 
						||
| 
								 | 
							
								        """test if function accepts specified keyword"""
							 | 
						||
| 
								 | 
							
								        params = inspect.signature(get_method_function(func)).parameters
							 | 
						||
| 
								 | 
							
								        if not params:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        arg = params.get(key)
							 | 
						||
| 
								 | 
							
								        if arg and arg.kind not in _VAR_ANY_SET:
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        # XXX: annoying what we have to do to determine if VAR_KWDS in use.
							 | 
						||
| 
								 | 
							
								        return params[list(params)[-1]].kind == _VAR_KEYWORD
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def accepts_keyword(func, key):
							 | 
						||
| 
								 | 
							
								        """test if function accepts specified keyword"""
							 | 
						||
| 
								 | 
							
								        spec = inspect.getargspec(get_method_function(func))
							 | 
						||
| 
								 | 
							
								        return key in spec.args or spec.keywords is not None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def update_mixin_classes(target, add=None, remove=None, append=False,
							 | 
						||
| 
								 | 
							
								                         before=None, after=None, dryrun=False):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    helper to update mixin classes installed in target class.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param target:
							 | 
						||
| 
								 | 
							
								        target class whose bases will be modified.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param add:
							 | 
						||
| 
								 | 
							
								        class / classes to install into target's base class list.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param remove:
							 | 
						||
| 
								 | 
							
								        class / classes to remove from target's base class list.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param append:
							 | 
						||
| 
								 | 
							
								        by default, prepends mixins to front of list.
							 | 
						||
| 
								 | 
							
								        if True, appends to end of list instead.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param after:
							 | 
						||
| 
								 | 
							
								        optionally make sure all mixins are inserted after
							 | 
						||
| 
								 | 
							
								        this class / classes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param before:
							 | 
						||
| 
								 | 
							
								        optionally make sure all mixins are inserted before
							 | 
						||
| 
								 | 
							
								        this class / classes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param dryrun:
							 | 
						||
| 
								 | 
							
								        optionally perform all calculations / raise errors,
							 | 
						||
| 
								 | 
							
								        but don't actually modify the class.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if isinstance(add, type):
							 | 
						||
| 
								 | 
							
								        add = [add]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    bases = list(target.__bases__)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # strip out requested mixins
							 | 
						||
| 
								 | 
							
								    if remove:
							 | 
						||
| 
								 | 
							
								        if isinstance(remove, type):
							 | 
						||
| 
								 | 
							
								            remove = [remove]
							 | 
						||
| 
								 | 
							
								        for mixin in remove:
							 | 
						||
| 
								 | 
							
								            if add and mixin in add:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            if mixin in bases:
							 | 
						||
| 
								 | 
							
								                bases.remove(mixin)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # add requested mixins
							 | 
						||
| 
								 | 
							
								    if add:
							 | 
						||
| 
								 | 
							
								        for mixin in add:
							 | 
						||
| 
								 | 
							
								            # if mixin already present (explicitly or not), leave alone
							 | 
						||
| 
								 | 
							
								            if any(issubclass(base, mixin) for base in bases):
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # determine insertion point
							 | 
						||
| 
								 | 
							
								            if append:
							 | 
						||
| 
								 | 
							
								                for idx, base in enumerate(bases):
							 | 
						||
| 
								 | 
							
								                    if issubclass(mixin, base):
							 | 
						||
| 
								 | 
							
								                        # don't insert mixin after one of it's own bases
							 | 
						||
| 
								 | 
							
								                        break
							 | 
						||
| 
								 | 
							
								                    if before and issubclass(base, before):
							 | 
						||
| 
								 | 
							
								                        # don't insert mixin after any <before> classes.
							 | 
						||
| 
								 | 
							
								                        break
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    # append to end
							 | 
						||
| 
								 | 
							
								                    idx = len(bases)
							 | 
						||
| 
								 | 
							
								            elif after:
							 | 
						||
| 
								 | 
							
								                for end_idx, base in enumerate(reversed(bases)):
							 | 
						||
| 
								 | 
							
								                    if issubclass(base, after):
							 | 
						||
| 
								 | 
							
								                        # don't insert mixin before any <after> classes.
							 | 
						||
| 
								 | 
							
								                        idx = len(bases) - end_idx
							 | 
						||
| 
								 | 
							
								                        assert bases[idx-1] == base
							 | 
						||
| 
								 | 
							
								                        break
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    idx = 0
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # insert at start
							 | 
						||
| 
								 | 
							
								                idx = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # insert mixin
							 | 
						||
| 
								 | 
							
								            bases.insert(idx, mixin)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # modify class
							 | 
						||
| 
								 | 
							
								    if not dryrun:
							 | 
						||
| 
								 | 
							
								        target.__bases__ = tuple(bases)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# collection helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								def batch(source, size):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    split iterable into chunks of <size> elements.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if size < 1:
							 | 
						||
| 
								 | 
							
								        raise ValueError("size must be positive integer")
							 | 
						||
| 
								 | 
							
								    if isinstance(source, Sequence):
							 | 
						||
| 
								 | 
							
								        end = len(source)
							 | 
						||
| 
								 | 
							
								        i = 0
							 | 
						||
| 
								 | 
							
								        while i < end:
							 | 
						||
| 
								 | 
							
								            n = i + size
							 | 
						||
| 
								 | 
							
								            yield source[i:n]
							 | 
						||
| 
								 | 
							
								            i = n
							 | 
						||
| 
								 | 
							
								    elif isinstance(source, Iterable):
							 | 
						||
| 
								 | 
							
								        itr = iter(source)
							 | 
						||
| 
								 | 
							
								        while True:
							 | 
						||
| 
								 | 
							
								            chunk_itr = itertools.islice(itr, size)
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                first = next(chunk_itr)
							 | 
						||
| 
								 | 
							
								            except StopIteration:
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								            yield itertools.chain((first,), chunk_itr)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise TypeError("source must be iterable")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# unicode helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# XXX: should this be moved to passlib.crypto, or compat backports?
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def consteq(left, right):
							 | 
						||
| 
								 | 
							
								    """Check two strings/bytes for equality.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function uses an approach designed to prevent
							 | 
						||
| 
								 | 
							
								    timing analysis, making it appropriate for cryptography.
							 | 
						||
| 
								 | 
							
								    a and b must both be of the same type: either str (ASCII only),
							 | 
						||
| 
								 | 
							
								    or any type that supports the buffer protocol (e.g. bytes).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Note: If a and b are of different lengths, or if an error occurs,
							 | 
						||
| 
								 | 
							
								    a timing attack could theoretically reveal information about the
							 | 
						||
| 
								 | 
							
								    types and lengths of a and b--but not their values.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # NOTE:
							 | 
						||
| 
								 | 
							
								    # resources & discussions considered in the design of this function:
							 | 
						||
| 
								 | 
							
								    #   hmac timing attack --
							 | 
						||
| 
								 | 
							
								    #       http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/
							 | 
						||
| 
								 | 
							
								    #   python developer discussion surrounding similar function --
							 | 
						||
| 
								 | 
							
								    #       http://bugs.python.org/issue15061
							 | 
						||
| 
								 | 
							
								    #       http://bugs.python.org/issue14955
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate types
							 | 
						||
| 
								 | 
							
								    if isinstance(left, unicode):
							 | 
						||
| 
								 | 
							
								        if not isinstance(right, unicode):
							 | 
						||
| 
								 | 
							
								            raise TypeError("inputs must be both unicode or both bytes")
							 | 
						||
| 
								 | 
							
								        is_py3_bytes = False
							 | 
						||
| 
								 | 
							
								    elif isinstance(left, bytes):
							 | 
						||
| 
								 | 
							
								        if not isinstance(right, bytes):
							 | 
						||
| 
								 | 
							
								            raise TypeError("inputs must be both unicode or both bytes")
							 | 
						||
| 
								 | 
							
								        is_py3_bytes = PY3
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise TypeError("inputs must be both unicode or both bytes")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # do size comparison.
							 | 
						||
| 
								 | 
							
								    # NOTE: the double-if construction below is done deliberately, to ensure
							 | 
						||
| 
								 | 
							
								    # the same number of operations (including branches) is performed regardless
							 | 
						||
| 
								 | 
							
								    # of whether left & right are the same size.
							 | 
						||
| 
								 | 
							
								    same_size = (len(left) == len(right))
							 | 
						||
| 
								 | 
							
								    if same_size:
							 | 
						||
| 
								 | 
							
								        # if sizes are the same, setup loop to perform actual check of contents.
							 | 
						||
| 
								 | 
							
								        tmp = left
							 | 
						||
| 
								 | 
							
								        result = 0
							 | 
						||
| 
								 | 
							
								    if not same_size:
							 | 
						||
| 
								 | 
							
								        # if sizes aren't the same, set 'result' so equality will fail regardless
							 | 
						||
| 
								 | 
							
								        # of contents. then, to ensure we do exactly 'len(right)' iterations
							 | 
						||
| 
								 | 
							
								        # of the loop, just compare 'right' against itself.
							 | 
						||
| 
								 | 
							
								        tmp = right
							 | 
						||
| 
								 | 
							
								        result = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # run constant-time string comparision
							 | 
						||
| 
								 | 
							
								    # TODO: use izip instead (but first verify it's faster than zip for this case)
							 | 
						||
| 
								 | 
							
								    if is_py3_bytes:
							 | 
						||
| 
								 | 
							
								        for l,r in zip(tmp, right):
							 | 
						||
| 
								 | 
							
								            result |= l ^ r
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        for l,r in zip(tmp, right):
							 | 
						||
| 
								 | 
							
								            result |= ord(l) ^ ord(r)
							 | 
						||
| 
								 | 
							
								    return result == 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# keep copy of this around since stdlib's version throws error on non-ascii chars in unicode strings.
							 | 
						||
| 
								 | 
							
								# our version does, but suffers from some underlying VM issues.  but something is better than
							 | 
						||
| 
								 | 
							
								# nothing for plaintext hashes, which need this.  everything else should use consteq(),
							 | 
						||
| 
								 | 
							
								# since the stdlib one is going to be as good / better in the general case.
							 | 
						||
| 
								 | 
							
								str_consteq = consteq
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    # for py3.3 and up, use the stdlib version
							 | 
						||
| 
								 | 
							
								    from hmac import compare_digest as consteq
							 | 
						||
| 
								 | 
							
								except ImportError:
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # TODO: could check for cryptography package's version,
							 | 
						||
| 
								 | 
							
								    #       but only operates on bytes, so would need a wrapper,
							 | 
						||
| 
								 | 
							
								    #       or separate consteq() into a unicode & a bytes variant.
							 | 
						||
| 
								 | 
							
								    # from cryptography.hazmat.primitives.constant_time import bytes_eq as consteq
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def splitcomma(source, sep=","):
							 | 
						||
| 
								 | 
							
								    """split comma-separated string into list of elements,
							 | 
						||
| 
								 | 
							
								    stripping whitespace.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    source = source.strip()
							 | 
						||
| 
								 | 
							
								    if source.endswith(sep):
							 | 
						||
| 
								 | 
							
								        source = source[:-1]
							 | 
						||
| 
								 | 
							
								    if not source:
							 | 
						||
| 
								 | 
							
								        return []
							 | 
						||
| 
								 | 
							
								    return [ elem.strip() for elem in source.split(sep) ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def saslprep(source, param="value"):
							 | 
						||
| 
								 | 
							
								    """Normalizes unicode strings using SASLPrep stringprep profile.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The SASLPrep profile is defined in :rfc:`4013`.
							 | 
						||
| 
								 | 
							
								    It provides a uniform scheme for normalizing unicode usernames
							 | 
						||
| 
								 | 
							
								    and passwords before performing byte-value sensitive operations
							 | 
						||
| 
								 | 
							
								    such as hashing. Among other things, it normalizes diacritic
							 | 
						||
| 
								 | 
							
								    representations, removes non-printing characters, and forbids
							 | 
						||
| 
								 | 
							
								    invalid characters such as ``\\n``. Properly internationalized
							 | 
						||
| 
								 | 
							
								    applications should run user passwords through this function
							 | 
						||
| 
								 | 
							
								    before hashing.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg source:
							 | 
						||
| 
								 | 
							
								        unicode string to normalize & validate
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param param:
							 | 
						||
| 
								 | 
							
								        Optional noun identifying source parameter in error messages
							 | 
						||
| 
								 | 
							
								        (Defaults to the string ``"value"``). This is mainly useful to make the caller's error
							 | 
						||
| 
								 | 
							
								        messages make more sense contextually.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises ValueError:
							 | 
						||
| 
								 | 
							
								        if any characters forbidden by the SASLPrep profile are encountered.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises TypeError:
							 | 
						||
| 
								 | 
							
								        if input is not :class:`!unicode`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        normalized unicode string
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. note::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        This function is not available under Jython,
							 | 
						||
| 
								 | 
							
								        as the Jython stdlib is missing the :mod:`!stringprep` module
							 | 
						||
| 
								 | 
							
								        (`Jython issue 1758320 <http://bugs.jython.org/issue1758320>`_).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. versionadded:: 1.6
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # saslprep - http://tools.ietf.org/html/rfc4013
							 | 
						||
| 
								 | 
							
								    # stringprep - http://tools.ietf.org/html/rfc3454
							 | 
						||
| 
								 | 
							
								    #              http://docs.python.org/library/stringprep.html
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate type
							 | 
						||
| 
								 | 
							
								    # XXX: support bytes (e.g. run through want_unicode)?
							 | 
						||
| 
								 | 
							
								    #      might be easier to just integrate this into cryptcontext.
							 | 
						||
| 
								 | 
							
								    if not isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								        raise TypeError("input must be unicode string, not %s" %
							 | 
						||
| 
								 | 
							
								                        (type(source),))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # mapping stage
							 | 
						||
| 
								 | 
							
								    #   - map non-ascii spaces to U+0020 (stringprep C.1.2)
							 | 
						||
| 
								 | 
							
								    #   - strip 'commonly mapped to nothing' chars (stringprep B.1)
							 | 
						||
| 
								 | 
							
								    in_table_c12 = stringprep.in_table_c12
							 | 
						||
| 
								 | 
							
								    in_table_b1 = stringprep.in_table_b1
							 | 
						||
| 
								 | 
							
								    data = join_unicode(
							 | 
						||
| 
								 | 
							
								        _USPACE if in_table_c12(c) else c
							 | 
						||
| 
								 | 
							
								        for c in source
							 | 
						||
| 
								 | 
							
								        if not in_table_b1(c)
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # normalize to KC form
							 | 
						||
| 
								 | 
							
								    data = unicodedata.normalize('NFKC', data)
							 | 
						||
| 
								 | 
							
								    if not data:
							 | 
						||
| 
								 | 
							
								        return _UEMPTY
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check for invalid bi-directional strings.
							 | 
						||
| 
								 | 
							
								    # stringprep requires the following:
							 | 
						||
| 
								 | 
							
								    #   - chars in C.8 must be prohibited.
							 | 
						||
| 
								 | 
							
								    #   - if any R/AL chars in string:
							 | 
						||
| 
								 | 
							
								    #       - no L chars allowed in string
							 | 
						||
| 
								 | 
							
								    #       - first and last must be R/AL chars
							 | 
						||
| 
								 | 
							
								    # this checks if start/end are R/AL chars. if so, prohibited loop
							 | 
						||
| 
								 | 
							
								    # will forbid all L chars. if not, prohibited loop will forbid all
							 | 
						||
| 
								 | 
							
								    # R/AL chars instead. in both cases, prohibited loop takes care of C.8.
							 | 
						||
| 
								 | 
							
								    is_ral_char = stringprep.in_table_d1
							 | 
						||
| 
								 | 
							
								    if is_ral_char(data[0]):
							 | 
						||
| 
								 | 
							
								        if not is_ral_char(data[-1]):
							 | 
						||
| 
								 | 
							
								            raise ValueError("malformed bidi sequence in " + param)
							 | 
						||
| 
								 | 
							
								        # forbid L chars within R/AL sequence.
							 | 
						||
| 
								 | 
							
								        is_forbidden_bidi_char = stringprep.in_table_d2
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        # forbid R/AL chars if start not setup correctly; L chars allowed.
							 | 
						||
| 
								 | 
							
								        is_forbidden_bidi_char = is_ral_char
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check for prohibited output - stringprep tables A.1, B.1, C.1.2, C.2 - C.9
							 | 
						||
| 
								 | 
							
								    in_table_a1 = stringprep.in_table_a1
							 | 
						||
| 
								 | 
							
								    in_table_c21_c22 = stringprep.in_table_c21_c22
							 | 
						||
| 
								 | 
							
								    in_table_c3 = stringprep.in_table_c3
							 | 
						||
| 
								 | 
							
								    in_table_c4 = stringprep.in_table_c4
							 | 
						||
| 
								 | 
							
								    in_table_c5 = stringprep.in_table_c5
							 | 
						||
| 
								 | 
							
								    in_table_c6 = stringprep.in_table_c6
							 | 
						||
| 
								 | 
							
								    in_table_c7 = stringprep.in_table_c7
							 | 
						||
| 
								 | 
							
								    in_table_c8 = stringprep.in_table_c8
							 | 
						||
| 
								 | 
							
								    in_table_c9 = stringprep.in_table_c9
							 | 
						||
| 
								 | 
							
								    for c in data:
							 | 
						||
| 
								 | 
							
								        # check for chars mapping stage should have removed
							 | 
						||
| 
								 | 
							
								        assert not in_table_b1(c), "failed to strip B.1 in mapping stage"
							 | 
						||
| 
								 | 
							
								        assert not in_table_c12(c), "failed to replace C.1.2 in mapping stage"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # check for forbidden chars
							 | 
						||
| 
								 | 
							
								        if in_table_a1(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("unassigned code points forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c21_c22(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("control characters forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c3(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("private use characters forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c4(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("non-char code points forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c5(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("surrogate codes forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c6(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("non-plaintext chars forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c7(c):
							 | 
						||
| 
								 | 
							
								            # XXX: should these have been caught by normalize?
							 | 
						||
| 
								 | 
							
								            # if so, should change this to an assert
							 | 
						||
| 
								 | 
							
								            raise ValueError("non-canonical chars forbidden in " + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c8(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("display-modifying / deprecated chars "
							 | 
						||
| 
								 | 
							
								                             "forbidden in" + param)
							 | 
						||
| 
								 | 
							
								        if in_table_c9(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("tagged characters forbidden in " + param)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # do bidi constraint check chosen by bidi init, above
							 | 
						||
| 
								 | 
							
								        if is_forbidden_bidi_char(c):
							 | 
						||
| 
								 | 
							
								            raise ValueError("forbidden bidi character in " + param)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return data
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# replace saslprep() with stub when stringprep is missing
							 | 
						||
| 
								 | 
							
								if stringprep is None: # pragma: no cover -- runtime detection
							 | 
						||
| 
								 | 
							
								    def saslprep(source, param="value"):
							 | 
						||
| 
								 | 
							
								        """stub for saslprep()"""
							 | 
						||
| 
								 | 
							
								        raise NotImplementedError("saslprep() support requires the 'stringprep' "
							 | 
						||
| 
								 | 
							
								                            "module, which is " + _stringprep_missing_reason)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# bytes helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								def render_bytes(source, *args):
							 | 
						||
| 
								 | 
							
								    """Peform ``%`` formating using bytes in a uniform manner across Python 2/3.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function is motivated by the fact that
							 | 
						||
| 
								 | 
							
								    :class:`bytes` instances do not support ``%`` or ``{}`` formatting under Python 3.
							 | 
						||
| 
								 | 
							
								    This function is an attempt to provide a replacement:
							 | 
						||
| 
								 | 
							
								    it converts everything to unicode (decoding bytes instances as ``latin-1``),
							 | 
						||
| 
								 | 
							
								    performs the required formatting, then encodes the result to ``latin-1``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Calling ``render_bytes(source, *args)`` should function roughly the same as
							 | 
						||
| 
								 | 
							
								    ``source % args`` under Python 2.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. todo::
							 | 
						||
| 
								 | 
							
								        python >= 3.5 added back limited support for bytes %,
							 | 
						||
| 
								 | 
							
								        can revisit when 3.3/3.4 is dropped.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								        source = source.decode("latin-1")
							 | 
						||
| 
								 | 
							
								    result = source % tuple(arg.decode("latin-1") if isinstance(arg, bytes)
							 | 
						||
| 
								 | 
							
								                            else arg for arg in args)
							 | 
						||
| 
								 | 
							
								    return result.encode("latin-1")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if PY3:
							 | 
						||
| 
								 | 
							
								    # new in py32
							 | 
						||
| 
								 | 
							
								    def bytes_to_int(value):
							 | 
						||
| 
								 | 
							
								        return int.from_bytes(value, 'big')
							 | 
						||
| 
								 | 
							
								    def int_to_bytes(value, count):
							 | 
						||
| 
								 | 
							
								        return value.to_bytes(count, 'big')
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    # XXX: can any of these be sped up?
							 | 
						||
| 
								 | 
							
								    from binascii import hexlify, unhexlify
							 | 
						||
| 
								 | 
							
								    def bytes_to_int(value):
							 | 
						||
| 
								 | 
							
								        return int(hexlify(value),16)
							 | 
						||
| 
								 | 
							
								    def int_to_bytes(value, count):
							 | 
						||
| 
								 | 
							
								        return unhexlify(('%%0%dx' % (count<<1)) % value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								add_doc(bytes_to_int, "decode byte string as single big-endian integer")
							 | 
						||
| 
								 | 
							
								add_doc(int_to_bytes, "encode integer as single big-endian byte string")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def xor_bytes(left, right):
							 | 
						||
| 
								 | 
							
								    """Perform bitwise-xor of two byte strings (must be same size)"""
							 | 
						||
| 
								 | 
							
								    return int_to_bytes(bytes_to_int(left) ^ bytes_to_int(right), len(left))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def repeat_string(source, size):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    repeat or truncate <source> string, so it has length <size>
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    mult = 1 + (size - 1) // len(source)
							 | 
						||
| 
								 | 
							
								    return (source * mult)[:size]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def utf8_repeat_string(source, size):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    variant of repeat_string() which truncates to nearest UTF8 boundary.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    mult = 1 + (size - 1) // len(source)
							 | 
						||
| 
								 | 
							
								    return utf8_truncate(source * mult, size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_BNULL = b"\x00"
							 | 
						||
| 
								 | 
							
								_UNULL = u("\x00")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def right_pad_string(source, size, pad=None):
							 | 
						||
| 
								 | 
							
								    """right-pad or truncate <source> string, so it has length <size>"""
							 | 
						||
| 
								 | 
							
								    cur = len(source)
							 | 
						||
| 
								 | 
							
								    if size > cur:
							 | 
						||
| 
								 | 
							
								        if pad is None:
							 | 
						||
| 
								 | 
							
								            pad = _UNULL if isinstance(source, unicode) else _BNULL
							 | 
						||
| 
								 | 
							
								        return source+pad*(size-cur)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return source[:size]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def utf8_truncate(source, index):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    helper to truncate UTF8 byte string to nearest character boundary ON OR AFTER <index>.
							 | 
						||
| 
								 | 
							
								    returned prefix will always have length of at least <index>, and will stop on the
							 | 
						||
| 
								 | 
							
								    first byte that's not a UTF8 continuation byte (128 - 191 inclusive).
							 | 
						||
| 
								 | 
							
								    since utf8 should never take more than 4 bytes to encode known unicode values,
							 | 
						||
| 
								 | 
							
								    we can stop after ``index+3`` is reached.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param bytes source:
							 | 
						||
| 
								 | 
							
								    :param int index:
							 | 
						||
| 
								 | 
							
								    :rtype: bytes
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # general approach:
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # * UTF8 bytes will have high two bits (0xC0) as one of:
							 | 
						||
| 
								 | 
							
								    #   00 -- ascii char
							 | 
						||
| 
								 | 
							
								    #   01 -- ascii char
							 | 
						||
| 
								 | 
							
								    #   10 -- continuation of multibyte char
							 | 
						||
| 
								 | 
							
								    #   11 -- start of multibyte char.
							 | 
						||
| 
								 | 
							
								    #   thus we can cut on anything where high bits aren't "10" (0x80; continuation byte)
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # * UTF8 characters SHOULD always be 1 to 4 bytes, though they may be unbounded.
							 | 
						||
| 
								 | 
							
								    #   so we just keep going until first non-continuation byte is encountered, or end of str.
							 | 
						||
| 
								 | 
							
								    #   this should work predictably even for malformed/non UTF8 inputs.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								        raise ExpectedTypeError(source, bytes, "source")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # validate index
							 | 
						||
| 
								 | 
							
								    end = len(source)
							 | 
						||
| 
								 | 
							
								    if index < 0:
							 | 
						||
| 
								 | 
							
								        index = max(0, index + end)
							 | 
						||
| 
								 | 
							
								    if index >= end:
							 | 
						||
| 
								 | 
							
								        return source
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # can stop search after 4 bytes, won't ever have longer utf8 sequence.
							 | 
						||
| 
								 | 
							
								    end = min(index + 3, end)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # loop until we find non-continuation byte
							 | 
						||
| 
								 | 
							
								    while index < end:
							 | 
						||
| 
								 | 
							
								        if byte_elem_value(source[index]) & 0xC0 != 0x80:
							 | 
						||
| 
								 | 
							
								            # found single-char byte, or start-char byte.
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								        # else: found continuation byte.
							 | 
						||
| 
								 | 
							
								        index += 1
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        assert index == end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # truncate at final index
							 | 
						||
| 
								 | 
							
								    result = source[:index]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def sanity_check():
							 | 
						||
| 
								 | 
							
								        # try to decode source
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            text = source.decode("utf-8")
							 | 
						||
| 
								 | 
							
								        except UnicodeDecodeError:
							 | 
						||
| 
								 | 
							
								            # if source isn't valid utf8, byte level match is enough
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # validate that result was cut on character boundary
							 | 
						||
| 
								 | 
							
								        assert text.startswith(result.decode("utf-8"))
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    assert sanity_check()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# encoding helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								_ASCII_TEST_BYTES = b"\x00\n aA:#!\x7f"
							 | 
						||
| 
								 | 
							
								_ASCII_TEST_UNICODE = _ASCII_TEST_BYTES.decode("ascii")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_ascii_codec(codec):
							 | 
						||
| 
								 | 
							
								    """Test if codec is compatible with 7-bit ascii (e.g. latin-1, utf-8; but not utf-16)"""
							 | 
						||
| 
								 | 
							
								    return _ASCII_TEST_UNICODE.encode(codec) == _ASCII_TEST_BYTES
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_same_codec(left, right):
							 | 
						||
| 
								 | 
							
								    """Check if two codec names are aliases for same codec"""
							 | 
						||
| 
								 | 
							
								    if left == right:
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								    if not (left and right):
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								    return _lookup_codec(left).name == _lookup_codec(right).name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_B80 = b'\x80'[0]
							 | 
						||
| 
								 | 
							
								_U80 = u('\x80')
							 | 
						||
| 
								 | 
							
								def is_ascii_safe(source):
							 | 
						||
| 
								 | 
							
								    """Check if string (bytes or unicode) contains only 7-bit ascii"""
							 | 
						||
| 
								 | 
							
								    r = _B80 if isinstance(source, bytes) else _U80
							 | 
						||
| 
								 | 
							
								    return all(c < r for c in source)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def to_bytes(source, encoding="utf-8", param="value", source_encoding=None):
							 | 
						||
| 
								 | 
							
								    """Helper to normalize input to bytes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg source:
							 | 
						||
| 
								 | 
							
								        Source bytes/unicode to process.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg encoding:
							 | 
						||
| 
								 | 
							
								        Target encoding (defaults to ``"utf-8"``).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param param:
							 | 
						||
| 
								 | 
							
								        Optional name of variable/noun to reference when raising errors
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param source_encoding:
							 | 
						||
| 
								 | 
							
								        If this is specified, and the source is bytes,
							 | 
						||
| 
								 | 
							
								        the source will be transcoded from *source_encoding* to *encoding*
							 | 
						||
| 
								 | 
							
								        (via unicode).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises TypeError: if source is not unicode or bytes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        * unicode strings will be encoded using *encoding*, and returned.
							 | 
						||
| 
								 | 
							
								        * if *source_encoding* is not specified, byte strings will be
							 | 
						||
| 
								 | 
							
								          returned unchanged.
							 | 
						||
| 
								 | 
							
								        * if *source_encoding* is specified, byte strings will be transcoded
							 | 
						||
| 
								 | 
							
								          to *encoding*.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert encoding
							 | 
						||
| 
								 | 
							
								    if isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								        if source_encoding and not is_same_codec(source_encoding, encoding):
							 | 
						||
| 
								 | 
							
								            return source.decode(source_encoding).encode(encoding)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            return source
							 | 
						||
| 
								 | 
							
								    elif isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								        return source.encode(encoding)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise ExpectedStringError(source, param)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def to_unicode(source, encoding="utf-8", param="value"):
							 | 
						||
| 
								 | 
							
								    """Helper to normalize input to unicode.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg source:
							 | 
						||
| 
								 | 
							
								        source bytes/unicode to process.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg encoding:
							 | 
						||
| 
								 | 
							
								        encoding to use when decoding bytes instances.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param param:
							 | 
						||
| 
								 | 
							
								        optional name of variable/noun to reference when raising errors.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises TypeError: if source is not unicode or bytes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        * returns unicode strings unchanged.
							 | 
						||
| 
								 | 
							
								        * returns bytes strings decoded using *encoding*
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert encoding
							 | 
						||
| 
								 | 
							
								    if isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								        return source
							 | 
						||
| 
								 | 
							
								    elif isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								        return source.decode(encoding)
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        raise ExpectedStringError(source, param)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if PY3:
							 | 
						||
| 
								 | 
							
								    def to_native_str(source, encoding="utf-8", param="value"):
							 | 
						||
| 
								 | 
							
								        if isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            return source.decode(encoding)
							 | 
						||
| 
								 | 
							
								        elif isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								            return source
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise ExpectedStringError(source, param)
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    def to_native_str(source, encoding="utf-8", param="value"):
							 | 
						||
| 
								 | 
							
								        if isinstance(source, bytes):
							 | 
						||
| 
								 | 
							
								            return source
							 | 
						||
| 
								 | 
							
								        elif isinstance(source, unicode):
							 | 
						||
| 
								 | 
							
								            return source.encode(encoding)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            raise ExpectedStringError(source, param)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								add_doc(to_native_str,
							 | 
						||
| 
								 | 
							
								    """Take in unicode or bytes, return native string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Python 2: encodes unicode using specified encoding, leaves bytes alone.
							 | 
						||
| 
								 | 
							
								    Python 3: leaves unicode alone, decodes bytes using specified encoding.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :raises TypeError: if source is not unicode or bytes.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg source:
							 | 
						||
| 
								 | 
							
								        source unicode or bytes string.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg encoding:
							 | 
						||
| 
								 | 
							
								        encoding to use when encoding unicode or decoding bytes.
							 | 
						||
| 
								 | 
							
								        this defaults to ``"utf-8"``.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param param:
							 | 
						||
| 
								 | 
							
								        optional name of variable/noun to reference when raising errors.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns: :class:`str` instance
							 | 
						||
| 
								 | 
							
								    """)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@deprecated_function(deprecated="1.6", removed="1.7")
							 | 
						||
| 
								 | 
							
								def to_hash_str(source, encoding="ascii"): # pragma: no cover -- deprecated & unused
							 | 
						||
| 
								 | 
							
								    """deprecated, use to_native_str() instead"""
							 | 
						||
| 
								 | 
							
								    return to_native_str(source, encoding, param="hash")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_true_set = set("true t yes y on 1 enable enabled".split())
							 | 
						||
| 
								 | 
							
								_false_set = set("false f no n off 0 disable disabled".split())
							 | 
						||
| 
								 | 
							
								_none_set = set(["", "none"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def as_bool(value, none=None, param="boolean"):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    helper to convert value to boolean.
							 | 
						||
| 
								 | 
							
								    recognizes strings such as "true", "false"
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    assert none in [True, False, None]
							 | 
						||
| 
								 | 
							
								    if isinstance(value, unicode_or_bytes_types):
							 | 
						||
| 
								 | 
							
								        clean = value.lower().strip()
							 | 
						||
| 
								 | 
							
								        if clean in _true_set:
							 | 
						||
| 
								 | 
							
								            return True
							 | 
						||
| 
								 | 
							
								        if clean in _false_set:
							 | 
						||
| 
								 | 
							
								            return False
							 | 
						||
| 
								 | 
							
								        if clean in _none_set:
							 | 
						||
| 
								 | 
							
								            return none
							 | 
						||
| 
								 | 
							
								        raise ValueError("unrecognized %s value: %r" % (param, value))
							 | 
						||
| 
								 | 
							
								    elif isinstance(value, bool):
							 | 
						||
| 
								 | 
							
								        return value
							 | 
						||
| 
								 | 
							
								    elif value is None:
							 | 
						||
| 
								 | 
							
								        return none
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return bool(value)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# host OS helpers
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_safe_crypt_input(value):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    UT helper --
							 | 
						||
| 
								 | 
							
								    test if value is safe to pass to crypt.crypt();
							 | 
						||
| 
								 | 
							
								    under PY3, can't pass non-UTF8 bytes to crypt.crypt.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if crypt_accepts_bytes or not isinstance(value, bytes):
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        value.decode("utf-8")
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								    except UnicodeDecodeError:
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    from crypt import crypt as _crypt
							 | 
						||
| 
								 | 
							
								except ImportError: # pragma: no cover
							 | 
						||
| 
								 | 
							
								    _crypt = None
							 | 
						||
| 
								 | 
							
								    has_crypt = False
							 | 
						||
| 
								 | 
							
								    crypt_accepts_bytes = False
							 | 
						||
| 
								 | 
							
								    crypt_needs_lock = False
							 | 
						||
| 
								 | 
							
								    _safe_crypt_lock = None
							 | 
						||
| 
								 | 
							
								    def safe_crypt(secret, hash):
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								else:
							 | 
						||
| 
								 | 
							
								    has_crypt = True
							 | 
						||
| 
								 | 
							
								    _NULL = '\x00'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # XXX: replace this with lazy-evaluated bug detection?
							 | 
						||
| 
								 | 
							
								    if threading and PYPY and (7, 2, 0) <= sys.pypy_version_info <= (7, 3, 3):
							 | 
						||
| 
								 | 
							
								        #: internal lock used to wrap crypt() calls.
							 | 
						||
| 
								 | 
							
								        #: WARNING: if non-passlib code invokes crypt(), this lock won't be enough!
							 | 
						||
| 
								 | 
							
								        _safe_crypt_lock = threading.Lock()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #: detect if crypt.crypt() needs a thread lock around calls.
							 | 
						||
| 
								 | 
							
								        crypt_needs_lock = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        from passlib.utils.compat import nullcontext
							 | 
						||
| 
								 | 
							
								        _safe_crypt_lock = nullcontext()
							 | 
						||
| 
								 | 
							
								        crypt_needs_lock = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # some crypt() variants will return various constant strings when
							 | 
						||
| 
								 | 
							
								    # an invalid/unrecognized config string is passed in; instead of
							 | 
						||
| 
								 | 
							
								    # returning NULL / None. examples include ":", ":0", "*0", etc.
							 | 
						||
| 
								 | 
							
								    # safe_crypt() returns None for any string starting with one of the
							 | 
						||
| 
								 | 
							
								    # chars in this string...
							 | 
						||
| 
								 | 
							
								    _invalid_prefixes = u("*:!")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if PY3:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # * pypy3 (as of v7.3.1) has a crypt which accepts bytes, or ASCII-only unicode.
							 | 
						||
| 
								 | 
							
								        # * whereas CPython3 (as of v3.9) has a crypt which doesn't take bytes,
							 | 
						||
| 
								 | 
							
								        #   but accepts ANY unicode (which it always encodes to UTF8).
							 | 
						||
| 
								 | 
							
								        crypt_accepts_bytes = True
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            _crypt(b"\xEE", "xx")
							 | 
						||
| 
								 | 
							
								        except TypeError:
							 | 
						||
| 
								 | 
							
								            # CPython will throw TypeError
							 | 
						||
| 
								 | 
							
								            crypt_accepts_bytes = False
							 | 
						||
| 
								 | 
							
								        except:  # no pragma
							 | 
						||
| 
								 | 
							
								            # don't care about other errors this might throw,
							 | 
						||
| 
								 | 
							
								            # just want to see if we get past initial type-coercion step.
							 | 
						||
| 
								 | 
							
								            pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def safe_crypt(secret, hash):
							 | 
						||
| 
								 | 
							
								            if crypt_accepts_bytes:
							 | 
						||
| 
								 | 
							
								                # PyPy3 -- all bytes accepted, but unicode encoded to ASCII,
							 | 
						||
| 
								 | 
							
								                # so handling that ourselves.
							 | 
						||
| 
								 | 
							
								                if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								                    secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								                if _BNULL in secret:
							 | 
						||
| 
								 | 
							
								                    raise ValueError("null character in secret")
							 | 
						||
| 
								 | 
							
								                if isinstance(hash, unicode):
							 | 
						||
| 
								 | 
							
								                    hash = hash.encode("ascii")
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                # CPython3's crypt() doesn't take bytes, only unicode; unicode which is then
							 | 
						||
| 
								 | 
							
								                # encoding using utf-8 before passing to the C-level crypt().
							 | 
						||
| 
								 | 
							
								                # so we have to decode the secret.
							 | 
						||
| 
								 | 
							
								                if isinstance(secret, bytes):
							 | 
						||
| 
								 | 
							
								                    orig = secret
							 | 
						||
| 
								 | 
							
								                    try:
							 | 
						||
| 
								 | 
							
								                        secret = secret.decode("utf-8")
							 | 
						||
| 
								 | 
							
								                    except UnicodeDecodeError:
							 | 
						||
| 
								 | 
							
								                        return None
							 | 
						||
| 
								 | 
							
								                    # sanity check it encodes back to original byte string,
							 | 
						||
| 
								 | 
							
								                    # otherwise when crypt() does it's encoding, it'll hash the wrong bytes!
							 | 
						||
| 
								 | 
							
								                    assert secret.encode("utf-8") == orig, \
							 | 
						||
| 
								 | 
							
								                                "utf-8 spec says this can't happen!"
							 | 
						||
| 
								 | 
							
								                if _NULL in secret:
							 | 
						||
| 
								 | 
							
								                    raise ValueError("null character in secret")
							 | 
						||
| 
								 | 
							
								                if isinstance(hash, bytes):
							 | 
						||
| 
								 | 
							
								                    hash = hash.decode("ascii")
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                with _safe_crypt_lock:
							 | 
						||
| 
								 | 
							
								                    result = _crypt(secret, hash)
							 | 
						||
| 
								 | 
							
								            except OSError:
							 | 
						||
| 
								 | 
							
								                # new in py39 -- per https://bugs.python.org/issue39289,
							 | 
						||
| 
								 | 
							
								                # crypt() now throws OSError for various things, mainly unknown hash formats
							 | 
						||
| 
								 | 
							
								                # translating that to None for now (may revise safe_crypt behavior in future)
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								            # NOTE: per issue 113, crypt() may return bytes in some odd cases.
							 | 
						||
| 
								 | 
							
								            #       assuming it should still return an ASCII hash though,
							 | 
						||
| 
								 | 
							
								            #       or there's a bigger issue at hand.
							 | 
						||
| 
								 | 
							
								            if isinstance(result, bytes):
							 | 
						||
| 
								 | 
							
								                result = result.decode("ascii")
							 | 
						||
| 
								 | 
							
								            if not result or result[0] in _invalid_prefixes:
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								            return result
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #: see feature-detection in PY3 fork above
							 | 
						||
| 
								 | 
							
								        crypt_accepts_bytes = True
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Python 2 crypt handler
							 | 
						||
| 
								 | 
							
								        def safe_crypt(secret, hash):
							 | 
						||
| 
								 | 
							
								            if isinstance(secret, unicode):
							 | 
						||
| 
								 | 
							
								                secret = secret.encode("utf-8")
							 | 
						||
| 
								 | 
							
								            if _NULL in secret:
							 | 
						||
| 
								 | 
							
								                raise ValueError("null character in secret")
							 | 
						||
| 
								 | 
							
								            if isinstance(hash, unicode):
							 | 
						||
| 
								 | 
							
								                hash = hash.encode("ascii")
							 | 
						||
| 
								 | 
							
								            with _safe_crypt_lock:
							 | 
						||
| 
								 | 
							
								                result = _crypt(secret, hash)
							 | 
						||
| 
								 | 
							
								            if not result:
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								            result = result.decode("ascii")
							 | 
						||
| 
								 | 
							
								            if result[0] in _invalid_prefixes:
							 | 
						||
| 
								 | 
							
								                return None
							 | 
						||
| 
								 | 
							
								            return result
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								add_doc(safe_crypt, """Wrapper around stdlib's crypt.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This is a wrapper around stdlib's :func:`!crypt.crypt`, which attempts
							 | 
						||
| 
								 | 
							
								    to provide uniform behavior across Python 2 and 3.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg secret:
							 | 
						||
| 
								 | 
							
								        password, as bytes or unicode (unicode will be encoded as ``utf-8``).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :arg hash:
							 | 
						||
| 
								 | 
							
								        hash or config string, as ascii bytes or unicode.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns:
							 | 
						||
| 
								 | 
							
								        resulting hash as ascii unicode; or ``None`` if the password
							 | 
						||
| 
								 | 
							
								        couldn't be hashed due to one of the issues:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * :func:`crypt()` not available on platform.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * Under Python 3, if *secret* is specified as bytes,
							 | 
						||
| 
								 | 
							
								          it must be use ``utf-8`` or it can't be passed
							 | 
						||
| 
								 | 
							
								          to :func:`crypt()`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * Some OSes will return ``None`` if they don't recognize
							 | 
						||
| 
								 | 
							
								          the algorithm being used (though most will simply fall
							 | 
						||
| 
								 | 
							
								          back to des-crypt).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        * Some OSes will return an error string if the input config
							 | 
						||
| 
								 | 
							
								          is recognized but malformed; current code converts these to ``None``
							 | 
						||
| 
								 | 
							
								          as well.
							 | 
						||
| 
								 | 
							
								    """)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def test_crypt(secret, hash):
							 | 
						||
| 
								 | 
							
								    """check if :func:`crypt.crypt` supports specific hash
							 | 
						||
| 
								 | 
							
								    :arg secret: password to test
							 | 
						||
| 
								 | 
							
								    :arg hash: known hash of password to use as reference
							 | 
						||
| 
								 | 
							
								    :returns: True or False
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # safe_crypt() always returns unicode, which means that for py3,
							 | 
						||
| 
								 | 
							
								    # 'hash' can't be bytes, or "== hash" will never be True.
							 | 
						||
| 
								 | 
							
								    # under py2 unicode & str(bytes) will compare fine;
							 | 
						||
| 
								 | 
							
								    # so just enforcing "unicode_or_str" limitation
							 | 
						||
| 
								 | 
							
								    assert isinstance(hash, unicode_or_str), \
							 | 
						||
| 
								 | 
							
								        "hash must be unicode_or_str, got %s" % type(hash)
							 | 
						||
| 
								 | 
							
								    assert hash, "hash must be non-empty"
							 | 
						||
| 
								 | 
							
								    return safe_crypt(secret, hash) == hash
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								timer = timeit.default_timer
							 | 
						||
| 
								 | 
							
								# legacy alias, will be removed in passlib 2.0
							 | 
						||
| 
								 | 
							
								tick = timer
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def parse_version(source):
							 | 
						||
| 
								 | 
							
								    """helper to parse version string"""
							 | 
						||
| 
								 | 
							
								    m = re.search(r"(\d+(?:\.\d+)+)", source)
							 | 
						||
| 
								 | 
							
								    if m:
							 | 
						||
| 
								 | 
							
								        return tuple(int(elem) for elem in m.group(1).split("."))
							 | 
						||
| 
								 | 
							
								    return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# randomness
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# setup rng for generating salts
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# NOTE:
							 | 
						||
| 
								 | 
							
								# generating salts (e.g. h64_gensalt, below) doesn't require cryptographically
							 | 
						||
| 
								 | 
							
								# strong randomness. it just requires enough range of possible outputs
							 | 
						||
| 
								 | 
							
								# that making a rainbow table is too costly. so it should be ok to
							 | 
						||
| 
								 | 
							
								# fall back on python's builtin mersenne twister prng, as long as it's seeded each time
							 | 
						||
| 
								 | 
							
								# this module is imported, using a couple of minor entropy sources.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								try:
							 | 
						||
| 
								 | 
							
								    os.urandom(1)
							 | 
						||
| 
								 | 
							
								    has_urandom = True
							 | 
						||
| 
								 | 
							
								except NotImplementedError: # pragma: no cover
							 | 
						||
| 
								 | 
							
								    has_urandom = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def genseed(value=None):
							 | 
						||
| 
								 | 
							
								    """generate prng seed value from system resources"""
							 | 
						||
| 
								 | 
							
								    from hashlib import sha512
							 | 
						||
| 
								 | 
							
								    if hasattr(value, "getstate") and hasattr(value, "getrandbits"):
							 | 
						||
| 
								 | 
							
								        # caller passed in RNG as seed value
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            value = value.getstate()
							 | 
						||
| 
								 | 
							
								        except NotImplementedError:
							 | 
						||
| 
								 | 
							
								            # this method throws error for e.g. SystemRandom instances,
							 | 
						||
| 
								 | 
							
								            # so fall back to extracting 4k of state
							 | 
						||
| 
								 | 
							
								            value = value.getrandbits(1 << 15)
							 | 
						||
| 
								 | 
							
								    text = u("%s %s %s %.15f %.15f %s") % (
							 | 
						||
| 
								 | 
							
								        # if caller specified a seed value, mix it in
							 | 
						||
| 
								 | 
							
								        value,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # add current process id
							 | 
						||
| 
								 | 
							
								        # NOTE: not available in some environments, e.g. GAE
							 | 
						||
| 
								 | 
							
								        os.getpid() if hasattr(os, "getpid") else None,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # id of a freshly created object.
							 | 
						||
| 
								 | 
							
								        # (at least 1 byte of which should be hard to predict)
							 | 
						||
| 
								 | 
							
								        id(object()),
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # the current time, to whatever precision os uses
							 | 
						||
| 
								 | 
							
								        time.time(),
							 | 
						||
| 
								 | 
							
								        tick(),
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # if urandom available, might as well mix some bytes in.
							 | 
						||
| 
								 | 
							
								        os.urandom(32).decode("latin-1") if has_urandom else 0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    # hash it all up and return it as int/long
							 | 
						||
| 
								 | 
							
								    return int(sha512(text.encode("utf-8")).hexdigest(), 16)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if has_urandom:
							 | 
						||
| 
								 | 
							
								    rng = random.SystemRandom()
							 | 
						||
| 
								 | 
							
								else: # pragma: no cover -- runtime detection
							 | 
						||
| 
								 | 
							
								    # NOTE: to reseed use ``rng.seed(genseed(rng))``
							 | 
						||
| 
								 | 
							
								    # XXX: could reseed on every call
							 | 
						||
| 
								 | 
							
								    rng = random.Random(genseed())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# some rng helpers
							 | 
						||
| 
								 | 
							
								#------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								def getrandbytes(rng, count):
							 | 
						||
| 
								 | 
							
								    """return byte-string containing *count* number of randomly generated bytes, using specified rng"""
							 | 
						||
| 
								 | 
							
								    # NOTE: would be nice if this was present in stdlib Random class
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ###just in case rng provides this...
							 | 
						||
| 
								 | 
							
								    ##meth = getattr(rng, "getrandbytes", None)
							 | 
						||
| 
								 | 
							
								    ##if meth:
							 | 
						||
| 
								 | 
							
								    ##    return meth(count)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not count:
							 | 
						||
| 
								 | 
							
								        return _BEMPTY
							 | 
						||
| 
								 | 
							
								    def helper():
							 | 
						||
| 
								 | 
							
								        # XXX: break into chunks for large number of bits?
							 | 
						||
| 
								 | 
							
								        value = rng.getrandbits(count<<3)
							 | 
						||
| 
								 | 
							
								        i = 0
							 | 
						||
| 
								 | 
							
								        while i < count:
							 | 
						||
| 
								 | 
							
								            yield value & 0xff
							 | 
						||
| 
								 | 
							
								            value >>= 3
							 | 
						||
| 
								 | 
							
								            i += 1
							 | 
						||
| 
								 | 
							
								    return join_byte_values(helper())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def getrandstr(rng, charset, count):
							 | 
						||
| 
								 | 
							
								    """return string containing *count* number of chars/bytes, whose elements are drawn from specified charset, using specified rng"""
							 | 
						||
| 
								 | 
							
								    # NOTE: tests determined this is 4x faster than rng.sample(),
							 | 
						||
| 
								 | 
							
								    # which is why that's not being used here.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # check alphabet & count
							 | 
						||
| 
								 | 
							
								    if count < 0:
							 | 
						||
| 
								 | 
							
								        raise ValueError("count must be >= 0")
							 | 
						||
| 
								 | 
							
								    letters = len(charset)
							 | 
						||
| 
								 | 
							
								    if letters == 0:
							 | 
						||
| 
								 | 
							
								        raise ValueError("alphabet must not be empty")
							 | 
						||
| 
								 | 
							
								    if letters == 1:
							 | 
						||
| 
								 | 
							
								        return charset * count
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # get random value, and write out to buffer
							 | 
						||
| 
								 | 
							
								    def helper():
							 | 
						||
| 
								 | 
							
								        # XXX: break into chunks for large number of letters?
							 | 
						||
| 
								 | 
							
								        value = rng.randrange(0, letters**count)
							 | 
						||
| 
								 | 
							
								        i = 0
							 | 
						||
| 
								 | 
							
								        while i < count:
							 | 
						||
| 
								 | 
							
								            yield charset[value % letters]
							 | 
						||
| 
								 | 
							
								            value //= letters
							 | 
						||
| 
								 | 
							
								            i += 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if isinstance(charset, unicode):
							 | 
						||
| 
								 | 
							
								        return join_unicode(helper())
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        return join_byte_elems(helper())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_52charset = '2346789ABCDEFGHJKMNPQRTUVWXYZabcdefghjkmnpqrstuvwxyz'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@deprecated_function(deprecated="1.7", removed="2.0",
							 | 
						||
| 
								 | 
							
								                     replacement="passlib.pwd.genword() / passlib.pwd.genphrase()")
							 | 
						||
| 
								 | 
							
								def generate_password(size=10, charset=_52charset):
							 | 
						||
| 
								 | 
							
								    """generate random password using given length & charset
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param size:
							 | 
						||
| 
								 | 
							
								        size of password.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param charset:
							 | 
						||
| 
								 | 
							
								        optional string specified set of characters to draw from.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        the default charset contains all normal alphanumeric characters,
							 | 
						||
| 
								 | 
							
								        except for the characters ``1IiLl0OoS5``, which were omitted
							 | 
						||
| 
								 | 
							
								        due to their visual similarity.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :returns: :class:`!str` containing randomly generated password.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    .. note::
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Using the default character set, on a OS with :class:`!SystemRandom` support,
							 | 
						||
| 
								 | 
							
								        this function should generate passwords with 5.7 bits of entropy per character.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    return getrandstr(rng, charset, size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# object type / interface tests
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								_handler_attrs = (
							 | 
						||
| 
								 | 
							
								        "name",
							 | 
						||
| 
								 | 
							
								        "setting_kwds", "context_kwds",
							 | 
						||
| 
								 | 
							
								        "verify", "hash", "identify",
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_crypt_handler(obj):
							 | 
						||
| 
								 | 
							
								    """check if object follows the :ref:`password-hash-api`"""
							 | 
						||
| 
								 | 
							
								    # XXX: change to use isinstance(obj, PasswordHash) under py26+?
							 | 
						||
| 
								 | 
							
								    return all(hasattr(obj, name) for name in _handler_attrs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_context_attrs = (
							 | 
						||
| 
								 | 
							
								        "needs_update",
							 | 
						||
| 
								 | 
							
								        "genconfig", "genhash",
							 | 
						||
| 
								 | 
							
								        "verify", "encrypt", "identify",
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_crypt_context(obj):
							 | 
						||
| 
								 | 
							
								    """check if object appears to be a :class:`~passlib.context.CryptContext` instance"""
							 | 
						||
| 
								 | 
							
								    # XXX: change to use isinstance(obj, CryptContext)?
							 | 
						||
| 
								 | 
							
								    return all(hasattr(obj, name) for name in _context_attrs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								##def has_many_backends(handler):
							 | 
						||
| 
								 | 
							
								##    "check if handler provides multiple baceknds"
							 | 
						||
| 
								 | 
							
								##    # NOTE: should also provide get_backend(), .has_backend(), and .backends attr
							 | 
						||
| 
								 | 
							
								##    return hasattr(handler, "set_backend")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def has_rounds_info(handler):
							 | 
						||
| 
								 | 
							
								    """check if handler provides the optional :ref:`rounds information <rounds-attributes>` attributes"""
							 | 
						||
| 
								 | 
							
								    return ('rounds' in handler.setting_kwds and
							 | 
						||
| 
								 | 
							
								            getattr(handler, "min_rounds", None) is not None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def has_salt_info(handler):
							 | 
						||
| 
								 | 
							
								    """check if handler provides the optional :ref:`salt information <salt-attributes>` attributes"""
							 | 
						||
| 
								 | 
							
								    return ('salt' in handler.setting_kwds and
							 | 
						||
| 
								 | 
							
								            getattr(handler, "min_salt_size", None) is not None)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								##def has_raw_salt(handler):
							 | 
						||
| 
								 | 
							
								##    "check if handler takes in encoded salt as unicode (False), or decoded salt as bytes (True)"
							 | 
						||
| 
								 | 
							
								##    sc = getattr(handler, "salt_chars", None)
							 | 
						||
| 
								 | 
							
								##    if sc is None:
							 | 
						||
| 
								 | 
							
								##        return None
							 | 
						||
| 
								 | 
							
								##    elif isinstance(sc, unicode):
							 | 
						||
| 
								 | 
							
								##        return False
							 | 
						||
| 
								 | 
							
								##    elif isinstance(sc, bytes):
							 | 
						||
| 
								 | 
							
								##        return True
							 | 
						||
| 
								 | 
							
								##    else:
							 | 
						||
| 
								 | 
							
								##        raise TypeError("handler.salt_chars must be None/unicode/bytes")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 | 
						||
| 
								 | 
							
								# eof
							 | 
						||
| 
								 | 
							
								#=============================================================================
							 |