mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-27 07:02:26 +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
|
Module in charge of handling the login process and verifications
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import binascii
|
import binascii
|
||||||
|
import crypt
|
||||||
|
import hashlib
|
||||||
import os
|
import os
|
||||||
from base64 import encodestring
|
from base64 import encodestring, decodestring, b64encode, b64decode
|
||||||
from base64 import decodestring
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.contrib.auth import hashers
|
from django.contrib.auth import hashers
|
||||||
|
from hmac import compare_digest as constant_time_compare
|
||||||
|
|
||||||
|
|
||||||
ALGO_NAME = "{SSHA}"
|
ALGO_NAME = "{SSHA}"
|
||||||
|
@ -64,17 +64,127 @@ def checkPassword(challenge_password, password):
|
||||||
salt = challenge_bytes[DIGEST_LEN:]
|
salt = challenge_bytes[DIGEST_LEN:]
|
||||||
hr = hashlib.sha1(password.encode())
|
hr = hashlib.sha1(password.encode())
|
||||||
hr.update(salt)
|
hr.update(salt)
|
||||||
valid_password = True
|
return constant_time_compare(digest, hr.digest())
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
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
|
algorithm = ALGO_NAME
|
||||||
|
|
|
@ -46,6 +46,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
# Auth definition
|
# Auth definition
|
||||||
PASSWORD_HASHERS = (
|
PASSWORD_HASHERS = (
|
||||||
're2o.login.SSHAPasswordHasher',
|
're2o.login.SSHAPasswordHasher',
|
||||||
|
're2o.login.MD5PasswordHasher',
|
||||||
|
're2o.login.CryptPasswordHasher',
|
||||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||||
)
|
)
|
||||||
AUTH_USER_MODEL = 'users.User' # The class to use for authentication
|
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() + '_'\
|
user_ldap.given_name = self.surname.lower() + '_'\
|
||||||
+ self.name.lower()[:3]
|
+ self.name.lower()[:3]
|
||||||
user_ldap.gid = LDAP['user_gid']
|
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:]
|
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()
|
user_ldap.sambat_nt_password = self.pwd_ntlm.upper()
|
||||||
if self.get_shell:
|
if self.get_shell:
|
||||||
user_ldap.login_shell = str(self.get_shell)
|
user_ldap.login_shell = str(self.get_shell)
|
||||||
|
|
Loading…
Reference in a new issue