Commit cf7be2b2 authored by chirac's avatar chirac

Merge branch 'support_old_hash' into 'dev'

Support old hashes, md5/crypt

See merge request federez/re2o!215
parents cf3edcef 89bd17a4
Pipeline #726 canceled with stage
......@@ -28,14 +28,14 @@
Module in charge of handling the login process and verifications
"""
import hashlib
import binascii
import crypt
import hashlib
import os
from base64 import encodestring
from base64 import decodestring
from base64 import encodestring, decodestring, b64encode, b64decode
from collections import OrderedDict
from django.contrib.auth import hashers
from hmac import compare_digest as constant_time_compare
ALGO_NAME = "{SSHA}"
......@@ -64,17 +64,127 @@ def checkPassword(challenge_password, password):
salt = challenge_bytes[DIGEST_LEN:]
hr = hashlib.sha1(password.encode())
hr.update(salt)
valid_password = True
# La comparaison est volontairement en temps constant
# (pour éviter les timing-attacks)
for i, j in zip(digest, hr.digest()):
valid_password &= i == j
return valid_password
return constant_time_compare(digest, hr.digest())
def hash_password_salt(hashed_password):
""" Extract the salt from a given hashed password """
if hashed_password.upper().startswith('{CRYPT}'):
hashed_password = hashed_password[7:]
if hashed_password.startswith('$'):
return '$'.join(hashed_password.split('$')[:-1])
else:
return hashed_password[:2]
elif hashed_password.upper().startswith('{SSHA}'):
try:
digest = b64decode(hashed_password[6:])
except TypeError as error:
raise ValueError("b64 error for `hashed_password` : %s" % error)
if len(digest) < 20:
raise ValueError("`hashed_password` too short")
return digest[20:]
elif hashed_password.upper().startswith('{SMD5}'):
try:
digest = b64decode(hashed_password[7:])
except TypeError as error:
raise ValueError("b64 error for `hashed_password` : %s" % error)
if len(digest) < 16:
raise ValueError("`hashed_password` too short")
return digest[16:]
else:
raise ValueError("`hashed_password` should start with '{SSHA}' or '{CRYPT}' or '{SMD5}'")
class CryptPasswordHasher(hashers.BasePasswordHasher):
"""
Crypt password hashing to allow for LDAP auth compatibility
We do not encode, this should bot be used !
The actual implementation may depend on the OS.
"""
algorithm = "{crypt}"
def encode(self, password, salt):
pass
def verify(self, password, encoded):
"""
Check password against encoded using CRYPT algorithm
"""
assert encoded.startswith(self.algorithm)
salt = hash_password_salt(challenge_password)
return constant_time_compare(crypt.crypt(password.encode(), salt),
challenge.encode())
def safe_summary(self, encoded):
"""
Provides a safe summary of the password
"""
assert encoded.startswith(self.algorithm)
hash_str = encoded[7:]
hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode()
return OrderedDict([
('algorithm', self.algorithm),
('iterations', 0),
('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)),
('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])),
])
def harden_runtime(self, password, encoded):
"""
Method implemented to shut up BasePasswordHasher warning
As we are not using multiple iterations the method is pretty useless
"""
pass
class MD5PasswordHasher(hashers.BasePasswordHasher):
"""
Salted MD5 password hashing to allow for LDAP auth compatibility
We do not encode, this should bot be used !
"""
algorithm = "{SMD5}"
def encode(self, password, salt):
pass
def verify(self, password, encoded):
"""
Check password against encoded using SMD5 algorithm
"""
assert encoded.startswith(self.algorithm)
salt = hash_password_salt(encoded)
return constant_time_compare(
b64encode(hashlib.md5(password.encode() + salt).digest() + salt),
encoded.encode())
def safe_summary(self, encoded):
"""
Provides a safe summary of the password
"""
assert encoded.startswith(self.algorithm)
hash_str = encoded[7:]
hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode()
return OrderedDict([
('algorithm', self.algorithm),
('iterations', 0),
('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)),
('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])),
])
def harden_runtime(self, password, encoded):
"""
Method implemented to shut up BasePasswordHasher warning
As we are not using multiple iterations the method is pretty useless
"""
pass
class SSHAPasswordHasher(hashers.BasePasswordHasher):
"""
SSHA password hashing to allow for LDAP auth compatibility
Salted SHA-1 password hashing to allow for LDAP auth compatibility
"""
algorithm = ALGO_NAME
......
......@@ -46,6 +46,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Auth definition
PASSWORD_HASHERS = (
're2o.login.SSHAPasswordHasher',
're2o.login.MD5PasswordHasher',
're2o.login.CryptPasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
)
AUTH_USER_MODEL = 'users.User' # The class to use for authentication
......
......@@ -537,7 +537,16 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
user_ldap.given_name = self.surname.lower() + '_'\
+ self.name.lower()[:3]
user_ldap.gid = LDAP['user_gid']
user_ldap.user_password = self.password[:6] + self.password[7:]
if '{SSHA}' in self.password or '{SMD5}' in self.password:
# We remove the extra $ added at import from ldap
user_ldap.user_password = self.password[:6] + self.password[7:]
elif '{crypt}' in self.password:
# depending on the length, we need to remove or not a $
if len(self.password)==41:
user_ldap.user_password = self.password
else:
user_ldap.user_password = self.password[:7] + self.password[8:]
user_ldap.sambat_nt_password = self.pwd_ntlm.upper()
if self.get_shell:
user_ldap.login_shell = str(self.get_shell)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment