Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5d3febf

Browse files
committed
Issue #17128: Use private version of OpenSSL for 3.x OS X 10.5+ installers.
Among other issues, the Apple-supplied 0.9.7 libs for the 10.5 ABI cannot verify newer SHA-256 certs as now used by python.org services. Document in the installer ReadMe some of the certificate management issues that users now need to be more concerned with due to PEP 476's enabling cert verification by default. For now, continue to use the Apple-supplied 0.9.8 libs for the 10.6+ installer since they use Apple private APIs to verify certificates using the system- and user-managed CA keychain stores.
1 parent 90783eb commit 5d3febf

9 files changed

Lines changed: 602 additions & 181 deletions

File tree

Doc/license.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,9 @@ OpenSSL
654654

655655
The modules :mod:`hashlib`, :mod:`posix`, :mod:`ssl`, :mod:`crypt` use
656656
the OpenSSL library for added performance if made available by the
657-
operating system. Additionally, the Windows installers for Python
658-
include a copy of the OpenSSL libraries, so we include a copy of the
659-
OpenSSL license here::
657+
operating system. Additionally, the Windows and Mac OS X installers for
658+
Python may include a copy of the OpenSSL libraries, so we include a copy
659+
of the OpenSSL license here::
660660

661661

662662
LICENSE ISSUES

Mac/BuildScript/build-installer.py

Lines changed: 218 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,16 @@ def shellQuote(value):
6262
return "'%s'"%(value.replace("'", "'\"'\"'"))
6363

6464
def grepValue(fn, variable):
65+
"""
66+
Return the unquoted value of a variable from a file..
67+
QUOTED_VALUE='quotes' -> str('quotes')
68+
UNQUOTED_VALUE=noquotes -> str('noquotes')
69+
"""
6570
variable = variable + '='
6671
for ln in open(fn, 'r'):
6772
if ln.startswith(variable):
6873
value = ln[len(variable):].strip()
69-
return value[1:-1]
74+
return value.strip("\"'")
7075
raise RuntimeError("Cannot find variable %s" % variable[:-1])
7176

7277
_cache_getVersion = None
@@ -78,9 +83,6 @@ def getVersion():
7883
os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
7984
return _cache_getVersion
8085

81-
def getVersionTuple():
82-
return tuple([int(n) for n in getVersion().split('.')])
83-
8486
def getVersionMajorMinor():
8587
return tuple([int(n) for n in getVersion().split('.', 2)])
8688

@@ -97,6 +99,9 @@ def getFullVersion():
9799
return _cache_getFullVersion
98100
raise RuntimeError("Cannot find full version??")
99101

102+
FW_PREFIX = ["Library", "Frameworks", "Python.framework"]
103+
FW_VERSION_PREFIX = "--undefined--" # initialized in parseOptions
104+
100105
# The directory we'll use to create the build (will be erased and recreated)
101106
WORKDIR = "/tmp/_py"
102107

@@ -164,7 +169,7 @@ def getTargetCompilers():
164169

165170
CC, CXX = getTargetCompilers()
166171

167-
PYTHON_3 = getVersionTuple() >= (3, 0)
172+
PYTHON_3 = getVersionMajorMinor() >= (3, 0)
168173

169174
USAGE = textwrap.dedent("""\
170175
Usage: build_python [options]
@@ -188,6 +193,10 @@ def getTargetCompilers():
188193
# '/Library/Frameworks/Tk.framework/Versions/8.5/Tk']
189194
EXPECTED_SHARED_LIBS = {}
190195

196+
# List of names of third party software built with this installer.
197+
# The names will be inserted into the rtf version of the License.
198+
THIRD_PARTY_LIBS = []
199+
191200
# Instructions for building libraries that are necessary for building a
192201
# batteries included python.
193202
# [The recipes are defined here for convenience but instantiated later after
@@ -197,6 +206,49 @@ def library_recipes():
197206

198207
LT_10_5 = bool(getDeptargetTuple() < (10, 5))
199208

209+
if getDeptargetTuple() < (10, 6):
210+
# The OpenSSL libs shipped with OS X 10.5 and earlier are
211+
# hopelessly out-of-date and do not include Apple's tie-in to
212+
# the root certificates in the user and system keychains via TEA
213+
# that was introduced in OS X 10.6. Note that this applies to
214+
# programs built and linked with a 10.5 SDK even when run on
215+
# newer versions of OS X.
216+
#
217+
# Dealing with CAs is messy. For now, just supply a
218+
# local libssl and libcrypto for the older installer variants
219+
# (e.g. the python.org 10.5+ 32-bit-only installer) that use the
220+
# same default ssl certfile location as the system libs do:
221+
# /System/Library/OpenSSL/cert.pem
222+
# Then at least TLS connections can be negotiated with sites that
223+
# use sha-256 certs like python.org, assuming the proper CA certs
224+
# have been supplied. The default CA cert management issues for
225+
# 10.5 and earlier builds are the same as before, other than it is
226+
# now more obvious with cert checking enabled by default in the
227+
# standard library.
228+
#
229+
# For builds with 10.6+ SDKs, continue to use the deprecated but
230+
# less out-of-date Apple 0.9.8 libs for now. While they are less
231+
# secure than using an up-to-date 1.0.1 version, doing so
232+
# avoids the big problems of forcing users to have to manage
233+
# default CAs themselves, thanks to the Apple libs using private TEA
234+
# APIs for cert validation from keychains if validation using the
235+
# standard OpenSSL locations (/System/Library/OpenSSL, normally empty)
236+
# fails.
237+
238+
result.extend([
239+
dict(
240+
name="OpenSSL 1.0.1j",
241+
url="https://www.openssl.org/source/openssl-1.0.1j.tar.gz",
242+
checksum='f7175c9cd3c39bb1907ac8bba9df8ed3',
243+
patches=[
244+
"openssl_sdk_makedepend.patch",
245+
],
246+
buildrecipe=build_universal_openssl,
247+
configure=None,
248+
install=None,
249+
),
250+
])
251+
200252
# Disable for now
201253
if False: # if getDeptargetTuple() > (10, 5):
202254
result.extend([
@@ -617,6 +669,7 @@ def parseOptions(args=None):
617669
"""
618670
global WORKDIR, DEPSRC, SDKPATH, SRCDIR, DEPTARGET
619671
global UNIVERSALOPTS, UNIVERSALARCHS, ARCHLIST, CC, CXX
672+
global FW_VERSION_PREFIX
620673

621674
if args is None:
622675
args = sys.argv[1:]
@@ -676,19 +729,21 @@ def parseOptions(args=None):
676729

677730
CC, CXX = getTargetCompilers()
678731

679-
print("Settings:")
680-
print(" * Source directory:", SRCDIR)
681-
print(" * Build directory: ", WORKDIR)
682-
print(" * SDK location: ", SDKPATH)
683-
print(" * Third-party source:", DEPSRC)
684-
print(" * Deployment target:", DEPTARGET)
685-
print(" * Universal architectures:", ARCHLIST)
686-
print(" * C compiler:", CC)
687-
print(" * C++ compiler:", CXX)
732+
FW_VERSION_PREFIX = FW_PREFIX[:] + ["Versions", getVersion()]
733+
734+
print("-- Settings:")
735+
print(" * Source directory: %s" % SRCDIR)
736+
print(" * Build directory: %s" % WORKDIR)
737+
print(" * SDK location: %s" % SDKPATH)
738+
print(" * Third-party source: %s" % DEPSRC)
739+
print(" * Deployment target: %s" % DEPTARGET)
740+
print(" * Universal archs: %s" % str(ARCHLIST))
741+
print(" * C compiler: %s" % CC)
742+
print(" * C++ compiler: %s" % CXX)
743+
print("")
744+
print(" -- Building a Python %s framework at patch level %s"
745+
% (getVersion(), getFullVersion()))
688746
print("")
689-
690-
691-
692747

693748
def extractArchive(builddir, archiveName):
694749
"""
@@ -780,6 +835,132 @@ def verifyThirdPartyFile(url, checksum, fname):
780835
% (shellQuote(fname), checksum) ):
781836
fatal('MD5 checksum mismatch for file %s' % fname)
782837

838+
def build_universal_openssl(basedir, archList):
839+
"""
840+
Special case build recipe for universal build of openssl.
841+
842+
The upstream OpenSSL build system does not directly support
843+
OS X universal builds. We need to build each architecture
844+
separately then lipo them together into fat libraries.
845+
"""
846+
847+
# OpenSSL fails to build with Xcode 2.5 (on OS X 10.4).
848+
# If we are building on a 10.4.x or earlier system,
849+
# unilaterally disable assembly code building to avoid the problem.
850+
no_asm = int(platform.release().split(".")[0]) < 9
851+
852+
def build_openssl_arch(archbase, arch):
853+
"Build one architecture of openssl"
854+
arch_opts = {
855+
"i386": ["darwin-i386-cc"],
856+
"x86_64": ["darwin64-x86_64-cc", "enable-ec_nistp_64_gcc_128"],
857+
"ppc": ["darwin-ppc-cc"],
858+
"ppc64": ["darwin64-ppc-cc"],
859+
}
860+
configure_opts = [
861+
"no-krb5",
862+
"no-idea",
863+
"no-mdc2",
864+
"no-rc5",
865+
"no-zlib",
866+
"enable-tlsext",
867+
"no-ssl2",
868+
"no-ssl3",
869+
"no-ssl3-method",
870+
# "enable-unit-test",
871+
"shared",
872+
"--install_prefix=%s"%shellQuote(archbase),
873+
"--prefix=%s"%os.path.join("/", *FW_VERSION_PREFIX),
874+
"--openssldir=/System/Library/OpenSSL",
875+
]
876+
if no_asm:
877+
configure_opts.append("no-asm")
878+
runCommand(" ".join(["perl", "Configure"]
879+
+ arch_opts[arch] + configure_opts))
880+
runCommand("make depend OSX_SDK=%s" % SDKPATH)
881+
runCommand("make all OSX_SDK=%s" % SDKPATH)
882+
runCommand("make install_sw OSX_SDK=%s" % SDKPATH)
883+
# runCommand("make test")
884+
return
885+
886+
srcdir = os.getcwd()
887+
universalbase = os.path.join(srcdir, "..",
888+
os.path.basename(srcdir) + "-universal")
889+
os.mkdir(universalbase)
890+
archbasefws = []
891+
for arch in archList:
892+
# fresh copy of the source tree
893+
archsrc = os.path.join(universalbase, arch, "src")
894+
shutil.copytree(srcdir, archsrc, symlinks=True)
895+
# install base for this arch
896+
archbase = os.path.join(universalbase, arch, "root")
897+
os.mkdir(archbase)
898+
# Python framework base within install_prefix:
899+
# the build will install into this framework..
900+
# This is to ensure that the resulting shared libs have
901+
# the desired real install paths built into them.
902+
archbasefw = os.path.join(archbase, *FW_VERSION_PREFIX)
903+
904+
# build one architecture
905+
os.chdir(archsrc)
906+
build_openssl_arch(archbase, arch)
907+
os.chdir(srcdir)
908+
archbasefws.append(archbasefw)
909+
910+
# copy arch-independent files from last build into the basedir framework
911+
basefw = os.path.join(basedir, *FW_VERSION_PREFIX)
912+
shutil.copytree(
913+
os.path.join(archbasefw, "include", "openssl"),
914+
os.path.join(basefw, "include", "openssl")
915+
)
916+
917+
shlib_version_number = grepValue(os.path.join(archsrc, "Makefile"),
918+
"SHLIB_VERSION_NUMBER")
919+
# e.g. -> "1.0.0"
920+
libcrypto = "libcrypto.dylib"
921+
libcrypto_versioned = libcrypto.replace(".", "."+shlib_version_number+".")
922+
# e.g. -> "libcrypto.1.0.0.dylib"
923+
libssl = "libssl.dylib"
924+
libssl_versioned = libssl.replace(".", "."+shlib_version_number+".")
925+
# e.g. -> "libssl.1.0.0.dylib"
926+
927+
try:
928+
os.mkdir(os.path.join(basefw, "lib"))
929+
except OSError:
930+
pass
931+
932+
# merge the individual arch-dependent shared libs into a fat shared lib
933+
archbasefws.insert(0, basefw)
934+
for (lib_unversioned, lib_versioned) in [
935+
(libcrypto, libcrypto_versioned),
936+
(libssl, libssl_versioned)
937+
]:
938+
runCommand("lipo -create -output " +
939+
" ".join(shellQuote(
940+
os.path.join(fw, "lib", lib_versioned))
941+
for fw in archbasefws))
942+
# and create an unversioned symlink of it
943+
os.symlink(lib_versioned, os.path.join(basefw, "lib", lib_unversioned))
944+
945+
# Create links in the temp include and lib dirs that will be injected
946+
# into the Python build so that setup.py can find them while building
947+
# and the versioned links so that the setup.py post-build import test
948+
# does not fail.
949+
relative_path = os.path.join("..", "..", "..", *FW_VERSION_PREFIX)
950+
for fn in [
951+
["include", "openssl"],
952+
["lib", libcrypto],
953+
["lib", libssl],
954+
["lib", libcrypto_versioned],
955+
["lib", libssl_versioned],
956+
]:
957+
os.symlink(
958+
os.path.join(relative_path, *fn),
959+
os.path.join(basedir, "usr", "local", *fn)
960+
)
961+
962+
return
963+
783964
def buildRecipe(recipe, basedir, archList):
784965
"""
785966
Build software using a recipe. This function does the
@@ -789,8 +970,10 @@ def buildRecipe(recipe, basedir, archList):
789970
curdir = os.getcwd()
790971

791972
name = recipe['name']
973+
THIRD_PARTY_LIBS.append(name)
792974
url = recipe['url']
793975
configure = recipe.get('configure', './configure')
976+
buildrecipe = recipe.get('buildrecipe', None)
794977
install = recipe.get('install', 'make && make install DESTDIR=%s'%(
795978
shellQuote(basedir)))
796979

@@ -888,8 +1071,13 @@ def buildRecipe(recipe, basedir, archList):
8881071
print("Running configure for %s"%(name,))
8891072
runCommand(' '.join(configure_args) + ' 2>&1')
8901073

891-
print("Running install for %s"%(name,))
892-
runCommand('{ ' + install + ' ;} 2>&1')
1074+
if buildrecipe is not None:
1075+
# call special-case build recipe, e.g. for openssl
1076+
buildrecipe(basedir, archList)
1077+
1078+
if install is not None:
1079+
print("Running install for %s"%(name,))
1080+
runCommand('{ ' + install + ' ;} 2>&1')
8931081

8941082
print("Done %s"%(name,))
8951083
print("")
@@ -1145,6 +1333,7 @@ def patchFile(inPath, outPath):
11451333
data = data.replace('$MACOSX_DEPLOYMENT_TARGET', ''.join((DEPTARGET, ' or later')))
11461334
data = data.replace('$ARCHITECTURES', ", ".join(universal_opts_map[UNIVERSALARCHS]))
11471335
data = data.replace('$INSTALL_SIZE', installSize())
1336+
data = data.replace('$THIRD_PARTY_LIBS', "\\\n".join(THIRD_PARTY_LIBS))
11481337

11491338
# This one is not handy as a template variable
11501339
data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
@@ -1327,8 +1516,6 @@ def buildInstaller():
13271516
else:
13281517
patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
13291518

1330-
shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
1331-
13321519

13331520
def installSize(clear=False, _saved=[]):
13341521
if clear:
@@ -1436,23 +1623,27 @@ def main():
14361623

14371624

14381625
# Prepare the applications folder
1439-
fn = os.path.join(WORKDIR, "_root", "Applications",
1440-
"Python %s"%(getVersion(),), "Update Shell Profile.command")
1441-
patchScript("scripts/postflight.patch-profile", fn)
1442-
14431626
folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
14441627
getVersion(),))
1628+
fn = os.path.join(folder, "License.rtf")
1629+
patchFile("resources/License.rtf", fn)
1630+
fn = os.path.join(folder, "ReadMe.rtf")
1631+
patchFile("resources/ReadMe.rtf", fn)
1632+
fn = os.path.join(folder, "Update Shell Profile.command")
1633+
patchScript("scripts/postflight.patch-profile", fn)
14451634
os.chmod(folder, STAT_0o755)
14461635
setIcon(folder, "../Icons/Python Folder.icns")
14471636

14481637
# Create the installer
14491638
buildInstaller()
14501639

14511640
# And copy the readme into the directory containing the installer
1452-
patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1641+
patchFile('resources/ReadMe.rtf',
1642+
os.path.join(WORKDIR, 'installer', 'ReadMe.rtf'))
14531643

14541644
# Ditto for the license file.
1455-
shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
1645+
patchFile('resources/License.rtf',
1646+
os.path.join(WORKDIR, 'installer', 'License.rtf'))
14561647

14571648
fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
14581649
fp.write("# BUILD INFO\n")

0 commit comments

Comments
 (0)