示例#1
0
def download_python_source_files():
    """Download CPython source files from Github.

    Verify the sha hash and redownload if they do not match.
    """
    log.info("Downloading and verifying python source files...")
    src_dir = os.path.join(CURRENT_DIR, '_src')
    if not os.path.exists(src_dir):
        log.debug("Creating _src directory...")
        mkpath(src_dir)
    os.chdir(src_dir)
    gh_url = 'https://raw.githubusercontent.com/python/cpython/v2.7.10/'
    # This ugly looking block of code is a pair that matches the filename,
    # github url, and sha256 hash for each required python source file
    fp = [
        ['ssl.py', '{}Lib/ssl.py'.format(gh_url), CONFIG['ssl_py_hash']],
        ['_ssl.c', '{}Modules/_ssl.c'.format(gh_url), CONFIG['ssl_c_hash']],
        [
            'make_ssl_data.py', '{}Tools/ssl/make_ssl_data.py'.format(gh_url),
            CONFIG['make_ssl_data_py_hash']
        ],
        [
            'socketmodule.h', '{}Modules/socketmodule.h'.format(gh_url),
            CONFIG['socketmodule_h_hash']
        ],
    ]
    # Verify we have the correct python source files else download it
    log.detail("Downloading & checking hash of python source files...")
    for fname, url, sha256 in fp:
        # This is a dual check step for file existence and hash matching
        log.debug("Checking source file: {}...".format(fname))
        if not os.path.isfile(fname) or (hash_helper.getsha256hash(fname) !=
                                         sha256):
            log.info("Downloading '{}' source file...".format(fname))
            log.debug("Download url: {}".format(url))
            try:
                data = urllib2.urlopen(url)
                f = open(fname, "w")
                content = data.read()
                f.write(content)
                f.close()
                # Verify the hash of the source file we just downloaded
                download_file_hash = hash_helper.getsha256hash(fname)
                if download_file_hash != sha256:
                    log.warn("The hash for '{}' does not match the expected "
                             "hash. The downloaded hash is '{}'".format(
                                 fname, download_file_hash))
                else:
                    log.debug("The download file '{}' matches our expected "
                              "hash of '{}'".format(fname, sha256))
            except (urllib2.HTTPError, urllib2.URLError, OSError,
                    IOError) as err:
                log.error("Unable to download '{}' "
                          "due to {}\n".format(fname, err))
                sys.exit(1)
    # We are done with _src directory for now so go back to script root path
    os.chdir(CURRENT_DIR)
示例#2
0
def dl_and_extract_python(dist_url, dist_hash):
    """Download Python distribution and extract it to PYTHON_BUILD_DIR."""
    if os.path.isdir(PYTHON_BUILD_DIR):
        shutil.rmtree(PYTHON_BUILD_DIR, ignore_errors=True)
    mkpath(PYTHON_BUILD_DIR)
    # Download Python
    log.info("Downloading Python from: {}".format(dist_url))
    temp_filename = os.path.join(tempfile.mkdtemp(), 'tempdata')
    cmd = [
        '/usr/bin/curl', '--show-error', '--no-buffer', '--fail',
        '--progress-bar', '--speed-time', '30', '--location', '--url',
        dist_url, '--output', temp_filename
    ]
    # We are calling os.system so we can get download progress live
    rc = runner.system(cmd)
    if rc == 0 or rc is True:
        log.debug("Python download successful")
    else:
        log.error("Python download failed with exit code: '{}'".format(rc))
        sys.exit(1)

    # Verify Python download hash
    download_hash = hash_helper.getsha256hash(temp_filename)
    config_hash = dist_hash
    if download_hash != config_hash:
        log.error("Hash verification of Python download has failed. Download "
                  "hash of '{}' does not match config hash '{}'".format(
                      download_hash, config_hash))
        sys.exit(1)
    else:
        log.detail("Hash verification of Python successful")

    # Extract Python to the PYTHON_BUILD_DIR
    log.info("Extracting Python...")
    cmd = [
        '/usr/bin/tar', '-xf', temp_filename, '-C', PYTHON_BUILD_DIR,
        '--strip-components', '1'
    ]
    out = runner.Popen(cmd)
    if out[2] == 0:
        log.debug("Extraction completed successfully")
    else:
        log.error("Extraction has failed: {}".format(out[0]))
    os.remove(temp_filename)
示例#3
0
def download_and_extract_openssl():
    """Download openssl distribution and extract it to OPENSSL_BUILD_DIR."""
    if os.path.isdir(OPENSSL_BUILD_DIR):
        shutil.rmtree(OPENSSL_BUILD_DIR, ignore_errors=True)
    mkpath(OPENSSL_BUILD_DIR)
    # Download openssl
    log.info("Downloading OpenSSL from: {}".format(CONFIG['openssl_dist']))
    temp_filename = os.path.join(tempfile.mkdtemp(), 'tempdata')
    cmd = [
        '/usr/bin/curl', '--show-error', '--no-buffer', '--fail',
        '--progress-bar', '--speed-time', '30', '--location', '--url',
        CONFIG['openssl_dist'], '--output', temp_filename
    ]
    # We are calling os.system so we can get download progress live
    rc = runner.system(cmd)
    if rc == 0 or rc is True:
        log.debug("OpenSSL download successfully")
    else:
        log.error("OpenSSL download failed with exit code: '{}'".format(rc))
        sys.exit(1)

    # Verify openssl download hash
    download_hash = hash_helper.getsha256hash(temp_filename)
    config_hash = CONFIG['openssl_dist_hash']
    if download_hash != config_hash:
        log.error("Hash verification of OpenSSL download has failed. Download "
                  "hash of '{}' does not match config hash '{}'".format(
                      download_hash, config_hash))
        sys.exit(1)
    else:
        log.detail("Hash verification of OpenSSL successfully")

    # Extract openssl to the openssl_build_dir
    log.info("Extracting OpenSSL...")
    cmd = [
        '/usr/bin/tar', '-xf', temp_filename, '-C', OPENSSL_BUILD_DIR,
        '--strip-components', '1'
    ]
    out = runner.Popen(cmd)
    if out[2] == 0:
        log.debug("Extraction completed successfullyly")
    else:
        log.error("Extraction has failed: {}".format(out[1]))
    os.remove(temp_filename)
示例#4
0
def patch():
    """Patch source files for the build phase."""
    log.info("Creating our patch files for tlsssl...")
    patch_dir = os.path.join(CURRENT_DIR, '_patch')
    if not os.path.exists(patch_dir):
        log.debug("Creating _patch directory...")
        mkpath(patch_dir)
    patch_pairs = [
        # ['_patch/_ssl.c',           '_src/_ssl.c', ],
        ['_patch/make_ssl_data.py', '_src/make_ssl_data.py'],
        # ['_patch/ssl.py',          '_src/ssl.py'],
    ]
    log.detail("Create our patch files if they do not exist...")
    for dest, source in patch_pairs:
        if not os.path.isfile(os.path.join(CURRENT_DIR, dest)):
            source = os.path.join(CURRENT_DIR, source)
            diff = os.path.join(CURRENT_DIR, '_diffs',
                                "{}.diff".format(os.path.basename(dest)))
            dest = os.path.join(CURRENT_DIR, dest)
            log.debug("Patching '{}'".format(dest))
            # TODO: Validate the return code and exist if something didn't work
            cmd = ['/usr/bin/patch', source, diff, "-o", dest]
            out = runner.Popen(cmd)
            runner.pprint(out)
    # Copy over the socketmodule.h file as well
    if not os.path.isfile(os.path.join(patch_dir, "socketmodule.h")):
        log.debug("Copying 'socketmodule.h' to the _patch dir")
        source = os.path.join(CURRENT_DIR, "_src", "socketmodule.h")
        shutil.copy(source, os.path.realpath(os.path.join(patch_dir)))

    if not os.path.isfile(os.path.join(patch_dir, "_ssl.c")):
        log.debug("Copying '_ssl.c' to the _patch dir")
        source = os.path.join(CURRENT_DIR, "_src", "_ssl.c")
        shutil.copy(source, os.path.realpath(os.path.join(patch_dir)))

    if not os.path.isfile(os.path.join(patch_dir, "ssl.py")):
        log.debug("Copying 'ssl.py' to the _patch dir")
        source = os.path.join(CURRENT_DIR, "_src", "ssl.py")
        shutil.copy(source, os.path.realpath(os.path.join(patch_dir)))

    log.detail("All patch files are created...")
示例#5
0
def build(py_version, py_install_path, skip):
    """Build custom Python from source."""
    py_major_ver = py_version.split('.')[0]
    log.debug("Currently building: {}".format(py_major_ver))
    # Step 1: change into our build directory
    os.chdir(PYTHON_BUILD_DIR)
    # Don't compile Python if the skip option is passed
    if skip:
        log.info("Python compile skipped due to -skip option")
        return
    # Step 1.5: Add extra modules
    if py_major_ver == '2':
        setup_dist = os.path.join(PYTHON_BUILD_DIR, 'Modules/Setup.dist')
        with open(setup_dist, "a") as f:
            log.debug("Adding additional modules to be included...")
            f.write("_socket socketmodule.c timemodule.c\n")
            f.write("_ssl _ssl.c -DUSE_SSL "
                    "-I{0}/include -I{0}/include/openssl -L{0}/lib "
                    "-lssl -lcrypto".format(OPENSSL_INSTALL_PATH))
    # Step 2: Run the Configure setup of Python to set correct paths
    os.chdir(PYTHON_BUILD_DIR)
    if os.path.isdir(py_install_path):
        shutil.rmtree(py_install_path, ignore_errors=True)
    mkpath(py_install_path)
    log.info("Configuring Python...")
    cmd = [
        './configure',
        '--prefix={}'.format(py_install_path),
        #    'CPPFLAGS=-I{}/include'.format(OPENSSL_INSTALL_PATH),
        #    'LDFLAGS=-L{}/lib'.format(OPENSSL_INSTALL_PATH),
        'CFLAGS=-I{}/include'.format(OPENSSL_INSTALL_PATH),
        'LDFLAGS=-L{}/lib'.format(OPENSSL_INSTALL_PATH),
        '--enable-shared',
        '--enable-toolbox-glue',
        '--with-ensurepip=install',
        '--enable-ipv6',
        '--with-threads',
        '--datarootdir={}/share'.format(py_install_path),
        '--datadir={}/share'.format(py_install_path),
        # '--enable-optimizations',  # adding this flag will run tests
    ]
    runner.Popen(cmd, stdout=sys.stdout)
    # Step 3: compile Python. this will take a while.

    # FIXME: We need to check return codes.
    log.info("Compiling Python. This will take a while time...")
    log.detail("Running Python make routine...")
    cmd = ['/usr/bin/make']
    runner.Popen(cmd, stdout=sys.stdout)
    sys.stdout.flush()  # does this help?

    log.debug("Create some temp files thats")
    log.detail("Running Python make install routine...")
    cmd = ['/usr/bin/make', 'install']
    runner.Popen(cmd, stdout=sys.stdout)
    sys.stdout.flush()  # does this help?
    # Step 4: Install pip + requirements
    os.chdir(os.path.join(py_install_path, 'bin'))
    # Update pip to latest
    log.info("Upgrading pip...")
    if py_major_ver == '2':
        cmd = ['./pip']
    elif py_major_ver == '3':
        cmd = ['./pip3']
    cmd = cmd + ['install', '--upgrade', 'pip']
    runner.Popen(cmd, stdout=sys.stdout)
    # Install all pip modules from requirements.txt
    log.info("Install requirements...")
    if py_major_ver == '2':
        cmd = [
            './python2.7', '-m', 'pip', 'install', '-r',
            os.path.join(CURRENT_DIR, 'requirements2.txt')
        ]
    elif py_major_ver == '3':
        cmd = [
            './python3.6', '-m', 'pip', 'install', '-r',
            os.path.join(CURRENT_DIR, 'requirements3.txt')
        ]
    runner.Popen(cmd, stdout=sys.stdout)
示例#6
0
def build():
    """Build OpenSSL from source."""
    # Step 1: change into our build directory
    os.chdir(OPENSSL_BUILD_DIR)
    # Don't compile openssl if the skip option is passed
    # Step 2: Run the Configure setup of OpenSSL to set correct paths
    openssl_install = os.path.join(BASE_INSTALL_PATH, 'openssl')
    log.info("Configuring OpenSSL...")
    cmd = [
        './Configure',
        '--prefix={}'.format(openssl_install),
        '--openssldir={}'.format(openssl_install),
        'darwin64-x86_64-cc',
        'enable-ec_nistp_64_gcc_128',
        'no-ssl2',
        'no-ssl3',
        'no-zlib',
        'shared',
        'enable-cms',
        'no-comp',
    ]
    # OpenSSL 1.0 to 1.1 has some pretty major API and build differences.
    # Manage the build control with the following. NOTE: 1.1 is not
    # supported at this time. Hopefully in a future release.
    OLD_VERSION = None
    # OpenSSL renamed this build flag so was less confusing
    # https://github.com/openssl/openssl/commit/3c65577f1af1109beb8de06420efa09188981628
    TMP_DIR_FLAG = None
    if OPENSSL_VERSION > "1.1.0":
        OLD_VERSION = False
        TMP_DIR_FLAG = "DESTDIR"
    else:
        OLD_VERSION = True
        TMP_DIR_FLAG = "INSTALL_PREFIX"
    # If running 1.0 use runner.system() else runner.Popen()
    if OLD_VERSION:
        out = runner.system(cmd)
    else:
        out = runner.Popen(cmd)
    log.debug("Configuring returned value: {}".format(out))

    # Step 3: compile openssl. this will take a while.
    # FIXME: We need to check return codes.
    #        This command is required for OpenSSL lower than 1.1
    log.info("Compiling OpenSSL. This will take a while time...")
    if OLD_VERSION:
        log.detail("Running OpenSSL make depend routine...")
        cmd = ['/usr/bin/make', 'depend']
        proc = subprocess.Popen(cmd, bufsize=-1, stdout=sys.stdout)
        (output, dummy_error) = proc.communicate()
        sys.stdout.flush()  # does this help?

    log.detail("Running OpenSSL make routine...")
    cmd = ['/usr/bin/make']
    proc = subprocess.Popen(cmd, bufsize=-1, stdout=sys.stdout)
    (output, dummy_error) = proc.communicate()
    sys.stdout.flush()  # does this help?

    # log.detail("Running OpenSSL make test routine...")
    # cmd = ['/usr/bin/make', 'test']
    # proc = subprocess.Popen(cmd, bufsize=-1, stdout=sys.stdout)
    # (output, dummy_error) = proc.communicate()
    # sys.stdout.flush()  # does this help?

    mkpath(PKG_PAYLOAD_DIR)
    log.detail("Running OpenSSL make install routine...")
    cmd = [
        '/usr/bin/make', '{}={}'.format(TMP_DIR_FLAG, PKG_PAYLOAD_DIR),
        'install'
    ]
    print("ran a command: {}".format(' '.join(cmd)))
    out = runner.Popen(cmd, stdout=sys.stdout)
    sys.stdout.flush()  # does this help?
示例#7
0
def main():
    """Build and package the tlsssl patch."""
    parser = argparse.ArgumentParser(prog='tlsssl setup',
                                     description='This script will compile '
                                     'tlsssl and optionally create '
                                     'a native macOS package.')
    parser.add_argument('-b',
                        '--build',
                        action='store_true',
                        help='Compile the tlsssl binaries')
    parser.add_argument('-p',
                        '--pkg',
                        action='store_true',
                        help='Package the tlsssl output directory.')
    parser.add_argument('-v',
                        '--verbose',
                        action='count',
                        default=1,
                        help="Increase verbosity level. Repeatable up to "
                        "2 times (-vv)")
    if len(sys.argv) < 2:
        parser.print_help()
        sys.exit(1)
    args = parser.parse_args()

    # set argument variables
    log.verbose = args.verbose

    if args.build:
        log.info("Bulding tslssl...")
        download_python_source_files()
        patch()
        build()

    if args.pkg:
        # FIXME: This has grown out of control. Move this outside of main!
        log.info("Building a package for tlsssl...")
        version = CONFIG['tlsssl_version']
        # we need to setup the payload
        payload_dir = os.path.join(CURRENT_DIR, 'payload')
        if os.path.exists(payload_dir):
            log.debug("Removing payload directory...")
            shutil.rmtree(payload_dir, ignore_errors=True)
        log.debug("Creating payload directory...")
        payload_lib_dir = os.path.join(payload_dir, LIBS_DEST.lstrip('/'))
        payload_root_dir = os.path.join(
            payload_dir, CONFIG['tlsssl_install_dir'].lstrip('/'))
        mkpath(payload_lib_dir)
        log.detail("Changing file permissions for 'ssl.py'...")
        # ssl.py needs to have chmod 644 so non-root users can import this
        os.chmod('build/ssl.py',
                 stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
        log.detail("Copying build files into payload directory")
        shutil.copy('build/_ssl.so', payload_root_dir)
        shutil.copy('build/ssl.py', payload_root_dir)
        shutil.copy('build/libtlscrypto.dylib', payload_lib_dir)
        shutil.copy('build/libtlsssl.dylib', payload_lib_dir)

        pth_fname = CONFIG['pth_fname']
        # if the pth_fname key is set write the .pth file
        if pth_fname is not '':
            log.debug("Write the '.pth' file so native python can read "
                      "this module without a sys.path.insert")
            python_sys = "/Library/Python/2.7/site-packages/"
            python_sys_local = os.path.join("payload", python_sys.lstrip('/'))
            log.debug("Make site-packages inside of payload")
            mkpath(python_sys_local)
            pth_file = os.path.join(python_sys_local, pth_fname)
            f = open(pth_file, 'w')
            # this hacky method will force this path to have a high priority
            # http://stackoverflow.com/a/37380306
            content = ("""import sys; sys.__plen = len(sys.path)
{}
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:]; \
p=getattr(sys,'__egginsert',0); \
sys.path[p:p]=new; sys.__egginsert = p+len(new)""".format(
                os.path.dirname(LIBS_DEST)))
            f.write(content)
            f.close()

        rc = package.pkg(
            root='payload',
            version=version,
            identifier="{}.tlsssl".format(CONFIG['pkgid']),
            output='tlsssl-{}.pkg'.format(version),
        )
        if rc == 0:
            log.info("tlsssl packaged properly")
        else:
            log.error("Looks like package creation failed")
示例#8
0
def build():
    """Build our tlsssl patch."""
    log.info("Building tlsssl...")
    patch_dir = os.path.join(CURRENT_DIR, '_patch')
    # Step 2: make sure the _ssl_data.h header has been generated
    ssl_data = os.path.join(patch_dir, "_ssl_data.h")
    if not os.path.isfile(ssl_data):
        log.debug("Generate _ssl_data.h header...")
        tool_path = os.path.join(CURRENT_DIR, "_patch", "make_ssl_data.py")
        # Run the generating script
        cmd = ['/usr/bin/python', tool_path, HEADER_SRC, ssl_data]
        out = runner.Popen(cmd)
        runner.pprint(out, 'debug')
    # Step 3: remove the temporary work directory under the build dir
    build_dir = os.path.join(CURRENT_DIR, 'build')
    if os.path.exists(build_dir):
        log.debug("Removing build directory...")
        shutil.rmtree(build_dir, ignore_errors=True)
    log.debug("Creating build directories...")
    mkpath(build_dir)
    # Step 3.5: copy ssl.py to the build directory
    log.info("Copy 'ssl.py' to the build directory...")
    shutil.copy(os.path.join(CURRENT_DIR, '_patch/ssl.py'), build_dir)
    workspace_rel = os.path.join(build_dir)
    workspace_abs = os.path.realpath(workspace_rel)
    # Step 4: copy and rename the dylibs to there
    log.detail("Copying dylibs to build directory")
    ssl_src = os.path.join(LIBS_SRC, "libssl.dylib")
    crypt_src = os.path.join(LIBS_SRC, "libcrypto.dylib")
    ssl_tmp = os.path.join(workspace_abs, "libtlsssl.dylib")
    crypt_tmp = os.path.join(workspace_abs, "libtlscrypto.dylib")
    try:
        shutil.copy(ssl_src, ssl_tmp)
        shutil.copy(crypt_src, crypt_tmp)
    except (IOError) as err:
        log.warn("tlsssl has a dependency on OpenSSL 1.0.1+ as such you "
                 "must build and install OpenSSL from ../openssl.")
        log.error("Build failed and will now exit!")
        log.error("{}".format(err))
        sys.exit(1)
    # Step 5: change the ids of the dylibs
    log.detail("Changing the ids of the dylibs...")
    ssl_dest = os.path.join(LIBS_DEST, "libtlsssl.dylib")
    crypt_dest = os.path.join(LIBS_DEST, "libtlscrypto.dylib")
    # (need to temporarily mark them as writeable)
    # NOTE: I don't think this I needed any longer
    st = os.stat(ssl_tmp)
    os.chmod(ssl_tmp, st.st_mode | stat.S_IWUSR)
    st = os.stat(crypt_tmp)
    os.chmod(crypt_tmp, st.st_mode | stat.S_IWUSR)

    cmd = ['/usr/bin/install_name_tool', '-id', ssl_dest, ssl_tmp]
    out = runner.Popen(cmd)
    runner.pprint(out, 'debug')

    cmd = ['/usr/bin/install_name_tool', '-id', crypt_dest, crypt_tmp]
    out = runner.Popen(cmd)
    runner.pprint(out, 'debug')

    # Step 6: change the link between ssl and crypto
    # This part is a bit trickier - we need to take the existing entry
    # for libcrypto on libssl and remap it to the new location
    cmd = ['/usr/bin/otool', '-L', ssl_tmp]
    out = runner.Popen(cmd)
    runner.pprint(out, 'debug')

    old_path = re.findall('^\t(/[^\(]+?libcrypto.*?.dylib)', out[0],
                          re.MULTILINE)[0]
    log.debug("The old path was: {}".format(old_path))

    cmd = [
        '/usr/bin/install_name_tool', '-change', old_path, crypt_dest, ssl_tmp
    ]
    out = runner.Popen(cmd)
    runner.pprint(out, 'debug')
    # Step 7: cleanup permissions
    # NOTE: Same. I don't think this I needed any longer
    st = os.stat(ssl_tmp)
    os.chmod(ssl_tmp, st.st_mode & ~stat.S_IWUSR)
    st = os.stat(crypt_tmp)
    os.chmod(crypt_tmp, st.st_mode & ~stat.S_IWUSR)
    # Step 8: patch in the additional paths and linkages
    # NOTE: This command will output a few warnings that are hidden at
    #       build time. Just an FYI in case this needs to be resolved in
    #       the future.
    system_python_path = ("/System/Library/Frameworks/Python.framework/"
                          "Versions/2.7/include/python2.7")
    cmd = [
        "cc", "-fno-strict-aliasing", "-fno-common", "-dynamic", "-arch",
        "x86_64", "-arch", "i386", "-g", "-Os", "-pipe", "-fno-common",
        "-fno-strict-aliasing", "-fwrapv", "-DENABLE_DTRACE", "-DMACOSX",
        "-DNDEBUG", "-Wall", "-Wstrict-prototypes", "-Wshorten-64-to-32",
        "-DNDEBUG", "-g", "-fwrapv", "-Os", "-Wall", "-Wstrict-prototypes",
        "-DENABLE_DTRACE", "-arch", "x86_64", "-arch", "i386", "-pipe",
        "-I{}".format(HEADER_SRC), "-I{}".format(system_python_path), "-c",
        "_patch/_ssl.c", "-o", "build/_ssl.o"
    ]
    out = runner.Popen(cmd)
    if out[2] == 0:
        log.debug("Build of '_ssl.o' completed successfullyly")
    else:
        log.error("Build has failed: {}".format(out[1]))

    cmd = [
        "cc", "-bundle", "-undefined", "dynamic_lookup", "-arch", "x86_64",
        "-arch", "i386", "-Wl,-F.", "build/_ssl.o",
        "-L{}".format(workspace_abs), "-ltlsssl", "-ltlsssl", "-o",
        "build/_ssl.so"
    ]
    out = runner.Popen(cmd)
    if out[2] == 0:
        log.debug("Build of '_ssl.so' completed successfullyly")
    else:
        log.error("Build has failed: {}".format(out[1]))

    log.debug("Remove temp '_ssl.o' from build directory")
    os.remove(os.path.join(build_dir, "_ssl.o"))