8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 15:33:45 +00:00

Merge branch 'support_old_hash' into 'dev'

Support old hashes, md5/crypt

See merge request federez/re2o!215
This commit is contained in:
chirac 2018-08-05 12:14:59 +02:00
commit cf7be2b21f
3 changed files with 133 additions and 12 deletions

View file

@ -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

View file

@ -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

View file

@ -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)