Skip to content

Commit 6905b66

Browse files
authored
Build OpenSSL with no-uplink for Python 3.12+ (#1132)
Following the behavior of CPython which dropped uplink support in 3.12, build OpenSSL with the `no-uplink` option starting with CPython 3.12. This allows a SSLContext to be created even when a SSLKEYLOGFILE environment variable is set on Windows. closes #640
1 parent 96a589a commit 6905b66

2 files changed

Lines changed: 62 additions & 28 deletions

File tree

cpython-windows/build.py

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -674,17 +674,17 @@ def hack_project_files(
674674
pass
675675

676676
# Our custom OpenSSL build has applink.c in a different location from the
677-
# binary OpenSSL distribution. This is no longer relevant for 3.12+ per
678-
# https://github.com/python/cpython/pull/131839, so we allow it to fail.swe
679-
try:
677+
# binary OpenSSL distribution.
678+
# Starting with 3.12 CPython on Windows is built without uplink support
679+
# so this modification is not needed.
680+
# https://github.com/python/cpython/pull/131839
681+
if meets_python_maximum_version(python_version, "3.11"):
680682
ssl_proj = pcbuild_path / "_ssl.vcxproj"
681683
static_replace_in_file(
682684
ssl_proj,
683685
rb'<ClCompile Include="$(opensslIncludeDir)\applink.c">',
684686
rb'<ClCompile Include="$(opensslIncludeDir)\openssl\applink.c">',
685687
)
686-
except NoSearchStringError:
687-
pass
688688

689689
# Python 3.12+ uses the the pre-built tk-windows-bin 8.6.12 which doesn't
690690
# have a standalone zlib DLL, so we remove references to it. For Python
@@ -791,6 +791,7 @@ def build_openssl_for_arch(
791791
build_root: pathlib.Path,
792792
*,
793793
jom_archive,
794+
with_uplink: bool = False,
794795
):
795796
nasm_version = DOWNLOADS["nasm-windows-bin"]["version"]
796797

@@ -810,14 +811,15 @@ def build_openssl_for_arch(
810811

811812
source_root = build_root / ("openssl-%s" % openssl_version)
812813

813-
# uplink.c tries to find the OPENSSL_Applink function exported from the current
814-
# executable. However, it is exported from _ssl[_d].pyd in shared builds. So
815-
# update its sounce to look for it from there.
816-
static_replace_in_file(
817-
source_root / "ms" / "uplink.c",
818-
b"((h = GetModuleHandle(NULL)) == NULL)",
819-
b'((h = GetModuleHandleA("_ssl.pyd")) == NULL) if ((h = GetModuleHandleA("_ssl_d.pyd")) == NULL) if ((h = GetModuleHandle(NULL)) == NULL)',
820-
)
814+
if with_uplink:
815+
# uplink.c tries to find the OPENSSL_Applink function exported from the
816+
# current executable. However, it is exported from _ssl[_d].pyd in shared
817+
# builds. So update its source to look for it from there.
818+
static_replace_in_file(
819+
source_root / "ms" / "uplink.c",
820+
b"((h = GetModuleHandle(NULL)) == NULL)",
821+
b'((h = GetModuleHandleA("_ssl.pyd")) == NULL) if ((h = GetModuleHandleA("_ssl_d.pyd")) == NULL) if ((h = GetModuleHandle(NULL)) == NULL)',
822+
)
821823

822824
if arch == "x86":
823825
configure = "VC-WIN32"
@@ -831,26 +833,27 @@ def build_openssl_for_arch(
831833
else:
832834
raise Exception("unhandled architecture: %s" % arch)
833835

834-
# The official CPython OpenSSL builds hack ms/uplink.c to change the
835-
# ``GetModuleHandle(NULL)`` invocation to load things from _ssl.pyd
836-
# instead. But since we statically link the _ssl extension, this hackery
837-
# is not required.
838-
839836
# Set DESTDIR to affect install location.
840837
dest_dir = build_root / "install"
841838
env["DESTDIR"] = str(dest_dir)
842839
install_root = dest_dir / prefix
843840

841+
configure_args = [
842+
str(perl_path),
843+
"Configure",
844+
configure,
845+
"no-idea",
846+
"no-mdc2",
847+
"no-tests",
848+
"--prefix=/%s" % prefix,
849+
]
850+
if with_uplink:
851+
log("building OpenSSL with uplink support for Python <3.12")
852+
else:
853+
configure_args.append("no-uplink")
854+
844855
exec_and_log(
845-
[
846-
str(perl_path),
847-
"Configure",
848-
configure,
849-
"no-idea",
850-
"no-mdc2",
851-
"no-tests",
852-
"--prefix=/%s" % prefix,
853-
],
856+
configure_args,
854857
source_root,
855858
{
856859
**env,
@@ -889,6 +892,7 @@ def build_openssl(
889892
perl_path: pathlib.Path,
890893
arch: str,
891894
dest_archive: pathlib.Path,
895+
with_uplink: bool = False,
892896
):
893897
"""Build OpenSSL from sources using the Perl executable specified."""
894898

@@ -916,6 +920,7 @@ def build_openssl(
916920
nasm_archive,
917921
root_32,
918922
jom_archive=jom_archive,
923+
with_uplink=with_uplink,
919924
)
920925
elif arch == "amd64":
921926
root_64.mkdir()
@@ -927,6 +932,7 @@ def build_openssl(
927932
nasm_archive,
928933
root_64,
929934
jom_archive=jom_archive,
935+
with_uplink=with_uplink,
930936
)
931937
elif arch == "arm64":
932938
root_arm64.mkdir()
@@ -938,6 +944,7 @@ def build_openssl(
938944
nasm_archive,
939945
root_arm64,
940946
jom_archive=jom_archive,
947+
with_uplink=with_uplink,
941948
)
942949
else:
943950
raise Exception("unhandled architecture: %s" % arch)
@@ -1987,8 +1994,14 @@ def main() -> None:
19871994
else:
19881995
openssl_entry = "openssl-3.5"
19891996

1997+
openssl_with_uplink = args.python in ["cpython-3.10", "cpython-3.11"]
1998+
if openssl_with_uplink:
1999+
openssl_build_options = f"{build_options}-uplink"
2000+
else:
2001+
openssl_build_options = f"{build_options}-no-uplink"
2002+
19902003
openssl_archive = BUILD / (
1991-
"%s-%s-%s.tar" % (openssl_entry, target_triple, build_options)
2004+
"%s-%s-%s.tar" % (openssl_entry, target_triple, openssl_build_options)
19922005
)
19932006
if not openssl_archive.exists():
19942007
perl_path = fetch_strawberry_perl() / "perl" / "bin" / "perl.exe"
@@ -1998,6 +2011,7 @@ def main() -> None:
19982011
perl_path,
19992012
arch,
20002013
dest_archive=openssl_archive,
2014+
with_uplink=openssl_with_uplink,
20012015
)
20022016

20032017
libffi_archive = BUILD / ("libffi-%s-%s.tar" % (target_triple, build_options))

pythonbuild/disttests/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,26 @@ def test_ssl(self):
217217

218218
ssl.create_default_context()
219219

220+
@unittest.skipIf(os.name != "nt", "Windows-specific OpenSSL uplink regression")
221+
def test_ssl_with_keylogfile(self):
222+
# Validate that a SSLContext can be created when SSLKEYLOGFILE is set
223+
# https://github.com/astral-sh/python-build-standalone/issues/640
224+
import ssl
225+
226+
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td:
227+
keylog_path = str(Path(td) / "sslkeylog.log")
228+
original_keylog_path = os.environ.get("SSLKEYLOGFILE")
229+
os.environ["SSLKEYLOGFILE"] = keylog_path
230+
try:
231+
context = ssl.create_default_context()
232+
self.assertEqual(context.keylog_filename, keylog_path)
233+
del context
234+
finally:
235+
if original_keylog_path is None:
236+
os.environ.pop("SSLKEYLOGFILE", None)
237+
else:
238+
os.environ["SSLKEYLOGFILE"] = original_keylog_path
239+
220240
@unittest.skipIf(
221241
sys.version_info[:2] < (3, 13),
222242
"Free-threaded builds are only available in 3.13+",

0 commit comments

Comments
 (0)