mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-23 11:53:12 +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:
commit
cf7be2b21f
3 changed files with 133 additions and 12 deletions
132
re2o/login.py
132
re2o/login.py
|
@ -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']
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue