from ..handler.ldap_password import WrongPwdSchemaError
from ..handler.ldap_password import LdapPasswordHandler
from ..handler.ldap_password import HAS_CRACKLIB
+from ..handler.ldap_password import WrongSaltError, WrongRoundsError
-__version__ = '0.7.2'
+__version__ = '0.8.1'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
app_group.add_argument(
'user', metavar=_('USER'), help=user_help)
+ app_group.add_argument(
+ '--salt', metavar='SALT', dest="salt",
+ help=_(
+ "A possible salt to use on hashing the password. Caution: "
+ "not all hashing schemes are supporting a salt.")
+ )
+
+ app_group.add_argument(
+ '--rounds', metavar='ROUNDS', dest="rounds", type=int,
+ help=_(
+ "The number of calculation rounds to use on hashing the password. Caution: "
+ "not all hashing schemes are supporting calculation rounds.")
+ )
+
super(SetLdapPasswordApplication, self).init_arg_parser()
# -------------------------------------------------------------------------
self.colored(self.user_dn, 'CYAN'))
print(msg)
+ salt = getattr(self.args, 'salt', None)
+ rounds = getattr(self.args, 'rounds', None)
+
LOG.debug(_("Used schema: {!r}.").format(self.pwd_handler.schema))
- hashed_passwd = self.pwd_handler.get_hash(self.new_password, self.pwd_handler.schema)
+ try:
+ hashed_passwd = self.pwd_handler.get_hash(
+ self.new_password, self.pwd_handler.schema, salt=salt, rounds=rounds)
+ except (WrongSaltError, WrongRoundsError) as e:
+ self.exit(1, str(e))
msg = _("New password hash: '{}'.").format(self.colored(hashed_passwd, 'CYAN'))
print(msg)
except ImportError:
pass
+from fb_tools.common import to_str, to_bytes
from fb_tools.handling_obj import HandlingObject
from fb_tools.errors import FbHandlerError
_ = XLATOR.gettext
ngettext = XLATOR.ngettext
-__version__ = '0.2.2'
+__version__ = '0.3.1'
# =============================================================================
return _("Encryption schema {!r} inot found.").format(self.schema)
+# =============================================================================
+class WrongSaltError(FbHandlerError):
+ """Exception class in case of a wrong salt."""
+ pass
+
+
+# =============================================================================
+class WrongRoundsError(FbHandlerError):
+ """Exception class in case of a wrong calculation rounds."""
+ pass
+
+
# =============================================================================
class LdapPasswordHandler(HandlingObject):
"""Handler class for handling LDAP passwords."""
'SHA-512'),
}
- default_rounds = {
- 'ldap_sha256_crypt': 64000,
- 'ldap_sha512_crypt': 64000,
- 'ldap_pbkdf2_sha512': 30000,
- }
-
passlib_context = None
default_schema = 'ldap_sha512_crypt'
default_schema_id = 'CRYPT-SHA512'
'default': cls.default_schema,
}
- for schema in cls.default_rounds:
- key = schema + '__rounds'
- context_opts[key] = cls.default_rounds[schema]
-
cls.passlib_context = passlib.context.CryptContext(**context_opts)
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
@property
- def salt_len(self):
- """Gives the valid length of a salt string in dependency to the current schema."""
- if hasattr(self, 'schema') and self.schema == 'ldap_des_crypt':
- return 2
- return 8
+ def salt_info(self):
+ """Gives information about possible salt of the current schema."""
+ if not hasattr(self.__class__, 'passlib_context'):
+ return None
+
+ default_schema = self.passlib_context.default_scheme()
+ default_handler = self.passlib_context.handler(default_schema)
+ if 'salt' in default_handler.setting_kwds:
+ ret = {
+ 'min_len': default_handler.min_salt_size,
+ 'max_len': default_handler.max_salt_size,
+ 'default_size': default_handler.default_salt_size,
+ 'usable_chars': default_handler.salt_chars,
+ }
+ else:
+ ret = None
+
+ return ret
# -------------------------------------------------------------------------
@property
- def salt(self):
- """The salt of the current schema."""
+ def rounds_info(self):
+ """Gives information about possible rounds parameter of the current schema."""
if not hasattr(self.__class__, 'passlib_context'):
return None
- return self.passlib_context.salt()
+
+ default_schema = self.passlib_context.default_scheme()
+ default_handler = self.passlib_context.handler(default_schema)
+ if 'rounds' in default_handler.setting_kwds:
+ ret = {
+ 'min': default_handler.min_rounds,
+ 'max': default_handler.max_rounds,
+ 'default': default_handler.default_rounds,
+ 'costs': default_handler.rounds_cost,
+ }
+ else:
+ ret = None
+
+ return ret
# -------------------------------------------------------------------------
def as_dict(self, short=True):
res = super(LdapPasswordHandler, self).as_dict(short=short)
+ default_schema = self.passlib_context.default_scheme()
+ default_handler = self.passlib_context.handler(default_schema)
+
res['available_schemes'] = self.available_schemes
res['passlib_context'] = self.passlib_context.to_dict(True)
- res['default_schema'] = self.passlib_context.default_scheme()
- # res['salt'] = self.salt
- res['salt_len'] = self.salt_len
+ res['default_schema'] = default_schema
+ res['default_handler'] = default_handler
+ res['rounds_info'] = self.rounds_info
+ res['salt_info'] = self.salt_info
res['schema_ids'] = self.schema_ids
return res
raise WrongPwdSchemaError(given_schema_id)
# -------------------------------------------------------------------------
- def get_hash(self, password, schema=None):
+ def verify_salt(self, salt, schema=None):
if not schema:
schema = self.schema
- hashed_passwd = self.passlib_context.hash(password)
+ handler = self.passlib_context.handler(schema)
+ if 'salt' not in handler.setting_kwds:
+ msg = _("The password schema {!r} does not support a password salt.").format(schema)
+ raise WrongSaltError(msg)
+
+ if len(salt) < handler.min_salt_size:
+ msg = _("The password salt must be at least by {} characters.").format(
+ handler.min_salt_size)
+ raise WrongSaltError(msg)
+ if len(salt) > handler.max_salt_size:
+ msg = _("The password salt may have a length of maximum {} characters.").format(
+ handler.max_salt_size)
+ raise WrongSaltError(msg)
+
+ if self.verbose > 1:
+ LOG.debug("Usable characters: {!r}".format(handler.salt_chars))
+ if isinstance(handler.salt_chars, (bytes, bytearray)):
+ salt = to_bytes(salt)
+ for character in salt:
+ if character not in handler.salt_chars:
+ msg = _("Found invalid character {!r} in password salt.").format(character)
+ raise WrongSaltError(msg)
+
+ return salt
+
+ # ------------------------------------------------------------------------
+ def verify_rounds(self, rounds, schema=None):
+
+ if not schema:
+ schema = self.schema
+
+ handler = self.passlib_context.handler(schema)
+ if 'rounds' not in handler.setting_kwds:
+ msg = _("The password schema {!r} does not support calculation rounds.").format(schema)
+ raise WrongRoundsError(msg)
+
+ try:
+ rounds = int(rounds)
+ except (TypeError, ValueError) as e:
+ msg = _("Wrong value {v!r} for calculation rounds: {e}").format(v=rounds, e=e)
+ raise WrongRoundsError(msg)
+
+ if rounds < handler.min_rounds:
+ msg = _("The value for the calculation rounds has to be at least {}.").format(
+ handler.min_rounds)
+ raise WrongRoundsError(msg)
+
+ if rounds > handler.max_rounds:
+ msg = _("The value for the calculation rounds has to at most {}.").format(
+ handler.max_rounds)
+ raise WrongRoundsError(msg)
+
+ return rounds
+
+ # -------------------------------------------------------------------------
+ def get_hash(self, password, schema=None, salt=None, rounds=None):
+
+ if not schema:
+ schema = self.schema
+
+ add_opts = {}
+
+ if salt:
+ if not isinstance(salt, str):
+ if isinstance(salt, (bytes, bytearray)):
+ salt = to_str(salt)
+ else:
+ salt = str(salt)
+ add_opts['salt'] = self.verify_salt(salt, schema)
+
+ if rounds:
+ rounds = self.verify_rounds(rounds, schema)
+ add_opts['rounds'] = rounds
+
+ hashed_passwd = self.passlib_context.hash(password, **add_opts)
return hashed_passwd
# -------------------------------------------------------------------------