fix(ldap): replace SHA512 user passwords with SSHA
This commit is contained in:
parent
5d936900be
commit
d8670fc558
@ -50,7 +50,7 @@ If you point Lumi2 at an existing LDAP server, make sure its DIT matches the str
|
||||
- `displayName` - preferred name (or nickname)
|
||||
- `mail` - email address
|
||||
- `jpegPhoto` - profile picture in JPEG format
|
||||
- `userPassword` - SHA512 password hash
|
||||
- `userPassword` - SSHA password hash
|
||||
|
||||
The `uid` (username) can contain latin characters, digits, underscores, hypens and periods, and must have a letter as the first character.
|
||||
|
||||
|
@ -623,9 +623,9 @@ def get_user(connection: Connection, uid: str) -> User:
|
||||
last_name = attributes['sn'][0]
|
||||
display_name = attributes['displayName'][0]
|
||||
|
||||
# Retrieve base64-encoded password hash prefixed with '{SHA512}'
|
||||
# Retrieve base64-encoded password hash prefixed with '{SSHA}'
|
||||
password_hash = attributes['userPassword'][0]
|
||||
expected_hash_type = '{SHA512}'
|
||||
expected_hash_type = '{SSHA}'
|
||||
if not password_hash.startswith(expected_hash_type):
|
||||
raise InvalidAttributeException(
|
||||
f"Unexpected password hash in entry '{user_dn}': expected " \
|
||||
@ -676,7 +676,7 @@ def create_user(connection: Connection, user: User) -> None:
|
||||
|
||||
attributes = {
|
||||
"uid": user.username,
|
||||
"userPassword": "{SHA512}" + user.password_hash,
|
||||
"userPassword": "{SSHA}" + user.password_hash,
|
||||
"cn": user.first_name,
|
||||
"sn": user.last_name,
|
||||
"displayName": user.display_name,
|
||||
@ -723,7 +723,7 @@ def update_user(connection: Connection, user: User) -> None:
|
||||
user.picture.save(new_picture_bytes, format="jpeg")
|
||||
|
||||
new_attributes = {
|
||||
"userPassword": [(MODIFY_REPLACE, ["{SHA512}" + user.password_hash])],
|
||||
"userPassword": [(MODIFY_REPLACE, ["{SSHA}" + user.password_hash])],
|
||||
"mail": [(MODIFY_REPLACE, [user.email])],
|
||||
"cn": [(MODIFY_REPLACE, [user.first_name])],
|
||||
"sn": [(MODIFY_REPLACE, [user.last_name])],
|
||||
|
@ -2,9 +2,10 @@
|
||||
|
||||
from string import ascii_lowercase, ascii_uppercase, digits, whitespace
|
||||
from base64 import b64encode, b64decode
|
||||
import hashlib
|
||||
from hashlib import sha1
|
||||
from binascii import Error as Base64DecodeError
|
||||
from pathlib import Path
|
||||
from os import urandom
|
||||
|
||||
from PIL import Image
|
||||
from PIL.JpegImagePlugin import JpegImageFile
|
||||
@ -21,7 +22,7 @@ class User:
|
||||
username : str
|
||||
The user's username.
|
||||
password_hash : str
|
||||
Base64-encoded SHA512 hash of the user's password.
|
||||
Base64-encoded SSHA hash of the user's password.
|
||||
email : str
|
||||
The user's email address.
|
||||
first_name : str
|
||||
@ -268,7 +269,9 @@ class User:
|
||||
|
||||
@staticmethod
|
||||
def generate_password_hash(password: str) -> str:
|
||||
"""Generates a base64-encoded SHA512 hash of the input string.
|
||||
"""Generates a base64-encoded SSHA hash of the input string.
|
||||
|
||||
The 4-byte salt is appended to the digest prior to base64-encoding.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@ -278,7 +281,7 @@ class User:
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
A base64-encoded SHA512 hash digest of the input string.
|
||||
A base64-encoded SSHA hash digest of the input string.
|
||||
|
||||
Raises
|
||||
------
|
||||
@ -293,9 +296,11 @@ class User:
|
||||
if not len(password):
|
||||
raise ValueError("Input string cannot be empty.")
|
||||
|
||||
hash_bytes = hashlib.sha512()
|
||||
salt = urandom(4)
|
||||
hash_bytes = sha1()
|
||||
hash_bytes.update(bytes(password, "UTF-8"))
|
||||
return b64encode(hash_bytes.digest()).decode("ASCII")
|
||||
hash_bytes.update(salt)
|
||||
return b64encode(hash_bytes.digest() + salt).decode("ASCII")
|
||||
|
||||
|
||||
@staticmethod
|
||||
@ -325,8 +330,8 @@ class User:
|
||||
username : str
|
||||
The username, valid as described by User.assert_is_valid_username().
|
||||
password_hash : str
|
||||
The user's base64-encoded SHA512-hashed password (without the
|
||||
'{SHA512}'-prefix expected by LDAP).
|
||||
The user's base64-encoded SSHA-hashed password (without the
|
||||
'{SSHA}'-prefix expected by LDAP).
|
||||
Must be valid as described by User.assert_is_valid_password_hash().
|
||||
email : str
|
||||
The User's email address.
|
||||
@ -438,6 +443,42 @@ class User:
|
||||
return groups
|
||||
|
||||
|
||||
def check_password(self, password_plaintext: str) -> bool:
|
||||
"""Checks the input plaintext password against this User's password hash.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
password_plaintext : str
|
||||
The plaintext password to check against this User's salted password
|
||||
hash.
|
||||
|
||||
Returns
|
||||
-------
|
||||
True : bool
|
||||
If the input password_plaintext is this User's password.
|
||||
False : bool
|
||||
Otherwise.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If password_plaintext is not of type string.
|
||||
"""
|
||||
|
||||
if not isinstance(password_plaintext, str):
|
||||
raise TypeError(f"Expected a string but got: '{type(password_plaintext)}'.")
|
||||
|
||||
password_hash_bytes = b64decode(self.password_hash)
|
||||
digest_bytes = password_hash_bytes[:20]
|
||||
salt = password_hash_bytes[20:]
|
||||
|
||||
validation_hash = sha1()
|
||||
validation_hash.update(bytes(password_plaintext, "UTF-8"))
|
||||
validation_hash.update(salt)
|
||||
|
||||
return validation_hash.digest() == digest_bytes
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.username == other.username
|
||||
|
||||
|
@ -98,7 +98,7 @@ def connection():
|
||||
- displayName (nickname)
|
||||
- mail (email)
|
||||
- jpegPhoto (profile picture)
|
||||
- password (sha512 hash of password 'test')
|
||||
- password (SSHA hash of password 'test')
|
||||
|
||||
Both groups are of type 'groupOfUniqueNames'. Alice is a member of both
|
||||
groups. Bob is a member of 'employees'.
|
||||
|
@ -95,7 +95,7 @@
|
||||
"alice"
|
||||
],
|
||||
"userPassword": [
|
||||
"{SHA512}7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w=="
|
||||
"{SSHA}LitOJVWDVqqglNuHS0o1ypQjyd4TUP4K"
|
||||
]
|
||||
},
|
||||
"dn": "uid=alice,ou=users,dc=example,dc=com",
|
||||
@ -126,7 +126,7 @@
|
||||
"alice"
|
||||
],
|
||||
"userPassword": [
|
||||
"{SHA512}7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w=="
|
||||
"{SSHA}LitOJVWDVqqglNuHS0o1ypQjyd4TUP4K"
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -156,7 +156,7 @@
|
||||
"bobbuilder"
|
||||
],
|
||||
"userPassword": [
|
||||
"{SHA512}7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w=="
|
||||
"{SSHA}LitOJVWDVqqglNuHS0o1ypQjyd4TUP4K"
|
||||
]
|
||||
},
|
||||
"dn": "uid=bobbuilder,ou=users,dc=example,dc=com",
|
||||
@ -187,7 +187,7 @@
|
||||
"bobbuilder"
|
||||
],
|
||||
"userPassword": [
|
||||
"{SHA512}7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w=="
|
||||
"{SSHA}LitOJVWDVqqglNuHS0o1ypQjyd4TUP4K"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user