From 02583542c07ca89843ab1338caa39112d4cd127b Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Mon, 23 Sep 2024 13:41:09 +0200 Subject: [PATCH] Switch to crypt from passlib Next step in getting rid of `passlib` is sha512_crypt. This one is tricky since the `crypt` lib is deprecated in py311 and dropped in py313 with the only replacement by `passlib`. But since passlib itself is abandoned it literally means there is nothing we could use. On the other hand `sha512_crypt` was "deprecated" already in pike and it is not possible to enable it in Keystone while old passwords are still supported. In this unlucky situation still get rid of passlib and support for this algorightm is going to be completely dropped in next Keystone release. Change-Id: If791c953bc2953f2c78ff91e4fc4a342b89d6d1b --- .../common/password_hashers/sha512_crypt.py | 39 +++++++++++++++++++ keystone/common/password_hashing.py | 3 +- .../unit/common/test_password_hashing.py | 20 ++++++++++ ...12_crypt_deprecation-91a19080f1e884e4.yaml | 7 ++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 keystone/common/password_hashers/sha512_crypt.py create mode 100644 releasenotes/notes/sha512_crypt_deprecation-91a19080f1e884e4.yaml diff --git a/keystone/common/password_hashers/sha512_crypt.py b/keystone/common/password_hashers/sha512_crypt.py new file mode 100644 index 0000000000..36cf602ad8 --- /dev/null +++ b/keystone/common/password_hashers/sha512_crypt.py @@ -0,0 +1,39 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import crypt + +from keystone.common import password_hashers + + +class Sha512_crypt(password_hashers.PasswordHasher): + """passlib transition class for sha512_crypt password hashing""" + + name: str = "sha512_crypt" + ident: str = "$6$" + + @staticmethod + def verify(password: bytes, hashed: str) -> bool: + """Verify hashing password would be equal to the `hashed` value + + :param bytes password: Password to verify + :param string hashed: Hashed password. Used to extract hashing + parameters + + :returns: boolean whether hashing password with the same parameters + would match hashed value + """ + + return ( + crypt.crypt(password.decode("utf8"), hashed[0 : hashed.rfind("$")]) + == hashed + ) diff --git a/keystone/common/password_hashing.py b/keystone/common/password_hashing.py index 0531f83259..b21a01ec7f 100644 --- a/keystone/common/password_hashing.py +++ b/keystone/common/password_hashing.py @@ -20,6 +20,7 @@ import passlib.hash from keystone.common.password_hashers import bcrypt from keystone.common.password_hashers import scrypt +from keystone.common.password_hashers import sha512_crypt import keystone.conf from keystone import exception from keystone.i18n import _ @@ -30,10 +31,10 @@ LOG = log.getLogger(__name__) SUPPORTED_HASHERS = frozenset( [ passlib.hash.pbkdf2_sha512, - passlib.hash.sha512_crypt, scrypt.Scrypt, bcrypt.Bcrypt, bcrypt.Bcrypt_sha256, + sha512_crypt.Sha512_crypt, ] ) diff --git a/keystone/tests/unit/common/test_password_hashing.py b/keystone/tests/unit/common/test_password_hashing.py index 137162ae60..677a495590 100644 --- a/keystone/tests/unit/common/test_password_hashing.py +++ b/keystone/tests/unit/common/test_password_hashing.py @@ -134,3 +134,23 @@ class TestPasswordHashing(unit.BaseTestCase): self.assertTrue( password_hashing.check_password(password, hashed_passlib) ) + + def test_sha512_crypt_passlib_compat(self): + self.config_fixture.config(strict_password_check=True) + # sha512_crypt is deprecated and is not supported to be set. + # We want to ensure that user is still able to login, thus + # set algo to whatever and go verify pwd + self.config_fixture.config( + group="identity", password_hash_algorithm="bcrypt" + ) + self.config_fixture.config(group="identity", max_password_length="72") + # few iterations to test multiple random values + for _ in range(self.ITERATIONS): + password: str = "".join( # type: ignore + secrets.choice(string.printable) + for i in range(random.randint(1, 72)) + ) + hashed_passlib = passlib.hash.sha512_crypt.hash(password) + self.assertTrue( + password_hashing.check_password(password, hashed_passlib) + ) diff --git a/releasenotes/notes/sha512_crypt_deprecation-91a19080f1e884e4.yaml b/releasenotes/notes/sha512_crypt_deprecation-91a19080f1e884e4.yaml new file mode 100644 index 0000000000..f4bd314a48 --- /dev/null +++ b/releasenotes/notes/sha512_crypt_deprecation-91a19080f1e884e4.yaml @@ -0,0 +1,7 @@ +--- +deprecations: + - | + This is the last release where passwords hashed using sha512_crypt + algorithm are supported. Since even support of that is being dropped in + python 3.13 it would be physically dropped from Keystone in the next + release (`Epoxy`).