diff --git a/roundup/admin.py b/roundup/admin.py --- a/roundup/admin.py +++ b/roundup/admin.py @@ -511,7 +511,7 @@ init.write_select_db(tracker_home, backend, tracker.config.DATABASE) # GO - tracker.init(password.Password(adminpw)) + tracker.init(password.Password(adminpw, config=tracker.config)) return 0 diff --git a/roundup/cgi/actions.py b/roundup/cgi/actions.py --- a/roundup/cgi/actions.py +++ b/roundup/cgi/actions.py @@ -353,7 +353,7 @@ if isinstance(prop, hyperdb.Multilink): value = value.split(':') elif isinstance(prop, hyperdb.Password): - value = password.Password(value) + value = password.Password(value, config=self.db.config) elif isinstance(prop, hyperdb.Interval): value = date.Interval(value) elif isinstance(prop, hyperdb.Date): @@ -711,7 +711,7 @@ # XXX we need to make the "default" page be able to display errors! try: # set the password - cl.set(uid, password=password.Password(newpw)) + cl.set(uid, password=password.Password(newpw, config=self.db.config)) # clear the props from the otk database otks.destroy(otk) self.db.commit() @@ -1010,7 +1010,7 @@ stored = self.db.user.get(userid, 'password') if passwd == stored: if self.db.config.UPGRADE_PASSWORDS_ON_WEB_LOGIN and stored.needsUpgrade(): - self.db.user.set(userid, password=password.Password(passwd)) + self.db.user.set(userid, password=password.Password(passwd, config=self.db.config)) return 1 if not passwd and not stored: return 1 diff --git a/roundup/cgi/form_parser.py b/roundup/cgi/form_parser.py --- a/roundup/cgi/form_parser.py +++ b/roundup/cgi/form_parser.py @@ -383,7 +383,7 @@ raise FormError, self._('Password and confirmation text ' 'do not match') try: - value = password.Password(value) + value = password.Password(value, config=self.db.config) except hyperdb.HyperdbValueError, msg: raise FormError, msg diff --git a/roundup/configuration.py b/roundup/configuration.py --- a/roundup/configuration.py +++ b/roundup/configuration.py @@ -540,6 +540,10 @@ (BooleanOption, "upgrade_passwords_on_web_login", "no", "On web-login, automatically re-encode passwords that are stored\n" "using a deprecated hash format."), + (IntegerNumberOption, 'password_pbkdf2_default_rounds', '10000', + "Sets the default number of rounds used when encoding passwords\n" + "using the PBKDF2 scheme. Set this to a higher value on faster\n" + "systems which want more security."), )), ("tracker", ( (Option, "name", "Roundup issue tracker", diff --git a/roundup/mailgw.py b/roundup/mailgw.py --- a/roundup/mailgw.py +++ b/roundup/mailgw.py @@ -1688,7 +1688,7 @@ try: return db.user.create(username=trying, address=address, realname=realname, roles=db.config.NEW_EMAIL_USER_ROLES, - password=password.Password(password.generatePassword()), + password=password.Password(password.generatePassword(), config=db.config), **user_props) except exceptions.Reject: return 0 diff --git a/roundup/password.py b/roundup/password.py --- a/roundup/password.py +++ b/roundup/password.py @@ -116,7 +116,7 @@ """ The password value is not valid """ pass -def encodePassword(plaintext, scheme, other=None): +def encodePassword(plaintext, scheme, other=None, config=None): """Encrypt the plaintext password. """ if plaintext is None: @@ -140,9 +140,10 @@ else: raw_salt = getrandbytes(20) salt = h64encode(raw_salt) - #FIXME: find way to access config, so default rounds - # can be altered for faster/slower hosts via config.ini - rounds = 10000 + if config: + rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS + else: + rounds = 10000 if rounds < 1000: raise PasswordValueError, "invalid PBKDF2 hash (rounds too low)" raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20) @@ -237,8 +238,8 @@ known_schemes = [ "PBKDF2", "SHA", "MD5", "crypt", "plaintext" ] deprecated_schemes = [ "SHA", "MD5", "crypt", "plaintext" ] - def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False): + def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False, config=None): """Call setPassword if plaintext is not None.""" if scheme is None: scheme = self.default_scheme if plaintext is not None: @@ -241,6 +242,6 @@ """Call setPassword if plaintext is not None.""" if scheme is None: scheme = self.default_scheme if plaintext is not None: - self.setPassword (plaintext, scheme) + self.setPassword (plaintext, scheme, config=config) elif encrypted is not None: @@ -246,7 +247,7 @@ elif encrypted is not None: - self.unpack(encrypted, scheme, strict=strict) + self.unpack(encrypted, scheme, strict=strict, config=config) else: self.scheme = self.default_scheme self.password = None self.plaintext = None @@ -248,9 +249,9 @@ else: self.scheme = self.default_scheme self.password = None self.plaintext = None - def unpack(self, encrypted, scheme=None, strict=False): + def unpack(self, encrypted, scheme=None, strict=False, config=None): """Set the password info from the scheme: string (the inverse of __str__) """ @@ -261,7 +262,7 @@ self.plaintext = None else: # currently plaintext - encrypt - self.setPassword(encrypted, scheme) + self.setPassword(encrypted, scheme, config=config) if strict and self.scheme not in self.known_schemes: raise PasswordValueError, "unknown encryption scheme: %r" % (self.scheme,) @@ -265,8 +266,8 @@ if strict and self.scheme not in self.known_schemes: raise PasswordValueError, "unknown encryption scheme: %r" % (self.scheme,) - def setPassword(self, plaintext, scheme=None): + def setPassword(self, plaintext, scheme=None, config=None): """Sets encrypts plaintext.""" if scheme is None: scheme = self.default_scheme self.scheme = scheme @@ -269,8 +270,8 @@ """Sets encrypts plaintext.""" if scheme is None: scheme = self.default_scheme self.scheme = scheme - self.password = encodePassword(plaintext, scheme) + self.password = encodePassword(plaintext, scheme, config=config) self.plaintext = plaintext def needsUpgrade(self):