diff --git a/salt/utils/rsax931.py b/salt/utils/rsax931.py index e3c68f8c8823..c60ef6ba0ed3 100644 --- a/salt/utils/rsax931.py +++ b/salt/utils/rsax931.py @@ -6,13 +6,13 @@ # Import Python libs from __future__ import absolute_import, print_function, unicode_literals +import ctypes.util import glob import os import sys # Import 3rd-party libs from ctypes import c_char_p, c_int, c_void_p, cdll, create_string_buffer, pointer -from ctypes.util import find_library # Import Salt libs import salt.utils.platform @@ -24,43 +24,57 @@ OPENSSL_INIT_NO_LOAD_CONFIG = 0x00000080 -def _load_libcrypto(): +def _find_libcrypto(): """ - Load OpenSSL libcrypto + Find the path (or return the short name) of libcrypto. """ if sys.platform.startswith("win"): - # cdll.LoadLibrary on windows requires an 'str' argument - return cdll.LoadLibrary( - str("libeay32") - ) # future lint: disable=blacklisted-function + lib = str("libeay32") elif getattr(sys, "frozen", False) and salt.utils.platform.is_smartos(): - return cdll.LoadLibrary( - glob.glob(os.path.join(os.path.dirname(sys.executable), "libcrypto.so*"))[0] - ) + lib = glob.glob(os.path.join(os.path.dirname(sys.executable), "libcrypto.so*")) + lib = lib[0] if lib else None else: - lib = find_library("crypto") - if not lib and salt.utils.platform.is_sunos(): - # Solaris-like distribution that use pkgsrc have - # libraries in a non standard location. - # (SmartOS, OmniOS, OpenIndiana, ...) - # This could be /opt/tools/lib (Global Zone) - # or /opt/local/lib (non-Global Zone), thus the - # two checks below - lib = glob.glob("/opt/local/lib/libcrypto.so*") + glob.glob( - "/opt/tools/lib/libcrypto.so*" - ) - lib = lib[0] if len(lib) > 0 else None - if not lib and salt.utils.platform.is_aix(): - if os.path.isdir("/opt/salt/lib"): - # preference for Salt installed fileset - lib = glob.glob("/opt/salt/lib/libcrypto.so*") - lib = lib[0] if len(lib) > 0 else None + lib = ctypes.util.find_library("crypto") + if not lib: + if salt.utils.platform.is_sunos(): + # Solaris-like distribution that use pkgsrc have libraries + # in a non standard location. + # (SmartOS, OmniOS, OpenIndiana, ...) + # This could be /opt/tools/lib (Global Zone) or + # /opt/local/lib (non-Global Zone), thus the two checks + # below + lib = glob.glob("/opt/local/lib/libcrypto.so*") + lib = lib or glob.glob("/opt/tools/lib/libcrypto.so*") + lib = lib[0] if lib else None + elif salt.utils.platform.is_aix(): + if os.path.isdir("/opt/salt/lib"): + # preference for Salt installed fileset + lib = glob.glob("/opt/salt/lib/libcrypto.so*") + else: + lib = glob.glob("/opt/freeware/lib/libcrypto.so*") + lib = lib[0] if lib else None + elif salt.utils.platform.is_darwin(): + # Find versioned libraries in system locations, being careful + # to avoid the unversioned stub which is no longer permitted. + lib = glob.glob("/usr/lib/libcrypto.*.dylib") + if lib: + # Sort so as to prefer the newest version. + lib = list(reversed(sorted(lib))) else: - lib = glob.glob("/opt/freeware/lib/libcrypto.so*") - lib = lib[0] if len(lib) > 0 else None - if lib: - return cdll.LoadLibrary(lib) + # Find library symlinks in Homebrew locations. + lib = glob.glob("/usr/local/opt/openssl/lib/libcrypto.dylib") + lib = lib or glob.glob("/usr/local/opt/openssl@*/lib/libcrypto.dylib") + lib = lib[0] if lib else None + if not lib: raise OSError("Cannot locate OpenSSL libcrypto") + return lib + + +def _load_libcrypto(): + """ + Attempt to load libcrypto. + """ + return cdll.LoadLibrary(_find_libcrypto()) def _init_libcrypto(): diff --git a/tests/unit/utils/test_rsax931.py b/tests/unit/utils/test_rsax931.py index 6b840370e16f..85d5e4e32f7d 100644 --- a/tests/unit/utils/test_rsax931.py +++ b/tests/unit/utils/test_rsax931.py @@ -6,11 +6,26 @@ # python libs from __future__ import absolute_import, print_function, unicode_literals +import ctypes +import ctypes.util +import fnmatch +import glob +import os +import sys + +import salt.utils.platform + # salt libs -from salt.utils.rsax931 import RSAX931Signer, RSAX931Verifier +from salt.utils.rsax931 import ( + RSAX931Signer, + RSAX931Verifier, + _find_libcrypto, + _load_libcrypto, +) +from tests.support.mock import patch # salt testing libs -from tests.support.unit import TestCase +from tests.support.unit import TestCase, skipIf class RSAX931Test(TestCase): @@ -108,3 +123,92 @@ def test_verifier(self): msg = verifier.verify(RSAX931Test.hello_world_sig) self.assertEqual(RSAX931Test.hello_world, msg) + + @skipIf(not salt.utils.platform.is_windows(), "Host OS is not Windows.") + def test_find_libcrypto_win32(self): + """ + Test _find_libcrypto on Windows hosts. + """ + lib_path = _find_libcrypto() + self.assertEqual(lib_path, "libeay32") + + @skipIf( + not getattr(sys, "frozen", False) and not salt.utils.platform.is_smartos(), + "Host OS is not SmartOS.", + ) + def test_find_libcrypto_smartos(self): + """ + Test _find_libcrypto on a SmartOS host. + """ + lib_path = _find_libcrypto() + self.assertTrue( + fnmatch.fnmatch( + lib_path, os.path.join(os.path.dirname(sys.executable), "libcrypto*") + ) + ) + + @skipIf(not salt.utils.platform.is_sunos(), "Host OS is not Solaris-like.") + def test_find_libcrypto_sunos(self): + """ + Test _find_libcrypto on a Solaris-like host. + """ + lib_path = _find_libcrypto() + passed = False + for i in ("/opt/local/lib/libcrypto.so*", "/opt/tools/lib/libcrypto.so*"): + if fnmatch.fnmatch(lib_path, i): + passed = True + break + self.assertTrue(passed) + + @skipIf(not salt.utils.platform.is_aix(), "Host OS is not IBM AIX.") + def test_find_libcrypto_aix(self): + """ + Test _find_libcrypto on an IBM AIX host. + """ + lib_path = _find_libcrypto() + if os.path.isdir("/opt/salt/lib"): + self.assertTrue(fnmatch.fnmatch(lib_path, "/opt/salt/lib/libcrypto.so*")) + else: + self.assertTrue( + fnmatch.fnmatch(lib_path, "/opt/freeware/lib/libcrypto.so*") + ) + + @skipIf(not salt.utils.platform.is_darwin(), "Host OS is not Darwin-like or macOS.") + def test_find_libcrypto_darwin(self): + """ + Test _find_libcrypto on a Darwin-like or macOS host. + """ + lib_path = _find_libcrypto() + passed = False + for i in ( + "/usr/lib/libcrypto.*.dylib", + "/usr/local/opt/openssl/lib/libcrypto.dylib", + "/usr/local/opt/openssl@*/lib/libcrypto.dylib", + ): + if fnmatch.fnmatch(lib_path, i): + passed = True + break + self.assertTrue(passed) + + @patch.object(ctypes.util, "find_library", lambda a: None) + @patch.object(glob, "glob", lambda a: []) + @patch.object(sys, "platform", "unknown") + def test_find_libcrypto_unsupported(self): + """ + Ensure that _find_libcrypto works correctly on an unsupported host OS. + """ + with self.assertRaises(OSError): + _find_libcrypto() + + def test_load_libcrypto(self): + """ + Test _load_libcrypto generically. + """ + lib = _load_libcrypto() + self.assertTrue(isinstance(lib, ctypes.CDLL)) + # Try to cover both pre and post OpenSSL 1.1. + self.assertTrue( + hasattr(lib, "OpenSSL_version_num") + or hasattr(lib, "OPENSSL_init_crypto") + or hasattr(lib, "OPENSSL_no_config") + )