Skip to content

Commit

Permalink
user/password: move to passlib hash.sha512_crypt to generate password…
Browse files Browse the repository at this point in the history
… hashes to replace deprecated crypt lib
  • Loading branch information
alexAubin committed Dec 27, 2023
1 parent f505efc commit 6655923
Show file tree
Hide file tree
Showing 6 changed files with 16 additions and 38 deletions.
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Depends: ${python3:Depends}, ${misc:Depends}
, python3-miniupnpc, python3-dbus, python3-jinja2
, python3-toml, python3-packaging, python3-publicsuffix2
, python3-ldap, python3-zeroconf (>= 0.47), python3-lexicon,
, python3-cryptography, python3-jwt, python3-magic
, python3-cryptography, python3-jwt, python3-passlib, python3-magic
, python-is-python3, python3-pydantic, python3-email-validator
, nginx, nginx-extras (>=1.22)
, apt, apt-transport-https, apt-utils, dirmngr
Expand Down
2 changes: 1 addition & 1 deletion src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3162,7 +3162,7 @@ def regen_mail_app_user_config_for_dovecot_and_postfix(only=None):
dovecot = True if only in [None, "dovecot"] else False
postfix = True if only in [None, "postfix"] else False

from yunohost.user import _hash_user_password
from yunohost.utils.password import _hash_user_password

postfix_map = []
dovecot_passwd = []
Expand Down
2 changes: 1 addition & 1 deletion src/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
import ldap
from moulinette.utils.filesystem import read_json
from yunohost.authenticators.ldap_ynhuser import Authenticator as Auth, user_is_allowed_on_domain
from yunohost.user import _hash_user_password
from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract, LDAPInterface
from yunohost.utils.password import (
assert_password_is_compatible,
assert_password_is_strong_enough,
_hash_user_password,
)

logger = logging.getLogger("portal")
Expand Down
2 changes: 1 addition & 1 deletion src/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ def tools_versions():


def tools_rootpw(new_password, check_strength=True):
from yunohost.user import _hash_user_password
from yunohost.utils.password import (
assert_password_is_strong_enough,
assert_password_is_compatible,
_hash_user_password,
)
import spwd

Expand Down
35 changes: 2 additions & 33 deletions src/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def user_create(
from yunohost.utils.password import (
assert_password_is_strong_enough,
assert_password_is_compatible,
_hash_user_password,
)
from yunohost.utils.ldap import _get_ldap_interface

Expand Down Expand Up @@ -381,6 +382,7 @@ def user_update(
from yunohost.utils.password import (
assert_password_is_strong_enough,
assert_password_is_compatible,
_hash_user_password,
)
from yunohost.utils.ldap import _get_ldap_interface
from yunohost.hook import hook_callback
Expand Down Expand Up @@ -1437,39 +1439,6 @@ def user_ssh_remove_key(username, key):
# End SSH subcategory
#


def _hash_user_password(password):
"""
This function computes and return a salted hash for the password in input.
This implementation is inspired from [1].
The hash follows SHA-512 scheme from Linux/glibc.
Hence the {CRYPT} and $6$ prefixes
- {CRYPT} means it relies on the OS' crypt lib
- $6$ corresponds to SHA-512, the strongest hash available on the system
The salt is generated using random.SystemRandom(). It is the crypto-secure
pseudo-random number generator according to the python doc [2] (c.f. the
red square). It internally relies on /dev/urandom
The salt is made of 16 characters from the set [./a-zA-Z0-9]. This is the
max sized allowed for salts according to [3]
[1] https://www.redpill-linpro.com/techblog/2016/08/16/ldap-password-hash.html
[2] https://docs.python.org/2/library/random.html
[3] https://www.safaribooksonline.com/library/view/practical-unix-and/0596003234/ch04s03.html
"""

# FIXME: 'crypt' is deprecated and slated for removal in Python 3.13
import crypt

char_set = string.ascii_uppercase + string.ascii_lowercase + string.digits + "./"
salt = "".join([random.SystemRandom().choice(char_set) for x in range(16)])

salt = "$6$" + salt + "$"
return "{CRYPT}" + crypt.crypt(str(password), salt)


def _update_admins_group_aliases(old_main_domain, new_main_domain):
current_admin_aliases = user_group_info("admins")["mail-aliases"]

Expand Down
11 changes: 10 additions & 1 deletion src/utils/password.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import sys

import os
import string
import subprocess
import yaml
import passlib.hash

Check notice

Code scanning / CodeQL

Unused import Note

Import of 'passlib' is not used.

SMALL_PWD_LIST = [
"yunohost",
Expand Down Expand Up @@ -71,6 +72,14 @@ def assert_password_is_strong_enough(profile, password):
PasswordValidator(profile).validate(password)


def _hash_user_password(password):
import passlib
# passlib will returns something like:
# $6$rounds=656000$AwCIMolbTAyQhtev$46UvYfVgs.k0Bt6fLTekBHyCcCFkix/NNfgAWiICX.9YUPVYZ3PsIAwY99yP5/tXhg2sYBaAhKj6W3kuYWaR3.
# cf https://passlib.readthedocs.io/en/stable/modular_crypt_format.html#modular-crypt-format
return "{CRYPT}" + passlib.hash.sha512_crypt.hash(password)


class PasswordValidator:
def __init__(self, profile):
"""
Expand Down

0 comments on commit 6655923

Please sign in to comment.