mirror of
https://github.com/ansible/ansible.git
synced 2026-07-28 08:05:22 +02:00
encrypt: fix bcrypt salt string formatting (#87184)
* fix bcrypt salt string formatting on musl libc by ensuring it is always zero-padded to 2 digits Fixes: #87180 Signed-off-by: Abhijeet Kasurde <Akasurde@redhat.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
bugfixes:
|
||||
- encrypt - fix bcrypt salt string formatting on musl libc by ensuring it is always zero-padded to 2 digits (https://github.com/ansible/ansible/issues/87180).
|
||||
@@ -12,7 +12,7 @@ from dataclasses import dataclass
|
||||
|
||||
__all__ = ['CryptFacade']
|
||||
|
||||
_FAILURE_TOKENS = frozenset({b'*0', b'*1'})
|
||||
_FAILURE_TOKENS = frozenset({b'*', b'*0', b'*1'})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -93,10 +93,9 @@ class CryptFacade:
|
||||
|
||||
if self._use_crypt_r:
|
||||
self._crypt_impl.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(self._CryptData)]
|
||||
self._crypt_impl.restype = ctypes.c_char_p
|
||||
else:
|
||||
self._crypt_impl.argtypes = [ctypes.c_char_p, ctypes.c_char_p]
|
||||
self._crypt_impl.restype = ctypes.c_char_p
|
||||
self._crypt_impl.restype = ctypes.c_char_p
|
||||
|
||||
# Try to load crypt_gensalt (available in libxcrypt)
|
||||
try:
|
||||
@@ -133,10 +132,7 @@ class CryptFacade:
|
||||
error_msg = os.strerror(errno)
|
||||
raise OSError(errno, f'crypt failed: {error_msg}')
|
||||
|
||||
if result is None:
|
||||
raise ValueError('crypt failed: invalid salt or unsupported algorithm')
|
||||
|
||||
if result in _FAILURE_TOKENS:
|
||||
if result is None or result in _FAILURE_TOKENS:
|
||||
raise ValueError('crypt failed: invalid salt or unsupported algorithm')
|
||||
|
||||
return result
|
||||
|
||||
@@ -181,7 +181,8 @@ class CryptHash(BaseHash):
|
||||
saltstring = f'${ident}' if ident else ''
|
||||
if rounds:
|
||||
if self.algo_data.rounds_format == 'cost':
|
||||
saltstring += f'${rounds}'
|
||||
# musl libc requires a 2-digit bcrypt cost ($2b$05$, not $2b$5$).
|
||||
saltstring += f'${rounds:02d}'
|
||||
else:
|
||||
saltstring += f'$rounds={rounds}'
|
||||
saltstring += f'${salt}'
|
||||
|
||||
@@ -82,7 +82,7 @@ class TestCryptFacade:
|
||||
|
||||
def test_crypt_fail_errno(self, mocker: MockerFixture) -> None:
|
||||
"""Test crypt() setting failure errno raises OSError."""
|
||||
mocker.patch('ctypes.get_errno', return_value=errno.EBADFD)
|
||||
mocker.patch('ctypes.get_errno', return_value=errno.EBADF)
|
||||
crypt_facade = CryptFacade()
|
||||
|
||||
with pytest.raises(OSError, match=r'crypt failed:'):
|
||||
@@ -96,10 +96,11 @@ class TestCryptFacade:
|
||||
with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'):
|
||||
crypt_facade.crypt(b"test", b"123")
|
||||
|
||||
def test_crypt_result_failure(self, mocker: MockerFixture) -> None:
|
||||
@pytest.mark.parametrize('failure_token', sorted(list(_FAILURE_TOKENS)))
|
||||
def test_crypt_result_failure(self, mocker: MockerFixture, failure_token: bytes) -> None:
|
||||
"""Test crypt() implementation returning failure token raises ValueError."""
|
||||
crypt_facade = CryptFacade()
|
||||
mocker.patch.object(crypt_facade, '_crypt_impl', return_value=list(_FAILURE_TOKENS)[0])
|
||||
mocker.patch.object(crypt_facade, '_crypt_impl', return_value=failure_token)
|
||||
|
||||
with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'):
|
||||
crypt_facade.crypt(b"test", b"123")
|
||||
@@ -124,7 +125,7 @@ class TestCryptFacade:
|
||||
|
||||
def test_crypt_gensalt_fail_errno(self, mocker: MockerFixture) -> None:
|
||||
"""Test crypt_gensalt() setting failure errno raises OSError."""
|
||||
mocker.patch('ctypes.get_errno', return_value=errno.EBADFD)
|
||||
mocker.patch('ctypes.get_errno', return_value=errno.EBADF)
|
||||
crypt_facade = CryptFacade()
|
||||
|
||||
with pytest.raises(OSError, match=r'crypt_gensalt failed:'):
|
||||
|
||||
@@ -288,6 +288,16 @@ class TestCryptHash:
|
||||
with pytest.raises(AnsibleError, match=r"crypt does not support 'sha256_crypt' algorithm"):
|
||||
crypt_hash.hash("123", salt="12345678")
|
||||
|
||||
def test_build_saltstring_bcrypt_cost_padding(self, mocker: MockerFixture) -> None:
|
||||
"""musl libc requires a 2-digit bcrypt cost in the salt string."""
|
||||
mocker.patch('ansible.utils.encrypt.HAS_CRYPT', True)
|
||||
mocker.patch('ansible._internal._encryption._crypt.CryptFacade.has_crypt_gensalt', False)
|
||||
|
||||
crypt_hash = encrypt.CryptHash("bcrypt")
|
||||
saltstring = crypt_hash._build_saltstring('2b', 5, '1234567890123456789012', None)
|
||||
|
||||
assert saltstring == '$2b$05$1234567890123456789012'
|
||||
|
||||
|
||||
class TestPasslibHash:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user